First commit
This commit is contained in:
parent
00852b774b
commit
bc375ad817
|
@ -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()
|
|
@ -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"
|
||||
]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import client / clientdispatch
|
||||
export clientdispatch
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import server / [servertypes, rpcconsts, serverdispatch]
|
||||
export servertypes, rpcconsts, serverdispatch
|
|
@ -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)
|
|
@ -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)
|
|
@ -0,0 +1,2 @@
|
|||
template ifDebug*(actions: untyped): untyped =
|
||||
when not defined(release): actions else: discard
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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
|
|
@ -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."
|
|
@ -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."
|
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue