diff --git a/.gitignore b/.gitignore index 7ff0c07b..82dfe354 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ contents.xcworkspacedata launch.json settings.json c_cpp_properties.json + +Tools/openrpc.json diff --git a/RPC/Sources/RPC/Handlers/AllHandlers.swift b/RPC/Sources/RPC/Handlers/AllHandlers.swift index 55469ce0..5ddacb1d 100644 --- a/RPC/Sources/RPC/Handlers/AllHandlers.swift +++ b/RPC/Sources/RPC/Handlers/AllHandlers.swift @@ -2,13 +2,16 @@ public enum AllHandlers { public static let handlers: [any RPCHandler.Type] = ChainHandlers.handlers + SystemHandlers.handlers + + StateHandlers.handlers + TelemetryHandlers.handlers + RPCHandlers.handlers public static func getHandlers(source: ChainDataSource & SystemDataSource & TelemetryDataSource) -> [any RPCHandler] { - ChainHandlers.getHandlers(source: source) + - SystemHandlers.getHandlers(source: source) + - TelemetryHandlers.getHandlers(source: source) + - RPCHandlers.getHandlers(source: handlers) + var handlers = ChainHandlers.getHandlers(source: source) + handlers.append(contentsOf: SystemHandlers.getHandlers(source: source)) + handlers.append(contentsOf: StateHandlers.getHandlers(source: source)) + handlers.append(contentsOf: TelemetryHandlers.getHandlers(source: source)) + handlers.append(contentsOf: RPCHandlers.getHandlers(source: Self.handlers)) + return handlers } } diff --git a/RPC/Sources/RPC/Handlers/ChainHandlers.swift b/RPC/Sources/RPC/Handlers/ChainHandlers.swift index 81922a5e..64282939 100644 --- a/RPC/Sources/RPC/Handlers/ChainHandlers.swift +++ b/RPC/Sources/RPC/Handlers/ChainHandlers.swift @@ -1,24 +1,31 @@ import Blockchain +import Codec import Foundation import Utils public enum ChainHandlers { public static let handlers: [any RPCHandler.Type] = [ GetBlock.self, + GetBlockHash.self, + GetFinalziedHead.self, + GetHeader.self, ] public static func getHandlers(source: ChainDataSource) -> [any RPCHandler] { [ GetBlock(source: source), + GetBlockHash(source: source), + GetFinalziedHead(source: source), + GetHeader(source: source), ] } public struct GetBlock: RPCHandler { public typealias Request = Request1 - public typealias Response = BlockRef? - public typealias DataSource = ChainDataSource + public typealias Response = Data? public static var method: String { "chain_getBlock" } + public static var requestNames: [String] { ["hash"] } public static var summary: String? { "Get block by hash. If hash is not provided, returns the best block." } private let source: ChainDataSource @@ -28,11 +35,71 @@ public enum ChainHandlers { } public func handle(request: Request) async throws -> Response? { - if let hash = request.value { + let block = if let hash = request.value { try await source.getBlock(hash: hash) } else { try await source.getBestBlock() } + return try block.map { try JamEncoder.encode($0) } + } + } + + public struct GetBlockHash: RPCHandler { + public typealias Request = Request1 + public typealias Response = Data? + + public static var method: String { "chain_getBlockHash" } + public static var requestNames: [String] { ["timeslot"] } + public static var summary: String? { "Get the block hash by timeslot. If timeslot is not provided, returns the best block hash." } + + private let source: ChainDataSource + + init(source: ChainDataSource) { + self.source = source + } + + public func handle(request _: Request) async throws -> Response? { + // TODO: implement + nil + } + } + + public struct GetFinalziedHead: RPCHandler { + public typealias Request = VoidRequest + public typealias Response = Data32? + + public static var method: String { "chain_getFinalziedHead" } + public static var summary: String? { "Get hash of the last finalized block in the canon chain." } + + private let source: ChainDataSource + + init(source: ChainDataSource) { + self.source = source + } + + public func handle(request _: Request) async throws -> Response? { + // TODO: implement + nil + } + } + + public struct GetHeader: RPCHandler { + public typealias Request = Request1 + public typealias Response = Data? + + public static var method: String { "chain_getHeader" } + public static var requestNames: [String] { ["hash"] } + public static var summary: String? { "Get block header by hash. If hash is not provided, returns the best block header." } + + private let source: ChainDataSource + + init(source: ChainDataSource) { + self.source = source + } + + public func handle(request _: Request) async throws -> Response? { + // TODO: implement + nil } } } diff --git a/RPC/Sources/RPC/Handlers/StateHandlers.swift b/RPC/Sources/RPC/Handlers/StateHandlers.swift new file mode 100644 index 00000000..1bc2fecc --- /dev/null +++ b/RPC/Sources/RPC/Handlers/StateHandlers.swift @@ -0,0 +1,57 @@ +import Blockchain +import Foundation +import Utils + +public enum StateHandlers { + public static let handlers: [any RPCHandler.Type] = [ + GetKeys.self, + GetStorage.self, + ] + + public static func getHandlers(source: ChainDataSource) -> [any RPCHandler] { + [ + GetKeys(source: source), + GetStorage(source: source), + ] + } + + public struct GetKeys: RPCHandler { + public typealias Request = Request4 + public typealias Response = [String] + + public static var method: String { "state_getKeys" } + public static var requestNames: [String] { ["prefix", "count", "startKey", "blockHash"] } + public static var summary: String? { "Returns the keys of the state." } + + private let source: ChainDataSource + + init(source: ChainDataSource) { + self.source = source + } + + public func handle(request _: Request) async throws -> Response? { + // TODO: implement + [] + } + } + + public struct GetStorage: RPCHandler { + public typealias Request = Request2 + public typealias Response = [String] + + public static var method: String { "state_getStorage" } + public static var requestNames: [String] { ["key", "blockHash"] } + public static var summary: String? { "Returns the storage entry for a key for blockHash or best head." } + + private let source: ChainDataSource + + init(source: ChainDataSource) { + self.source = source + } + + public func handle(request _: Request) async throws -> Response? { + // TODO: implement + [] + } + } +} diff --git a/RPC/Sources/RPC/Handlers/SystemHandlers.swift b/RPC/Sources/RPC/Handlers/SystemHandlers.swift index ee2ecdf8..5d33d060 100644 --- a/RPC/Sources/RPC/Handlers/SystemHandlers.swift +++ b/RPC/Sources/RPC/Handlers/SystemHandlers.swift @@ -3,17 +3,25 @@ import Utils public enum SystemHandlers { public static let handlers: [any RPCHandler.Type] = [ Health.self, + Implementation.self, Version.self, + Properties.self, + NodeRoles.self, + Chain.self, ] - public static func getHandlers(source _: SystemDataSource) -> [any RPCHandler] { + public static func getHandlers(source: SystemDataSource) -> [any RPCHandler] { [ Health(), - Version(), + Implementation(), + Version(source: source), + Properties(source: source), + NodeRoles(source: source), + Chain(source: source), ] } - public struct Health: RPCHandler { + struct Health: RPCHandler { public typealias Request = VoidRequest public typealias Response = Bool @@ -25,7 +33,7 @@ public enum SystemHandlers { } } - public struct Implementation: RPCHandler { + struct Implementation: RPCHandler { public typealias Request = VoidRequest public typealias Response = String @@ -37,15 +45,79 @@ public enum SystemHandlers { } } - public struct Version: RPCHandler { + struct Version: RPCHandler { public typealias Request = VoidRequest public typealias Response = String public static var method: String { "system_version" } public static var summary: String? { "Returns the version of the node." } + private let source: SystemDataSource + + init(source: SystemDataSource) { + self.source = source + } + public func handle(request _: Request) async throws -> Response? { + // TODO: read it from somewhere "0.0.1" } } + + struct Properties: RPCHandler { + public typealias Request = VoidRequest + public typealias Response = JSON + + public static var method: String { "system_properties" } + public static var summary: String? { "Get a custom set of properties as a JSON object, defined in the chain spec." } + + private let source: SystemDataSource + + init(source: SystemDataSource) { + self.source = source + } + + public func handle(request _: Request) async throws -> Response? { + // TODO: implement + JSON.array([]) + } + } + + struct NodeRoles: RPCHandler { + public typealias Request = VoidRequest + public typealias Response = [String] + + public static var method: String { "system_nodeRoles" } + public static var summary: String? { "Returns the roles the node is running as." } + + private let source: SystemDataSource + + init(source: SystemDataSource) { + self.source = source + } + + public func handle(request _: Request) async throws -> Response? { + // TODO: implement + [] + } + } + + struct Chain: RPCHandler { + public typealias Request = VoidRequest + public typealias Response = String + + public static var method: String { "system_chain" } + public static var summary: String? { "Returns the chain name, defined in the chain spec." } + + private let source: SystemDataSource + + init(source: SystemDataSource) { + self.source = source + } + + public func handle(request _: Request) async throws -> Response? { + // TODO: implement + "dev" + } + } } diff --git a/RPC/Sources/RPC/Handlers/TelemetryHandlers.swift b/RPC/Sources/RPC/Handlers/TelemetryHandlers.swift index baf97d90..9f031cc2 100644 --- a/RPC/Sources/RPC/Handlers/TelemetryHandlers.swift +++ b/RPC/Sources/RPC/Handlers/TelemetryHandlers.swift @@ -4,47 +4,61 @@ import Utils public enum TelemetryHandlers { public static let handlers: [any RPCHandler.Type] = [ - GetUpdate.self, Name.self, + PeersCount.self, + NetworkKey.self, ] - public static func getHandlers(source: TelemetryDataSource & ChainDataSource) -> [any RPCHandler] { + public static func getHandlers(source: TelemetryDataSource) -> [any RPCHandler] { [ - GetUpdate(source: source), Name(source: source), + PeersCount(source: source), + NetworkKey(source: source), ] } - public struct GetUpdate: RPCHandler { + public struct Name: RPCHandler { public typealias Request = VoidRequest - public typealias Response = [String: String] + public typealias Response = String - public static var method: String { "telemetry_getUpdate" } - public static var summary: String? { "Returns the latest telemetry update." } + public static var method: String { "telemetry_name" } + public static var summary: String? { "Returns the name of the node." } - private let source: TelemetryDataSource & ChainDataSource + private let source: TelemetryDataSource - init(source: TelemetryDataSource & ChainDataSource) { + init(source: TelemetryDataSource) { self.source = source } public func handle(request _: Request) async throws -> Response? { - let block = try await source.getBestBlock() - let peerCount = try await source.getPeersCount() - return [ - "chainHead": block.header.timeslot.description, - "blockHash": block.hash.description, - "peerCount": peerCount.description, - ] + try await source.name() } } - public struct Name: RPCHandler { + public struct PeersCount: RPCHandler { + public typealias Request = VoidRequest + public typealias Response = Int + + public static var method: String { "telemetry_peersCount" } + public static var summary: String? { "Returns the number of connected peers." } + + private let source: TelemetryDataSource + + init(source: TelemetryDataSource) { + self.source = source + } + + public func handle(request _: Request) async throws -> Response? { + try await source.getPeersCount() + } + } + + public struct NetworkKey: RPCHandler { public typealias Request = VoidRequest public typealias Response = String - public static var method: String { "telemetry_name" } - public static var summary: String? { "Returns the name of the node." } + public static var method: String { "telemetry_networkKey" } + public static var summary: String? { "Returns the Ed25519 key for p2p networks." } private let source: TelemetryDataSource @@ -53,7 +67,8 @@ public enum TelemetryHandlers { } public func handle(request _: Request) async throws -> Response? { - try await source.name() + // TODO: implement + nil } } } diff --git a/RPC/Sources/RPC/JSONRPC/RPCHandler.swift b/RPC/Sources/RPC/JSONRPC/RPCHandler.swift index ee51b5fa..e28fe375 100644 --- a/RPC/Sources/RPC/JSONRPC/RPCHandler.swift +++ b/RPC/Sources/RPC/JSONRPC/RPCHandler.swift @@ -15,6 +15,7 @@ public protocol RPCHandler: Sendable { static var summary: String? { get } static var requestType: any RequestParameter.Type { get } + static var requestNames: [String] { get } static var responseType: any Encodable.Type { get } } @@ -35,4 +36,8 @@ extension RPCHandler { public static var responseType: any Encodable.Type { Response.self } + + public static var requestNames: [String] { + [] + } } diff --git a/Tools/Sources/Tools/OpenRPC.swift b/Tools/Sources/Tools/OpenRPC.swift index ef753046..be6dbbf2 100644 --- a/Tools/Sources/Tools/OpenRPC.swift +++ b/Tools/Sources/Tools/OpenRPC.swift @@ -31,8 +31,8 @@ struct OpenRPC: AsyncParsableCommand { name: h.method, summary: h.summary, description: nil, - params: h.requestType.types.map { createSpecContent(type: $0) }, - result: createSpecContent(type: h.responseType), + params: h.requestType.types.enumerated().map { createSpecContent(type: $1, name: h.requestNames[safe: $0]) }, + result: createSpecContent(type: h.responseType, name: nil), examples: nil ) } @@ -60,7 +60,7 @@ func build(@JSONSchemaBuilder _ content: () -> any JSONSchemaComponent) -> any J content() } -func createSpecContent(type: Any.Type) -> SpecContent { +func createSpecContent(type: Any.Type, name: String?) -> SpecContent { // if it is optional if let type = type as? OptionalProtocol.Type { return createSpecContentInner(type: type.wrappedType, required: false) @@ -70,7 +70,7 @@ func createSpecContent(type: Any.Type) -> SpecContent { func createSpecContentInner(type: Any.Type, required: Bool) -> SpecContent { .init( - name: getName(type: type), + name: name ?? getName(type: type), summary: nil, description: nil, required: required, @@ -97,7 +97,6 @@ func getSchema(type: Any.Type) -> any JSONSchemaComponent { return type.schema } - print(type) let info = try! typeInfo(of: type) switch info.kind { case .struct, .class: