From bc375ad817faadbd1f16c65daf524a747d4ce98f Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 2 Mar 2018 11:46:59 +0000 Subject: [PATCH] First commit --- src/client/clientdispatch.nim | 110 ++++++++++ src/client/ethcalls.nim | 66 ++++++ src/rpcclient.nim | 3 + src/rpcserver.nim | 2 + src/server/asyncutils.nim | 15 ++ src/server/jsonutils.nim | 38 ++++ src/server/private/debugutils.nim | 2 + src/server/private/transportutils.nim | 43 ++++ src/server/rpcconsts.nim | 7 + src/server/rpcprocs.nim | 278 ++++++++++++++++++++++++++ src/server/serverdispatch.nim | 58 ++++++ src/server/servertypes.nim | 25 +++ tests/testclient.nim | 13 ++ tests/testserver.nim | 11 + tests/testutils.nim | 16 ++ 15 files changed, 687 insertions(+) create mode 100644 src/client/clientdispatch.nim create mode 100644 src/client/ethcalls.nim create mode 100644 src/rpcclient.nim create mode 100644 src/rpcserver.nim create mode 100644 src/server/asyncutils.nim create mode 100644 src/server/jsonutils.nim create mode 100644 src/server/private/debugutils.nim create mode 100644 src/server/private/transportutils.nim create mode 100644 src/server/rpcconsts.nim create mode 100644 src/server/rpcprocs.nim create mode 100644 src/server/serverdispatch.nim create mode 100644 src/server/servertypes.nim create mode 100644 tests/testclient.nim create mode 100644 tests/testserver.nim create mode 100644 tests/testutils.nim diff --git a/src/client/clientdispatch.nim b/src/client/clientdispatch.nim new file mode 100644 index 0000000..cf47072 --- /dev/null +++ b/src/client/clientdispatch.nim @@ -0,0 +1,110 @@ +import asyncnet, asyncdispatch, tables, json, oids, ethcalls, macros + +type + RpcClient* = ref object + socket: AsyncSocket + awaiting: Table[string, Future[Response]] + address: string + port: Port + Response* = tuple[error: bool, result: JsonNode] + +proc newRpcClient*(): RpcClient = + ## Creates a new ``RpcClient`` instance. + RpcClient( + socket: newAsyncSocket(), + awaiting: initTable[string, Future[Response]]() + ) + +proc call*(self: RpcClient, name: string, params: JsonNode): Future[Response] {.async.} = + ## Remotely calls the specified RPC method. + let id = $genOid() # TODO: finalise approach + let msg = %{"jsonrpc": %"2.0", "method": %name, "params": params, "id": %id} + await self.socket.send($msg & "\c\l") + + # This Future is completed by processMessage. + var idFut = newFuture[Response]() + self.awaiting[id] = idFut # add to awaiting responses + result = await idFut + +proc isNull(node: JsonNode): bool = node.kind == JNull + +proc processMessage(self: RpcClient, line: string) = + let node = parseJson(line) + + assert node.hasKey("jsonrpc") + assert node["jsonrpc"].str == "2.0" + assert node.hasKey("id") + assert self.awaiting.hasKey(node["id"].str) + + if node["error"].kind == JNull: + self.awaiting[node["id"].str].complete((false, node["result"])) + self.awaiting.del(node["id"].str) + else: + if not node["id"].isNull: + # If the node id is null, we cannot complete the future. + self.awaiting[node["id"].str].complete((true, node["error"])) + # TODO: Safe to delete here? + self.awaiting.del(node["id"].str) + +proc connect*(self: RpcClient, address: string, port: Port): Future[void] + +proc processData(self: RpcClient) {.async.} = + while true: + # read until no data + let line = await self.socket.recvLine() + + if line == "": + # transmission ends + self.socket.close() # TODO: Do we need to drop/reacquire sockets? + self.socket = newAsyncSocket() + break + + processMessage(self, line) + # async loop reconnection and waiting + await connect(self, self.address, self.port) + +proc connect*(self: RpcClient, address: string, port: Port) {.async.} = + await self.socket.connect(address, port) + self.address = address + self.port = port + asyncCheck processData(self) + +proc makeTemplate(name: string, params: NimNode, body: NimNode, starred: bool = false): NimNode = + # set up template AST + result = newNimNode(nnkTemplateDef) + if starred: + var nPostFix = newNimNode(nnkPostFix) + nPostFix.add ident("*"), ident(name) + result.add nPostFix + else: + result.add ident(name) + result.add newEmptyNode(), newEmptyNode(), params, newEmptyNode(), newEmptyNode(), body + +proc appendFormalParam(formalParams: NimNode, identName, typeName: string) = + # set up formal params AST + formalParams.expectKind(nnkFormalParams) + if formalParams.len == 0: formalParams.add newEmptyNode() + var identDef = newIdentDefs(ident(identName), ident(typeName)) + formalParams.add identDef + +macro generateCalls: untyped = + ## Generate templates for client calls so that: + ## client.call("web3_clientVersion", params) + ## can be written as: + ## client.web3_clientVersion(params) + result = newStmtList() + for callName in ETHEREUM_RPC_CALLS: + var + # TODO: Use macros.newProc + params = newNimNode(nnkFormalParams) + call = newNimNode(nnkCall) + body = newStmtList().add call + templ = makeTemplate(callName, params, body, true) + call.add newDotExpr(ident("client"), ident("call")), newStrLitNode(callName), ident("params") + params.add newNimNode(nnkBracketExpr).add(ident("Future"), ident("Response")) + params.appendFormalParam("client", "RpcClient") + params.appendFormalParam("params", "JsonNode") + result.add templ + +# generate all client ethereum rpc calls +generateCalls() diff --git a/src/client/ethcalls.nim b/src/client/ethcalls.nim new file mode 100644 index 0000000..7bedbb5 --- /dev/null +++ b/src/client/ethcalls.nim @@ -0,0 +1,66 @@ +const + ETHEREUM_RPC_CALLS* = [ + "web3_clientVersion", + "web3_sha3", + "net_version", + "net_peerCount", + "net_listening", + "eth_protocolVersion", + "eth_syncing", + "eth_coinbase", + "eth_mining", + "eth_hashrate", + "eth_gasPrice", + "eth_accounts", + "eth_blockNumber", + "eth_getBalance", + "eth_getStorageAt", + "eth_getTransactionCount", + "eth_getBlockTransactionCountByHash", + "eth_getBlockTransactionCountByNumber", + "eth_getUncleCountByBlockHash", + "eth_getUncleCountByBlockNumber", + "eth_getCode", + "eth_sign", + "eth_sendTransaction", + "eth_sendRawTransaction", + "eth_call", + "eth_estimateGas", + "eth_getBlockByHash", + "eth_getBlockByNumber", + "eth_getTransactionByHash", + "eth_getTransactionByBlockHashAndIndex", + "eth_getTransactionByBlockNumberAndIndex", + "eth_getTransactionReceipt", + "eth_getUncleByBlockHashAndIndex", + "eth_getUncleByBlockNumberAndIndex", + "eth_getCompilers", + "eth_compileLLL", + "eth_compileSolidity", + "eth_compileSerpent", + "eth_newFilter", + "eth_newBlockFilter", + "eth_newPendingTransactionFilter", + "eth_uninstallFilter", + "eth_getFilterChanges", + "eth_getFilterLogs", + "eth_getLogs", + "eth_getWork", + "eth_submitWork", + "eth_submitHashrate", + "db_putString", + "db_getString", + "db_putHex", + "db_getHex", + "shh_post", + "shh_version", + "shh_newIdentity", + "shh_hasIdentity", + "shh_newGroup", + "shh_addToGroup", + "shh_newFilter", + "shh_uninstallFilter", + "shh_getFilterChanges", + "shh_getMessages" + ] + diff --git a/src/rpcclient.nim b/src/rpcclient.nim new file mode 100644 index 0000000..8cbb260 --- /dev/null +++ b/src/rpcclient.nim @@ -0,0 +1,3 @@ +import client / clientdispatch +export clientdispatch + diff --git a/src/rpcserver.nim b/src/rpcserver.nim new file mode 100644 index 0000000..ba40b91 --- /dev/null +++ b/src/rpcserver.nim @@ -0,0 +1,2 @@ +import server / [servertypes, rpcconsts, serverdispatch] +export servertypes, rpcconsts, serverdispatch diff --git a/src/server/asyncutils.nim b/src/server/asyncutils.nim new file mode 100644 index 0000000..52b7e95 --- /dev/null +++ b/src/server/asyncutils.nim @@ -0,0 +1,15 @@ +import json, asyncdispatch, asyncnet, jsonutils, private / debugutils + +proc wrapReply*(id: JsonNode, value: JsonNode, error: JsonNode): JsonNode = + return %{"jsonrpc": %"2.0", "result": value, "error": error, "id": id} + +proc sendError*(client: AsyncSocket, code: int, msg: string, id: JsonNode, data: JsonNode = newJNull()) {.async.} = + ## Send error message to client + let error = %{"code": %(code), "message": %msg, "data": data} + ifDebug: echo "Send error json: ", wrapReply(newJNull(), error, id).pretty & "\c\l" + result = client.send($wrapReply(id, newJNull(), error) & "\c\l") + +proc sendJsonError*(state: RpcJsonError, client: AsyncSocket, id: JsonNode, data = newJNull()) {.async.} = + ## Send client response for invalid json state + let errMsgs = jsonErrorMessages[state] + await client.sendError(errMsgs[0], errMsgs[1], id, data) \ No newline at end of file diff --git a/src/server/jsonutils.nim b/src/server/jsonutils.nim new file mode 100644 index 0000000..52a6b0b --- /dev/null +++ b/src/server/jsonutils.nim @@ -0,0 +1,38 @@ +import rpcconsts, options, json + +type + RpcJsonError* = enum rjeInvalidJson, rjeVersionError, rjeNoMethod, rjeNoId + RpcJsonErrorContainer* = tuple[err: RpcJsonError, msg: string] + +const + jsonErrorMessages*: array[RpcJsonError, (int, string)] = + [ + (JSON_PARSE_ERROR, "Invalid JSON"), + (INVALID_REQUEST, "JSON 2.0 required"), + (INVALID_REQUEST, "No method requested"), + (INVALID_REQUEST, "No id specified") + ] + +template jsonValid*(jsonString: string, node: var JsonNode): (bool, string) = + var + valid = true + msg = "" + try: node = parseJson(line) + except: + valid = false + msg = getCurrentExceptionMsg() + (valid, msg) + +proc checkJsonErrors*(line: string, node: var JsonNode): Option[RpcJsonErrorContainer] = + ## Tries parsing line into node, if successful checks required fields + ## Returns: error state or none + let res = jsonValid(line, node) + if not res[0]: + return some((rjeInvalidJson, res[1])) + if not node.hasKey("jsonrpc"): + return some((rjeVersionError, "")) + if not node.hasKey("method"): + return some((rjeNoMethod, "")) + if not node.hasKey("id"): + return some((rjeNoId, "")) + return none(RpcJsonErrorContainer) diff --git a/src/server/private/debugutils.nim b/src/server/private/debugutils.nim new file mode 100644 index 0000000..5de259c --- /dev/null +++ b/src/server/private/debugutils.nim @@ -0,0 +1,2 @@ +template ifDebug*(actions: untyped): untyped = + when not defined(release): actions else: discard \ No newline at end of file diff --git a/src/server/private/transportutils.nim b/src/server/private/transportutils.nim new file mode 100644 index 0000000..eece466 --- /dev/null +++ b/src/server/private/transportutils.nim @@ -0,0 +1,43 @@ +from asyncdispatch import Port + +proc `$`*(port: Port): string = $int(port) + +iterator bytes*[T: SomeUnsignedInt](value: T): byte {.inline.} = + ## Traverse the bytes of a value in little endian + let argSize = sizeOf(T) + for bIdx in 0 ..< argSize: + let + shift = bIdx.uint * 8 + mask = 0xff'u64 shl shift + yield byte((value and mask) shr shift) + +iterator bytePairs*[T: SomeUnsignedInt](value: T): tuple[key: int, val: byte] {.inline.} = + let argSize = sizeOf(T) + for bIdx in 0 ..< argSize: + let + shift = bIdx.uint * 8 + mask = 0xff'u64 shl shift + yield (bIdx, byte((value and mask) shr shift)) + +template stripLeadingZeros(value: string): string = + var cidx = 0 + # ignore the last character so we retain '0' on zero value + while cidx < value.len - 1 and value[cidx] == '0': + cidx.inc + value[cidx .. ^1] + +proc encodeQuantity*(value: SomeUnsignedInt): string = + var hValue = value.toHex.stripLeadingZeros + result = "0x" & hValue + +proc encodeData*[T: SomeUnsignedInt](values: seq[T]): string = + ## Translates seq of values to hex string + let argSize = sizeOf(T) + result = newString((values.len * argSize) * 2 + 2) # reserve 2 bytes for "0x" + result[0..1] = "0x" + var cPos = 0 + for idx, value in values: + for bValue in values[idx].bytes: + result[cPos .. cPos + 1] = bValue.int.toHex(2) + cPos = cPos + 2 + diff --git a/src/server/rpcconsts.nim b/src/server/rpcconsts.nim new file mode 100644 index 0000000..2d69042 --- /dev/null +++ b/src/server/rpcconsts.nim @@ -0,0 +1,7 @@ +const + JSON_PARSE_ERROR* = -32700 + INVALID_REQUEST* = -32600 + METHOD_NOT_FOUND* = -32601 + INVALID_PARAMS* = -32602 + INTERNAL_ERROR* = -32603 + SERVER_ERROR* = -32000 \ No newline at end of file diff --git a/src/server/rpcprocs.nim b/src/server/rpcprocs.nim new file mode 100644 index 0000000..bd03426 --- /dev/null +++ b/src/server/rpcprocs.nim @@ -0,0 +1,278 @@ +import servertypes, json, asyncdispatch, macros + +macro rpc*(prc: untyped): untyped = + result = prc + let params = prc.findChild(it.kind == nnkFormalParams) + assert params != nil + for param in params.children: + if param.kind == nnkIdentDefs: + if param[1] == ident("JsonNode"): + return + var identDefs = newNimNode(nnkIdentDefs) + identDefs.add ident("params"), ident("JsonNode"), newEmptyNode() + # proc result + # check there isn't already a result type + assert params.len == 1 and params[0].kind == nnkEmpty + params[0] = ident("JsonNode") + params.add identDefs + # finally, register with server's table. + # this requires a server variable to be passed somehow + + #var body = prc.findChild(it.kind == nnkStmtList) + #body.add(quote do: + # server.register "web3_clientVersion", web3_clientVersion + #) + + +proc web3_clientVersion {.rpc.} = + return %("Nimbus-RPC-Test") + +proc web3_sha3 {.rpc.} = + discard + +proc net_version {.rpc.} = + discard + +proc net_peerCount {.rpc.} = + discard + +proc net_listening {.rpc.} = + discard + +proc eth_protocolVersion {.rpc.} = + discard + +proc eth_syncing {.rpc.} = + discard + +proc eth_coinbase {.rpc.} = + discard + +proc eth_mining {.rpc.} = + discard + +proc eth_hashrate {.rpc.} = + discard + +proc eth_gasPrice {.rpc.} = + discard + +proc eth_accounts {.rpc.} = + discard + +proc eth_blockNumber {.rpc.} = + discard + +proc eth_getBalance {.rpc.} = + discard + +proc eth_getStorageAt {.rpc.} = + discard + +proc eth_getTransactionCount {.rpc.} = + discard + +proc eth_getBlockTransactionCountByHash {.rpc.} = + discard + +proc eth_getBlockTransactionCountByNumber {.rpc.} = + discard + +proc eth_getUncleCountByBlockHash {.rpc.} = + discard + +proc eth_getUncleCountByBlockNumber {.rpc.} = + discard + +proc eth_getCode {.rpc.} = + discard + +proc eth_sign {.rpc.} = + discard + +proc eth_sendTransaction {.rpc.} = + discard + +proc eth_sendRawTransaction {.rpc.} = + discard + +proc eth_call {.rpc.} = + discard + +proc eth_estimateGas {.rpc.} = + discard + +proc eth_getBlockByHash {.rpc.} = + discard + +proc eth_getBlockByNumber {.rpc.} = + discard + +proc eth_getTransactionByHash {.rpc.} = + discard + +proc eth_getTransactionByBlockHashAndIndex {.rpc.} = + discard + +proc eth_getTransactionByBlockNumberAndIndex {.rpc.} = + discard + +proc eth_getTransactionReceipt {.rpc.} = + discard + +proc eth_getUncleByBlockHashAndIndex {.rpc.} = + discard + +proc eth_getUncleByBlockNumberAndIndex {.rpc.} = + discard + +proc eth_getCompilers {.rpc.} = + discard + +proc eth_compileLLL {.rpc.} = + discard + +proc eth_compileSolidity {.rpc.} = + discard + +proc eth_compileSerpent {.rpc.} = + discard + +proc eth_newFilter {.rpc.} = + discard + +proc eth_newBlockFilter {.rpc.} = + discard + +proc eth_newPendingTransactionFilter {.rpc.} = + discard + +proc eth_uninstallFilter {.rpc.} = + discard + +proc eth_getFilterChanges {.rpc.} = + discard + +proc eth_getFilterLogs {.rpc.} = + discard + +proc eth_getLogs {.rpc.} = + discard + +proc eth_getWork {.rpc.} = + discard + +proc eth_submitWork {.rpc.} = + discard + +proc eth_submitHashrate {.rpc.} = + discard + +proc db_putString {.rpc.} = + discard + +proc db_getString {.rpc.} = + discard + +proc db_putHex {.rpc.} = + discard + +proc db_getHex {.rpc.} = + discard + +proc shh_post {.rpc.} = + discard + +proc shh_version {.rpc.} = + discard + +proc shh_newIdentity {.rpc.} = + discard + +proc shh_hasIdentity {.rpc.} = + discard + +proc shh_newGroup {.rpc.} = + discard + +proc shh_addToGroup {.rpc.} = + discard + +proc shh_newFilter {.rpc.} = + discard + +proc shh_uninstallFilter {.rpc.} = + discard + +proc shh_getFilterChanges {.rpc.} = + discard + +proc shh_getMessages {.rpc.} = + discard + +proc registerEthereumRpcs*(server: RpcServer) = + ## Register all ethereum rpc calls to the server + # TODO: Automate this + server.register "web3_clientVersion", web3_clientVersion + server.register "web3_sha3", web3_sha3 + server.register "net_version", net_version + server.register "net_peerCount", net_peerCount + server.register "net_listening", net_listening + server.register "eth_protocolVersion", eth_protocolVersion + server.register "eth_syncing", eth_syncing + server.register "eth_coinbase", eth_coinbase + server.register "eth_mining", eth_mining + server.register "eth_hashrate", eth_hashrate + server.register "eth_gasPrice", eth_gasPrice + server.register "eth_accounts", eth_accounts + server.register "eth_blockNumber", eth_blockNumber + server.register "eth_getBalance", eth_getBalance + server.register "eth_getStorageAt", eth_getStorageAt + server.register "eth_getTransactionCount", eth_getTransactionCount + server.register "eth_getBlockTransactionCountByHash", eth_getBlockTransactionCountByHash + server.register "eth_getBlockTransactionCountByNumber", eth_getBlockTransactionCountByNumber + server.register "eth_getUncleCountByBlockHash", eth_getUncleCountByBlockHash + server.register "eth_getUncleCountByBlockNumber", eth_getUncleCountByBlockNumber + server.register "eth_getCode", eth_getCode + server.register "eth_sign", eth_sign + server.register "eth_sendTransaction", eth_sendTransaction + server.register "eth_sendRawTransaction", eth_sendRawTransaction + server.register "eth_call", eth_call + server.register "eth_estimateGas", eth_estimateGas + server.register "eth_getBlockByHash", eth_getBlockByHash + server.register "eth_getBlockByNumber", eth_getBlockByNumber + server.register "eth_getTransactionByHash", eth_getTransactionByHash + server.register "eth_getTransactionByBlockHashAndIndex", eth_getTransactionByBlockHashAndIndex + server.register "eth_getTransactionByBlockNumberAndIndex", eth_getTransactionByBlockNumberAndIndex + server.register "eth_getTransactionReceipt", eth_getTransactionReceipt + server.register "eth_getUncleByBlockHashAndIndex", eth_getUncleByBlockHashAndIndex + server.register "eth_getUncleByBlockNumberAndIndex", eth_getUncleByBlockNumberAndIndex + server.register "eth_getCompilers", eth_getCompilers + server.register "eth_compileLLL", eth_compileLLL + server.register "eth_compileSolidity", eth_compileSolidity + server.register "eth_compileSerpent", eth_compileSerpent + server.register "eth_newFilter", eth_newFilter + server.register "eth_newBlockFilter", eth_newBlockFilter + server.register "eth_newPendingTransactionFilter", eth_newPendingTransactionFilter + server.register "eth_uninstallFilter", eth_uninstallFilter + server.register "eth_getFilterChanges", eth_getFilterChanges + server.register "eth_getFilterLogs", eth_getFilterLogs + server.register "eth_getLogs", eth_getLogs + server.register "eth_getWork", eth_getWork + server.register "eth_submitWork", eth_submitWork + server.register "eth_submitHashrate", eth_submitHashrate + server.register "db_putString", db_putString + server.register "db_getString", db_getString + server.register "db_putHex", db_putHex + server.register "db_getHex", db_getHex + server.register "shh_post", shh_post + server.register "shh_version", shh_version + server.register "shh_newIdentity", shh_newIdentity + server.register "shh_hasIdentity", shh_hasIdentity + server.register "shh_newGroup", shh_newGroup + server.register "shh_addToGroup", shh_addToGroup + server.register "shh_newFilter", shh_newFilter + server.register "shh_uninstallFilter", shh_uninstallFilter + server.register "shh_getFilterChanges", shh_getFilterChanges + server.register "shh_getMessages", shh_getMessages + diff --git a/src/server/serverdispatch.nim b/src/server/serverdispatch.nim new file mode 100644 index 0000000..bf6cda3 --- /dev/null +++ b/src/server/serverdispatch.nim @@ -0,0 +1,58 @@ +import asyncdispatch, asyncnet, json, tables, strutils, + servertypes, rpcconsts, private / [transportutils, debugutils], jsonutils, asyncutils, rpcprocs, + options + +proc processMessage(server: RpcServer, client: AsyncSocket, line: string) {.async.} = + var + node: JsonNode + jsonErrorState = checkJsonErrors(line, node) # set up node and/or flag errors + if jsonErrorState.isSome: + let errState = jsonErrorState.get + var id: JsonNode + if errState.err == rjeInvalidJson: id = newJNull() # id cannot be retrieved + else: id = node["id"] + await errState.err.sendJsonError(client, id, %errState.msg) + else: + let + methodName = node["method"].str + id = node["id"] + + if not server.procs.hasKey(methodName): + await client.sendError(METHOD_NOT_FOUND, "Method not found", id, %(methodName & " is not a registered method.")) + else: + # TODO: Performance or other effects from NOT calling rpc with await? + let callRes = server.procs[methodName](node["params"]) + await client.send($wrapReply(id, callRes, newJNull()) & "\c\l") + +proc processClient(server: RpcServer, client: AsyncSocket) {.async.} = + while true: + let line = await client.recvLine() + if line == "": + # Disconnected. + client.close() + echo server.port, " request with no data" + break + + ifDebug: echo "Process client: ", server.port, ":" & line + + let fut = processMessage(server, client, line) + await fut + if fut.failed: + if fut.readError of RpcProcError: + # This error signifies that the proc wants us to respond with a custom + # error object. + let err = fut.readError.RpcProcError + await client.sendError(err.code, err.msg, err.data) + else: + await client.sendError(-32000, "Error", %getCurrentExceptionMsg()) + +proc serve*(server: RpcServer) {.async.} = + server.registerEthereumRpcs + server.socket.bindAddr(server.port, server.address) + server.socket.listen() + + while true: + let client = await server.socket.accept() + asyncCheck server.processClient(client) + + diff --git a/src/server/servertypes.nim b/src/server/servertypes.nim new file mode 100644 index 0000000..a133823 --- /dev/null +++ b/src/server/servertypes.nim @@ -0,0 +1,25 @@ +import asyncdispatch, asyncnet, json, tables + +type + RpcProc* = proc (params: JsonNode): JsonNode + + RpcServer* = ref object + socket*: AsyncSocket + port*: Port + address*: string + procs*: TableRef[string, RpcProc] + + RpcProcError* = ref object of Exception + code*: int + data*: JsonNode + +proc newRpcServer*(address: string, port: Port = Port(8545)): RpcServer = + RpcServer( + socket: newAsyncSocket(), + port: port, + address: address, + procs: newTable[string, RpcProc]() + ) + +proc register*(server: RpcServer, name: string, rpc: RpcProc) = + server.procs[name] = rpc \ No newline at end of file diff --git a/tests/testclient.nim b/tests/testclient.nim new file mode 100644 index 0000000..65a9e4e --- /dev/null +++ b/tests/testclient.nim @@ -0,0 +1,13 @@ +import ../src/rpcclient, asyncdispatch, json + +when isMainModule: + proc main {.async.} = + var client = newRpcClient() + await client.connect("localhost", Port(8545)) + var + response: Response + + for i in 0..1000: + response = waitFor client.web3_clientVersion(newJNull()) + waitFor main() + echo "Finished." \ No newline at end of file diff --git a/tests/testserver.nim b/tests/testserver.nim new file mode 100644 index 0000000..dbd98e8 --- /dev/null +++ b/tests/testserver.nim @@ -0,0 +1,11 @@ +import ../src/rpcserver, asyncdispatch + +when isMainModule: + echo "Initialising server..." + # create on localhost, default port + var srv = newRpcServer("") + echo "Server started." + asyncCheck srv.serve() + runForever() + + echo "Server stopped." \ No newline at end of file diff --git a/tests/testutils.nim b/tests/testutils.nim new file mode 100644 index 0000000..57f2986 --- /dev/null +++ b/tests/testutils.nim @@ -0,0 +1,16 @@ +import strutils, ../src/server/private/transportutils, unittest + +suite "Encoding": + test "Encode quantity": + check 0.encodeQuantity == "0x0" + check 0x1000.encodeQuantity == "0x1000" + test "Encode data": + var i = 0 + for b in bytes(0x07_06_05_04_03_02_01_00'u64): + check b == i.byte + i.inc + test "Encode data pairs": + for i, b in bytePairs(0x07_06_05_04_03_02_01_00'u64): + check b == i.byte + + \ No newline at end of file