diff --git a/execution_chain/rpc/common.nim b/execution_chain/rpc/common.nim index 6a153278d5..80e83c150d 100644 --- a/execution_chain/rpc/common.nim +++ b/execution_chain/rpc/common.nim @@ -20,7 +20,7 @@ import web3/conversions, beacon_chain/process_state -from json_rpc/server import RpcServer, rpc +from json_rpc/server import RpcServer, rpc, rpcContext {.push raises: [].} @@ -51,98 +51,100 @@ type network*: PeerNetworkInfo protocols*: JsonNode # Protocol-specific data -NodePorts.useDefaultSerializationIn JrpcConv -NodeInfo.useDefaultSerializationIn JrpcConv -PeerNetworkInfo.useDefaultSerializationIn JrpcConv -PeerInfo.useDefaultSerializationIn JrpcConv +NodePorts.useDefaultSerializationIn EthJson +NodeInfo.useDefaultSerializationIn EthJson +PeerNetworkInfo.useDefaultSerializationIn EthJson +PeerInfo.useDefaultSerializationIn EthJson -JrpcConv.automaticSerialization(int, true) +EthJson.automaticSerialization(int, true) proc setupCommonRpc*(node: EthereumNode, config: ExecutionClientConf, server: RpcServer) = - server.rpc("web3_clientVersion") do() -> string: - result = config.agentString + server.rpcContext(EthJson): + rpc("web3_clientVersion") do() -> string: + result = config.agentString - server.rpc("web3_sha3") do(data: seq[byte]) -> Hash32: - result = keccak256(data) + rpc("web3_sha3") do(data: seq[byte]) -> Hash32: + result = keccak256(data) - server.rpc("net_version") do() -> string: - result = $config.networkId + rpc("net_version") do() -> string: + result = $config.networkId - server.rpc("net_listening") do() -> bool: - let numPeers = node.numPeers - result = numPeers < config.maxPeers + rpc("net_listening") do() -> bool: + let numPeers = node.numPeers + result = numPeers < config.maxPeers - server.rpc("net_peerCount") do() -> Quantity: - let peerCount = uint node.numPeers - result = w3Qty(peerCount) + rpc("net_peerCount") do() -> Quantity: + let peerCount = uint node.numPeers + result = w3Qty(peerCount) proc setupAdminRpc*(nimbus: NimbusNode, config: ExecutionClientConf, server: RpcServer) = let node = nimbus.ethNode - server.rpc("admin_nodeInfo") do() -> NodeInfo: - let - enode = toENode(node) - nodeId = toNodeId(node.keys.pubkey) - nodeInfo = NodeInfo( - id: nodeId.toHex, - name: config.agentString, - enode: $enode, - ip: $enode.address.ip, - ports: NodePorts( - discovery: int(enode.address.udpPort), - listener: int(enode.address.tcpPort) - ) - ) - - return nodeInfo - - server.rpc("admin_addPeer") do(enode: string) -> bool: - var res = ENode.fromString(enode) - if res.isOk: - asyncSpawn node.connectToNode(res.get()) - return true - # Weird it is, but when addPeer fails, the calee expect - # invalid params `-32602`(kurtosis test) - raise (ref ApplicationError)(code: -32602, msg: "Invalid ENode") - - server.rpc("admin_peers") do() -> seq[PeerInfo]: - var peers: seq[PeerInfo] - for peer in node.peerPool.peers: - if peer.connectionState == Connected: - let - nodeId = peer.remote.id - clientId = peer.clientId - enode = $peer.remote.node - remoteIp = $peer.remote.node.address.ip - remoteTcpPort = $peer.remote.node.address.tcpPort - localEnode = toENode(node) - localIp = $localEnode.address.ip - localTcpPort = $localEnode.address.tcpPort - caps = node.capabilities.mapIt(it.name & "/" & $it.version) - - # Create protocols object with version info - var protocolsObj = newJObject() - for capability in node.capabilities: - protocolsObj[capability.name] = %*{"version": capability.version} - - let peerInfo = PeerInfo( - caps: caps, - enode: enode, + server.rpcContext(EthJson): + rpc("admin_nodeInfo") do() -> NodeInfo: + let + enode = toENode(node) + nodeId = toNodeId(node.keys.pubkey) + nodeInfo = NodeInfo( id: nodeId.toHex, - name: clientId, - network: PeerNetworkInfo( - inbound: peer.inbound, - localAddress: localIp & ":" & localTcpPort, - remoteAddress: remoteIp & ":" & remoteTcpPort, - `static`: false, # TODO: implement static peer tracking - trusted: false # TODO: implement trusted peer tracking - ), - protocols: protocolsObj + name: config.agentString, + enode: $enode, + ip: $enode.address.ip, + ports: NodePorts( + discovery: int(enode.address.udpPort), + listener: int(enode.address.tcpPort) + ) ) - peers.add(peerInfo) - - return peers - server.rpc("admin_quit") do() -> string: - ProcessState.scheduleStop("admin_quit") - result = "EXITING" + return nodeInfo + + rpc("admin_addPeer") do(enode: string) -> bool: + var res = ENode.fromString(enode) + if res.isOk: + asyncSpawn node.connectToNode(res.get()) + return true + # Weird it is, but when addPeer fails, the calee expect + # invalid params `-32602`(kurtosis test) + raise (ref ApplicationError)(code: -32602, msg: "Invalid ENode") + + rpc("admin_peers") do() -> seq[PeerInfo]: + var peers: seq[PeerInfo] + for peer in node.peerPool.peers: + if peer.connectionState == Connected: + let + nodeId = peer.remote.id + clientId = peer.clientId + enode = $peer.remote.node + remoteIp = $peer.remote.node.address.ip + remoteTcpPort = $peer.remote.node.address.tcpPort + localEnode = toENode(node) + localIp = $localEnode.address.ip + localTcpPort = $localEnode.address.tcpPort + caps = node.capabilities.mapIt(it.name & "/" & $it.version) + + # Create protocols object with version info + var protocolsObj = newJObject() + for capability in node.capabilities: + protocolsObj[capability.name] = %*{"version": capability.version} + + let peerInfo = PeerInfo( + caps: caps, + enode: enode, + id: nodeId.toHex, + name: clientId, + network: PeerNetworkInfo( + inbound: peer.inbound, + localAddress: localIp & ":" & localTcpPort, + remoteAddress: remoteIp & ":" & remoteTcpPort, + `static`: false, # TODO: implement static peer tracking + trusted: false # TODO: implement trusted peer tracking + ), + protocols: protocolsObj + ) + peers.add(peerInfo) + + return peers + + rpc("admin_quit") do() -> string: + ProcessState.scheduleStop("admin_quit") + result = "EXITING" diff --git a/execution_chain/rpc/debug.nim b/execution_chain/rpc/debug.nim index 7545e15df3..cd65cee1c0 100644 --- a/execution_chain/rpc/debug.nim +++ b/execution_chain/rpc/debug.nim @@ -24,16 +24,15 @@ import ../core/chain/forked_chain, ../stateless/witness_types -type - BadBlock = object - `block`: BlockObject - generatedBlockAccessList: Opt[BlockAccessList] - hash: Hash32 - rlp: seq[byte] +type BadBlock = object + `block`: BlockObject + generatedBlockAccessList: Opt[BlockAccessList] + hash: Hash32 + rlp: seq[byte] -BadBlock.useDefaultSerializationIn JrpcConv +BadBlock.useDefaultSerializationIn EthJson -ExecutionWitness.useDefaultSerializationIn JrpcConv +ExecutionWitness.useDefaultSerializationIn EthJson #type # TraceOptions = object @@ -43,7 +42,7 @@ ExecutionWitness.useDefaultSerializationIn JrpcConv # disableState: Opt[bool] # disableStateDiff: Opt[bool] -# TraceOptions.useDefaultSerializationIn JrpcConv +# TraceOptions.useDefaultSerializationIn EthJson # proc isTrue(x: Opt[bool]): bool = # result = x.isSome and x.get() == true @@ -73,7 +72,9 @@ proc headerFromTag(chain: ForkedChainRef, blockTag: BlockTag): Result[Header, st let blockNum = base.BlockNumber blockTag.number return chain.headerByNumber(blockNum) -proc getExecutionWitness*(chain: ForkedChainRef, blockHash: Hash32): Result[ExecutionWitness, string] = +proc getExecutionWitness*( + chain: ForkedChainRef, blockHash: Hash32 +): Result[ExecutionWitness, string] = let txFrame = chain.txFrame(blockHash).txFrameBegin() defer: txFrame.dispose() @@ -81,7 +82,8 @@ proc getExecutionWitness*(chain: ForkedChainRef, blockHash: Hash32): Result[Exec let witness = txFrame.getWitness(blockHash).valueOr: return err("Witness not found") - var executionWitness = ExecutionWitness.init(state = witness.state, keys = witness.keys) + var executionWitness = + ExecutionWitness.init(state = witness.state, keys = witness.keys) for codeHash in witness.codeHashes: let code = txFrame.getCodeByHash(codeHash).valueOr: return err("Code not found") @@ -99,184 +101,190 @@ proc setupDebugRpc*(com: CommonRef, txPool: TxPoolRef, server: RpcServer) = # chainDB = com.db chain = txPool.chain - # server.rpc("debug_traceTransaction") do(data: Hash32, options: Opt[TraceOptions]) -> JsonNode: - # ## The traceTransaction debugging method will attempt to run the transaction in the exact - # ## same manner as it was executed on the network. It will replay any transaction that may - # ## have been executed prior to this one before it will finally attempt to execute the - # ## transaction that corresponds to the given hash. - # ## - # ## In addition to the hash of the transaction you may give it a secondary optional argument, - # ## which specifies the options for this specific call. The possible options are: - # ## - # ## * disableStorage: BOOL. Setting this to true will disable storage capture (default = false). - # ## * disableMemory: BOOL. Setting this to true will disable memory capture (default = false). - # ## * disableStack: BOOL. Setting this to true will disable stack capture (default = false). - # ## * disableState: BOOL. Setting this to true will disable state trie capture (default = false). - # let - # txHash = data - # txDetails = chainDB.getTransactionKey(txHash) - # header = chainDB.getBlockHeader(txDetails.blockNumber) - # transactions = chainDB.getTransactions(header.txRoot) - # flags = traceOptionsToFlags(options) - - # traceTransaction(com, header, transactions, txDetails.index, flags) - - # server.rpc("debug_dumpBlockStateByNumber") do(quantityTag: BlockTag) -> JsonNode: - # ## Retrieves the state that corresponds to the block number and returns - # ## a list of accounts (including storage and code). - # ## - # ## quantityTag: integer of a block number, or the string "earliest", - # ## "latest" or "pending", as in the default block parameter. - # var - # header = chainDB.headerFromTag(quantityTag) - # blockHash = chainDB.getBlockHash(header.number) - # body = chainDB.getBlockBody(blockHash) - - # dumpBlockState(com, EthBlock.init(move(header), move(body))) - - # server.rpc("debug_dumpBlockStateByHash") do(data: Hash32) -> JsonNode: - # ## Retrieves the state that corresponds to the block number and returns - # ## a list of accounts (including storage and code). - # ## - # ## data: Hash of a block. - # var - # h = data - # blk = chainDB.getEthBlock(h) - - # dumpBlockState(com, blk) - - # server.rpc("debug_traceBlockByNumber") do(quantityTag: BlockTag, options: Opt[TraceOptions]) -> JsonNode: - # ## The traceBlock method will return a full stack trace of all invoked opcodes of all transaction - # ## that were included included in this block. - # ## - # ## quantityTag: integer of a block number, or the string "earliest", - # ## "latest" or "pending", as in the default block parameter. - # ## options: see debug_traceTransaction - # var - # header = chainDB.headerFromTag(quantityTag) - # blockHash = chainDB.getBlockHash(header.number) - # body = chainDB.getBlockBody(blockHash) - # flags = traceOptionsToFlags(options) - - # traceBlock(com, EthBlock.init(move(header), move(body)), flags) - - # server.rpc("debug_traceBlockByHash") do(data: Hash32, options: Opt[TraceOptions]) -> JsonNode: - # ## The traceBlock method will return a full stack trace of all invoked opcodes of all transaction - # ## that were included included in this block. - # ## - # ## data: Hash of a block. - # ## options: see debug_traceTransaction - # var - # h = data - # header = chainDB.getBlockHeader(h) - # blockHash = chainDB.getBlockHash(header.number) - # body = chainDB.getBlockBody(blockHash) - # flags = traceOptionsToFlags(options) - - # traceBlock(com, EthBlock.init(move(header), move(body)), flags) - - # server.rpc("debug_setHead") do(quantityTag: BlockTag) -> bool: - # ## Sets the current head of the local chain by block number. - # ## Note, this is a destructive action and may severely damage your chain. - # ## Use with extreme caution. - # let - # header = chainDB.headerFromTag(quantityTag) - # chainDB.setHead(header) - - # server.rpc("debug_getRawBlock") do(quantityTag: BlockTag) -> seq[byte]: - # ## Returns an RLP-encoded block. - # var - # header = chainDB.headerFromTag(quantityTag) - # blockHash = chainDB.getBlockHash(header.number) - # body = chainDB.getBlockBody(blockHash) - - # rlp.encode(EthBlock.init(move(header), move(body))) - - # server.rpc("debug_getRawHeader") do(quantityTag: BlockTag) -> seq[byte]: - # ## Returns an RLP-encoded header. - # let header = chainDB.headerFromTag(quantityTag) - # rlp.encode(header) - - # server.rpc("debug_getRawReceipts") do(quantityTag: BlockTag) -> seq[seq[byte]]: - # ## Returns an array of EIP-2718 binary-encoded receipts. - # let header = chainDB.headerFromTag(quantityTag) - # for receipt in chainDB.getReceipts(header.receiptsRoot): - # result.add rlp.encode(receipt) - - # server.rpc("debug_getRawTransaction") do(data: Hash32) -> seq[byte]: - # ## Returns an EIP-2718 binary-encoded transaction. - # let txHash = data - # let res = txPool.getItem(txHash) - # if res.isOk: - # return rlp.encode(res.get().tx) - - # let txDetails = chainDB.getTransactionKey(txHash) - # if txDetails.index < 0: - # raise newException(ValueError, "Transaction not found " & data.toHex) - - # let header = chainDB.getBlockHeader(txDetails.blockNumber) - # var tx: Transaction - # if chainDB.getTransaction(header.txRoot, txDetails.index, tx): - # return rlp.encode(tx) - - # raise newException(ValueError, "Transaction not found " & data.toHex) - - server.rpc("debug_executionWitness") do(quantityTag: BlockTag) -> ExecutionWitness: - ## Returns an execution witness for the given block number. - let header = chain.headerFromTag(quantityTag).valueOr: - raise newException(ValueError, "Header not found") - - chain.getExecutionWitness(header.computeBlockHash()).valueOr: - raise newException(ValueError, error) - - server.rpc("debug_executionWitnessByBlockHash") do(blockHash: Hash32) -> ExecutionWitness: - ## Returns an execution witness for the given block hash. - chain.getExecutionWitness(blockHash).valueOr: - raise newException(ValueError, error) - - server.rpc("debug_getHeaderByNumber") do(blockTag: BlockTag) -> string: - ## Returns the rlp encoded block header in hex for the given block number / tag. - # Note: When proposing this method for inclusion in the JSON-RPC spec, - # consider returning a header JSON object instead of RLP. Likely to be more accepted. - let header = chain.headerFromTag(blockTag).valueOr: - raise newException(ValueError, error) - - rlp.encode(header).to0xHex() - - server.rpc("debug_getBadBlocks") do() -> seq[BadBlock]: - ## Returns a list of the most recently processed bad blocks. - var badBlocks: seq[BadBlock] - - let blks = chain.getBadBlocks() - for b in blks: - let - (blk, bal) = b - blkHash = blk.header.computeBlockHash() - - badBlocks.add BadBlock( - `block`: populateBlockObject( - blkHash, blk, chain.getTotalDifficulty(blkHash, blk.header), fullTx = true), - generatedBlockAccessList: bal.map(proc (bal: auto): auto = bal[]), - hash: blkHash, - rlp: rlp.encode(blk)) - - badBlocks - - # We should remove these two block access list endpoints at some point - # or at least update them to return the BAL in RLP format since the same - # functionality is now provied by eth_getBlockAccessListByBlockNumber - # and eth_getBlockAccessListByBlockHash - - server.rpc("debug_getBlockAccessList") do(quantityTag: BlockTag) -> BlockAccessList: - ## Returns a block access list for the given block number. - let header = chain.headerFromTag(quantityTag).valueOr: - raise newException(ValueError, "Header not found") - - chain.getBlockAccessList(header.computeBlockHash()).valueOr: - raise newException(ValueError, "Block access list not found") - - server.rpc("debug_getBlockAccessListByBlockHash") do(blockHash: Hash32) -> BlockAccessList: - ## Returns a block access list for the given block hash. - - chain.getBlockAccessList(blockHash).valueOr: - raise newException(ValueError, "Block access list not found") + server.rpcContext(EthJson): + # rpc("debug_traceTransaction") do(data: Hash32, options: Opt[TraceOptions]) -> JsonNode: + # ## The traceTransaction debugging method will attempt to run the transaction in the exact + # ## same manner as it was executed on the network. It will replay any transaction that may + # ## have been executed prior to this one before it will finally attempt to execute the + # ## transaction that corresponds to the given hash. + # ## + # ## In addition to the hash of the transaction you may give it a secondary optional argument, + # ## which specifies the options for this specific call. The possible options are: + # ## + # ## * disableStorage: BOOL. Setting this to true will disable storage capture (default = false). + # ## * disableMemory: BOOL. Setting this to true will disable memory capture (default = false). + # ## * disableStack: BOOL. Setting this to true will disable stack capture (default = false). + # ## * disableState: BOOL. Setting this to true will disable state trie capture (default = false). + # let + # txHash = data + # txDetails = chainDB.getTransactionKey(txHash) + # header = chainDB.getBlockHeader(txDetails.blockNumber) + # transactions = chainDB.getTransactions(header.txRoot) + # flags = traceOptionsToFlags(options) + + # traceTransaction(com, header, transactions, txDetails.index, flags) + + # rpc("debug_dumpBlockStateByNumber") do(quantityTag: BlockTag) -> JsonNode: + # ## Retrieves the state that corresponds to the block number and returns + # ## a list of accounts (including storage and code). + # ## + # ## quantityTag: integer of a block number, or the string "earliest", + # ## "latest" or "pending", as in the default block parameter. + # var + # header = chainDB.headerFromTag(quantityTag) + # blockHash = chainDB.getBlockHash(header.number) + # body = chainDB.getBlockBody(blockHash) + + # dumpBlockState(com, EthBlock.init(move(header), move(body))) + + # rpc("debug_dumpBlockStateByHash") do(data: Hash32) -> JsonNode: + # ## Retrieves the state that corresponds to the block number and returns + # ## a list of accounts (including storage and code). + # ## + # ## data: Hash of a block. + # var + # h = data + # blk = chainDB.getEthBlock(h) + + # dumpBlockState(com, blk) + + # rpc("debug_traceBlockByNumber") do(quantityTag: BlockTag, options: Opt[TraceOptions]) -> JsonNode: + # ## The traceBlock method will return a full stack trace of all invoked opcodes of all transaction + # ## that were included included in this block. + # ## + # ## quantityTag: integer of a block number, or the string "earliest", + # ## "latest" or "pending", as in the default block parameter. + # ## options: see debug_traceTransaction + # var + # header = chainDB.headerFromTag(quantityTag) + # blockHash = chainDB.getBlockHash(header.number) + # body = chainDB.getBlockBody(blockHash) + # flags = traceOptionsToFlags(options) + + # traceBlock(com, EthBlock.init(move(header), move(body)), flags) + + # rpc("debug_traceBlockByHash") do(data: Hash32, options: Opt[TraceOptions]) -> JsonNode: + # ## The traceBlock method will return a full stack trace of all invoked opcodes of all transaction + # ## that were included included in this block. + # ## + # ## data: Hash of a block. + # ## options: see debug_traceTransaction + # var + # h = data + # header = chainDB.getBlockHeader(h) + # blockHash = chainDB.getBlockHash(header.number) + # body = chainDB.getBlockBody(blockHash) + # flags = traceOptionsToFlags(options) + + # traceBlock(com, EthBlock.init(move(header), move(body)), flags) + + # rpc("debug_setHead") do(quantityTag: BlockTag) -> bool: + # ## Sets the current head of the local chain by block number. + # ## Note, this is a destructive action and may severely damage your chain. + # ## Use with extreme caution. + # let + # header = chainDB.headerFromTag(quantityTag) + # chainDB.setHead(header) + + # rpc("debug_getRawBlock") do(quantityTag: BlockTag) -> seq[byte]: + # ## Returns an RLP-encoded block. + # var + # header = chainDB.headerFromTag(quantityTag) + # blockHash = chainDB.getBlockHash(header.number) + # body = chainDB.getBlockBody(blockHash) + + # rlp.encode(EthBlock.init(move(header), move(body))) + + # rpc("debug_getRawHeader") do(quantityTag: BlockTag) -> seq[byte]: + # ## Returns an RLP-encoded header. + # let header = chainDB.headerFromTag(quantityTag) + # rlp.encode(header) + + # rpc("debug_getRawReceipts") do(quantityTag: BlockTag) -> seq[seq[byte]]: + # ## Returns an array of EIP-2718 binary-encoded receipts. + # let header = chainDB.headerFromTag(quantityTag) + # for receipt in chainDB.getReceipts(header.receiptsRoot): + # result.add rlp.encode(receipt) + + # rpc("debug_getRawTransaction") do(data: Hash32) -> seq[byte]: + # ## Returns an EIP-2718 binary-encoded transaction. + # let txHash = data + # let res = txPool.getItem(txHash) + # if res.isOk: + # return rlp.encode(res.get().tx) + + # let txDetails = chainDB.getTransactionKey(txHash) + # if txDetails.index < 0: + # raise newException(ValueError, "Transaction not found " & data.toHex) + + # let header = chainDB.getBlockHeader(txDetails.blockNumber) + # var tx: Transaction + # if chainDB.getTransaction(header.txRoot, txDetails.index, tx): + # return rlp.encode(tx) + + # raise newException(ValueError, "Transaction not found " & data.toHex) + + rpc("debug_executionWitness") do(quantityTag: BlockTag) -> ExecutionWitness: + ## Returns an execution witness for the given block number. + let header = chain.headerFromTag(quantityTag).valueOr: + raise newException(ValueError, "Header not found") + + chain.getExecutionWitness(header.computeBlockHash()).valueOr: + raise newException(ValueError, error) + + rpc("debug_executionWitnessByBlockHash") do(blockHash: Hash32) -> ExecutionWitness: + ## Returns an execution witness for the given block hash. + chain.getExecutionWitness(blockHash).valueOr: + raise newException(ValueError, error) + + rpc("debug_getHeaderByNumber") do(blockTag: BlockTag) -> string: + ## Returns the rlp encoded block header in hex for the given block number / tag. + # Note: When proposing this method for inclusion in the JSON-RPC spec, + # consider returning a header JSON object instead of RLP. Likely to be more accepted. + let header = chain.headerFromTag(blockTag).valueOr: + raise newException(ValueError, error) + + rlp.encode(header).to0xHex() + + rpc("debug_getBadBlocks") do() -> seq[BadBlock]: + ## Returns a list of the most recently processed bad blocks. + var badBlocks: seq[BadBlock] + + let blks = chain.getBadBlocks() + for b in blks: + let + (blk, bal) = b + blkHash = blk.header.computeBlockHash() + + badBlocks.add BadBlock( + `block`: populateBlockObject( + blkHash, blk, chain.getTotalDifficulty(blkHash, blk.header), fullTx = true + ), + generatedBlockAccessList: bal.map( + proc(bal: auto): auto = + bal[] + ), + hash: blkHash, + rlp: rlp.encode(blk), + ) + + badBlocks + + # We should remove these two block access list endpoints at some point + # or at least update them to return the BAL in RLP format since the same + # functionality is now provied by eth_getBlockAccessListByBlockNumber + # and eth_getBlockAccessListByBlockHash + + rpc("debug_getBlockAccessList") do(quantityTag: BlockTag) -> BlockAccessList: + ## Returns a block access list for the given block number. + let header = chain.headerFromTag(quantityTag).valueOr: + raise newException(ValueError, "Header not found") + + chain.getBlockAccessList(header.computeBlockHash()).valueOr: + raise newException(ValueError, "Block access list not found") + + rpc("debug_getBlockAccessListByBlockHash") do(blockHash: Hash32) -> BlockAccessList: + ## Returns a block access list for the given block hash. + + chain.getBlockAccessList(blockHash).valueOr: + raise newException(ValueError, "Block access list not found") diff --git a/execution_chain/rpc/engine_api.nim b/execution_chain/rpc/engine_api.nim index cbc90036cb..ce3e2a8bcf 100644 --- a/execution_chain/rpc/engine_api.nim +++ b/execution_chain/rpc/engine_api.nim @@ -47,91 +47,112 @@ const supportedMethods: HashSet[string] = # bodies up to the various procs above. Once we have multiple # versions, they'll need to be able to share code. proc setupEngineAPI*(engine: BeaconEngineRef, server: RpcServer) = - - server.rpc("engine_exchangeCapabilities") do(methods: seq[string]) -> seq[string]: - return methods.filterIt(supportedMethods.contains(it)) - - server.rpc("engine_newPayloadV1") do(payload: ExecutionPayloadV1) -> PayloadStatusV1: - await engine.newPayload(Version.V1, payload.executionPayload) - - server.rpc("engine_newPayloadV2") do(payload: ExecutionPayload) -> PayloadStatusV1: - await engine.newPayload(Version.V2, payload) - - server.rpc("engine_newPayloadV3") do(payload: ExecutionPayload, - expectedBlobVersionedHashes: Opt[seq[Hash32]], - parentBeaconBlockRoot: Opt[Hash32]) -> PayloadStatusV1: - await engine.newPayload(Version.V3, payload, expectedBlobVersionedHashes, parentBeaconBlockRoot) - - server.rpc("engine_newPayloadV4") do(payload: ExecutionPayload, - expectedBlobVersionedHashes: Opt[seq[Hash32]], - parentBeaconBlockRoot: Opt[Hash32], - executionRequests: Opt[seq[seq[byte]]]) -> PayloadStatusV1: - await engine.newPayload(Version.V4, payload, - expectedBlobVersionedHashes, parentBeaconBlockRoot, executionRequests) - - server.rpc("engine_newPayloadV5") do(payload: ExecutionPayload, - expectedBlobVersionedHashes: Opt[seq[Hash32]], - parentBeaconBlockRoot: Opt[Hash32], - executionRequests: Opt[seq[seq[byte]]]) -> PayloadStatusV1: - await engine.newPayload(Version.V5, payload, - expectedBlobVersionedHashes, parentBeaconBlockRoot, executionRequests) - - server.rpc("engine_getPayloadV1") do(payloadId: Bytes8) -> ExecutionPayloadV1: - return engine.getPayload(Version.V1, payloadId).executionPayload.V1 - - server.rpc("engine_getPayloadV2") do(payloadId: Bytes8) -> GetPayloadV2Response: - return engine.getPayload(Version.V2, payloadId) - - server.rpc("engine_getPayloadV3") do(payloadId: Bytes8) -> GetPayloadV3Response: - return engine.getPayloadV3(payloadId) - - server.rpc("engine_getPayloadV4") do(payloadId: Bytes8) -> GetPayloadV4Response: - return engine.getPayloadV4(payloadId) - - server.rpc("engine_getPayloadV5") do(payloadId: Bytes8) -> GetPayloadV5Response: - return engine.getPayloadV5(payloadId) - - server.rpc("engine_getPayloadV6") do(payloadId: Bytes8) -> GetPayloadV6Response: - return engine.getPayloadV6(payloadId) - - server.rpc("engine_forkchoiceUpdatedV1") do(update: ForkchoiceStateV1, - attrs: Opt[PayloadAttributesV1]) -> ForkchoiceUpdatedResponse: - await engine.forkchoiceUpdated(Version.V1, update, attrs.payloadAttributes) - - server.rpc("engine_forkchoiceUpdatedV2") do(update: ForkchoiceStateV1, - attrs: Opt[PayloadAttributes]) -> ForkchoiceUpdatedResponse: - await engine.forkchoiceUpdated(Version.V2, update, attrs) - - server.rpc("engine_forkchoiceUpdatedV3") do(update: ForkchoiceStateV1, - attrs: Opt[PayloadAttributes]) -> ForkchoiceUpdatedResponse: - await engine.forkchoiceUpdated(Version.V3, update, attrs) - - server.rpc("engine_getPayloadBodiesByHashV1") do(hashes: seq[Hash32]) -> - seq[Opt[ExecutionPayloadBodyV1]]: - return engine.getPayloadBodiesByHash(hashes) - - server.rpc("engine_getPayloadBodiesByRangeV1") do( - start: Quantity, count: Quantity) -> seq[Opt[ExecutionPayloadBodyV1]]: - return engine.getPayloadBodiesByRange(start.uint64, count.uint64) - - server.rpc("engine_getClientVersionV1") do(version: ClientVersionV1) -> - seq[ClientVersionV1]: - # TODO: what should we do with the `version` parameter? - return @[ClientVersionV1( - code: "NB", - name: NimbusName, - version: NimbusVersion, - commit: FixedBytes[4](GitRevisionBytes), - )] - - server.rpc("engine_getBlobsV1") do(versionedHashes: seq[VersionedHash]) -> - seq[Opt[BlobAndProofV1]]: - return engine.getBlobsV1(versionedHashes) - - server.rpc("engine_getBlobsV2") do(versionedHashes: seq[VersionedHash]) -> - Opt[seq[BlobAndProofV2]]: - return engine.getBlobsV2(versionedHashes) - - server.rpc("engine_getBlobsV3") do(versionedHashes: seq[VersionedHash]) -> - seq[Opt[BlobAndProofV2]]: - return engine.getBlobsV3(versionedHashes) + server.rpcContext(EthJson): + rpc("engine_exchangeCapabilities") do(methods: seq[string]) -> seq[string]: + return methods.filterIt(supportedMethods.contains(it)) + + rpc("engine_newPayloadV1") do(payload: ExecutionPayloadV1) -> PayloadStatusV1: + await engine.newPayload(Version.V1, payload.executionPayload) + + rpc("engine_newPayloadV2") do(payload: ExecutionPayload) -> PayloadStatusV1: + await engine.newPayload(Version.V2, payload) + + rpc("engine_newPayloadV3") do( + payload: ExecutionPayload, + expectedBlobVersionedHashes: Opt[seq[Hash32]], + parentBeaconBlockRoot: Opt[Hash32] + ) -> PayloadStatusV1: + await engine.newPayload( + Version.V3, payload, expectedBlobVersionedHashes, parentBeaconBlockRoot + ) + + rpc("engine_newPayloadV4") do( + payload: ExecutionPayload, + expectedBlobVersionedHashes: Opt[seq[Hash32]], + parentBeaconBlockRoot: Opt[Hash32], + executionRequests: Opt[seq[seq[byte]]] + ) -> PayloadStatusV1: + await engine.newPayload( + Version.V4, payload, expectedBlobVersionedHashes, parentBeaconBlockRoot, + executionRequests, + ) + + rpc("engine_newPayloadV5") do( + payload: ExecutionPayload, + expectedBlobVersionedHashes: Opt[seq[Hash32]], + parentBeaconBlockRoot: Opt[Hash32], + executionRequests: Opt[seq[seq[byte]]] + ) -> PayloadStatusV1: + await engine.newPayload( + Version.V5, payload, expectedBlobVersionedHashes, parentBeaconBlockRoot, + executionRequests + ) + + rpc("engine_getPayloadV1") do(payloadId: Bytes8) -> ExecutionPayloadV1: + return engine.getPayload(Version.V1, payloadId).executionPayload.V1 + + rpc("engine_getPayloadV2") do(payloadId: Bytes8) -> GetPayloadV2Response: + return engine.getPayload(Version.V2, payloadId) + + rpc("engine_getPayloadV3") do(payloadId: Bytes8) -> GetPayloadV3Response: + return engine.getPayloadV3(payloadId) + + rpc("engine_getPayloadV4") do(payloadId: Bytes8) -> GetPayloadV4Response: + return engine.getPayloadV4(payloadId) + + rpc("engine_getPayloadV5") do(payloadId: Bytes8) -> GetPayloadV5Response: + return engine.getPayloadV5(payloadId) + + rpc("engine_getPayloadV6") do(payloadId: Bytes8) -> GetPayloadV6Response: + return engine.getPayloadV6(payloadId) + + rpc("engine_forkchoiceUpdatedV1") do( + update: ForkchoiceStateV1, attrs: Opt[PayloadAttributesV1] + ) -> ForkchoiceUpdatedResponse: + await engine.forkchoiceUpdated(Version.V1, update, attrs.payloadAttributes) + + rpc("engine_forkchoiceUpdatedV2") do( + update: ForkchoiceStateV1, attrs: Opt[PayloadAttributes] + ) -> ForkchoiceUpdatedResponse: + await engine.forkchoiceUpdated(Version.V2, update, attrs) + + rpc("engine_forkchoiceUpdatedV3") do( + update: ForkchoiceStateV1, attrs: Opt[PayloadAttributes] + ) -> ForkchoiceUpdatedResponse: + await engine.forkchoiceUpdated(Version.V3, update, attrs) + + rpc("engine_getPayloadBodiesByHashV1") do( + hashes: seq[Hash32] + ) -> seq[Opt[ExecutionPayloadBodyV1]]: + return engine.getPayloadBodiesByHash(hashes) + + rpc("engine_getPayloadBodiesByRangeV1") do( + start: Quantity, count: Quantity + ) -> seq[Opt[ExecutionPayloadBodyV1]]: + return engine.getPayloadBodiesByRange(start.uint64, count.uint64) + + rpc("engine_getClientVersionV1") do( + version: ClientVersionV1 + ) -> seq[ClientVersionV1]: + # TODO: what should we do with the `version` parameter? + return @[ClientVersionV1( + code: "NB", + name: NimbusName, + version: NimbusVersion, + commit: FixedBytes[4](GitRevisionBytes), + )] + + rpc("engine_getBlobsV1") do( + versionedHashes: seq[VersionedHash] + ) -> seq[Opt[BlobAndProofV1]]: + return engine.getBlobsV1(versionedHashes) + + rpc("engine_getBlobsV2") do( + versionedHashes: seq[VersionedHash] + ) -> Opt[seq[BlobAndProofV2]]: + return engine.getBlobsV2(versionedHashes) + + rpc("engine_getBlobsV3") do( + versionedHashes: seq[VersionedHash] + ) -> seq[Opt[BlobAndProofV2]]: + return engine.getBlobsV3(versionedHashes) diff --git a/execution_chain/rpc/rpc_types.nim b/execution_chain/rpc/rpc_types.nim index 7a2d3d59d6..be670a4464 100644 --- a/execution_chain/rpc/rpc_types.nim +++ b/execution_chain/rpc/rpc_types.nim @@ -12,9 +12,7 @@ import web3/[eth_api_types, conversions], ../beacon/web3_eth_conv -export - eth_api_types, - web3_eth_conv +export eth_api_types, web3_eth_conv type FilterLog* = eth_api_types.LogObject @@ -24,9 +22,9 @@ type BlockTag* = eth_api_types.RtBlockIdentifier # Block access list json serialization -AccountChanges.useDefaultSerializationIn JrpcConv -SlotChanges.useDefaultSerializationIn JrpcConv -StorageChange.useDefaultSerializationIn JrpcConv -BalanceChange.useDefaultSerializationIn JrpcConv -NonceChange.useDefaultSerializationIn JrpcConv -CodeChange.useDefaultSerializationIn JrpcConv +AccountChanges.useDefaultSerializationIn EthJson +SlotChanges.useDefaultSerializationIn EthJson +StorageChange.useDefaultSerializationIn EthJson +BalanceChange.useDefaultSerializationIn EthJson +NonceChange.useDefaultSerializationIn EthJson +CodeChange.useDefaultSerializationIn EthJson diff --git a/execution_chain/rpc/server_api.nim b/execution_chain/rpc/server_api.nim index 29e4830bda..8b197a65d9 100644 --- a/execution_chain/rpc/server_api.nim +++ b/execution_chain/rpc/server_api.nim @@ -133,634 +133,635 @@ proc blockFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[Block, string] return api.chain.blockByNumber(blockNum) proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, am: ref AccountsManager) = - server.rpc("eth_getBalance") do(data: Address, blockTag: BlockTag) -> UInt256: - ## Returns the balance of the account of given address. - let - ledger = api.ledgerFromTag(blockTag).valueOr: - raise newException(ValueError, error) - address = data - ledger.getBalance(address) - - server.rpc("eth_getStorageAt") do( - data: Address, slot: UInt256, blockTag: BlockTag - ) -> FixedBytes[32]: - ## Returns the value from a storage position at a given address. - let - ledger = api.ledgerFromTag(blockTag).valueOr: - raise newException(ValueError, error) - address = data - value = ledger.getStorage(address, slot) - value.to(Bytes32) - - server.rpc("eth_getTransactionCount") do( - data: Address, blockTag: BlockTag - ) -> Quantity: - ## Returns the number of transactions ak.s. nonce sent from an address. - let - ledger = api.ledgerFromTag(blockTag).valueOr: - raise newException(ValueError, error) - address = data - nonce = ledger.getNonce(address) - Quantity(nonce) - - server.rpc("eth_blockNumber") do() -> Quantity: - ## Returns integer of the current block number the client is on. - Quantity(api.chain.latestNumber) - - server.rpc("eth_chainId") do() -> UInt256: - return api.com.chainId - - server.rpc("eth_getCode") do(data: Address, blockTag: BlockTag) -> seq[byte]: - ## Returns code at a given address. - ## - ## data: address - ## blockTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns the code from the given address. - let - ledger = api.ledgerFromTag(blockTag).valueOr: - raise newException(ValueError, error) - address = data - ledger.getCode(address).bytes() - - server.rpc("eth_getBlockByHash") do( - data: Hash32, fullTransactions: bool - ) -> BlockObject: - ## Returns information about a block by hash. - ## - ## data: Hash of a block. - ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. - ## Returns BlockObject or nil when no block was found. - let blockHash = data - - let blk = api.chain.blockByHash(blockHash).valueOr: - return nil - - return populateBlockObject( - blockHash, blk, api.getTotalDifficulty(blockHash, blk.header), fullTransactions - ) + server.rpcContext(EthJson): + rpc("eth_getBalance") do(data: Address, blockTag: BlockTag) -> UInt256: + ## Returns the balance of the account of given address. + let + ledger = api.ledgerFromTag(blockTag).valueOr: + raise newException(ValueError, error) + address = data + ledger.getBalance(address) + + rpc("eth_getStorageAt") do( + data: Address, slot: UInt256, blockTag: BlockTag + ) -> FixedBytes[32]: + ## Returns the value from a storage position at a given address. + let + ledger = api.ledgerFromTag(blockTag).valueOr: + raise newException(ValueError, error) + address = data + value = ledger.getStorage(address, slot) + value.to(Bytes32) + + rpc("eth_getTransactionCount") do( + data: Address, blockTag: BlockTag + ) -> Quantity: + ## Returns the number of transactions ak.s. nonce sent from an address. + let + ledger = api.ledgerFromTag(blockTag).valueOr: + raise newException(ValueError, error) + address = data + nonce = ledger.getNonce(address) + Quantity(nonce) + + rpc("eth_blockNumber") do() -> Quantity: + ## Returns integer of the current block number the client is on. + Quantity(api.chain.latestNumber) + + rpc("eth_chainId") do() -> UInt256: + return api.com.chainId + + rpc("eth_getCode") do(data: Address, blockTag: BlockTag) -> seq[byte]: + ## Returns code at a given address. + ## + ## data: address + ## blockTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns the code from the given address. + let + ledger = api.ledgerFromTag(blockTag).valueOr: + raise newException(ValueError, error) + address = data + ledger.getCode(address).bytes() + + rpc("eth_getBlockByHash") do( + data: Hash32, fullTransactions: bool + ) -> BlockObject: + ## Returns information about a block by hash. + ## + ## data: Hash of a block. + ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. + ## Returns BlockObject or nil when no block was found. + let blockHash = data + + let blk = api.chain.blockByHash(blockHash).valueOr: + return nil - server.rpc("eth_getBlockByNumber") do( - blockTag: BlockTag, fullTransactions: bool - ) -> BlockObject: - ## Returns information about a block by block number. - ## - ## blockTag: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. - ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. - ## Returns BlockObject or nil when no block was found. - let blk = api.blockFromTag(blockTag).valueOr: - return nil - - let blockHash = blk.header.computeBlockHash - return populateBlockObject( - blockHash, blk, api.getTotalDifficulty(blockHash, blk.header), fullTransactions - ) + return populateBlockObject( + blockHash, blk, api.getTotalDifficulty(blockHash, blk.header), fullTransactions + ) - server.rpc("eth_syncing") do() -> SyncingStatus: - ## Returns SyncObject or false when not syncing. - let (start, current, target) = api.com.beaconSyncerProgress() - if start == 0 and current == 0 and target == 0: - return SyncingStatus(syncing: false) - else: - let sync = SyncObject( - startingBlock: Quantity(start), - currentBlock: Quantity(current), - highestBlock: Quantity(target), + rpc("eth_getBlockByNumber") do( + blockTag: BlockTag, fullTransactions: bool + ) -> BlockObject: + ## Returns information about a block by block number. + ## + ## blockTag: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. + ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. + ## Returns BlockObject or nil when no block was found. + let blk = api.blockFromTag(blockTag).valueOr: + return nil + + let blockHash = blk.header.computeBlockHash + return populateBlockObject( + blockHash, blk, api.getTotalDifficulty(blockHash, blk.header), fullTransactions ) - return SyncingStatus(syncing: true, syncObject: sync) - proc getLogsForBlock( - chain: ForkedChainRef, header: Header, opts: FilterOptions - ): Opt[seq[FilterLog]] = - if headerBloomFilter(header, opts.address, opts.topics): - let - blkHash = header.computeBlockHash - blockBody = chain.blockBodyByHash(blkHash).valueOr: - return Opt.none(seq[FilterLog]) - receipts = chain.receiptsByBlockHash(blkHash).valueOr: + rpc("eth_syncing") do() -> SyncingStatus: + ## Returns SyncObject or false when not syncing. + let (start, current, target) = api.com.beaconSyncerProgress() + if start == 0 and current == 0 and target == 0: + return SyncingStatus(syncing: false) + else: + let sync = SyncObject( + startingBlock: Quantity(start), + currentBlock: Quantity(current), + highestBlock: Quantity(target), + ) + return SyncingStatus(syncing: true, syncObject: sync) + + proc getLogsForBlock( + chain: ForkedChainRef, header: Header, opts: FilterOptions + ): Opt[seq[FilterLog]] = + if headerBloomFilter(header, opts.address, opts.topics): + let + blkHash = header.computeBlockHash + blockBody = chain.blockBodyByHash(blkHash).valueOr: + return Opt.none(seq[FilterLog]) + receipts = chain.receiptsByBlockHash(blkHash).valueOr: + return Opt.none(seq[FilterLog]) + cachedHashes = chain.memoryTxHashesForBlock(blkHash) + # Note: this will hit assertion error if number of block transactions + # do not match block receipts. + # Although this is fine as number of receipts should always match number + # of transactions + if blockBody.transactions.len != receipts.len: + warn "Transactions and receipts length mismatch", + number = header.number, hash = blkHash.short, + txs = blockBody.transactions.len, receipts = receipts.len return Opt.none(seq[FilterLog]) - cachedHashes = chain.memoryTxHashesForBlock(blkHash) - # Note: this will hit assertion error if number of block transactions - # do not match block receipts. - # Although this is fine as number of receipts should always match number - # of transactions - if blockBody.transactions.len != receipts.len: - warn "Transactions and receipts length mismatch", - number = header.number, hash = blkHash.short, - txs = blockBody.transactions.len, receipts = receipts.len - return Opt.none(seq[FilterLog]) - let logs = deriveLogs(header, blockBody.transactions, receipts, opts, cachedHashes) - return Opt.some(logs) - else: - return Opt.some(newSeq[FilterLog](0)) - - proc getLogsForRange( - chain: ForkedChainRef, - start: base.BlockNumber, - finish: base.BlockNumber, - opts: FilterOptions, - ): seq[FilterLog] = - var - logs = newSeq[FilterLog]() - blockNum = start - - while blockNum <= finish: + let logs = deriveLogs(header, blockBody.transactions, receipts, opts, cachedHashes) + return Opt.some(logs) + else: + return Opt.some(newSeq[FilterLog](0)) + + proc getLogsForRange( + chain: ForkedChainRef, + start: base.BlockNumber, + finish: base.BlockNumber, + opts: FilterOptions, + ): seq[FilterLog] = + var + logs = newSeq[FilterLog]() + blockNum = start + + while blockNum <= finish: + let + header = chain.headerByNumber(blockNum).valueOr: + return logs + filtered = chain.getLogsForBlock(header, opts).valueOr: + return logs + logs.add(filtered) + blockNum = blockNum + 1 + return logs + + rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[FilterLog]: + ## filterOptions: settings for this filter. + ## Returns a list of all logs matching a given filter object. + ## TODO: Current implementation is pretty naive and not efficient + ## as it requires to fetch all transactions and all receipts from database. + ## Other clients (Geth): + ## - Store logs related data in receipts. + ## - Have separate indexes for Logs in given block + ## Both of those changes require improvements to the way how we keep our data + ## in Nimbus. + if filterOptions.blockHash.isSome(): + let + hash = filterOptions.blockHash.expect("blockHash") + header = api.chain.headerByHash(hash).valueOr: + raise newException(ValueError, "Block not found") + logs = getLogsForBlock(api.chain, header, filterOptions).valueOr: + raise newException(ValueError, "getLogsForBlock error") + return logs + else: + # TODO: do something smarter with tags. It would be the best if + # tag would be an enum (Earliest, Latest, Pending, Number), and all operations + # would operate on this enum instead of raw strings. This change would need + # to be done on every endpoint to be consistent. + let + blockFrom = api.headerFromTag(filterOptions.fromBlock).valueOr: + raise newException(ValueError, "Block not found") + blockTo = api.headerFromTag(filterOptions.toBlock).valueOr: + raise newException(ValueError, "Block not found") + + # Note: if fromHeader.number > toHeader.number, no logs will be + # returned. This is consistent with, what other ethereum clients return + return api.chain.getLogsForRange(blockFrom.number, blockTo.number, filterOptions) + + rpc("eth_sendRawTransaction") do(txBytes: seq[byte]) -> Hash32: + ## Creates new message call transaction or a contract creation for signed transactions. + ## + ## data: the signed transaction data. + ## Returns the transaction hash, or the zero hash if the transaction is not yet available. + ## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract. let - header = chain.headerByNumber(blockNum).valueOr: - return logs - filtered = chain.getLogsForBlock(header, opts).valueOr: - return logs - logs.add(filtered) - blockNum = blockNum + 1 - return logs - - server.rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[FilterLog]: - ## filterOptions: settings for this filter. - ## Returns a list of all logs matching a given filter object. - ## TODO: Current implementation is pretty naive and not efficient - ## as it requires to fetch all transactions and all receipts from database. - ## Other clients (Geth): - ## - Store logs related data in receipts. - ## - Have separate indexes for Logs in given block - ## Both of those changes require improvements to the way how we keep our data - ## in Nimbus. - if filterOptions.blockHash.isSome(): + pooledTx = decodePooledTx(txBytes) + txHash = computeRlpHash(pooledTx.tx) + sender = pooledTx.tx.recoverSender().get() + + api.txPool.addTx(pooledTx).isOkOr: + raise newException(ValueError, $error) + + info "Submitted transaction", + endpoint = "eth_sendRawTransaction", + txHash = txHash, + sender = sender, + recipient = pooledTx.tx.getRecipient(sender), + nonce = pooledTx.tx.nonce, + value = pooledTx.tx.value + + txHash + + rpc("eth_call") do(args: TransactionArgs, blockTag: BlockTag) -> seq[byte]: + ## Executes a new message call immediately without creating a transaction on the block chain. + ## + ## call: the transaction call object. + ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns the return value of executed contract. let - hash = filterOptions.blockHash.expect("blockHash") - header = api.chain.headerByHash(hash).valueOr: + header = api.headerFromTag(blockTag).valueOr: raise newException(ValueError, "Block not found") - logs = getLogsForBlock(api.chain, header, filterOptions).valueOr: - raise newException(ValueError, "getLogsForBlock error") - return logs - else: - # TODO: do something smarter with tags. It would be the best if - # tag would be an enum (Earliest, Latest, Pending, Number), and all operations - # would operate on this enum instead of raw strings. This change would need - # to be done on every endpoint to be consistent. + headerHash = header.computeBlockHash + txFrame = api.chain.txFrame(headerHash) + res = rpcCallEvm(args, header, headerHash, api.com, txFrame).valueOr: + raise newException(ValueError, "rpcCallEvm error: " & $error.code) + res.output + + rpc("eth_getTransactionReceipt") do(data: Hash32) -> ReceiptObject: + ## Returns the receipt of a transaction by transaction hash. + ## + ## data: Hash of a transaction. + ## Returns ReceiptObject or nil when no receipt was found. let - blockFrom = api.headerFromTag(filterOptions.fromBlock).valueOr: - raise newException(ValueError, "Block not found") - blockTo = api.headerFromTag(filterOptions.toBlock).valueOr: + txHash = data + (blockHash, txid) = api.chain.txDetailsByTxHash(txHash).valueOr: + return nil + blk = api.chain.blockByHash(blockHash).valueOr: + return nil + receipts = api.chain.receiptsByBlockHash(blockHash).valueOr: + return nil + + var prevGasUsed = 0'u64 + for idx, receipt in receipts: + let gasUsed = receipt.cumulativeGasUsed - prevGasUsed + prevGasUsed = receipt.cumulativeGasUsed + + if txid == uint64(idx): + return populateReceipt(receipt, gasUsed, blk.transactions[txid], txid, blk.header, api.com) + + rpc("eth_estimateGas") do(args: TransactionArgs) -> Quantity: + ## Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. + ## The transaction will not be added to the blockchain. Note that the estimate may be significantly more than + ## the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance. + ## + ## args: the transaction call object. + ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns the amount of gas used. + let + header = api.headerFromTag(blockId("latest")).valueOr: raise newException(ValueError, "Block not found") + headerHash = header.computeBlockHash + txFrame = api.chain.txFrame(headerHash) + #TODO: change 0 to configureable gas cap + gasUsed = rpcEstimateGas(args, header, headerHash, api.com, txFrame, DEFAULT_RPC_GAS_CAP).valueOr: + raise newException(ValueError, "rpcEstimateGas error: " & $error.code) + Quantity(gasUsed) + + rpc("eth_gasPrice") do() -> Quantity: + ## Returns an integer of the current gas price in wei. + w3Qty(calculateMedianGasPrice(api.chain).uint64) + + rpc("eth_accounts") do() -> seq[Address]: + ## Returns a list of addresses owned by client. + result = newSeqOfCap[Address](am[].numAccounts) + for k in am[].addresses: + result.add k + + rpc("eth_getBlockTransactionCountByHash") do(data: Hash32) -> Quantity: + ## Returns the number of transactions in a block from a block matching the given block hash. + ## + ## data: hash of a block + ## Returns integer of the number of transactions in this block. + let blk = api.chain.blockByHash(data).valueOr: + raise newException(ValueError, "Block not found") - # Note: if fromHeader.number > toHeader.number, no logs will be - # returned. This is consistent with, what other ethereum clients return - return api.chain.getLogsForRange(blockFrom.number, blockTo.number, filterOptions) - - server.rpc("eth_sendRawTransaction") do(txBytes: seq[byte]) -> Hash32: - ## Creates new message call transaction or a contract creation for signed transactions. - ## - ## data: the signed transaction data. - ## Returns the transaction hash, or the zero hash if the transaction is not yet available. - ## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract. - let - pooledTx = decodePooledTx(txBytes) - txHash = computeRlpHash(pooledTx.tx) - sender = pooledTx.tx.recoverSender().get() - - api.txPool.addTx(pooledTx).isOkOr: - raise newException(ValueError, $error) - - info "Submitted transaction", - endpoint = "eth_sendRawTransaction", - txHash = txHash, - sender = sender, - recipient = pooledTx.tx.getRecipient(sender), - nonce = pooledTx.tx.nonce, - value = pooledTx.tx.value - - txHash - - server.rpc("eth_call") do(args: TransactionArgs, blockTag: BlockTag) -> seq[byte]: - ## Executes a new message call immediately without creating a transaction on the block chain. - ## - ## call: the transaction call object. - ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns the return value of executed contract. - let - header = api.headerFromTag(blockTag).valueOr: + Quantity(blk.transactions.len) + + rpc("eth_getBlockTransactionCountByNumber") do( + blockTag: BlockTag + ) -> Quantity: + ## Returns the number of transactions in a block from a block matching the given block number. + ## + ## blockTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns integer of the number of transactions in this block. + let blk = api.blockFromTag(blockTag).valueOr: raise newException(ValueError, "Block not found") - headerHash = header.computeBlockHash - txFrame = api.chain.txFrame(headerHash) - res = rpcCallEvm(args, header, headerHash, api.com, txFrame).valueOr: - raise newException(ValueError, "rpcCallEvm error: " & $error.code) - res.output - - server.rpc("eth_getTransactionReceipt") do(data: Hash32) -> ReceiptObject: - ## Returns the receipt of a transaction by transaction hash. - ## - ## data: Hash of a transaction. - ## Returns ReceiptObject or nil when no receipt was found. - let - txHash = data - (blockHash, txid) = api.chain.txDetailsByTxHash(txHash).valueOr: - return nil - blk = api.chain.blockByHash(blockHash).valueOr: - return nil - receipts = api.chain.receiptsByBlockHash(blockHash).valueOr: - return nil - var prevGasUsed = 0'u64 - for idx, receipt in receipts: - let gasUsed = receipt.cumulativeGasUsed - prevGasUsed - prevGasUsed = receipt.cumulativeGasUsed - - if txid == uint64(idx): - return populateReceipt(receipt, gasUsed, blk.transactions[txid], txid, blk.header, api.com) - - server.rpc("eth_estimateGas") do(args: TransactionArgs) -> Quantity: - ## Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. - ## The transaction will not be added to the blockchain. Note that the estimate may be significantly more than - ## the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance. - ## - ## args: the transaction call object. - ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns the amount of gas used. - let - header = api.headerFromTag(blockId("latest")).valueOr: + Quantity(blk.transactions.len) + + rpc("eth_getUncleCountByBlockHash") do(data: Hash32) -> Quantity: + ## Returns the number of uncles in a block from a block matching the given block hash. + ## + ## data: hash of a block. + ## Returns integer of the number of uncles in this block. + let blk = api.chain.blockByHash(data).valueOr: raise newException(ValueError, "Block not found") - headerHash = header.computeBlockHash - txFrame = api.chain.txFrame(headerHash) - #TODO: change 0 to configureable gas cap - gasUsed = rpcEstimateGas(args, header, headerHash, api.com, txFrame, DEFAULT_RPC_GAS_CAP).valueOr: - raise newException(ValueError, "rpcEstimateGas error: " & $error.code) - Quantity(gasUsed) - - server.rpc("eth_gasPrice") do() -> Quantity: - ## Returns an integer of the current gas price in wei. - w3Qty(calculateMedianGasPrice(api.chain).uint64) - - server.rpc("eth_accounts") do() -> seq[Address]: - ## Returns a list of addresses owned by client. - result = newSeqOfCap[Address](am[].numAccounts) - for k in am[].addresses: - result.add k - - server.rpc("eth_getBlockTransactionCountByHash") do(data: Hash32) -> Quantity: - ## Returns the number of transactions in a block from a block matching the given block hash. - ## - ## data: hash of a block - ## Returns integer of the number of transactions in this block. - let blk = api.chain.blockByHash(data).valueOr: - raise newException(ValueError, "Block not found") - - Quantity(blk.transactions.len) - - server.rpc("eth_getBlockTransactionCountByNumber") do( - blockTag: BlockTag - ) -> Quantity: - ## Returns the number of transactions in a block from a block matching the given block number. - ## - ## blockTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns integer of the number of transactions in this block. - let blk = api.blockFromTag(blockTag).valueOr: - raise newException(ValueError, "Block not found") - - Quantity(blk.transactions.len) - - server.rpc("eth_getUncleCountByBlockHash") do(data: Hash32) -> Quantity: - ## Returns the number of uncles in a block from a block matching the given block hash. - ## - ## data: hash of a block. - ## Returns integer of the number of uncles in this block. - let blk = api.chain.blockByHash(data).valueOr: - raise newException(ValueError, "Block not found") - - Quantity(blk.uncles.len) - - server.rpc("eth_getUncleCountByBlockNumber") do(blockTag: BlockTag) -> Quantity: - ## Returns the number of uncles in a block from a block matching the given block number. - ## - ## blockTag: integer of a block number, or the string "latest", see the default block parameter. - ## Returns integer of the number of uncles in this block. - let blk = api.blockFromTag(blockTag).valueOr: - raise newException(ValueError, "Block not found") - - Quantity(blk.uncles.len) - - template sign(privateKey: PrivateKey, message: string): seq[byte] = - # message length encoded as ASCII representation of decimal - let msgData = "\x19Ethereum Signed Message:\n" & $message.len & message - @(sign(privateKey, msgData.toBytes()).toRaw()) - - server.rpc("eth_sign") do(data: Address, message: seq[byte]) -> seq[byte]: - ## The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))). - ## By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature. - ## This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim. - ## Note the address to sign with must be unlocked. - ## - ## data: address. - ## message: message to sign. - ## Returns signature. - let - address = data - acc = am[].getAccount(address).tryGet() - - if not acc.unlocked: - raise newException(ValueError, "Account locked, please unlock it first") - sign(acc.privateKey, cast[string](message)) - - server.rpc("eth_signTransaction") do(data: TransactionArgs) -> seq[byte]: - ## Signs a transaction that can be submitted to the network at a later time using with - ## eth_sendRawTransaction - let - address = data.`from`.get() - acc = am[].getAccount(address).tryGet() - - if not acc.unlocked: - raise newException(ValueError, "Account locked, please unlock it first") - - let - accDB = api.ledgerFromTag(blockId("latest")).valueOr: - raise newException(ValueError, "Latest Block not found") - tx = unsignedTx(data, api.chain, accDB.getNonce(address) + 1, api.com.chainId) - eip155 = api.com.isEIP155(api.chain.latestNumber) - signedTx = signTransaction(tx, acc.privateKey, eip155) - return rlp.encode(signedTx) - - server.rpc("eth_sendTransaction") do(data: TransactionArgs) -> Hash32: - ## Creates new message call transaction or a contract creation, if the data field contains code. - ## - ## obj: the transaction object. - ## Returns the transaction hash, or the zero hash if the transaction is not yet available. - ## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract. - let - address = data.`from`.get() - acc = am[].getAccount(address).tryGet() - - if not acc.unlocked: - raise newException(ValueError, "Account locked, please unlock it first") - - let - accDB = api.ledgerFromTag(blockId("latest")).valueOr: - raise newException(ValueError, "Latest Block not found") - tx = unsignedTx(data, api.chain, accDB.getNonce(address) + 1, api.com.chainId) - eip155 = api.com.isEIP155(api.chain.latestNumber) - signedTx = signTransaction(tx, acc.privateKey, eip155) - blobsBundle = - if signedTx.txType == TxEip4844: - if data.blobs.isNone or data.commitments.isNone or data.proofs.isNone: - raise newException(ValueError, "EIP-4844 transaction needs blobs") - if data.blobs.get.len != signedTx.versionedHashes.len: - raise newException(ValueError, "Incorrect number of blobs") - if data.commitments.get.len != signedTx.versionedHashes.len: - raise newException(ValueError, "Incorrect number of commitments") - if data.proofs.get.len != signedTx.versionedHashes.len: - raise newException(ValueError, "Incorrect number of proofs") - BlobsBundle( - blobs: data.blobs.get, - commitments: data.commitments.get, - proofs: data.proofs.get, - ) - else: - if data.blobs.isSome or data.commitments.isSome or data.proofs.isSome: - raise newException(ValueError, "Blobs require EIP-4844 transaction") - nil - pooledTx = PooledTransaction(tx: signedTx, blobsBundle: blobsBundle) - - api.txPool.addTx(pooledTx).isOkOr: - raise newException(ValueError, $error) - - let txHash = computeRlpHash(signedTx) - info "Submitted transaction", - endpoint = "eth_sendTransaction", - txHash = txHash, - sender = address, - recipient = data.`to`.get(), - nonce = pooledTx.tx.nonce, - value = pooledTx.tx.value - - txHash - - server.rpc("eth_getTransactionByHash") do(data: Hash32) -> TransactionObject: - ## Returns the information about a transaction requested by transaction hash. - ## - ## data: hash of a transaction. - ## Returns requested transaction information. - let - txHash = data - res = api.txPool.getItem(txHash) - if res.isOk: - return populateTransactionObject(res.get().tx, Opt.none(Hash32), Opt.none(uint64)) - - let - (blockHash, txId) = api.chain.txDetailsByTxHash(txHash).valueOr: + + Quantity(blk.uncles.len) + + rpc("eth_getUncleCountByBlockNumber") do(blockTag: BlockTag) -> Quantity: + ## Returns the number of uncles in a block from a block matching the given block number. + ## + ## blockTag: integer of a block number, or the string "latest", see the default block parameter. + ## Returns integer of the number of uncles in this block. + let blk = api.blockFromTag(blockTag).valueOr: + raise newException(ValueError, "Block not found") + + Quantity(blk.uncles.len) + + template sign(privateKey: PrivateKey, message: string): seq[byte] = + # message length encoded as ASCII representation of decimal + let msgData = "\x19Ethereum Signed Message:\n" & $message.len & message + @(sign(privateKey, msgData.toBytes()).toRaw()) + + rpc("eth_sign") do(data: Address, message: seq[byte]) -> seq[byte]: + ## The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))). + ## By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature. + ## This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim. + ## Note the address to sign with must be unlocked. + ## + ## data: address. + ## message: message to sign. + ## Returns signature. + let + address = data + acc = am[].getAccount(address).tryGet() + + if not acc.unlocked: + raise newException(ValueError, "Account locked, please unlock it first") + sign(acc.privateKey, cast[string](message)) + + rpc("eth_signTransaction") do(data: TransactionArgs) -> seq[byte]: + ## Signs a transaction that can be submitted to the network at a later time using with + ## eth_sendRawTransaction + let + address = data.`from`.get() + acc = am[].getAccount(address).tryGet() + + if not acc.unlocked: + raise newException(ValueError, "Account locked, please unlock it first") + + let + accDB = api.ledgerFromTag(blockId("latest")).valueOr: + raise newException(ValueError, "Latest Block not found") + tx = unsignedTx(data, api.chain, accDB.getNonce(address) + 1, api.com.chainId) + eip155 = api.com.isEIP155(api.chain.latestNumber) + signedTx = signTransaction(tx, acc.privateKey, eip155) + return rlp.encode(signedTx) + + rpc("eth_sendTransaction") do(data: TransactionArgs) -> Hash32: + ## Creates new message call transaction or a contract creation, if the data field contains code. + ## + ## obj: the transaction object. + ## Returns the transaction hash, or the zero hash if the transaction is not yet available. + ## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract. + let + address = data.`from`.get() + acc = am[].getAccount(address).tryGet() + + if not acc.unlocked: + raise newException(ValueError, "Account locked, please unlock it first") + + let + accDB = api.ledgerFromTag(blockId("latest")).valueOr: + raise newException(ValueError, "Latest Block not found") + tx = unsignedTx(data, api.chain, accDB.getNonce(address) + 1, api.com.chainId) + eip155 = api.com.isEIP155(api.chain.latestNumber) + signedTx = signTransaction(tx, acc.privateKey, eip155) + blobsBundle = + if signedTx.txType == TxEip4844: + if data.blobs.isNone or data.commitments.isNone or data.proofs.isNone: + raise newException(ValueError, "EIP-4844 transaction needs blobs") + if data.blobs.get.len != signedTx.versionedHashes.len: + raise newException(ValueError, "Incorrect number of blobs") + if data.commitments.get.len != signedTx.versionedHashes.len: + raise newException(ValueError, "Incorrect number of commitments") + if data.proofs.get.len != signedTx.versionedHashes.len: + raise newException(ValueError, "Incorrect number of proofs") + BlobsBundle( + blobs: data.blobs.get, + commitments: data.commitments.get, + proofs: data.proofs.get, + ) + else: + if data.blobs.isSome or data.commitments.isSome or data.proofs.isSome: + raise newException(ValueError, "Blobs require EIP-4844 transaction") + nil + pooledTx = PooledTransaction(tx: signedTx, blobsBundle: blobsBundle) + + api.txPool.addTx(pooledTx).isOkOr: + raise newException(ValueError, $error) + + let txHash = computeRlpHash(signedTx) + info "Submitted transaction", + endpoint = "eth_sendTransaction", + txHash = txHash, + sender = address, + recipient = data.`to`.get(), + nonce = pooledTx.tx.nonce, + value = pooledTx.tx.value + + txHash + + rpc("eth_getTransactionByHash") do(data: Hash32) -> TransactionObject: + ## Returns the information about a transaction requested by transaction hash. + ## + ## data: hash of a transaction. + ## Returns requested transaction information. + let + txHash = data + res = api.txPool.getItem(txHash) + if res.isOk: + return populateTransactionObject(res.get().tx, Opt.none(Hash32), Opt.none(uint64)) + + let + (blockHash, txId) = api.chain.txDetailsByTxHash(txHash).valueOr: + return nil + blk = api.chain.blockByHash(blockHash).valueOr: + return nil + + if blk.transactions.len <= int(txId): return nil - blk = api.chain.blockByHash(blockHash).valueOr: + + return populateTransactionObject( + blk.transactions[txId], + Opt.some(blockHash), + Opt.some(blk.header.number), + Opt.some(txId), + ) + + rpc("eth_getTransactionByBlockHashAndIndex") do( + data: Hash32, quantity: Quantity + ) -> TransactionObject: + ## Returns information about a transaction by block hash and transaction index position. + ## + ## data: hash of a block. + ## quantity: integer of the transaction index position. + ## Returns requested transaction information. + let index = uint64(quantity) + let blk = api.chain.blockByHash(data).valueOr: return nil - if blk.transactions.len <= int(txId): - return nil + if index >= uint64(blk.transactions.len): + return nil - return populateTransactionObject( - blk.transactions[txId], - Opt.some(blockHash), - Opt.some(blk.header.number), - Opt.some(txId), - ) + populateTransactionObject( + blk.transactions[index], Opt.some(data), Opt.some(blk.header.number), Opt.some(index) + ) - server.rpc("eth_getTransactionByBlockHashAndIndex") do( - data: Hash32, quantity: Quantity - ) -> TransactionObject: - ## Returns information about a transaction by block hash and transaction index position. - ## - ## data: hash of a block. - ## quantity: integer of the transaction index position. - ## Returns requested transaction information. - let index = uint64(quantity) - let blk = api.chain.blockByHash(data).valueOr: - return nil - - if index >= uint64(blk.transactions.len): - return nil - - populateTransactionObject( - blk.transactions[index], Opt.some(data), Opt.some(blk.header.number), Opt.some(index) - ) + rpc("eth_getTransactionByBlockNumberAndIndex") do( + quantityTag: BlockTag, quantity: Quantity + ) -> TransactionObject: + ## Returns information about a transaction by block number and transaction index position. + ## + ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. + ## quantity: the transaction index position. + ## NOTE : "pending" blockTag is not supported. + let index = uint64(quantity) + let blk = api.blockFromTag(quantityTag).valueOr: + return nil - server.rpc("eth_getTransactionByBlockNumberAndIndex") do( - quantityTag: BlockTag, quantity: Quantity - ) -> TransactionObject: - ## Returns information about a transaction by block number and transaction index position. - ## - ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. - ## quantity: the transaction index position. - ## NOTE : "pending" blockTag is not supported. - let index = uint64(quantity) - let blk = api.blockFromTag(quantityTag).valueOr: - return nil - - if index >= uint64(blk.transactions.len): - return nil - - populateTransactionObject( - blk.transactions[index], Opt.some(blk.header.computeBlockHash), Opt.some(blk.header.number), Opt.some(index) - ) + if index >= uint64(blk.transactions.len): + return nil - server.rpc("eth_getProof") do( - data: Address, slots: seq[UInt256], quantityTag: BlockTag - ) -> ProofResponse: - ## Returns information about an account and storage slots (if the account is a contract - ## and the slots are requested) along with account and storage proofs which prove the - ## existence of the values in the state. - ## See spec here: https://eips.ethereum.org/EIPS/eip-1186 - ## - ## data: address of the account. - ## slots: integers of the positions in the storage to return with storage proofs. - ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns: the proof response containing the account, account proof and storage proof - let accDB = api.ledgerFromTag(quantityTag).valueOr: - raise newException(ValueError, "Block not found") - - getProof(accDB, data, slots) - - server.rpc("eth_getBlockReceipts") do( - quantityTag: BlockTag - ) -> Opt[seq[ReceiptObject]]: - ## Returns the receipts of a block. - let - blk = api.blockFromTag(quantityTag).valueOr: + populateTransactionObject( + blk.transactions[index], Opt.some(blk.header.computeBlockHash), Opt.some(blk.header.number), Opt.some(index) + ) + + rpc("eth_getProof") do( + data: Address, slots: seq[UInt256], quantityTag: BlockTag + ) -> ProofResponse: + ## Returns information about an account and storage slots (if the account is a contract + ## and the slots are requested) along with account and storage proofs which prove the + ## existence of the values in the state. + ## See spec here: https://eips.ethereum.org/EIPS/eip-1186 + ## + ## data: address of the account. + ## slots: integers of the positions in the storage to return with storage proofs. + ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns: the proof response containing the account, account proof and storage proof + let accDB = api.ledgerFromTag(quantityTag).valueOr: raise newException(ValueError, "Block not found") - blkHash = blk.header.computeBlockHash - receipts = api.chain.receiptsByBlockHash(blkHash).valueOr: + + getProof(accDB, data, slots) + + rpc("eth_getBlockReceipts") do( + quantityTag: BlockTag + ) -> Opt[seq[ReceiptObject]]: + ## Returns the receipts of a block. + let + blk = api.blockFromTag(quantityTag).valueOr: + raise newException(ValueError, "Block not found") + blkHash = blk.header.computeBlockHash + receipts = api.chain.receiptsByBlockHash(blkHash).valueOr: + return Opt.none(seq[ReceiptObject]) + + var + prevGasUsed = GasInt(0) + recs: seq[ReceiptObject] + index = 0'u64 + + try: + for receipt in receipts: + let gasUsed = receipt.cumulativeGasUsed - prevGasUsed + prevGasUsed = receipt.cumulativeGasUsed + recs.add populateReceipt(receipt, gasUsed, blk.transactions[index], index, blk.header, api.com) + inc index + return Opt.some(recs) + except CatchableError: return Opt.none(seq[ReceiptObject]) - var - prevGasUsed = GasInt(0) - recs: seq[ReceiptObject] - index = 0'u64 + rpc("eth_createAccessList") do( + args: TransactionArgs, quantityTag: BlockTag + ) -> AccessListResult: + ## Generates an access list for a transaction. + try: + let header = api.headerFromTag(quantityTag).valueOr: + raise newException(ValueError, "Block not found") + return createAccessList(header, api.com, api.chain, args) + except CatchableError as exc: + return AccessListResult(error: Opt.some("createAccessList error: " & exc.msg)) - try: - for receipt in receipts: - let gasUsed = receipt.cumulativeGasUsed - prevGasUsed - prevGasUsed = receipt.cumulativeGasUsed - recs.add populateReceipt(receipt, gasUsed, blk.transactions[index], index, blk.header, api.com) - inc index - return Opt.some(recs) - except CatchableError: - return Opt.none(seq[ReceiptObject]) - - server.rpc("eth_createAccessList") do( - args: TransactionArgs, quantityTag: BlockTag - ) -> AccessListResult: - ## Generates an access list for a transaction. - try: - let header = api.headerFromTag(quantityTag).valueOr: + rpc("eth_blobBaseFee") do() -> Quantity: + ## Returns the base fee per blob gas in wei. + let header = api.headerFromTag(blockId("latest")).valueOr: raise newException(ValueError, "Block not found") - return createAccessList(header, api.com, api.chain, args) - except CatchableError as exc: - return AccessListResult(error: Opt.some("createAccessList error: " & exc.msg)) - - server.rpc("eth_blobBaseFee") do() -> Quantity: - ## Returns the base fee per blob gas in wei. - let header = api.headerFromTag(blockId("latest")).valueOr: - raise newException(ValueError, "Block not found") - if header.blobGasUsed.isNone: - raise newException(ValueError, "blobGasUsed missing from latest header") - if header.excessBlobGas.isNone: - raise newException(ValueError, "excessBlobGas missing from latest header") - let blobBaseFee = - getBlobBaseFee(header.excessBlobGas.get, api.com, api.com.toEVMFork(header)) * header.blobGasUsed.get.u256 - if blobBaseFee > high(uint64).u256: - raise newException(ValueError, "blobBaseFee is bigger than uint64.max") - return w3Qty blobBaseFee.truncate(uint64) - - server.rpc("eth_getUncleByBlockHashAndIndex") do( - data: Hash32, quantity: Quantity - ) -> BlockObject: - ## Returns information about a uncle of a block by hash and uncle index position. - ## - ## data: hash of block. - ## quantity: the uncle's index position. - ## Returns BlockObject or nil when no block was found. - let index = uint64(quantity) - let blk = api.chain.blockByHash(data).valueOr: - return nil - - if index < 0 or index >= blk.uncles.len.uint64: - return nil - - let - uncle = api.chain.blockByHash(blk.uncles[index].computeBlockHash).valueOr: + if header.blobGasUsed.isNone: + raise newException(ValueError, "blobGasUsed missing from latest header") + if header.excessBlobGas.isNone: + raise newException(ValueError, "excessBlobGas missing from latest header") + let blobBaseFee = + getBlobBaseFee(header.excessBlobGas.get, api.com, api.com.toEVMFork(header)) * header.blobGasUsed.get.u256 + if blobBaseFee > high(uint64).u256: + raise newException(ValueError, "blobBaseFee is bigger than uint64.max") + return w3Qty blobBaseFee.truncate(uint64) + + rpc("eth_getUncleByBlockHashAndIndex") do( + data: Hash32, quantity: Quantity + ) -> BlockObject: + ## Returns information about a uncle of a block by hash and uncle index position. + ## + ## data: hash of block. + ## quantity: the uncle's index position. + ## Returns BlockObject or nil when no block was found. + let index = uint64(quantity) + let blk = api.chain.blockByHash(data).valueOr: return nil - uncleHash = uncle.header.computeBlockHash - return populateBlockObject( - uncleHash, uncle, api.getTotalDifficulty(uncleHash, uncle.header), false, true - ) + if index < 0 or index >= blk.uncles.len.uint64: + return nil + + let + uncle = api.chain.blockByHash(blk.uncles[index].computeBlockHash).valueOr: + return nil + uncleHash = uncle.header.computeBlockHash - server.rpc("eth_getUncleByBlockNumberAndIndex") do( - quantityTag: BlockTag, quantity: Quantity - ) -> BlockObject: - # Returns information about a uncle of a block by number and uncle index position. - ## - ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. - ## quantity: the uncle's index position. - ## Returns BlockObject or nil when no block was found. - let index = uint64(quantity) - let blk = api.blockFromTag(quantityTag).valueOr: - return nil - - if index < 0 or index >= blk.uncles.len.uint64: - return nil - - let - uncle = api.chain.blockByHash(blk.uncles[index].computeBlockHash).valueOr: + return populateBlockObject( + uncleHash, uncle, api.getTotalDifficulty(uncleHash, uncle.header), false, true + ) + + rpc("eth_getUncleByBlockNumberAndIndex") do( + quantityTag: BlockTag, quantity: Quantity + ) -> BlockObject: + # Returns information about a uncle of a block by number and uncle index position. + ## + ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. + ## quantity: the uncle's index position. + ## Returns BlockObject or nil when no block was found. + let index = uint64(quantity) + let blk = api.blockFromTag(quantityTag).valueOr: return nil - uncleHash = uncle.header.computeBlockHash - return populateBlockObject( - uncleHash, uncle, api.getTotalDifficulty(uncleHash, uncle.header), false, true - ) + if index < 0 or index >= blk.uncles.len.uint64: + return nil - server.rpc("eth_config") do() -> EthConfigObject: - ## Returns the current, next and last configuration - ## Doesn't work pre-shangai - ## https://eips.ethereum.org/EIPS/eip-7910 - let currentFork = api.com.toHardFork(api.chain.latestHeader.forkDeterminationInfo) + let + uncle = api.chain.blockByHash(blk.uncles[index].computeBlockHash).valueOr: + return nil + uncleHash = uncle.header.computeBlockHash - if currentFork < Shanghai: - return nil + return populateBlockObject( + uncleHash, uncle, api.getTotalDifficulty(uncleHash, uncle.header), false, true + ) - let - nextFork = api.com.nextFork(currentFork) - lastFork = api.com.lastFork(currentFork) + rpc("eth_config") do() -> EthConfigObject: + ## Returns the current, next and last configuration + ## Doesn't work pre-shangai + ## https://eips.ethereum.org/EIPS/eip-7910 + let currentFork = api.com.toHardFork(api.chain.latestHeader.forkDeterminationInfo) - return api.com.getEthConfigObject(api.chain, currentFork, nextFork, lastFork) + if currentFork < Shanghai: + return nil - server.rpc("eth_getBlockAccessListByBlockHash") do(data: Hash32) -> Opt[BlockAccessList]: - ## Returns the block access list for a block by block hash. - ## - ## data: hash of block. - let header = api.chain.headerByHash(data).valueOr: - raise newException(ValueError, "Block not found") + let + nextFork = api.com.nextFork(currentFork) + lastFork = api.com.lastFork(currentFork) - if not api.com.isAmsterdamOrLater(header.timestamp): - raise newException(ValueError, "Block access list not available for pre-Amsterdam blocks") + return api.com.getEthConfigObject(api.chain, currentFork, nextFork, lastFork) - let bal = api.chain.getBlockAccessList(data).valueOr: - if header.number <= api.chain.resolvedFinNumber: - # This block is finalized so if the bal is missing it means it was pruned. - raise newException(ValueError, "Pruned history unavailable") - else: - return Opt.none(BlockAccessList) + rpc("eth_getBlockAccessListByBlockHash") do(data: Hash32) -> Opt[BlockAccessList]: + ## Returns the block access list for a block by block hash. + ## + ## data: hash of block. + let header = api.chain.headerByHash(data).valueOr: + raise newException(ValueError, "Block not found") - Opt.some(bal) + if not api.com.isAmsterdamOrLater(header.timestamp): + raise newException(ValueError, "Block access list not available for pre-Amsterdam blocks") - server.rpc("eth_getBlockAccessListByBlockNumber") do(quantityTag: BlockTag) -> Opt[BlockAccessList]: - ## Returns the block access list for a block by number. - ## - ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. - let header = api.headerFromTag(quantityTag).valueOr: - raise newException(ValueError, "Block not found") + let bal = api.chain.getBlockAccessList(data).valueOr: + if header.number <= api.chain.resolvedFinNumber: + # This block is finalized so if the bal is missing it means it was pruned. + raise newException(ValueError, "Pruned history unavailable") + else: + return Opt.none(BlockAccessList) - if not api.com.isAmsterdamOrLater(header.timestamp): - raise newException(ValueError, "Block access list not available for pre-Amsterdam blocks") + Opt.some(bal) - let bal = api.chain.getBlockAccessList(header.computeRlpHash()).valueOr: - if header.number <= api.chain.resolvedFinNumber: - # This block is finalized so if the bal is missing it means it was pruned. - raise newException(ValueError, "Pruned history unavailable") - else: - return Opt.none(BlockAccessList) + rpc("eth_getBlockAccessListByBlockNumber") do(quantityTag: BlockTag) -> Opt[BlockAccessList]: + ## Returns the block access list for a block by number. + ## + ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. + let header = api.headerFromTag(quantityTag).valueOr: + raise newException(ValueError, "Block not found") + + if not api.com.isAmsterdamOrLater(header.timestamp): + raise newException(ValueError, "Block access list not available for pre-Amsterdam blocks") + + let bal = api.chain.getBlockAccessList(header.computeRlpHash()).valueOr: + if header.number <= api.chain.resolvedFinNumber: + # This block is finalized so if the bal is missing it means it was pruned. + raise newException(ValueError, "Pruned history unavailable") + else: + return Opt.none(BlockAccessList) - Opt.some(bal) + Opt.some(bal) diff --git a/execution_chain/stateless/stateless_execution_helpers.nim b/execution_chain/stateless/stateless_execution_helpers.nim index 2038066d42..0b6905d809 100644 --- a/execution_chain/stateless/stateless_execution_helpers.nim +++ b/execution_chain/stateless/stateless_execution_helpers.nim @@ -19,7 +19,7 @@ from ../../hive_integration/engine_client import toBlockHeader, toTransactions export stateless_execution -ExecutionWitness.useDefaultSerializationIn JrpcConv +ExecutionWitness.useDefaultSerializationIn EthJson proc readFileToStr*(filePath: string): Result[string, string] = let fileStr = io2.readAllChars(filePath).valueOr: @@ -34,13 +34,13 @@ func toBytes*(hexStr: string): Result[seq[byte], string] = func decodeJson*(T: type ExecutionWitness, jsonStr: string): Result[T, string] = try: - ok(JrpcConv.decode(jsonStr, T)) + ok(EthJson.decode(jsonStr, T)) except SerializationError as e: err("Error decoding json string: " & e.msg) func decodeJson*(T: type BlockObject, jsonStr: string): Result[T, string] = try: - ok(JrpcConv.decode(jsonStr, T)) + ok(EthJson.decode(jsonStr, T)) except SerializationError as e: err("Error decoding json string: " & e.msg) diff --git a/hive_integration/engine_client.nim b/hive_integration/engine_client.nim index 756a3398f4..8b103c1f7c 100644 --- a/hive_integration/engine_client.nim +++ b/hive_integration/engine_client.nim @@ -14,7 +14,7 @@ import stew/byteutils, eth/rlp, eth/common/eth_types_rlp, chronos, - json_rpc/[rpcclient, errors, jsonmarshal], + json_rpc/[rpcclient, errors], ../execution_chain/beacon/web3_eth_conv, ../execution_chain/core/pooled_txs_rlp, ./types diff --git a/nimbus_verified_proxy/json_rpc_frontend.nim b/nimbus_verified_proxy/json_rpc_frontend.nim index f8c34c21b6..cb1d27f756 100644 --- a/nimbus_verified_proxy/json_rpc_frontend.nim +++ b/nimbus_verified_proxy/json_rpc_frontend.nim @@ -86,144 +86,129 @@ template unpackEngineResult[T](res: EngineResult[T]): T = raise newException(ValueError, $error.errType & " -> " & error.errMsg) proc injectEngineFrontend*(server: JsonRpcServer, frontend: EthApiFrontend) = - server.getServer().rpc("eth_blockNumber") do() -> uint64: - unpackEngineResult(await frontend.eth_blockNumber()) - - server.getServer().rpc("eth_getBalance") do( - address: Address, quantityTag: BlockTag - ) -> UInt256: - unpackEngineResult(await frontend.eth_getBalance(address, quantityTag)) - - server.getServer().rpc("eth_getStorageAt") do( - address: Address, slot: UInt256, quantityTag: BlockTag - ) -> FixedBytes[32]: - unpackEngineResult(await frontend.eth_getStorageAt(address, slot, quantityTag)) - - server.getServer().rpc("eth_getTransactionCount") do( - address: Address, quantityTag: BlockTag - ) -> Quantity: - unpackEngineResult(await frontend.eth_getTransactionCount(address, quantityTag)) - - server.getServer().rpc("eth_getCode") do( - address: Address, quantityTag: BlockTag - ) -> seq[byte]: - unpackEngineResult(await frontend.eth_getCode(address, quantityTag)) - - server.getServer().rpc("eth_getBlockByHash") do( - blockHash: Hash32, fullTransactions: bool - ) -> BlockObject: - unpackEngineResult(await frontend.eth_getBlockByHash(blockHash, fullTransactions)) - - server.getServer().rpc("eth_getBlockByNumber") do( - blockTag: BlockTag, fullTransactions: bool - ) -> BlockObject: - unpackEngineResult(await frontend.eth_getBlockByNumber(blockTag, fullTransactions)) - - server.getServer().rpc("eth_getUncleCountByBlockNumber") do( - blockTag: BlockTag - ) -> Quantity: - unpackEngineResult(await frontend.eth_getUncleCountByBlockNumber(blockTag)) - - server.getServer().rpc("eth_getUncleCountByBlockHash") do( - blockHash: Hash32 - ) -> Quantity: - unpackEngineResult(await frontend.eth_getUncleCountByBlockHash(blockHash)) - - server.getServer().rpc("eth_getBlockTransactionCountByNumber") do( - blockTag: BlockTag - ) -> Quantity: - unpackEngineResult(await frontend.eth_getBlockTransactionCountByNumber(blockTag)) - - server.getServer().rpc("eth_getBlockTransactionCountByHash") do( - blockHash: Hash32 - ) -> Quantity: - unpackEngineResult(await frontend.eth_getBlockTransactionCountByHash(blockHash)) - - server.getServer().rpc("eth_getTransactionByBlockNumberAndIndex") do( - blockTag: BlockTag, index: Quantity - ) -> TransactionObject: - unpackEngineResult( - await frontend.eth_getTransactionByBlockNumberAndIndex(blockTag, index) - ) - - server.getServer().rpc("eth_getTransactionByBlockHashAndIndex") do( - blockHash: Hash32, index: Quantity - ) -> TransactionObject: - unpackEngineResult( - await frontend.eth_getTransactionByBlockHashAndIndex(blockHash, index) - ) - - server.getServer().rpc("eth_call") do( - tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: Opt[bool] - ) -> seq[byte]: - unpackEngineResult( - await frontend.eth_call(tx, blockTag, optimisticStateFetch.get(true)) - ) - - server.getServer().rpc("eth_createAccessList") do( - tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: Opt[bool] - ) -> AccessListResult: - unpackEngineResult( - await frontend.eth_createAccessList(tx, blockTag, optimisticStateFetch.get(true)) - ) - - server.getServer().rpc("eth_estimateGas") do( - tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: Opt[bool] - ) -> Quantity: - unpackEngineResult( - await frontend.eth_estimateGas(tx, blockTag, optimisticStateFetch.get(true)) - ) - - server.getServer().rpc("eth_getTransactionByHash") do( - txHash: Hash32 - ) -> TransactionObject: - unpackEngineResult(await frontend.eth_getTransactionByHash(txHash)) - - server.getServer().rpc("eth_getBlockReceipts") do( - blockTag: BlockTag - ) -> Opt[seq[ReceiptObject]]: - unpackEngineResult(await frontend.eth_getBlockReceipts(blockTag)) - - server.getServer().rpc("eth_getTransactionReceipt") do( - txHash: Hash32 - ) -> ReceiptObject: - unpackEngineResult(await frontend.eth_getTransactionReceipt(txHash)) - - server.getServer().rpc("eth_getLogs") do( - filterOptions: FilterOptions - ) -> seq[LogObject]: - unpackEngineResult(await frontend.eth_getLogs(filterOptions)) - - server.getServer().rpc("eth_newFilter") do(filterOptions: FilterOptions) -> string: - unpackEngineResult(await frontend.eth_newFilter(filterOptions)) - - server.getServer().rpc("eth_uninstallFilter") do(filterId: string) -> bool: - unpackEngineResult(await frontend.eth_uninstallFilter(filterId)) - - server.getServer().rpc("eth_getFilterLogs") do(filterId: string) -> seq[LogObject]: - unpackEngineResult(await frontend.eth_getFilterLogs(filterId)) - - server.getServer().rpc("eth_getFilterChanges") do(filterId: string) -> seq[LogObject]: - unpackEngineResult(await frontend.eth_getFilterChanges(filterId)) - - server.getServer().rpc("eth_blobBaseFee") do() -> UInt256: - unpackEngineResult(await frontend.eth_blobBaseFee()) - - server.getServer().rpc("eth_gasPrice") do() -> Quantity: - unpackEngineResult(await frontend.eth_gasPrice()) - - server.getServer().rpc("eth_maxPriorityFeePerGas") do() -> Quantity: - unpackEngineResult(await frontend.eth_maxPriorityFeePerGas()) - - server.getServer().rpc("eth_feeHistory") do( - blockCount: Quantity, newestBlock: BlockTag, rewardPercentiles: Opt[seq[float64]] - ) -> FeeHistoryResult: - unpackEngineResult( - await frontend.eth_feeHistory(blockCount, newestBlock, rewardPercentiles) - ) - - server.getServer().rpc("eth_sendRawTransaction") do(txBytes: seq[byte]) -> Hash32: - unpackEngineResult(await frontend.eth_sendRawTransaction(txBytes)) + server.getServer().rpcContext(EthJson): + rpc("eth_blockNumber") do() -> uint64: + unpackEngineResult(await frontend.eth_blockNumber()) + + rpc("eth_getBalance") do(address: Address, quantityTag: BlockTag) -> UInt256: + unpackEngineResult(await frontend.eth_getBalance(address, quantityTag)) + + rpc("eth_getStorageAt") do( + address: Address, slot: UInt256, quantityTag: BlockTag + ) -> FixedBytes[32]: + unpackEngineResult(await frontend.eth_getStorageAt(address, slot, quantityTag)) + + rpc("eth_getTransactionCount") do( + address: Address, quantityTag: BlockTag + ) -> Quantity: + unpackEngineResult(await frontend.eth_getTransactionCount(address, quantityTag)) + + rpc("eth_getCode") do(address: Address, quantityTag: BlockTag) -> seq[byte]: + unpackEngineResult(await frontend.eth_getCode(address, quantityTag)) + + rpc("eth_getBlockByHash") do( + blockHash: Hash32, fullTransactions: bool + ) -> BlockObject: + unpackEngineResult(await frontend.eth_getBlockByHash(blockHash, fullTransactions)) + + rpc("eth_getBlockByNumber") do( + blockTag: BlockTag, fullTransactions: bool + ) -> BlockObject: + unpackEngineResult( + await frontend.eth_getBlockByNumber(blockTag, fullTransactions) + ) + + rpc("eth_getUncleCountByBlockNumber") do(blockTag: BlockTag) -> Quantity: + unpackEngineResult(await frontend.eth_getUncleCountByBlockNumber(blockTag)) + + rpc("eth_getUncleCountByBlockHash") do(blockHash: Hash32) -> Quantity: + unpackEngineResult(await frontend.eth_getUncleCountByBlockHash(blockHash)) + + rpc("eth_getBlockTransactionCountByNumber") do(blockTag: BlockTag) -> Quantity: + unpackEngineResult(await frontend.eth_getBlockTransactionCountByNumber(blockTag)) + + rpc("eth_getBlockTransactionCountByHash") do(blockHash: Hash32) -> Quantity: + unpackEngineResult(await frontend.eth_getBlockTransactionCountByHash(blockHash)) + + rpc("eth_getTransactionByBlockNumberAndIndex") do( + blockTag: BlockTag, index: Quantity + ) -> TransactionObject: + unpackEngineResult( + await frontend.eth_getTransactionByBlockNumberAndIndex(blockTag, index) + ) + + rpc("eth_getTransactionByBlockHashAndIndex") do( + blockHash: Hash32, index: Quantity + ) -> TransactionObject: + unpackEngineResult( + await frontend.eth_getTransactionByBlockHashAndIndex(blockHash, index) + ) + + rpc("eth_call") do( + tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: Opt[bool] + ) -> seq[byte]: + unpackEngineResult( + await frontend.eth_call(tx, blockTag, optimisticStateFetch.get(true)) + ) + + rpc("eth_createAccessList") do( + tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: Opt[bool] + ) -> AccessListResult: + unpackEngineResult( + await frontend.eth_createAccessList( + tx, blockTag, optimisticStateFetch.get(true) + ) + ) + + rpc("eth_estimateGas") do( + tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: Opt[bool] + ) -> Quantity: + unpackEngineResult( + await frontend.eth_estimateGas(tx, blockTag, optimisticStateFetch.get(true)) + ) + + rpc("eth_getTransactionByHash") do(txHash: Hash32) -> TransactionObject: + unpackEngineResult(await frontend.eth_getTransactionByHash(txHash)) + + rpc("eth_getBlockReceipts") do(blockTag: BlockTag) -> Opt[seq[ReceiptObject]]: + unpackEngineResult(await frontend.eth_getBlockReceipts(blockTag)) + + rpc("eth_getTransactionReceipt") do(txHash: Hash32) -> ReceiptObject: + unpackEngineResult(await frontend.eth_getTransactionReceipt(txHash)) + + rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[LogObject]: + unpackEngineResult(await frontend.eth_getLogs(filterOptions)) + + rpc("eth_newFilter") do(filterOptions: FilterOptions) -> string: + unpackEngineResult(await frontend.eth_newFilter(filterOptions)) + + rpc("eth_uninstallFilter") do(filterId: string) -> bool: + unpackEngineResult(await frontend.eth_uninstallFilter(filterId)) + + rpc("eth_getFilterLogs") do(filterId: string) -> seq[LogObject]: + unpackEngineResult(await frontend.eth_getFilterLogs(filterId)) + + rpc("eth_getFilterChanges") do(filterId: string) -> seq[LogObject]: + unpackEngineResult(await frontend.eth_getFilterChanges(filterId)) + + rpc("eth_blobBaseFee") do() -> UInt256: + unpackEngineResult(await frontend.eth_blobBaseFee()) + + rpc("eth_gasPrice") do() -> Quantity: + unpackEngineResult(await frontend.eth_gasPrice()) + + rpc("eth_maxPriorityFeePerGas") do() -> Quantity: + unpackEngineResult(await frontend.eth_maxPriorityFeePerGas()) + + rpc("eth_feeHistory") do( + blockCount: Quantity, newestBlock: BlockTag, rewardPercentiles: Opt[seq[float64]] + ) -> FeeHistoryResult: + unpackEngineResult( + await frontend.eth_feeHistory(blockCount, newestBlock, rewardPercentiles) + ) + + rpc("eth_sendRawTransaction") do(txBytes: seq[byte]) -> Hash32: + unpackEngineResult(await frontend.eth_sendRawTransaction(txBytes)) proc stop*(server: JsonRpcServer) {.async: (raises: []).} = case server.kind diff --git a/nimbus_verified_proxy/libverifproxy/verifproxy.nim b/nimbus_verified_proxy/libverifproxy/verifproxy.nim index bbc4698b1d..1bc17af3e3 100644 --- a/nimbus_verified_proxy/libverifproxy/verifproxy.nim +++ b/nimbus_verified_proxy/libverifproxy/verifproxy.nim @@ -11,7 +11,6 @@ import std/[atomics, json, net, strutils, lists], beacon_chain/spec/[digest, network], beacon_chain/nimbus_binary_common, - json_rpc/[jsonmarshal], web3/[eth_api_types, conversions], ../engine/types, ../nimbus_verified_proxy_conf, @@ -190,7 +189,7 @@ func unpackArg( # generalized overloading func unpackArg(arg: string, argType: type): Result[argType, string] {.raises: [].} = try: - ok(JrpcConv.decode(arg, argType)) + ok(EthJson.decode(arg, argType)) except CatchableError as e: err("Parameter of type " & $argType & " coudln't be decoded: " & e.msg) diff --git a/nimbus_verified_proxy/tests/test_utils.nim b/nimbus_verified_proxy/tests/test_utils.nim index 16ac3f0d1e..082e716287 100644 --- a/nimbus_verified_proxy/tests/test_utils.nim +++ b/nimbus_verified_proxy/tests/test_utils.nim @@ -11,7 +11,6 @@ import stint, chronos, - json_rpc/jsonmarshal, stew/[io2, byteutils], web3/[eth_api_types, conversions], eth/common/eth_types_rlp, @@ -24,52 +23,52 @@ type TestProxyError* = object of CatchableError proc getBlockFromJson*(filepath: string): BlockObject {.raises: [SerializationError].} = let blkBytes = readAllBytes(filepath) - JrpcConv.decode(blkBytes.get, BlockObject) + EthJson.decode(blkBytes.get, BlockObject) proc getReceiptsFromJson*( filepath: string ): seq[ReceiptObject] {.raises: [SerializationError].} = let rxBytes = readAllBytes(filepath) - JrpcConv.decode(rxBytes.get, seq[ReceiptObject]) + EthJson.decode(rxBytes.get, seq[ReceiptObject]) proc getLogsFromJson*( filepath: string ): seq[LogObject] {.raises: [SerializationError].} = let logBytes = readAllBytes(filepath) - JrpcConv.decode(logBytes.get, seq[LogObject]) + EthJson.decode(logBytes.get, seq[LogObject]) proc getProofFromJson*( filepath: string ): ProofResponse {.raises: [SerializationError].} = let proofBytes = readAllBytes(filepath) - JrpcConv.decode(proofBytes.get, ProofResponse) + EthJson.decode(proofBytes.get, ProofResponse) proc getAccessListFromJson*( filepath: string ): AccessListResult {.raises: [SerializationError].} = let filebytes = readAllBytes(filepath) - JrpcConv.decode(filebytes.get, AccessListResult) + EthJson.decode(filebytes.get, AccessListResult) proc getCodeFromJson*( filepath: string ): seq[byte] {.raises: [SerializationError, ValueError].} = let filebytes = readAllBytes(filepath) - JrpcConv.decode(filebytes.get, string).hexToSeqByte() + EthJson.decode(filebytes.get, string).hexToSeqByte() template `==`*(b1: BlockObject, b2: BlockObject): bool = - JrpcConv.encode(b1).JsonString == JrpcConv.encode(b2).JsonString + EthJson.encode(b1).JsonString == EthJson.encode(b2).JsonString template `==`*(tx1: TransactionObject, tx2: TransactionObject): bool = - JrpcConv.encode(tx1).JsonString == JrpcConv.encode(tx2).JsonString + EthJson.encode(tx1).JsonString == EthJson.encode(tx2).JsonString template `==`*(rx1: ReceiptObject, rx2: ReceiptObject): bool = - JrpcConv.encode(rx1).JsonString == JrpcConv.encode(rx2).JsonString + EthJson.encode(rx1).JsonString == EthJson.encode(rx2).JsonString template `==`*(rxs1: seq[ReceiptObject], rxs2: seq[ReceiptObject]): bool = - JrpcConv.encode(rxs1).JsonString == JrpcConv.encode(rxs2).JsonString + EthJson.encode(rxs1).JsonString == EthJson.encode(rxs2).JsonString template `==`*(logs1: seq[LogObject], logs2: seq[LogObject]): bool = - JrpcConv.encode(logs1).JsonString == JrpcConv.encode(logs2).JsonString + EthJson.encode(logs1).JsonString == EthJson.encode(logs2).JsonString proc initTestEngine*( testState: TestApiState, headerCacheLen: int, maxBlockWalk: uint64 diff --git a/portal/client/nimbus_portal_client.nim b/portal/client/nimbus_portal_client.nim index 127d24cc3b..c9c1f4ef8e 100644 --- a/portal/client/nimbus_portal_client.nim +++ b/portal/client/nimbus_portal_client.nim @@ -44,7 +44,7 @@ const chronicles.formatIt(IoErrorCode): $it -createRpcSigsFromNim(RpcClient): +createRpcSigsFromNim(RpcClient, EthJson): # EL debug call to get header for validation proc debug_getHeaderByNumber(blockNumber: BlockIdentifier): string diff --git a/portal/rpc/rpc_calls/rpc_discovery_calls.nim b/portal/rpc/rpc_calls/rpc_discovery_calls.nim index d22029f3f0..4dba9def19 100644 --- a/portal/rpc/rpc_calls/rpc_discovery_calls.nim +++ b/portal/rpc/rpc_calls/rpc_discovery_calls.nim @@ -11,7 +11,7 @@ import json_rpc/rpcclient, ../rpc_types, ../rpc_discovery_api export rpc_types, rpc_discovery_api -createRpcSigsFromNim(RpcClient): +createRpcSigsFromNim(RpcClient, EthJson): # Discovery v5 json-rpc calls proc discv5_nodeInfo(): NodeInfo proc discv5_updateNodeInfo(kvPairs: seq[(string, string)]): RoutingTableInfo diff --git a/portal/rpc/rpc_calls/rpc_portal_calls.nim b/portal/rpc/rpc_calls/rpc_portal_calls.nim index be6507e71f..6c97d47d62 100644 --- a/portal/rpc/rpc_calls/rpc_portal_calls.nim +++ b/portal/rpc/rpc_calls/rpc_portal_calls.nim @@ -11,7 +11,7 @@ import std/json, json_rpc/rpcclient, ../rpc_types export rpc_types -createRpcSigsFromNim(RpcClient): +createRpcSigsFromNim(RpcClient, EthJson): ## Portal History Network json-rpc calls proc portal_historyNodeInfo(): NodeInfo proc portal_historyRoutingTableInfo(): RoutingTableInfo diff --git a/portal/rpc/rpc_calls/rpc_portal_debug_calls.nim b/portal/rpc/rpc_calls/rpc_portal_debug_calls.nim index 792dc19e86..7873a78bba 100644 --- a/portal/rpc/rpc_calls/rpc_portal_debug_calls.nim +++ b/portal/rpc/rpc_calls/rpc_portal_debug_calls.nim @@ -11,9 +11,9 @@ import json_rpc/rpcclient, ../rpc_types export rpc_types -Opt[string].useDefaultSerializationIn JrpcConv +Opt[string].useDefaultSerializationIn EthJson -createRpcSigsFromNim(RpcClient): +createRpcSigsFromNim(RpcClient, EthJson): ## Portal History Network json-rpc debug & custom calls proc portal_debug_historyGossipHeaders( era1File: string, epochRecordFile: Opt[string] diff --git a/portal/rpc/rpc_discovery_api.nim b/portal/rpc/rpc_discovery_api.nim index 02383c09bb..9015822512 100644 --- a/portal/rpc/rpc_discovery_api.nim +++ b/portal/rpc/rpc_discovery_api.nim @@ -21,110 +21,109 @@ type PongResponse* = object recipientIP: string recipientPort: uint16 -PongResponse.useDefaultSerializationIn JrpcConv +PongResponse.useDefaultSerializationIn EthJson proc installDiscoveryApiHandlers*(rpcServer: RpcServer, d: discv5_protocol.Protocol) = ## Discovery v5 JSON-RPC API such as defined here: ## https://github.com/ethereum/portal-network-specs/tree/master/jsonrpc - rpcServer.rpc("discv5_nodeInfo") do() -> NodeInfo: - return d.routingTable.getNodeInfo() - - rpcServer.rpc("discv5_updateNodeInfo") do(kvPairs: seq[(string, string)]) -> NodeInfo: - # TODO: Not according to spec, as spec only allows socket address. - # portal-specs PR has been created with suggested change as is here. - let enrFields = kvPairs.map( - proc(n: (string, string)): (string, seq[byte]) {.raises: [ValueError].} = - (n[0], hexToSeqByte(n[1])) - ) - let updated = d.updateRecord(enrFields) - if updated.isErr(): - raise newException(ValueError, $updated.error) - - return d.routingTable.getNodeInfo() - - rpcServer.rpc("discv5_routingTableInfo") do() -> RoutingTableInfo: - return getRoutingTableInfo(d.routingTable) - - rpcServer.rpc("discv5_addEnr") do(enr: Record) -> bool: - let node = Node.fromRecord(enr) - let res = d.addNode(node) - if res: - d.routingTable.setJustSeen(node) - return res - - rpcServer.rpc("discv5_addEnrs") do(enrs: seq[Record]) -> bool: - # Note: unspecified RPC, but useful for our local testnet test - # TODO: We could also adjust the API of addNode & fromRecord to accept a seen - # parameter, but perhaps only if that makes sense on other locations in - # discv5/portal that are not testing/debug related. - for enr in enrs: - let node = Node.fromRecord(enr) - if d.addNode(node): - d.routingTable.setJustSeen(node) + rpcServer.rpcContext(EthJson): + rpc("discv5_nodeInfo") do() -> NodeInfo: + return d.routingTable.getNodeInfo() - return true + rpc("discv5_updateNodeInfo") do(kvPairs: seq[(string, string)]) -> NodeInfo: + # TODO: Not according to spec, as spec only allows socket address. + # portal-specs PR has been created with suggested change as is here. + let enrFields = kvPairs.map( + proc(n: (string, string)): (string, seq[byte]) {.raises: [ValueError].} = + (n[0], hexToSeqByte(n[1])) + ) + let updated = d.updateRecord(enrFields) + if updated.isErr(): + raise newException(ValueError, $updated.error) - rpcServer.rpc("discv5_getEnr") do(nodeId: NodeId) -> Record: - let node = d.getNode(nodeId) - if node.isSome(): - return node.get().record - else: - raise newException(ValueError, "Record not in local routing table.") + return d.routingTable.getNodeInfo() + + rpc("discv5_routingTableInfo") do() -> RoutingTableInfo: + return getRoutingTableInfo(d.routingTable) + + rpc("discv5_addEnr") do(enr: Record) -> bool: + let node = Node.fromRecord(enr) + let res = d.addNode(node) + if res: + d.routingTable.setJustSeen(node) + return res + + rpc("discv5_addEnrs") do(enrs: seq[Record]) -> bool: + # Note: unspecified RPC, but useful for our local testnet test + # TODO: We could also adjust the API of addNode & fromRecord to accept a seen + # parameter, but perhaps only if that makes sense on other locations in + # discv5/portal that are not testing/debug related. + for enr in enrs: + let node = Node.fromRecord(enr) + if d.addNode(node): + d.routingTable.setJustSeen(node) - rpcServer.rpc("discv5_deleteEnr") do(nodeId: NodeId) -> bool: - # TODO: Adjust `removeNode` to accept NodeId as param and to return bool. - let node = d.getNode(nodeId) - if node.isSome(): - d.routingTable.removeNode(node.get()) return true - else: - raise newException(ValueError, "Record not in local routing table.") - - rpcServer.rpc("discv5_lookupEnr") do(nodeId: NodeId) -> Record: - let lookup = await d.resolve(nodeId) - if lookup.isSome(): - return lookup.get().record - else: - raise newException(ValueError, "Record not found in DHT lookup.") - - rpcServer.rpc("discv5_ping") do(enr: Record) -> PongResponse: - let - node = toNodeWithAddress(enr) - pong = await d.ping(node) - - if pong.isErr(): - raise newException(ValueError, $pong.error) - else: - let p = pong.get() - return PongResponse(enrSeq: p.enrSeq, recipientIP: $p.ip, recipientPort: p.port) - - rpcServer.rpc("discv5_findNode") do( - enr: Record, distances: seq[uint16] - ) -> seq[Record]: - let - node = toNodeWithAddress(enr) - nodes = await d.findNode(node, distances) - if nodes.isErr(): - raise newException(ValueError, $nodes.error) - else: - return nodes.get().map( - proc(n: Node): Record = - n.record - ) - - rpcServer.rpc("discv5_talkReq") do(enr: Record, protocol, payload: string) -> string: - let - node = toNodeWithAddress(enr) - talkresp = await d.talkReq(node, hexToSeqByte(protocol), hexToSeqByte(payload)) - if talkresp.isErr(): - raise newException(ValueError, $talkresp.error) - else: - return talkresp.get().toHex() - - rpcServer.rpc("discv5_recursiveFindNodes") do(nodeId: NodeId) -> seq[Record]: - let discovered = await d.lookup(nodeId) - return discovered.map( - proc(n: Node): Record = - n.record - ) + + rpc("discv5_getEnr") do(nodeId: NodeId) -> Record: + let node = d.getNode(nodeId) + if node.isSome(): + return node.get().record + else: + raise newException(ValueError, "Record not in local routing table.") + + rpc("discv5_deleteEnr") do(nodeId: NodeId) -> bool: + # TODO: Adjust `removeNode` to accept NodeId as param and to return bool. + let node = d.getNode(nodeId) + if node.isSome(): + d.routingTable.removeNode(node.get()) + return true + else: + raise newException(ValueError, "Record not in local routing table.") + + rpc("discv5_lookupEnr") do(nodeId: NodeId) -> Record: + let lookup = await d.resolve(nodeId) + if lookup.isSome(): + return lookup.get().record + else: + raise newException(ValueError, "Record not found in DHT lookup.") + + rpc("discv5_ping") do(enr: Record) -> PongResponse: + let + node = toNodeWithAddress(enr) + pong = await d.ping(node) + + if pong.isErr(): + raise newException(ValueError, $pong.error) + else: + let p = pong.get() + return PongResponse(enrSeq: p.enrSeq, recipientIP: $p.ip, recipientPort: p.port) + + rpc("discv5_findNode") do(enr: Record, distances: seq[uint16]) -> seq[Record]: + let + node = toNodeWithAddress(enr) + nodes = await d.findNode(node, distances) + if nodes.isErr(): + raise newException(ValueError, $nodes.error) + else: + return nodes.get().map( + proc(n: Node): Record = + n.record + ) + + rpc("discv5_talkReq") do(enr: Record, protocol, payload: string) -> string: + let + node = toNodeWithAddress(enr) + talkresp = await d.talkReq(node, hexToSeqByte(protocol), hexToSeqByte(payload)) + if talkresp.isErr(): + raise newException(ValueError, $talkresp.error) + else: + return talkresp.get().toHex() + + rpc("discv5_recursiveFindNodes") do(nodeId: NodeId) -> seq[Record]: + let discovered = await d.lookup(nodeId) + return discovered.map( + proc(n: Node): Record = + n.record + ) diff --git a/portal/rpc/rpc_portal_beacon_api.nim b/portal/rpc/rpc_portal_beacon_api.nim index 3fe5a9d426..6b8aaefe2f 100644 --- a/portal/rpc/rpc_portal_beacon_api.nim +++ b/portal/rpc/rpc_portal_beacon_api.nim @@ -23,150 +23,144 @@ export tables # Portal Network JSON-RPC implementation as per specification: # https://github.com/ethereum/portal-network-specs/tree/master/jsonrpc -ContentInfo.useDefaultSerializationIn JrpcConv -TraceContentLookupResult.useDefaultSerializationIn JrpcConv -TraceObject.useDefaultSerializationIn JrpcConv -NodeMetadata.useDefaultSerializationIn JrpcConv -TraceResponse.useDefaultSerializationIn JrpcConv +ContentInfo.useDefaultSerializationIn EthJson +TraceContentLookupResult.useDefaultSerializationIn EthJson +TraceObject.useDefaultSerializationIn EthJson +NodeMetadata.useDefaultSerializationIn EthJson +TraceResponse.useDefaultSerializationIn EthJson proc installPortalBeaconApiHandlers*(rpcServer: RpcServer, p: PortalProtocol) = - rpcServer.rpc("portal_beaconFindContent") do( - enr: Record, contentKey: string - ) -> JsonString: - let - node = toNodeWithAddress(enr) - foundContentResult = - await p.findContent(node, ContentKeyByteList.init(hexToSeqByte(contentKey))) - - if foundContentResult.isErr(): - raise newException(ValueError, $foundContentResult.error) - else: - let foundContent = foundContentResult.get() - case foundContent.kind - of Content: - let res = ContentInfo( - content: foundContent.content.to0xHex(), utpTransfer: foundContent.utpTransfer - ) - JrpcConv.encode(res).JsonString - of Nodes: - let enrs = foundContent.nodes.map( - proc(n: Node): Record = - n.record - ) - let jsonEnrs = JrpcConv.encode(enrs) - ("{\"enrs\":" & jsonEnrs & "}").JsonString + rpcServer.rpcContext(EthJson): + rpc("portal_beaconFindContent") do(enr: Record, contentKey: string) -> JsonString: + let + node = toNodeWithAddress(enr) + foundContentResult = + await p.findContent(node, ContentKeyByteList.init(hexToSeqByte(contentKey))) + + if foundContentResult.isErr(): + raise newException(ValueError, $foundContentResult.error) + else: + let foundContent = foundContentResult.get() + case foundContent.kind + of Content: + let res = ContentInfo( + content: foundContent.content.to0xHex(), + utpTransfer: foundContent.utpTransfer, + ) + EthJson.encode(res).JsonString + of Nodes: + let enrs = foundContent.nodes.map( + proc(n: Node): Record = + n.record + ) + let jsonEnrs = EthJson.encode(enrs) + ("{\"enrs\":" & jsonEnrs & "}").JsonString + + rpc("portal_beaconOffer") do(enr: Record, contentItems: seq[ContentItem]) -> string: + let node = toNodeWithAddress(enr) + + var contentOffers: seq[ContentKV] + for contentItem in contentItems: + let + keyBytes = ContentKeyByteList.init(hexToSeqByte(contentItem[0])) + offerValueBytes = hexToSeqByte(contentItem[1]) + contentOffers.add(ContentKV(contentKey: keyBytes, content: offerValueBytes)) + + let offerResult = (await p.offer(node, contentOffers)).valueOr: + raise newException(ValueError, $error) + + SSZ.encode(offerResult).to0xHex() + + rpc("portal_beaconGetContent") do(contentKey: string) -> ContentInfo: + let + keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) + contentId = p.toContentId(keyBytes).valueOr: + raise invalidContentKeyError() + + contentLookupResult = (await p.contentLookup(keyBytes, contentId)).valueOr: + raise contentNotFoundErr() + content = contentLookupResult.content.to0xHex() - rpcServer.rpc("portal_beaconOffer") do( - enr: Record, contentItems: seq[ContentItem] - ) -> string: - let node = toNodeWithAddress(enr) + ContentInfo(content: content, utpTransfer: contentLookupResult.utpTransfer) - var contentOffers: seq[ContentKV] - for contentItem in contentItems: + rpc("portal_beaconTraceGetContent") do( + contentKey: string + ) -> TraceContentLookupResult: let - keyBytes = ContentKeyByteList.init(hexToSeqByte(contentItem[0])) - offerValueBytes = hexToSeqByte(contentItem[1]) - contentOffers.add(ContentKV(contentKey: keyBytes, content: offerValueBytes)) - - let offerResult = (await p.offer(node, contentOffers)).valueOr: - raise newException(ValueError, $error) - - SSZ.encode(offerResult).to0xHex() - - rpcServer.rpc("portal_beaconGetContent") do(contentKey: string) -> ContentInfo: - let - keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) - contentId = p.toContentId(keyBytes).valueOr: - raise invalidContentKeyError() - - contentLookupResult = (await p.contentLookup(keyBytes, contentId)).valueOr: - raise contentNotFoundErr() - content = contentLookupResult.content.to0xHex() - - ContentInfo(content: content, utpTransfer: contentLookupResult.utpTransfer) - - rpcServer.rpc("portal_beaconTraceGetContent") do( - contentKey: string - ) -> TraceContentLookupResult: - let - keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) - contentId = p.toContentId(keyBytes).valueOr: - raise invalidContentKeyError() - - # TODO: Might want to restructure the lookup result here. Potentially doing - # the json conversion in this module. - let - res = await p.traceContentLookup(keyBytes, contentId) - _ = res.content.valueOr: - let data = Opt.some(JrpcConv.encode(res.trace).JsonString) - raise contentNotFoundErrWithTrace(data) - - res - - rpcServer.rpc("portal_beaconStore") do( - contentKey: string, contentValue: string - ) -> bool: - let - keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) - offerValueBytes = hexToSeqByte(contentValue) - contentId = p.toContentId(keyBytes).valueOr: - raise invalidContentKeyError() - - # TODO: Do we need to convert the received offer to a value without proofs before storing? - p.storeContent(keyBytes, contentId, offerValueBytes) - - rpcServer.rpc("portal_beaconLocalContent") do(contentKey: string) -> string: - let - keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) - contentId = p.toContentId(keyBytes).valueOr: - raise invalidContentKeyError() - - valueBytes = p.dbGet(keyBytes, contentId).valueOr: - raise contentNotFoundErr() - - valueBytes.to0xHex() - - rpcServer.rpc("portal_beaconPutContent") do( - contentKey: string, contentValue: string - ) -> PutContentResult: - let - keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) - _ = p.toContentId(keyBytes).valueOr: - raise invalidContentKeyError() - offerValueBytes = hexToSeqByte(contentValue) + keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) + contentId = p.toContentId(keyBytes).valueOr: + raise invalidContentKeyError() + + # TODO: Might want to restructure the lookup result here. Potentially doing + # the json conversion in this module. + let + res = await p.traceContentLookup(keyBytes, contentId) + _ = res.content.valueOr: + let data = Opt.some(EthJson.encode(res.trace).JsonString) + raise contentNotFoundErrWithTrace(data) + + res + + rpc("portal_beaconStore") do(contentKey: string, contentValue: string) -> bool: + let + keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) + offerValueBytes = hexToSeqByte(contentValue) + contentId = p.toContentId(keyBytes).valueOr: + raise invalidContentKeyError() # TODO: Do we need to convert the received offer to a value without proofs before storing? - # TODO: validate and store content locally - # storedLocally = p.storeContent(keyBytes, contentId, valueBytes) - gossipMetadata = await p.neighborhoodGossip( - Opt.none(NodeId), - ContentKeysList(@[keyBytes]), - @[offerValueBytes], - enableNodeLookup = true, - ) + p.storeContent(keyBytes, contentId, offerValueBytes) - PutContentResult( - storedLocally: false, - peerCount: gossipMetadata.successCount, - acceptMetadata: AcceptMetadata( - acceptedCount: gossipMetadata.acceptedCount, - genericDeclineCount: gossipMetadata.genericDeclineCount, - alreadyStoredCount: gossipMetadata.alreadyStoredCount, - notWithinRadiusCount: gossipMetadata.notWithinRadiusCount, - rateLimitedCount: gossipMetadata.rateLimitedCount, - transferInProgressCount: gossipMetadata.transferInProgressCount, - ), - ) - - rpcServer.rpc("portal_beaconRandomGossip") do( - contentKey: string, contentValue: string - ) -> int: - let - keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) - offerValueBytes = hexToSeqByte(contentValue) - - gossipMetadata = await p.randomGossip( - Opt.none(NodeId), ContentKeysList(@[keyBytes]), @[offerValueBytes] + rpc("portal_beaconLocalContent") do(contentKey: string) -> string: + let + keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) + contentId = p.toContentId(keyBytes).valueOr: + raise invalidContentKeyError() + + valueBytes = p.dbGet(keyBytes, contentId).valueOr: + raise contentNotFoundErr() + + valueBytes.to0xHex() + + rpc("portal_beaconPutContent") do( + contentKey: string, contentValue: string + ) -> PutContentResult: + let + keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) + _ = p.toContentId(keyBytes).valueOr: + raise invalidContentKeyError() + offerValueBytes = hexToSeqByte(contentValue) + + # TODO: Do we need to convert the received offer to a value without proofs before storing? + # TODO: validate and store content locally + # storedLocally = p.storeContent(keyBytes, contentId, valueBytes) + gossipMetadata = await p.neighborhoodGossip( + Opt.none(NodeId), + ContentKeysList(@[keyBytes]), + @[offerValueBytes], + enableNodeLookup = true, + ) + + PutContentResult( + storedLocally: false, + peerCount: gossipMetadata.successCount, + acceptMetadata: AcceptMetadata( + acceptedCount: gossipMetadata.acceptedCount, + genericDeclineCount: gossipMetadata.genericDeclineCount, + alreadyStoredCount: gossipMetadata.alreadyStoredCount, + notWithinRadiusCount: gossipMetadata.notWithinRadiusCount, + rateLimitedCount: gossipMetadata.rateLimitedCount, + transferInProgressCount: gossipMetadata.transferInProgressCount, + ), ) - gossipMetadata.successCount + rpc("portal_beaconRandomGossip") do(contentKey: string, contentValue: string) -> int: + let + keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey)) + offerValueBytes = hexToSeqByte(contentValue) + + gossipMetadata = await p.randomGossip( + Opt.none(NodeId), ContentKeysList(@[keyBytes]), @[offerValueBytes] + ) + + gossipMetadata.successCount diff --git a/portal/rpc/rpc_portal_common_api.nim b/portal/rpc/rpc_portal_common_api.nim index 847e90cd65..0853a53c24 100644 --- a/portal/rpc/rpc_portal_common_api.nim +++ b/portal/rpc/rpc_portal_common_api.nim @@ -28,108 +28,107 @@ proc installPortalCommonApiHandlers*( ) = const networkStr = network.symbolName() - rpcServer.rpc("portal_" & networkStr & "NodeInfo") do() -> NodeInfo: - return p.routingTable.getNodeInfo() - - rpcServer.rpc("portal_" & networkStr & "RoutingTableInfo") do() -> RoutingTableInfo: - return getRoutingTableInfo(p.routingTable) - - rpcServer.rpc("portal_" & networkStr & "AddEnr") do(enr: Record) -> bool: - let node = Node.fromRecord(enr) - if p.addNode(node) == Added: - p.routingTable.setJustSeen(node) - true - else: - false - - rpcServer.rpc("portal_" & networkStr & "AddEnrs") do(enrs: seq[Record]) -> bool: - # Note: unspecified RPC, but useful for our local testnet test - for enr in enrs: + rpcServer.rpcContext(EthJson): + rpc("portal_" & networkStr & "NodeInfo") do() -> NodeInfo: + return p.routingTable.getNodeInfo() + + rpc("portal_" & networkStr & "RoutingTableInfo") do() -> RoutingTableInfo: + return getRoutingTableInfo(p.routingTable) + + rpc("portal_" & networkStr & "AddEnr") do(enr: Record) -> bool: let node = Node.fromRecord(enr) if p.addNode(node) == Added: p.routingTable.setJustSeen(node) + true + else: + false - return true - - rpcServer.rpc("portal_" & networkStr & "GetEnr") do(nodeId: NodeId) -> Record: - if p.localNode.id == nodeId: - return p.localNode.record - - let node = p.getNode(nodeId) - if node.isSome(): - return node.get().record - else: - raise newException(ValueError, "Record not in local routing table.") + rpc("portal_" & networkStr & "AddEnrs") do(enrs: seq[Record]) -> bool: + # Note: unspecified RPC, but useful for our local testnet test + for enr in enrs: + let node = Node.fromRecord(enr) + if p.addNode(node) == Added: + p.routingTable.setJustSeen(node) - rpcServer.rpc("portal_" & networkStr & "DeleteEnr") do(nodeId: NodeId) -> bool: - # TODO: Adjust `removeNode` to accept NodeId as param and to return bool. - let node = p.getNode(nodeId) - if node.isSome(): - p.routingTable.removeNode(node.get()) return true - else: - return false - - rpcServer.rpc("portal_" & networkStr & "LookupEnr") do(nodeId: NodeId) -> Record: - let lookup = await p.resolve(nodeId) - if lookup.isSome(): - return lookup.get().record - else: - raise newException(ValueError, "Record not found in DHT lookup.") - - rpcServer.rpc("portal_" & networkStr & "Ping") do( - enr: Record, payloadType: Opt[uint16], payload: Opt[UnknownPayload] - ) -> PingResult: - if payloadType.isSome() and payloadType.get() != CapabilitiesType: - # We only support sending the default CapabilitiesPayload for now. - # This is fine because according to the spec clients are only required - # to support the standard extensions. - raise payloadTypeNotSupportedError() - - if payload.isSome(): - # We don't support passing in a custom payload. In order to implement - # this we use the empty UnknownPayload type which is defined in the spec - # as a json object with no required fields. Just using it here to indicate - # if an object was supplied or not and then throw the correct error if so. - raise userSpecifiedPayloadBlockedByClientError() - - let - node = toNodeWithAddress(enr) - pong = (await p.ping(node)).valueOr: - raise newException(ValueError, $error) - - let - (enrSeq, payloadType, capabilitiesPayload) = pong - clientInfo = capabilitiesPayload.client_info.asSeq() - payload = ( - string.fromBytes(clientInfo), - capabilitiesPayload.data_radius, - capabilitiesPayload.capabilities.asSeq(), - ) - return PingResult( - enrSeq: EnrSeqNumber(enrSeq), payloadType: payloadType, payload: payload - ) - - rpcServer.rpc("portal_" & networkStr & "FindNodes") do( - enr: Record, distances: seq[uint16] - ) -> seq[Record]: - let - node = toNodeWithAddress(enr) - nodes = await p.findNodes(node, distances) - if nodes.isErr(): - raise newException(ValueError, $nodes.error) - else: - return nodes.get().map( - proc(n: Node): Record = - n.record + rpc("portal_" & networkStr & "GetEnr") do(nodeId: NodeId) -> Record: + if p.localNode.id == nodeId: + return p.localNode.record + + let node = p.getNode(nodeId) + if node.isSome(): + return node.get().record + else: + raise newException(ValueError, "Record not in local routing table.") + + rpc("portal_" & networkStr & "DeleteEnr") do(nodeId: NodeId) -> bool: + # TODO: Adjust `removeNode` to accept NodeId as param and to return bool. + let node = p.getNode(nodeId) + if node.isSome(): + p.routingTable.removeNode(node.get()) + return true + else: + return false + + rpc("portal_" & networkStr & "LookupEnr") do(nodeId: NodeId) -> Record: + let lookup = await p.resolve(nodeId) + if lookup.isSome(): + return lookup.get().record + else: + raise newException(ValueError, "Record not found in DHT lookup.") + + rpc("portal_" & networkStr & "Ping") do( + enr: Record, payloadType: Opt[uint16], payload: Opt[UnknownPayload] + ) -> PingResult: + if payloadType.isSome() and payloadType.get() != CapabilitiesType: + # We only support sending the default CapabilitiesPayload for now. + # This is fine because according to the spec clients are only required + # to support the standard extensions. + raise payloadTypeNotSupportedError() + + if payload.isSome(): + # We don't support passing in a custom payload. In order to implement + # this we use the empty UnknownPayload type which is defined in the spec + # as a json object with no required fields. Just using it here to indicate + # if an object was supplied or not and then throw the correct error if so. + raise userSpecifiedPayloadBlockedByClientError() + + let + node = toNodeWithAddress(enr) + pong = (await p.ping(node)).valueOr: + raise newException(ValueError, $error) + + let + (enrSeq, payloadType, capabilitiesPayload) = pong + clientInfo = capabilitiesPayload.client_info.asSeq() + payload = ( + string.fromBytes(clientInfo), + capabilitiesPayload.data_radius, + capabilitiesPayload.capabilities.asSeq(), ) - rpcServer.rpc("portal_" & networkStr & "RecursiveFindNodes") do( - nodeId: NodeId - ) -> seq[Record]: - let discovered = await p.lookup(nodeId) - return discovered.map( - proc(n: Node): Record = - n.record - ) + return PingResult( + enrSeq: EnrSeqNumber(enrSeq), payloadType: payloadType, payload: payload + ) + + rpc("portal_" & networkStr & "FindNodes") do( + enr: Record, distances: seq[uint16] + ) -> seq[Record]: + let + node = toNodeWithAddress(enr) + nodes = await p.findNodes(node, distances) + if nodes.isErr(): + raise newException(ValueError, $nodes.error) + else: + return nodes.get().map( + proc(n: Node): Record = + n.record + ) + + rpc("portal_" & networkStr & "RecursiveFindNodes") do(nodeId: NodeId) -> seq[Record]: + let discovered = await p.lookup(nodeId) + return discovered.map( + proc(n: Node): Record = + n.record + ) diff --git a/portal/rpc/rpc_portal_history_api.nim b/portal/rpc/rpc_portal_history_api.nim index e4e813f829..ce434a3bcd 100644 --- a/portal/rpc/rpc_portal_history_api.nim +++ b/portal/rpc/rpc_portal_history_api.nim @@ -30,133 +30,134 @@ export tables # These methods to be used for EL client integration. # They require the Header parameter in order for validation on the JSON-RPC server side (= Portal node). -ContentInfo.useDefaultSerializationIn JrpcConv -TraceContentLookupResult.useDefaultSerializationIn JrpcConv -TraceObject.useDefaultSerializationIn JrpcConv -FailureInfo.useDefaultSerializationIn JrpcConv -NodeMetadata.useDefaultSerializationIn JrpcConv -TraceResponse.useDefaultSerializationIn JrpcConv +ContentInfo.useDefaultSerializationIn EthJson +TraceContentLookupResult.useDefaultSerializationIn EthJson +TraceObject.useDefaultSerializationIn EthJson +FailureInfo.useDefaultSerializationIn EthJson +NodeMetadata.useDefaultSerializationIn EthJson +TraceResponse.useDefaultSerializationIn EthJson # TODO: It would be cleaner to use the existing getContent call for # less code duplication + automatic retries, but the specific error messages + extra content # info would need to be added to the existing calls. proc installPortalHistoryApiHandlers*(rpcServer: RpcServer, n: HistoryNetwork) = - rpcServer.rpc("portal_historyGetContent") do(contentKeyBytes: string) -> ContentInfo: - let - contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) - contentKey = decode(contentKeyByteList).valueOr: - raise invalidContentKeyError() - contentId = toContentId(contentKey) - - n.portalProtocol.getLocalContent(contentKeyByteList, contentId).isErrOr: - let content = value.to0xHex() - return ContentInfo(content: content, utpTransfer: false) - - let - contentLookupResult = ( - await n.portalProtocol.contentLookup(contentKeyByteList, contentId) - ).valueOr: - raise contentNotFoundErr() - content = contentLookupResult.content.to0xHex() - ContentInfo(content: content, utpTransfer: contentLookupResult.utpTransfer) - - rpcServer.rpc("portal_historyTraceGetContent") do( - contentKeyBytes: string - ) -> TraceContentLookupResult: - let - contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) - contentKey = decode(contentKeyByteList).valueOr: - raise invalidContentKeyError() - contentId = toContentId(contentKey) - - n.portalProtocol.getLocalContent(contentKeyByteList, contentId).isErrOr: - return TraceContentLookupResult( - content: Opt.some(value), - utpTransfer: false, - trace: TraceObject( - origin: n.localNode.id, - targetId: contentId, - receivedFrom: Opt.some(n.localNode.id), + rpcServer.rpcContext(EthJson): + rpc("portal_historyGetContent") do(contentKeyBytes: string) -> ContentInfo: + let + contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) + contentKey = decode(contentKeyByteList).valueOr: + raise invalidContentKeyError() + contentId = toContentId(contentKey) + + n.portalProtocol.getLocalContent(contentKeyByteList, contentId).isErrOr: + let content = value.to0xHex() + return ContentInfo(content: content, utpTransfer: false) + + let + contentLookupResult = ( + await n.portalProtocol.contentLookup(contentKeyByteList, contentId) + ).valueOr: + raise contentNotFoundErr() + content = contentLookupResult.content.to0xHex() + ContentInfo(content: content, utpTransfer: contentLookupResult.utpTransfer) + + rpc("portal_historyTraceGetContent") do( + contentKeyBytes: string + ) -> TraceContentLookupResult: + let + contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) + contentKey = decode(contentKeyByteList).valueOr: + raise invalidContentKeyError() + contentId = toContentId(contentKey) + + n.portalProtocol.getLocalContent(contentKeyByteList, contentId).isErrOr: + return TraceContentLookupResult( + content: Opt.some(value), + utpTransfer: false, + trace: TraceObject( + origin: n.localNode.id, + targetId: contentId, + receivedFrom: Opt.some(n.localNode.id), + ), + ) + + # TODO: Might want to restructure the lookup result here. Potentially doing + # the json conversion in this module. + let + res = await n.portalProtocol.traceContentLookup(contentKeyByteList, contentId) + valueBytes = res.content.valueOr: + let data = Opt.some(EthJson.encode(res.trace).JsonString) + raise contentNotFoundErrWithTrace(data) + + res + + rpc("portal_historyPutContent") do( + contentKeyBytes: string, contentValueBytes: string + ) -> PutContentResult: + let + contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) + _ = decode(contentKeyByteList).valueOr: + raise invalidContentKeyError() + offerValueBytes = hexToSeqByte(contentValueBytes) + + # Note: Not validating content as this would have a high impact on bridge + # gossip performance. It is also not possible without having the Header + gossipMetadata = await n.portalProtocol.neighborhoodGossip( + Opt.none(NodeId), + ContentKeysList(@[contentKeyByteList]), + @[offerValueBytes], + enableNodeLookup = true, + ) + + PutContentResult( + storedLocally: false, + peerCount: gossipMetadata.successCount, + acceptMetadata: AcceptMetadata( + acceptedCount: gossipMetadata.acceptedCount, + genericDeclineCount: gossipMetadata.genericDeclineCount, + alreadyStoredCount: gossipMetadata.alreadyStoredCount, + notWithinRadiusCount: gossipMetadata.notWithinRadiusCount, + rateLimitedCount: gossipMetadata.rateLimitedCount, + transferInProgressCount: gossipMetadata.transferInProgressCount, ), ) - # TODO: Might want to restructure the lookup result here. Potentially doing - # the json conversion in this module. - let - res = await n.portalProtocol.traceContentLookup(contentKeyByteList, contentId) - valueBytes = res.content.valueOr: - let data = Opt.some(JrpcConv.encode(res.trace).JsonString) - raise contentNotFoundErrWithTrace(data) - - res - - rpcServer.rpc("portal_historyPutContent") do( - contentKeyBytes: string, contentValueBytes: string - ) -> PutContentResult: - let - contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) - _ = decode(contentKeyByteList).valueOr: - raise invalidContentKeyError() - offerValueBytes = hexToSeqByte(contentValueBytes) - - # Note: Not validating content as this would have a high impact on bridge - # gossip performance. It is also not possible without having the Header - gossipMetadata = await n.portalProtocol.neighborhoodGossip( - Opt.none(NodeId), - ContentKeysList(@[contentKeyByteList]), - @[offerValueBytes], - enableNodeLookup = true, - ) + rpc("portal_historyStore") do( + contentKeyBytes: string, contentValueBytes: string + ) -> bool: + let + contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) + offerValueBytes = hexToSeqByte(contentValueBytes) + contentId = n.portalProtocol.toContentId(contentKeyByteList).valueOr: + raise invalidContentKeyError() - PutContentResult( - storedLocally: false, - peerCount: gossipMetadata.successCount, - acceptMetadata: AcceptMetadata( - acceptedCount: gossipMetadata.acceptedCount, - genericDeclineCount: gossipMetadata.genericDeclineCount, - alreadyStoredCount: gossipMetadata.alreadyStoredCount, - notWithinRadiusCount: gossipMetadata.notWithinRadiusCount, - rateLimitedCount: gossipMetadata.rateLimitedCount, - transferInProgressCount: gossipMetadata.transferInProgressCount, - ), - ) - - rpcServer.rpc("portal_historyStore") do( - contentKeyBytes: string, contentValueBytes: string - ) -> bool: - let - contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) - offerValueBytes = hexToSeqByte(contentValueBytes) - contentId = n.portalProtocol.toContentId(contentKeyByteList).valueOr: - raise invalidContentKeyError() - - n.portalProtocol.storeContent(contentKeyByteList, contentId, offerValueBytes) - - rpcServer.rpc("portal_historyLocalContent") do(contentKeyBytes: string) -> string: - let - contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) - contentId = n.portalProtocol.toContentId(contentKeyByteList).valueOr: - raise invalidContentKeyError() - - valueBytes = n.portalProtocol.getLocalContent(contentKeyByteList, contentId).valueOr: - raise contentNotFoundErr() + n.portalProtocol.storeContent(contentKeyByteList, contentId, offerValueBytes) - valueBytes.to0xHex() + rpc("portal_historyLocalContent") do(contentKeyBytes: string) -> string: + let + contentKeyByteList = ContentKeyByteList.init(hexToSeqByte(contentKeyBytes)) + contentId = n.portalProtocol.toContentId(contentKeyByteList).valueOr: + raise invalidContentKeyError() - rpcServer.rpc("portal_historyGetBlockBody") do(headerBytes: string) -> string: - let header = decodeRlp(hexToSeqByte(headerBytes), Header).valueOr: - raise applicationError((code: -39010, msg: "Failed to decode header: " & error)) + valueBytes = n.portalProtocol.getLocalContent(contentKeyByteList, contentId).valueOr: + raise contentNotFoundErr() - let blockBody = (await n.getBlockBody(header)).valueOr: - raise contentNotFoundErr() + valueBytes.to0xHex() - rlp.encode(blockBody).to0xHex() + rpc("portal_historyGetBlockBody") do(headerBytes: string) -> string: + let header = decodeRlp(hexToSeqByte(headerBytes), Header).valueOr: + raise applicationError((code: -39010, msg: "Failed to decode header: " & error)) - rpcServer.rpc("portal_historyGetReceipts") do(headerBytes: string) -> string: - let header = decodeRlp(hexToSeqByte(headerBytes), Header).valueOr: - raise applicationError((code: -39010, msg: "Failed to decode header: " & error)) + let blockBody = (await n.getBlockBody(header)).valueOr: + raise contentNotFoundErr() - let receipts = (await n.getReceipts(header)).valueOr: - raise contentNotFoundErr() + rlp.encode(blockBody).to0xHex() + + rpc("portal_historyGetReceipts") do(headerBytes: string) -> string: + let header = decodeRlp(hexToSeqByte(headerBytes), Header).valueOr: + raise applicationError((code: -39010, msg: "Failed to decode header: " & error)) + + let receipts = (await n.getReceipts(header)).valueOr: + raise contentNotFoundErr() - rlp.encode(receipts).to0xHex() + rlp.encode(receipts).to0xHex() diff --git a/portal/rpc/rpc_portal_nimbus_beacon_api.nim b/portal/rpc/rpc_portal_nimbus_beacon_api.nim index cd80b44f16..dfd1f2be1c 100644 --- a/portal/rpc/rpc_portal_nimbus_beacon_api.nim +++ b/portal/rpc/rpc_portal_nimbus_beacon_api.nim @@ -7,13 +7,15 @@ {.push raises: [].} -import json_rpc/rpcserver, ../network/beacon/beacon_light_client +import json_rpc/rpcserver, ./rpc_types, ../network/beacon/beacon_light_client export rpcserver # nimbus portal specific RPC methods for the Portal beacon network. proc installPortalNimbusBeaconApiHandlers*(rpcServer: RpcServer, lc: LightClient) = - rpcServer.rpc("portal_nimbus_beaconSetTrustedBlockRoot") do(blockRoot: string) -> bool: + rpcServer.rpc("portal_nimbus_beaconSetTrustedBlockRoot", EthJson) do( + blockRoot: string + ) -> bool: let root = Digest.fromHex(blockRoot) await lc.resetToTrustedBlockRoot(root) true diff --git a/portal/rpc/rpc_types.nim b/portal/rpc/rpc_types.nim index 6016b85f26..b76f791fe6 100644 --- a/portal/rpc/rpc_types.nim +++ b/portal/rpc/rpc_types.nim @@ -9,13 +9,14 @@ import stint, - json_rpc/[jsonmarshal, errors], + json_rpc/errors, stew/byteutils, results, eth/p2p/discoveryv5/[routing_table, node], - json_serialization/pkg/results + json_serialization/pkg/results, + web3/eth_json_marshal -export jsonmarshal, routing_table, enr, node, results +export eth_json_marshal, routing_table, enr, node, results # Portal Network JSON-RPC errors @@ -84,7 +85,7 @@ type # Note: # Need to add a distinct type here with its own readValue & writeValue to avoid - # using the default one of JrpcConv which uses hex strings. Needs to be a JSON number. + # using the default one of EthJson which uses hex strings. Needs to be a JSON number. EnrSeqNumber* = distinct uint64 PingResult* = object @@ -111,22 +112,22 @@ type peerCount*: int acceptMetadata*: AcceptMetadata -NodeInfo.useDefaultSerializationIn JrpcConv -RoutingTableInfo.useDefaultSerializationIn JrpcConv -PingResult.useDefaultSerializationIn JrpcConv -(string, string).useDefaultSerializationIn JrpcConv -ContentInfo.useDefaultSerializationIn JrpcConv -AcceptMetadata.useDefaultSerializationIn JrpcConv -PutContentResult.useDefaultSerializationIn JrpcConv - -JrpcConv.automaticSerialization(int, true) -JrpcConv.automaticSerialization(int64, true) -JrpcConv.automaticSerialization(uint64, true) -JrpcConv.automaticSerialization(uint16, true) -JrpcConv.automaticSerialization(seq, true) -JrpcConv.automaticSerialization(string, true) -JrpcConv.automaticSerialization(bool, true) -JrpcConv.automaticSerialization(JsonString, true) +NodeInfo.useDefaultSerializationIn EthJson +RoutingTableInfo.useDefaultSerializationIn EthJson +PingResult.useDefaultSerializationIn EthJson +(string, string).useDefaultSerializationIn EthJson +ContentInfo.useDefaultSerializationIn EthJson +AcceptMetadata.useDefaultSerializationIn EthJson +PutContentResult.useDefaultSerializationIn EthJson + +EthJson.automaticSerialization(int, true) +EthJson.automaticSerialization(int64, true) +EthJson.automaticSerialization(uint64, true) +EthJson.automaticSerialization(uint16, true) +EthJson.automaticSerialization(seq, true) +EthJson.automaticSerialization(string, true) +EthJson.automaticSerialization(bool, true) +EthJson.automaticSerialization(JsonString, true) func getNodeInfo*(r: RoutingTable): NodeInfo = NodeInfo(enr: r.localNode.record, nodeId: r.localNode.id) @@ -152,29 +153,29 @@ func toNodeWithAddress*(enr: Record): Node {.raises: [ValueError].} = node proc readValue*( - r: var JsonReader[JrpcConv], val: var EnrSeqNumber + r: var JsonReader[EthJson], val: var EnrSeqNumber ) {.gcsafe, raises: [IOError, JsonReaderError].} = val = EnrSeqNumber(r.parseInt(uint64)) proc writeValue*( - w: var JsonWriter[JrpcConv], v: EnrSeqNumber + w: var JsonWriter[EthJson], v: EnrSeqNumber ) {.gcsafe, raises: [IOError].} = w.writeValue(uint64(v)) -proc writeValue*(w: var JsonWriter[JrpcConv], v: Record) {.gcsafe, raises: [IOError].} = +proc writeValue*(w: var JsonWriter[EthJson], v: Record) {.gcsafe, raises: [IOError].} = w.writeValue(v.toURI()) proc readValue*( - r: var JsonReader[JrpcConv], val: var Record + r: var JsonReader[EthJson], val: var Record ) {.gcsafe, raises: [IOError, JsonReaderError].} = val = Record.fromURI(r.parseString()).valueOr: r.raiseUnexpectedValue("Invalid ENR") -proc writeValue*(w: var JsonWriter[JrpcConv], v: NodeId) {.gcsafe, raises: [IOError].} = +proc writeValue*(w: var JsonWriter[EthJson], v: NodeId) {.gcsafe, raises: [IOError].} = w.writeValue(v.toBytesBE().to0xHex()) proc writeValue*( - w: var JsonWriter[JrpcConv], v: Opt[NodeId] + w: var JsonWriter[EthJson], v: Opt[NodeId] ) {.gcsafe, raises: [IOError].} = if v.isSome(): w.writeValue(v.get()) @@ -182,7 +183,7 @@ proc writeValue*( w.writeValue(JsonString("null")) proc readValue*( - r: var JsonReader[JrpcConv], val: var NodeId + r: var JsonReader[EthJson], val: var NodeId ) {.gcsafe, raises: [IOError, JsonReaderError].} = try: val = NodeId.fromHex(r.parseString()) @@ -190,7 +191,7 @@ proc readValue*( r.raiseUnexpectedValue("NodeId parser error: " & exc.msg) proc writeValue*( - w: var JsonWriter[JrpcConv], v: Opt[seq[byte]] + w: var JsonWriter[EthJson], v: Opt[seq[byte]] ) {.gcsafe, raises: [IOError].} = if v.isSome(): w.writeValue(v.get().to0xHex()) @@ -198,7 +199,7 @@ proc writeValue*( w.writeValue("0x") proc readValue*( - r: var JsonReader[JrpcConv], val: var seq[byte] + r: var JsonReader[EthJson], val: var seq[byte] ) {.gcsafe, raises: [IOError, JsonReaderError].} = try: val = hexToSeqByte(r.parseString()) @@ -206,7 +207,7 @@ proc readValue*( r.raiseUnexpectedValue("seq[byte] parser error: " & exc.msg) proc writeValue*( - w: var JsonWriter[JrpcConv], v: CapabilitiesPayload + w: var JsonWriter[EthJson], v: CapabilitiesPayload ) {.gcsafe, raises: [IOError].} = w.beginRecord() @@ -218,7 +219,7 @@ proc writeValue*( w.endRecord() proc readValue*( - r: var JsonReader[JrpcConv], val: var CapabilitiesPayload + r: var JsonReader[EthJson], val: var CapabilitiesPayload ) {.gcsafe, raises: [IOError, SerializationError].} = try: for field in r.readObjectFields(): @@ -241,7 +242,7 @@ proc readValue*( # a JSON array with less than size n items is provided. And default objects # (in this case empty string) will be applied for the missing items. proc readValue*( - r: var JsonReader[JrpcConv], value: var ContentItem + r: var JsonReader[EthJson], value: var ContentItem ) {.gcsafe, raises: [IOError, SerializationError].} = type IDX = typeof low(value) var count = 0 @@ -255,7 +256,7 @@ proc readValue*( r.raiseUnexpectedValue("Array length mismatch") proc readValue*( - r: var JsonReader[JrpcConv], val: var Opt[uint16] + r: var JsonReader[EthJson], val: var Opt[uint16] ) {.gcsafe, raises: [IOError, SerializationError].} = if r.tokKind == JsonValueKind.Null: reset val @@ -264,7 +265,7 @@ proc readValue*( val.ok r.readValue(uint16) proc readValue*( - r: var JsonReader[JrpcConv], val: var Opt[UnknownPayload] + r: var JsonReader[EthJson], val: var Opt[UnknownPayload] ) {.gcsafe, raises: [IOError, SerializationError].} = if r.tokKind == JsonValueKind.Null: reset val diff --git a/portal/rpc/rpc_web3_api.nim b/portal/rpc/rpc_web3_api.nim index 944568fb56..5a3c02ec81 100644 --- a/portal/rpc/rpc_web3_api.nim +++ b/portal/rpc/rpc_web3_api.nim @@ -7,8 +7,8 @@ {.push raises: [].} -import json_rpc/rpcserver, ../version +import json_rpc/rpcserver, ./rpc_types, ../version proc installWeb3ApiHandlers*(rpcServer: RpcServer) = - rpcServer.rpc("web3_clientVersion") do() -> string: + rpcServer.rpc("web3_clientVersion", EthJson) do() -> string: return clientVersion diff --git a/portal/tests/rpc_tests/test_discovery_rpc.nim b/portal/tests/rpc_tests/test_discovery_rpc.nim index 303719bc74..d7cfdbf3e5 100644 --- a/portal/tests/rpc_tests/test_discovery_rpc.nim +++ b/portal/tests/rpc_tests/test_discovery_rpc.nim @@ -77,7 +77,7 @@ procSuite "Discovery v5 JSON-RPC API": asyncTest "Get local node info": let tc = await setupTest(rng) let jsonBytes = await tc.client.call("discv5_nodeInfo", %[]) - let resp = JrpcConv.decode(jsonBytes.string, JsonNode) + let resp = EthJson.decode(jsonBytes.string, JsonNode) check: resp.contains("nodeId") diff --git a/portal/tests/rpc_tests/test_portal_common_rpc.nim b/portal/tests/rpc_tests/test_portal_common_rpc.nim index 6aaab4853a..7694e561b2 100644 --- a/portal/tests/rpc_tests/test_portal_common_rpc.nim +++ b/portal/tests/rpc_tests/test_portal_common_rpc.nim @@ -76,7 +76,7 @@ procSuite "Portal Common JSON-RPC API": let tc = await setupHistoryTest(rng) jsonBytes = await tc.client.call("portal_historyNodeInfo", %[]) - resp = JrpcConv.decode(jsonBytes.string, JsonNode) + resp = EthJson.decode(jsonBytes.string, JsonNode) check: resp.contains("enr") @@ -94,7 +94,7 @@ procSuite "Portal Common JSON-RPC API": let tc = await setupHistoryTest(rng) jsonBytes = await tc.client.call("portal_historyRoutingTableInfo", %[]) - resp = JrpcConv.decode(jsonBytes.string, JsonNode) + resp = EthJson.decode(jsonBytes.string, JsonNode) check: resp.contains("localNodeId") @@ -112,7 +112,7 @@ procSuite "Portal Common JSON-RPC API": testEnr = testHistoryNode.localNode.record jsonBytes = await tc.client.call("portal_historyAddEnr", %[testEnr.toURI()]) - check JrpcConv.decode(jsonBytes.string, bool) + check EthJson.decode(jsonBytes.string, bool) await testHistoryNode.stop() await tc.stop() @@ -124,7 +124,7 @@ procSuite "Portal Common JSON-RPC API": nodeId = tc.historyNode.localNode.id jsonBytes = await tc.client.call("portal_historyGetEnr", %[nodeId.toBytesBE().to0xHex()]) - check JrpcConv.decode(jsonBytes.string, string) == + check EthJson.decode(jsonBytes.string, string) == tc.historyNode.localNode.record.toURI() await tc.stop() @@ -142,7 +142,7 @@ procSuite "Portal Common JSON-RPC API": let jsonBytes = await tc.client.call( "portal_historyDeleteEnr", %[testNodeId.toBytesBE().to0xHex()] ) - check JrpcConv.decode(jsonBytes.string, bool) + check EthJson.decode(jsonBytes.string, bool) await testHistoryNode.stop() await tc.stop() @@ -161,7 +161,7 @@ procSuite "Portal Common JSON-RPC API": params.add(%testHistoryNode.localNode.record.toURI()) # Note: Not adding payloadType and payload to use defaults let jsonBytes = await tc.client.call("portal_historyPing", params) - let resp = JrpcConv.decode(jsonBytes.string, JsonNode) + let resp = EthJson.decode(jsonBytes.string, JsonNode) check: resp.contains("enrSeq") @@ -194,7 +194,7 @@ procSuite "Portal Common JSON-RPC API": params.add(%testHistoryNode.localNode.record.toURI()) params.add(%[0]) # 0 = own ENR let jsonBytes = await tc.client.call("portal_historyFindNodes", params) - check JrpcConv.decode(jsonBytes.string, JsonNode).kind == JArray + check EthJson.decode(jsonBytes.string, JsonNode).kind == JArray await testHistoryNode.stop() await tc.stop() @@ -207,6 +207,6 @@ procSuite "Portal Common JSON-RPC API": let jsonBytes = await tc.client.call( "portal_historyRecursiveFindNodes", %[nodeId.toBytesBE().to0xHex()] ) - check JrpcConv.decode(jsonBytes.string, JsonNode).kind == JArray + check EthJson.decode(jsonBytes.string, JsonNode).kind == JArray await tc.stop() diff --git a/portal/tools/utp_testing/utp_rpc_types.nim b/portal/tools/utp_testing/utp_rpc_types.nim index e9e7ccfd63..0b4bb0e56e 100644 --- a/portal/tools/utp_testing/utp_rpc_types.nim +++ b/portal/tools/utp_testing/utp_rpc_types.nim @@ -8,25 +8,25 @@ import std/[hashes, json], - json_rpc/jsonmarshal, stew/[byteutils, endians2], eth/p2p/discoveryv5/node, - eth/utp/[utp_discv5_protocol, utp_router] + eth/utp/[utp_discv5_protocol, utp_router], + web3/eth_json_marshal -export jsonmarshal, json +export eth_json_marshal, json type SKey* = object id*: uint16 nodeId*: NodeId -proc writeValue*(w: var JsonWriter[JrpcConv], v: SKey) {.gcsafe, raises: [IOError].} = +proc writeValue*(w: var JsonWriter[EthJson], v: SKey) {.gcsafe, raises: [IOError].} = let hex = v.nodeId.toBytesBE().toHex() let numId = v.id.toBytesBE().toHex() let finalStr = hex & numId w.writeValue(finalStr) proc readValue*( - r: var JsonReader[JrpcConv], val: var SKey + r: var JsonReader[EthJson], val: var SKey ) {.gcsafe, raises: [IOError, JsonReaderError].} = let str = r.parseString() if str.len < 64: diff --git a/portal/tools/utp_testing/utp_test_app.nim b/portal/tools/utp_testing/utp_test_app.nim index 3a4790c573..6c677c2c6f 100644 --- a/portal/tools/utp_testing/utp_test_app.nim +++ b/portal/tools/utp_testing/utp_test_app.nim @@ -40,11 +40,11 @@ type AppConf* = object name: "rpc-listen-address" .}: IpAddress -proc writeValue*(w: var JsonWriter[JrpcConv], v: Record) {.gcsafe, raises: [IOError].} = +proc writeValue*(w: var JsonWriter[EthJson], v: Record) {.gcsafe, raises: [IOError].} = w.writeValue(v.toURI()) proc readValue*( - r: var JsonReader[JrpcConv], val: var Record + r: var JsonReader[EthJson], val: var Record ) {.gcsafe, raises: [IOError, JsonReaderError].} = val = enr.Record.fromURI(r.parseString()).valueOr: r.raiseUnexpectedValue("Invalid ENR") @@ -55,57 +55,58 @@ proc installUtpHandlers( s: UtpDiscv5Protocol, t: ref Table[SKey, UtpSocket[NodeAddress]], ) {.raises: [].} = - srv.rpc("utp_connect") do(r: enr.Record) -> SKey: - let node = Node.fromRecord(r) - let nodeAddress = NodeAddress.init(node).unsafeGet() - discard d.addNode(node) - let connResult = await s.connectTo(nodeAddress) - if (connResult.isOk()): - let socket = connResult.get() - let sKey = socket.socketKey.toSKey() - t[sKey] = socket - return sKey - else: - raise newException(ValueError, "Connection to node Failed.") - - srv.rpc("utp_write") do(k: SKey, b: string) -> bool: - let sock = t.getOrDefault(k) - let bytes = hexToSeqByte(b) - if sock != nil: - # TODO consider doing it async to avoid json-rpc timeouts in case of large writes - let res = await sock.write(bytes) - if res.isOk(): + srv.rpcContext(EthJson): + rpc("utp_connect") do(r: enr.Record) -> SKey: + let node = Node.fromRecord(r) + let nodeAddress = NodeAddress.init(node).unsafeGet() + discard d.addNode(node) + let connResult = await s.connectTo(nodeAddress) + if (connResult.isOk()): + let socket = connResult.get() + let sKey = socket.socketKey.toSKey() + t[sKey] = socket + return sKey + else: + raise newException(ValueError, "Connection to node Failed.") + + rpc("utp_write") do(k: SKey, b: string) -> bool: + let sock = t.getOrDefault(k) + let bytes = hexToSeqByte(b) + if sock != nil: + # TODO consider doing it async to avoid json-rpc timeouts in case of large writes + let res = await sock.write(bytes) + if res.isOk(): + return true + else: + # TODO return correct errors instead of just true/false + return false + else: + raise newException(ValueError, "Socket with provided key is missing") + + rpc("utp_get_connections") do() -> seq[SKey]: + var keys = newSeq[SKey]() + + for k in t.keys: + keys.add(k) + + return keys + + rpc("utp_read") do(k: SKey, n: int) -> string: + let sock = t.getOrDefault(k) + if sock != nil: + let res = await sock.read(n) + let asHex = res.toHex() + return asHex + else: + raise newException(ValueError, "Socket with provided key is missing") + + rpc("utp_close") do(k: SKey) -> bool: + let sock = t.getOrDefault(k) + if sock != nil: + await sock.closeWait() return true else: - # TODO return correct errors instead of just true/false - return false - else: - raise newException(ValueError, "Socket with provided key is missing") - - srv.rpc("utp_get_connections") do() -> seq[SKey]: - var keys = newSeq[SKey]() - - for k in t.keys: - keys.add(k) - - return keys - - srv.rpc("utp_read") do(k: SKey, n: int) -> string: - let sock = t.getOrDefault(k) - if sock != nil: - let res = await sock.read(n) - let asHex = res.toHex() - return asHex - else: - raise newException(ValueError, "Socket with provided key is missing") - - srv.rpc("utp_close") do(k: SKey) -> bool: - let sock = t.getOrDefault(k) - if sock != nil: - await sock.closeWait() - return true - else: - raise newException(ValueError, "Socket with provided key is missing") + raise newException(ValueError, "Socket with provided key is missing") proc buildAcceptConnection( t: ref Table[SKey, UtpSocket[NodeAddress]] diff --git a/portal/tools/utp_testing/utp_test_rpc_calls.nim b/portal/tools/utp_testing/utp_test_rpc_calls.nim index c05c9a1ad0..9a05f6584d 100644 --- a/portal/tools/utp_testing/utp_test_rpc_calls.nim +++ b/portal/tools/utp_testing/utp_test_rpc_calls.nim @@ -10,7 +10,7 @@ import json_rpc/rpcclient, ../../rpc/rpc_types, ./utp_rpc_types export utp_rpc_types -createRpcSigsFromNim(RpcClient): +createRpcSigsFromNim(RpcClient, EthJson): proc utp_connect(enr: Record): SKey proc utp_write(k: SKey, b: string): bool proc utp_read(k: SKey, n: int): string diff --git a/tests/eest/eest_helpers.nim b/tests/eest/eest_helpers.nim index 7511deb161..a9ee254a7d 100644 --- a/tests/eest/eest_helpers.nim +++ b/tests/eest/eest_helpers.nim @@ -117,12 +117,12 @@ type EngineFixture* = object units*: seq[EngineUnitDesc] -GenesisHeader.useDefaultReaderIn JrpcConv -PayloadItem.useDefaultReaderIn JrpcConv -EngineUnitEnv.useDefaultReaderIn JrpcConv -BlockchainUnitEnv.useDefaultReaderIn JrpcConv -EnvConfig.useDefaultReaderIn JrpcConv -BlobSchedule.useDefaultReaderIn JrpcConv +GenesisHeader.useDefaultReaderIn EthJson +PayloadItem.useDefaultReaderIn EthJson +EngineUnitEnv.useDefaultReaderIn EthJson +BlockchainUnitEnv.useDefaultReaderIn EthJson +EnvConfig.useDefaultReaderIn EthJson +BlobSchedule.useDefaultReaderIn EthJson template wrapValueError(body: untyped) = try: @@ -131,13 +131,13 @@ template wrapValueError(body: untyped) = r.raiseUnexpectedValue(exc.msg) proc readValue*( - r: var JsonReader[JrpcConv], val: var Numero + r: var JsonReader[EthJson], val: var Numero ) {.gcsafe, raises: [IOError, SerializationError].} = wrapValueError: val = fromHex[uint64](r.readValue(string)).Numero proc readValue*( - r: var JsonReader[JrpcConv], + r: var JsonReader[EthJson], value: var array[HardFork.Cancun .. HardFork.high, Opt[BlobSchedule]], ) {.gcsafe, raises: [SerializationError, IOError].} = wrapValueError: @@ -145,7 +145,7 @@ proc readValue*( blobScheduleParser(r, key, value) proc readValue*( - r: var JsonReader[JrpcConv], val: var PayloadParam + r: var JsonReader[EthJson], val: var PayloadParam ) {.gcsafe, raises: [IOError, SerializationError].} = wrapValueError: r.parseArray(i): @@ -162,14 +162,14 @@ proc readValue*( r.raiseUnexpectedValue("Unexpected element") proc readValue*( - r: var JsonReader[JrpcConv], val: var EngineFixture + r: var JsonReader[EthJson], val: var EngineFixture ) {.gcsafe, raises: [IOError, SerializationError].} = wrapValueError: parseObject(r, key): val.units.add EngineUnitDesc(name: key, unit: r.readValue(EngineUnitEnv)) proc readValue*( - r: var JsonReader[JrpcConv], val: var BlockchainFixture + r: var JsonReader[EthJson], val: var BlockchainFixture ) {.gcsafe, raises: [IOError, SerializationError].} = wrapValueError: parseObject(r, key): @@ -287,7 +287,7 @@ proc close*(env: TestEnv) = template parseAnyFixture(fileName: string, T: typedesc) = try: - result = JrpcConv.loadFile(fileName, T) + result = EthJson.loadFile(fileName, T) except JsonReaderError as exc: debugEcho exc.formatMsg(fileName) quit(QuitFailure) diff --git a/tests/test_engine_api.nim b/tests/test_engine_api.nim index 8e1f5d18bb..0bf9c5a0f6 100644 --- a/tests/test_engine_api.nim +++ b/tests/test_engine_api.nim @@ -46,7 +46,7 @@ type genesisFile: string testProc: proc(env: TestEnv): Result[void, string] -NewPayloadV4Params.useDefaultSerializationIn JrpcConv +NewPayloadV4Params.useDefaultSerializationIn EthJson const defaultGenesisFile = "tests/customgenesis/engine_api_genesis.json" @@ -190,7 +190,7 @@ proc newPayloadV4ParamsTest(env: TestEnv): Result[void, string] = for paramsFile in paramsFiles: let client = env.client - params = JrpcConv.loadFile(paramsFile, NewPayloadV4Params) + params = EthJson.loadFile(paramsFile, NewPayloadV4Params) res = ?client.newPayloadV4( params.payload, params.expectedBlobVersionedHashes, @@ -214,7 +214,7 @@ proc genesisShouldCanonicalTest(env: TestEnv): Result[void, string] = let client = env.client - params = JrpcConv.loadFile(paramsFile, NewPayloadV4Params) + params = EthJson.loadFile(paramsFile, NewPayloadV4Params) res = ? client.newPayloadV3( params.payload, params.expectedBlobVersionedHashes, @@ -250,7 +250,7 @@ proc newPayloadV4InvalidRequests(env: TestEnv): Result[void, string] = for paramsFile in paramsFiles: let client = env.client - params = JrpcConv.loadFile(paramsFile, NewPayloadV4Params) + params = EthJson.loadFile(paramsFile, NewPayloadV4Params) res = client.newPayloadV4( params.payload, params.expectedBlobVersionedHashes, @@ -274,7 +274,7 @@ proc newPayloadV4InvalidRequestType(env: TestEnv): Result[void, string] = let client = env.client - params = JrpcConv.loadFile(paramsFile, NewPayloadV4Params) + params = EthJson.loadFile(paramsFile, NewPayloadV4Params) res = client.newPayloadV4( params.payload, params.expectedBlobVersionedHashes, diff --git a/tests/test_jwt_auth.nim b/tests/test_jwt_auth.nim index 1f4fa6255a..98391d73d5 100644 --- a/tests/test_jwt_auth.nim +++ b/tests/test_jwt_auth.nim @@ -22,7 +22,8 @@ import nimcrypto/[hmac, sha2, utils], unittest2, websock/websock, - json_rpc/[rpcserver, rpcclient] + json_rpc/[rpcserver, rpcclient], + web3/eth_json_marshal from std/base64 import encode from std/os import DirSep, fileExists, removeFile, splitFile, splitPath, `/` @@ -103,7 +104,7 @@ func getHttpAuthReqHeader(secret: JwtSharedKey; time: uint64): HttpTable = # ------------------------------------------------------------------------------ func installRPC(server: RpcServer) = - server.rpc("rpc_echo") do(input: int) -> string: + server.rpc("rpc_echo", EthJson) do(input: int) -> string: "hello: " & $input proc setupComboServer(hooks: sink seq[RpcAuthHook]): HttpResult[NimbusHttpServerRef] = @@ -120,7 +121,7 @@ proc setupComboServer(hooks: sink seq[RpcAuthHook]): HttpResult[NimbusHttpServer let address = initTAddress("127.0.0.1:0") newHttpServerWithParams(address, hooks, handlers) -createRpcSigsFromNim(RpcClient): +createRpcSigsFromNim(RpcClient, EthJson): proc rpc_echo(input: int): string # ------------------------------------------------------------------------------ diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index 8cd4795324..4ea8b95a1b 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -280,7 +280,7 @@ proc generateBlock(env: var TestEnv) = env.txHash = tx1.computeRlpHash env.blockHash = blk.header.computeBlockHash -createRpcSigsFromNim(RpcClient): +createRpcSigsFromNim(RpcClient, EthJson): proc web3_clientVersion(): string proc web3_sha3(data: seq[byte]): Hash32 proc net_version(): string diff --git a/tools/t8n/t8n_debug.nim b/tools/t8n/t8n_debug.nim index b27d00b947..f0c0745624 100644 --- a/tools/t8n/t8n_debug.nim +++ b/tools/t8n/t8n_debug.nim @@ -20,7 +20,7 @@ const testFile = "tests/fixtures/eest/blockchain_tests/stChainId/chainId.json" type - BCTConv* = JrpcConv + BCTConv* = EthJson BCTBlock* = object rlp*: seq[byte] diff --git a/vendor/nim-confutils b/vendor/nim-confutils index 5286ed67d3..f684e55d56 160000 --- a/vendor/nim-confutils +++ b/vendor/nim-confutils @@ -1 +1 @@ -Subproject commit 5286ed67d363a3630ece85a0e8c1141d1d52b434 +Subproject commit f684e55d56ba4016e2add64f74c4840476aa493d diff --git a/vendor/nim-json-rpc b/vendor/nim-json-rpc index 841d1c9fcc..ed0e4ee193 160000 --- a/vendor/nim-json-rpc +++ b/vendor/nim-json-rpc @@ -1 +1 @@ -Subproject commit 841d1c9fcc7ce7ebbd1f9a774cfdcd7470e5e976 +Subproject commit ed0e4ee193b06787de388700c792a3a778eed836 diff --git a/vendor/nim-web3 b/vendor/nim-web3 index 08c5a3a9ee..7b28083aaf 160000 --- a/vendor/nim-web3 +++ b/vendor/nim-web3 @@ -1 +1 @@ -Subproject commit 08c5a3a9ee6c0baf41e7eca1cffa50e3737c4ab8 +Subproject commit 7b28083aafe31ee1f72595a7a3340db768e97050 diff --git a/vendor/nimbus-eth2 b/vendor/nimbus-eth2 index 80b66380f9..6c9acdc313 160000 --- a/vendor/nimbus-eth2 +++ b/vendor/nimbus-eth2 @@ -1 +1 @@ -Subproject commit 80b66380f9be0aab908256aeaba8f9dcad119c7a +Subproject commit 6c9acdc3134da3a18cfe3c41da2d98378bf5f7e9