From 34b9c16f93d6205ad7d44fd41297b73e1493682f Mon Sep 17 00:00:00 2001 From: shawn Date: Mon, 29 Jul 2024 23:40:26 +0800 Subject: [PATCH] RPC setup --- RPC/Package.resolved | 222 ++++++++++++++++++++++++++++ RPC/Package.swift | 37 +++++ RPC/Sources/RPC/RPC.swift | 17 +++ RPC/Sources/RPC/RPCController.swift | 76 ++++++++++ RPC/Sources/RPC/RPCModels.swift | 92 ++++++++++++ 5 files changed, 444 insertions(+) create mode 100644 RPC/Package.resolved create mode 100644 RPC/Package.swift create mode 100644 RPC/Sources/RPC/RPC.swift create mode 100644 RPC/Sources/RPC/RPCController.swift create mode 100644 RPC/Sources/RPC/RPCModels.swift diff --git a/RPC/Package.resolved b/RPC/Package.resolved new file mode 100644 index 00000000..7242c151 --- /dev/null +++ b/RPC/Package.resolved @@ -0,0 +1,222 @@ +{ + "originHash" : "fcf25dc9b4931a1d81f53cd6efcf7914dd698adfbfd85aebcc2589b96787a187", + "pins" : [ + { + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "0ae99db85b2b9d1e79b362bd31fd1ffe492f7c47", + "version" : "1.21.2" + } + }, + { + "identity" : "async-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/async-kit.git", + "state" : { + "revision" : "7ece208cd401687641c88367a00e3ea2b04311f1", + "version" : "1.19.0" + } + }, + { + "identity" : "blake2.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tesseract-one/Blake2.swift.git", + "state" : { + "revision" : "29c55c8fe42d6661e5a32cc5bbbad1fff64fd01e", + "version" : "0.2.0" + } + }, + { + "identity" : "console-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/console-kit.git", + "state" : { + "revision" : "9f7932f22ab6f64aafadc14491e694179b7d0f6f", + "version" : "4.14.3" + } + }, + { + "identity" : "multipart-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/multipart-kit.git", + "state" : { + "revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68", + "version" : "4.7.0" + } + }, + { + "identity" : "routing-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/routing-kit.git", + "state" : { + "revision" : "8c9a227476555c55837e569be71944e02a056b72", + "version" : "4.9.1" + } + }, + { + "identity" : "scalecodec.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AcalaNetwork/ScaleCodec.swift.git", + "state" : { + "branch" : "main", + "revision" : "dac3e7161de34c60c82794d031de0231b5a5746e" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", + "version" : "1.1.2" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "46072478ca365fe48370993833cb22de9b41567f", + "version" : "3.5.2" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" + } + }, + { + "identity" : "swift-metrics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-metrics.git", + "state" : { + "revision" : "e0165b53d49b413dd987526b641e05e246782685", + "version" : "2.5.0" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "fc79798d5a150d61361a27ce0c51169b889e23de", + "version" : "2.68.0" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "05c36b57453d23ea63785d58a7dbc7b70ba1745e", + "version" : "1.23.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "a0224f3d20438635dd59c9fcc593520d80d131d0", + "version" : "1.33.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "2b09805797f21c380f7dc9bedaab3157c5508efb", + "version" : "2.27.0" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "38ac8221dd20674682148d6451367f89c2652980", + "version" : "1.21.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "6a9e38e7bd22a3b8ba80bddf395623cf68f57807", + "version" : "1.3.1" + } + }, + { + "identity" : "tuples.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tesseract-one/Tuples.swift.git", + "state" : { + "revision" : "4d2cf7c64443cdf4df833d0bedd767bf9dbc49d9", + "version" : "0.1.3" + } + }, + { + "identity" : "vapor", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/vapor.git", + "state" : { + "revision" : "a823735db57b46100b0c61cdfc5a08525b1e7cad", + "version" : "4.102.1" + } + }, + { + "identity" : "websocket-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/websocket-kit.git", + "state" : { + "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740", + "version" : "2.15.0" + } + } + ], + "version" : 3 +} diff --git a/RPC/Package.swift b/RPC/Package.swift new file mode 100644 index 00000000..cd367b9f --- /dev/null +++ b/RPC/Package.swift @@ -0,0 +1,37 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "RPC", + platforms: [ + .macOS(.v14), + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "RPC", + targets: ["RPC"] + ), + ], + dependencies: [ + .package(path: "../Blockchain"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.102.1"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "RPC", + dependencies: [ + "Blockchain", + .product(name: "Vapor", package: "vapor"), + ] + ), + .testTarget( + name: "RPCTests", + dependencies: ["RPC"] + ), + ] +) diff --git a/RPC/Sources/RPC/RPC.swift b/RPC/Sources/RPC/RPC.swift new file mode 100644 index 00000000..7b45c1e6 --- /dev/null +++ b/RPC/Sources/RPC/RPC.swift @@ -0,0 +1,17 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book +import Vapor + +public func startServer() throws { + let env = try Environment.detect() + let app = Application(env) + defer { app.shutdown() } + try configure(app) + try app.run() +} + +public func configure(_ app: Application) throws { + // Register routes + let rpcController = RPCController() + try app.register(collection: rpcController) +} diff --git a/RPC/Sources/RPC/RPCController.swift b/RPC/Sources/RPC/RPCController.swift new file mode 100644 index 00000000..7e255056 --- /dev/null +++ b/RPC/Sources/RPC/RPCController.swift @@ -0,0 +1,76 @@ +import Blockchain +import Vapor + +final class RPCController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + // HTTP JSON-RPC route + routes.post("rpc", use: handleRPCRequest) + + // WebSocket JSON-RPC route + routes.webSocket("ws", onUpgrade: handleWebSocket) + } + + func handleRPCRequest(_ req: Request) -> EventLoopFuture { + do { + let rpcRequest = try req.content.decode(RPCRequest.self) + // Handle the JSON-RPC request + let result = try RPCController.handleMethod(rpcRequest.method, params: rpcRequest.params) + let rpcResponse = RPCResponse(jsonrpc: "2.0", result: AnyContent(result ?? ""), error: nil, id: rpcRequest.id) + return try req.eventLoop.makeSucceededFuture(Response(status: .ok, body: .init(data: JSONEncoder().encode(rpcResponse)))) + } catch { + let rpcError = RPCError(code: -32600, message: "Invalid Request") + let rpcResponse = RPCResponse(jsonrpc: "2.0", result: nil, error: rpcError, id: nil) + return req.eventLoop.makeSucceededFuture(Response(status: .badRequest, body: .init(data: try! JSONEncoder().encode(rpcResponse)))) + } + } + + func handleWebSocket(req _: Request, ws: WebSocket) { + ws.onText { ws, text in + Task { + await RPCController.processWebSocketRequest(ws, text: text) + } + } + } + + private static func processWebSocketRequest(_ ws: WebSocket, text: String) async { + do { + let rpcRequest = try JSONDecoder().decode(RPCRequest.self, from: Data(text.utf8)) + let result = try handleMethod(rpcRequest.method, params: rpcRequest.params?.value) + let rpcResponse = RPCResponse(jsonrpc: "2.0", result: AnyContent(result ?? ""), error: nil, id: rpcRequest.id) + let responseData = try JSONEncoder().encode(rpcResponse) + try await ws.send(String(data: responseData, encoding: .utf8)!) + } catch { + let rpcError = RPCError(code: -32600, message: "Invalid Request") + let rpcResponse = RPCResponse(jsonrpc: "2.0", result: nil, error: rpcError, id: nil) + + do { + let responseData = try JSONEncoder().encode(rpcResponse) + try await ws.send(String(data: responseData, encoding: .utf8)!) + } catch { + // Handle the error appropriately, e.g., log it + print("Failed to send WebSocket error response: \(error)") + } + } + } + + static func handleChainGetBlock(params _: BlockParams?) -> CodableBlock? { + // Fetch the block by hash or number + nil + } + + static func handleChainGetHeader(params _: HeaderParams?) -> CodableHeader? { + // Fetch the header by hash or number + nil + } + + static func handleMethod(_ method: String, params: Any?) throws -> Any? { + switch method { + case "chain_getBlock": + return handleChainGetBlock(params: params as? BlockParams) + case "chain_getHeader": + return handleChainGetHeader(params: params as? HeaderParams) + default: + throw RPCError(code: -32601, message: "Method not found") + } + } +} diff --git a/RPC/Sources/RPC/RPCModels.swift b/RPC/Sources/RPC/RPCModels.swift new file mode 100644 index 00000000..20c4edf4 --- /dev/null +++ b/RPC/Sources/RPC/RPCModels.swift @@ -0,0 +1,92 @@ +import Blockchain +import Utils +import Vapor + +public struct RPCRequest: Content { + public let jsonrpc: String + public let method: String + public let params: T? + public let id: Int? +} + +public struct RPCResponse: Content { + public let jsonrpc: String + public let result: T? + public let error: RPCError? + public let id: Int? +} + +public struct RPCError: Content, Error { + public let code: Int + public let message: String +} + +public struct RPCParams: Content { + // Generic params structure if needed +} + +public struct RPCResult: Content { + // Generic result structure if needed +} + +public struct BlockParams: Content { + let blockHash: String? +} + +public struct HeaderParams: Content { + let blockHash: String? +} + +public struct CodableBlock: Codable { + let property1: String + let property2: String + // Add all properties from the original Block type + + init(from block: Block) { + property1 = block.header.parentHash.description + property2 = block.header.extrinsicsRoot.description + // Initialize all properties from the original Block type + } +} + +public struct CodableHeader: Codable { + let property1: String + let property2: String + + init(from header: Header) { + property1 = header.parentHash.description + property2 = header.extrinsicsRoot.description + } +} + +public struct AnyContent: Content { + public let value: Any + + public init(_ value: Any) { + self.value = value + } + + public func encode(to encoder: Encoder) throws { + if let value = value as? CodableBlock { + try value.encode(to: encoder) + } else if let value = value as? CodableHeader { + try value.encode(to: encoder) + } else { + var container = encoder.singleValueContainer() + try container.encodeNil() + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + // Assuming a common set of types we expect + if let value = try? container.decode(CodableBlock.self) { + self.value = value + } else if let value = try? container.decode(CodableHeader.self) { + self.value = value + } else { + value = "" + } + } +}