From e4d1c6817ad03334e8527f6d4468b6d1caba0679 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 13 Aug 2018 17:33:57 +0100 Subject: [PATCH 01/20] Added EthAddressStr to validation --- nimbus/rpc/hexstrings.nim | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index 12e9bd4f1..23b978474 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -13,6 +13,11 @@ type HexQuantityStr* = distinct string HexDataStr* = distinct string + EthAddressStr* = distinct string + +func len*(data: HexQuantityStr): int = data.string.len +func len*(data: HexDataStr): int = data.string.len +func len*(data: EthAddressStr): int = data.string.len # Hex validation @@ -60,9 +65,14 @@ func isValidHexData*(value: string): bool = return false return true +func isValidEthAddress*(value: string): bool = + # 20 bytes for EthAddress plus "0x" + result = value.len <= 42 and value.isValidHexData + const SInvalidQuantity = "Invalid hex quantity format for Ethereum" SInvalidData = "Invalid hex data format for Ethereum" + SInvalidAddress = "Invalid address format for Ethereum" proc validateHexQuantity*(value: string) {.inline.} = if unlikely(not value.isValidHexQuantity): @@ -72,6 +82,10 @@ proc validateHexData*(value: string) {.inline.} = if unlikely(not value.isValidHexData): raise newException(ValueError, SInvalidData & ": " & value) +proc validateHexAddressStr*(value: string) {.inline.} = + if unlikely(not value.isValidEthAddress): + raise newException(ValueError, SInvalidAddress & ": " & value) + # Initialisation proc hexQuantityStr*(value: string): HexQuantityStr {.inline.} = @@ -82,6 +96,10 @@ proc hexDataStr*(value: string): HexDataStr {.inline.} = value.validateHexData result = value.HexDataStr +proc ethAddressStr*(value: string): EthAddressStr {.inline.} = + value.validateHexAddressStr + result = value.EthAddressStr + # Converters for use in RPC import json @@ -93,6 +111,9 @@ proc `%`*(value: HexQuantityStr): JsonNode = proc `%`*(value: HexDataStr): JsonNode = result = %(value.string) +proc `%`*(value: EthAddressStr): JsonNode = + result = %(value.string) + proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) = # Note that '0x' is stripped after validation n.kind.expect(JString, argName) @@ -106,6 +127,14 @@ proc fromJson*(n: JsonNode, argName: string, result: var HexDataStr) = n.kind.expect(JString, argName) let hexStr = n.getStr() if not hexStr.isValidHexData: - raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as a Ethereum data \"" & hexStr & "\"") + raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as Ethereum data \"" & hexStr & "\"") result = hexStr.hexDataStr +proc fromJson*(n: JsonNode, argName: string, result: var EthAddressStr) = + # Note that '0x' is stripped after validation + n.kind.expect(JString, argName) + let hexStr = n.getStr() + if not hexStr.isValidEthAddress: + raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as an Ethereum address \"" & hexStr & "\"") + result = hexStr.EthAddressStr + From 6aab8b05c59b1152ff84b4372644c904a2b64b7f Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 13 Aug 2018 17:34:14 +0100 Subject: [PATCH 02/20] Included tests for EthAddressStr --- tests/rpcclient/test_hexstrings.nim | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/rpcclient/test_hexstrings.nim b/tests/rpcclient/test_hexstrings.nim index a7a7eff96..68936c1be 100644 --- a/tests/rpcclient/test_hexstrings.nim +++ b/tests/rpcclient/test_hexstrings.nim @@ -97,3 +97,28 @@ proc doHexStrTests* = source = "0x0123" x = hexDataStr source check %x == %source + + suite "[RPC] Eth address strings": + test "Valid address": + let + e = "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" + e_addr = e.ethAddressStr + check e == e_addr.string + let + short_e = "0x0f572e5295c57f" + short_e_addr = short_e.ethAddressStr + check short_e == short_e_addr.string + test "Too long": + expect ValueError: + let + # too long + e = "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec66" + e_addr = e.ethAddressStr + check e == e_addr.string + test "Wrong format": + expect ValueError: + let + # too long + e = "000f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" + e_addr = e.ethAddressStr + check e == e_addr.string From d1f283b0043243dcb9d074ea42f2885dfb862905 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 13 Aug 2018 17:34:54 +0100 Subject: [PATCH 03/20] Updated test_rpc to use EthAddressStr --- tests/test_rpc.nim | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index 999e7af7f..3c81e6c13 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -1,7 +1,7 @@ import unittest, json, strformat, nimcrypto, rlp, json_rpc/[rpcserver, rpcclient], - ../nimbus/rpc/[common, p2p], + ../nimbus/rpc/[common, p2p, hexstrings], ../nimbus/constants, ../nimbus/nimbus/[account, vm_state, config], ../nimbus/db/[state_db, db_chain], eth_common, byteutils, @@ -35,6 +35,9 @@ proc setupEthNode: EthereumNode = result = newEthereumNode(keypair, srvAddress, conf.net.networkId, nil, "nimbus 0.1.0") +proc toEthAddressStr(address: EthAddress): EthAddressStr = + result = ("0x" & address.toHex).ethAddressStr + proc doTests = # TODO: Include other transports such as Http var ethNode = setupEthNode() @@ -46,8 +49,9 @@ proc doTests = state = newBaseVMState(header, chain) ethNode.chain = chain - let balance = 100.u256 - var address: EthAddress = hexToByteArray[20]("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6") + let + balance = 100.u256 + address: EthAddress = hexToByteArray[20]("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6") state.mutateStateDB: db.setBalance(address, balance) @@ -66,10 +70,10 @@ proc doTests = test "eth_getBalance": expect ValueError: # check error is raised on null address - var r = waitFor client.eth_getBalance(ZERO_ADDRESS, "0x0") + var r = waitFor client.eth_getBalance(ZERO_ADDRESS.toEthAddressStr, "0x0") let blockNum = state.blockheader.blockNumber - var r = waitFor client.eth_getBalance(address, "0x" & blockNum.toHex) + var r = waitFor client.eth_getBalance(address.toEthAddressStr, "0x" & blockNum.toHex) echo r rpcServer.stop() From 4c38ede9c93413dab1c1b7f499bef6b5fda41f4c Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 13 Aug 2018 18:39:17 +0100 Subject: [PATCH 04/20] Add EthHashStr validation --- nimbus/rpc/hexstrings.nim | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index 23b978474..1fd089589 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -13,11 +13,13 @@ type HexQuantityStr* = distinct string HexDataStr* = distinct string - EthAddressStr* = distinct string + EthAddressStr* = distinct string # Same as HexDataStr but must be less <= 20 bytes + EthHashStr* = distinct string # Same as HexDataStr but must be exactly 32 bytes -func len*(data: HexQuantityStr): int = data.string.len +func len*(quantity: HexQuantityStr): int = quantity.string.len func len*(data: HexDataStr): int = data.string.len func len*(data: EthAddressStr): int = data.string.len +func len*(data: EthHashStr): int = data.string.len # Hex validation @@ -67,12 +69,20 @@ func isValidHexData*(value: string): bool = func isValidEthAddress*(value: string): bool = # 20 bytes for EthAddress plus "0x" + # Addresses are allowed to be shorter than 20 bytes for convenience result = value.len <= 42 and value.isValidHexData +func isValidEthHash*(value: string): bool = + # 32 bytes for EthAddress plus "0x" + # Currently hashes are required to be exact lengths + # TODO: Allow shorter hashes (pad with zeros) for convenience? + result = value.len == 66 and value.isValidHexData + const SInvalidQuantity = "Invalid hex quantity format for Ethereum" SInvalidData = "Invalid hex data format for Ethereum" SInvalidAddress = "Invalid address format for Ethereum" + SInvalidHash = "Invalid hash format for Ethereum" proc validateHexQuantity*(value: string) {.inline.} = if unlikely(not value.isValidHexQuantity): @@ -86,6 +96,10 @@ proc validateHexAddressStr*(value: string) {.inline.} = if unlikely(not value.isValidEthAddress): raise newException(ValueError, SInvalidAddress & ": " & value) +proc validateHashStr*(value: string) {.inline.} = + if unlikely(not value.isValidEthHash): + raise newException(ValueError, SInvalidHash & ": " & value) + # Initialisation proc hexQuantityStr*(value: string): HexQuantityStr {.inline.} = @@ -100,6 +114,10 @@ proc ethAddressStr*(value: string): EthAddressStr {.inline.} = value.validateHexAddressStr result = value.EthAddressStr +proc ethHashStr*(value: string): EthHashStr {.inline.} = + value.validateHashStr + result = value.EthHashStr + # Converters for use in RPC import json @@ -114,6 +132,9 @@ proc `%`*(value: HexDataStr): JsonNode = proc `%`*(value: EthAddressStr): JsonNode = result = %(value.string) +proc `%`*(value: EthHashStr): JsonNode = + result = %(value.string) + proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) = # Note that '0x' is stripped after validation n.kind.expect(JString, argName) @@ -138,3 +159,11 @@ proc fromJson*(n: JsonNode, argName: string, result: var EthAddressStr) = raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as an Ethereum address \"" & hexStr & "\"") result = hexStr.EthAddressStr +proc fromJson*(n: JsonNode, argName: string, result: var EthHashStr) = + # Note that '0x' is stripped after validation + n.kind.expect(JString, argName) + let hexStr = n.getStr() + if not hexStr.isValidEthHash: + raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as an Ethereum hash \"" & hexStr & "\"") + result = hexStr.EthHashStr + From 0b5d8d53beeaac9c6dffafbf626a8fc32d30a85d Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 13 Aug 2018 19:03:09 +0100 Subject: [PATCH 05/20] Added EthHashStr tests --- tests/rpcclient/test_hexstrings.nim | 33 +++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/tests/rpcclient/test_hexstrings.nim b/tests/rpcclient/test_hexstrings.nim index 68936c1be..b5efd4014 100644 --- a/tests/rpcclient/test_hexstrings.nim +++ b/tests/rpcclient/test_hexstrings.nim @@ -111,14 +111,39 @@ proc doHexStrTests* = test "Too long": expect ValueError: let - # too long - e = "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec66" + e = "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec667" e_addr = e.ethAddressStr check e == e_addr.string - test "Wrong format": + test "\"0x\" header": expect ValueError: let - # too long + # no 0x e = "000f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" e_addr = e.ethAddressStr check e == e_addr.string + + suite "[RPC] Eth hash strings": + test "Valid hash": + let + e = "0x1234567890123456789012345678901234567890123456789012345678901234" + e_addr = e.ethHashStr + check e == e_addr.string + test "Too short": + expect ValueError: + let + short_e = "0x12345678901234567890123456789012345678901234567890123456789012" + short_e_addr = short_e.ethHashStr + check short_e == short_e_addr.string + test "Too long": + expect ValueError: + let + e = "0x123456789012345678901234567890123456789012345678901234567890123456" + e_addr = e.ethHashStr + check e == e_addr.string + test "\"0x\" header": + expect ValueError: + let + # no 0x + e = "000x12345678901234567890123456789012" + e_addr = e.ethHashStr + check e == e_addr.string From 370da80478bf5faaa90e38f2d89db7a6f98416aa Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 13 Aug 2018 19:25:21 +0100 Subject: [PATCH 06/20] Add types to support RPC data transfer --- nimbus/rpc/rpc_types.nim | 79 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 nimbus/rpc/rpc_types.nim diff --git a/nimbus/rpc/rpc_types.nim b/nimbus/rpc/rpc_types.nim new file mode 100644 index 000000000..e6e4aef04 --- /dev/null +++ b/nimbus/rpc/rpc_types.nim @@ -0,0 +1,79 @@ +import eth_common, hexstrings + +#[ + Notes: + * Some of the types suppose 'null' when there is no appropriate value. + To allow for this, currently these values are refs so the JSON transform can convert to `JNull`. +]# + +type + SyncState* = object + startingBlock*: HexDataStr + currentBlock*: HexDataStr + highestBlock*: HexDataStr + + EthSend* = object + source*: EthAddressStr # the address the transaction is send from. + to*: EthAddressStr # (optional when creating new contract) the address the transaction is directed to. + gas*: GasInt # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas. + gasPrice*: GasInt # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas. + value*: int # (optional) integer of the value sent with this transaction. + data*: EthHashStr # TODO: Support more data. The compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI. + nonce*: int # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce + + EthCall* = object + source*: EthAddressStr # (optional) The address the transaction is send from. + to*: EthAddressStr # The address the transaction is directed to. + gas*: GasInt # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. + gasPrice*: GasInt # (optional) Integer of the gasPrice used for each paid gas. + value*: int # (optional) Integer of the value sent with this transaction. + data*: EthHashStr # (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI. + + ## A block object, or null when no block was found + ## Note that this includes slightly different information from eth_common.BlockHeader + BlockObject* = ref object + number*: int # the block number. null when its pending block. + hash*: EthHashStr # hash of the block. null when its pending block. + parentHash*: EthHashStr # hash of the parent block. + nonce*: int64 # hash of the generated proof-of-work. null when its pending block. + sha3Uncles*: EthHashStr # SHA3 of the uncles data in the block. + logsBloom*: HexDataStr # the bloom filter for the logs of the block. null when its pending block. + transactionsRoot*: EthHashStr # the root of the transaction trie of the block. + stateRoot*: EthHashStr # the root of the final state trie of the block. + receiptsRoot*: EthHashStr # the root of the receipts trie of the block. + miner*: EthAddressStr # the address of the beneficiary to whom the mining rewards were given. + difficulty*: int # integer of the difficulty for this block. + totalDifficulty*: int # integer of the total difficulty of the chain until this block. + extraData*: string # the "extra data" field of this block. + size*: int # integer the size of this block in bytes. + gasLimit*: int # the maximum gas allowed in this block. + gasUsed*: int # the total used gas by all transactions in this block. + timestamp*: int # the unix timestamp for when the block was collated. + transactions*: seq[EthHashStr] # list of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter. + uncles*: seq[EthHashStr] # list of uncle hashes. + + TransactionObject* = object # A transaction object, or null when no transaction was found: + hash*: EthHashStr # hash of the transaction. + nonce*: int64 # TODO: Is int? the number of transactions made by the sender prior to this one. + blockHash*: EthHashStr # hash of the block where this transaction was in. null when its pending. + blockNumber*: HexQuantityStr # block number where this transaction was in. null when its pending. + transactionIndex*: int64 # integer of the transactions index position in the block. null when its pending. + source*: EthAddressStr # address of the sender. + to*: EthAddressStr # address of the receiver. null when its a contract creation transaction. + value*: int64 # value transferred in Wei. + gasPrice*: GasInt # gas price provided by the sender in Wei. + gas*: GasInt # gas provided by the sender. + input*: HexDataStr # the data send along with the transaction. + + LogObject* = object + removed*: bool # true when the log was removed, due to a chain reorganization. false if its a valid log. + logIndex*: int # integer of the log index position in the block. null when its pending log. + transactionIndex*: ref int # integer of the transactions index position log was created from. null when its pending log. + transactionHash*: EthHashStr # hash of the transactions this log was created from. null when its pending log. + blockHash*: ref EthHashStr # hash of the block where this log was in. null when its pending. null when its pending log. + blockNumber*: ref HexDataStr # the block number where this log was in. null when its pending. null when its pending log. + address*: EthAddressStr # address from which this log originated. + data*: seq[EthHashStr] # contains one or more 32 Bytes non-indexed arguments of the log. + topics*: array[4, EthHashStr] # array of 0 to 4 32 Bytes DATA of indexed log arguments. + # (In solidity: The first topic is the hash of the signature of the event. + # (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.) From fb185b7965e612893a2b27f55abc932d670bfc8e Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 13 Aug 2018 19:26:34 +0100 Subject: [PATCH 07/20] Update RPC signatures --- tests/rpcclient/ethcallsigs.nim | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/rpcclient/ethcallsigs.nim b/tests/rpcclient/ethcallsigs.nim index 7fd109511..9527c36d1 100644 --- a/tests/rpcclient/ethcallsigs.nim +++ b/tests/rpcclient/ethcallsigs.nim @@ -1,7 +1,7 @@ ## This module contains signatures for the Ethereum client RPCs. ## The signatures are not imported directly, but read and processed with parseStmt, ## then a procedure body is generated to marshal native Nim parameters to json and visa versa. -import json, stint, eth_common +import json, stint, eth_common, ../../nimbus/rpc/hexstrings proc web3_clientVersion(): string proc web3_sha3(data: string): string @@ -10,21 +10,21 @@ proc net_peerCount(): int proc net_listening(): bool proc eth_protocolVersion(): string proc eth_syncing(): JsonNode -proc eth_coinbase(): string +proc eth_coinbase(): EthAddressStr proc eth_mining(): bool proc eth_hashrate(): int -proc eth_gasPrice(): int64 -proc eth_accounts(): seq[array[20, byte]] -proc eth_blockNumber(): int -proc eth_getBalance(data: array[20, byte], quantityTag: string): int -proc eth_getStorageAt(data: array[20, byte], quantity: int, quantityTag: string): seq[byte] -proc eth_getTransactionCount(data: array[20, byte], quantityTag: string) +proc eth_gasPrice(): GasInt +proc eth_accounts(): seq[EthAddressStr] +proc eth_blockNumber(): BlockNumber +proc eth_getBalance(data: EthAddressStr, quantityTag: string): int +proc eth_getStorageAt(data: EthAddressStr, quantity: int, quantityTag: string): seq[byte] +proc eth_getTransactionCount(data: EthAddressStr, quantityTag: string) proc eth_getBlockTransactionCountByHash(data: array[32, byte]) proc eth_getBlockTransactionCountByNumber(quantityTag: string) proc eth_getUncleCountByBlockHash(data: array[32, byte]) proc eth_getUncleCountByBlockNumber(quantityTag: string) -proc eth_getCode(data: array[20, byte], quantityTag: string): seq[byte] -proc eth_sign(data: array[20, byte], message: seq[byte]): seq[byte] +proc eth_getCode(data: EthAddressStr, quantityTag: string): HexDataStr +proc eth_sign(data:EthAddressStr, message: HexDataStr): HexDataStr # TODO: Use eth_common types From c3fbb0a0981d8174bcff0df31490e0060feac6f0 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 13 Aug 2018 19:29:38 +0100 Subject: [PATCH 08/20] Add eth_syncing, eth_getStorageAt, eth_blockNumber, eth_coinbase and some stubs --- nimbus/rpc/p2p.nim | 320 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 310 insertions(+), 10 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index e98fa2a43..277b19db4 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -7,9 +7,9 @@ # This file may not be copied, modified, or distributed except according to # those terms. import - nimcrypto, json_rpc/server, eth_p2p, hexstrings, strutils, stint, + nimcrypto, json_rpc/rpcserver, eth_p2p, hexstrings, strutils, stint, ../config, ../vm_state, ../constants, eth_trie/[memdb, types], - ../db/[db_chain, state_db], eth_common + ../db/[db_chain, state_db], eth_common, rpc_types, byteutils func headerFromTag(chain:BaseChainDB, blockTag: string): BlockHeader = let tag = blockTag.toLowerAscii @@ -25,23 +25,323 @@ func headerFromTag(chain:BaseChainDB, blockTag: string): BlockHeader = let blockNum = stint.fromHex(UInt256, tag) result = chain.getCanonicalBlockHeaderByNumber(blockNum) -proc setupP2PRPC*(server: EthereumNode, rpcsrv: RpcServer) = +proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = + template chain: untyped = BaseChainDB(node.chain) # TODO: Sensible casting + + proc accountDbFromTag(tag: string): AccountStateDb = + # Note: This is a read only account + let + header = chain.headerFromTag(tag) + vmState = newBaseVMState(header, chain) + result = vmState.readOnlyStateDb + rpcsrv.rpc("net_version") do() -> uint: let conf = getConfiguration() result = conf.net.networkId + + rpcsrv.rpc("eth_syncing") do() -> JsonNode: + ## Returns SyncObject or false when not syncing. + # TODO: Requires PeerPool to check sync state. + # TODO: Use variant objects + var + res: JsonNode + sync: SyncState + if true: + # TODO: Populate sync state, this is a placeholder + sync.startingBlock = GENESIS_BLOCK_NUMBER.toHex.HexDataStr + sync.currentBlock = chain.getCanonicalHead().blockNumber.toHex.HexDataStr + sync.highestBlock = chain.getCanonicalHead().blockNumber.toHex.HexDataStr + result = %sync + else: + result = newJBool(false) + + rpcsrv.rpc("eth_coinbase") do() -> EthAddress: + ## Returns the current coinbase address. + result = chain.getCanonicalHead().coinbase - rpcsrv.rpc("eth_getBalance") do(address: array[20, byte], quantityTag: string) -> int: + rpcsrv.rpc("eth_mining") do() -> bool: + ## Returns true if the client is mining, otherwise false. + discard + + rpcsrv.rpc("eth_hashrate") do() -> int: + ## Returns the number of hashes per second that the node is mining with. + discard + + rpcsrv.rpc("eth_gasPrice") do() -> int64: + ## Returns an integer of the current gas price in wei. + discard + + rpcsrv.rpc("eth_accounts") do() -> seq[EthAddressStr]: + ## Returns a list of addresses owned by client. + result = @[] + + rpcsrv.rpc("eth_blockNumber") do() -> BlockNumber: + ## Returns integer of the current block number the client is on. + result = chain.getCanonicalHead().blockNumber + + rpcsrv.rpc("eth_getBalance") do(data: EthAddressStr, quantityTag: string) -> int: ## Returns the balance of the account of given address. ## ## data: address to check for balance. ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns integer of the current balance in wei. - template chain: untyped = BaseChainDB(server.chain) # TODO: Sensible casting let - header = chain.headerFromTag(quantityTag) - vmState = newBaseVMState(header, chain) - account_db = vmState.readOnlyStateDb - balance = account_db.get_balance(address) + account_db = accountDbFromTag(quantityTag) + addrBytes = hexToPaddedByteArray[20](data.string) + balance = account_db.get_balance(addrBytes) - return balance.toInt + result = balance.toInt + + rpcsrv.rpc("eth_getStorageAt") do(data: EthAddressStr, quantity: int, quantityTag: string) -> HexDataStr: + ## Returns the value from a storage position at a given address. + ## + ## data: address of the storage. + ## quantity: integer of the position in the storage. + ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns: the value at this storage position. + let + account_db = accountDbFromTag(quantityTag) + addrBytes = hexToPaddedByteArray[20](data.string) + storage = account_db.getStorage(addrBytes, quantity.u256) + if storage[1]: + result = ("0x" & storage[0].toHex).HexDataStr + + + rpcsrv.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string): + ## Returns the number of transactions sent from an address. + ## + ## data: address. + ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns integer of the number of transactions send from this address. + discard + + rpcsrv.rpc("eth_getBlockTransactionCountByHash") do(data: HexDataStr) -> int: + ## 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. + discard + + rpcsrv.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> int: + ## Returns the number of transactions in a block matching the given block number. + ## + ## data: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. + ## Returns integer of the number of transactions in this block. + discard + + rpcsrv.rpc("eth_getUncleCountByBlockHash") do(data: HexDataStr): + ## 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. + discard + + rpcsrv.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string): + ## Returns the number of uncles in a block from a block matching the given block number. + ## + ## quantityTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns integer of uncles in this block. + discard + + rpcsrv.rpc("eth_getCode") do(data: EthAddressStr, quantityTag: string) -> HexDataStr: + ## Returns code at a given address. + ## + ## data: address + ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns the code from the given address. + discard + + rpcsrv.rpc("eth_sign") do(data: EthAddressStr, message: HexDataStr) -> HexDataStr: + ## 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. + discard + + rpcsrv.rpc("eth_sendTransaction") do(obj: EthSend) -> HexDataStr: + ## 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. + discard + + rpcsrv.rpc("eth_sendRawTransaction") do(data: string, quantityTag: int) -> HexDataStr: + ## 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. + discard + + rpcsrv.rpc("eth_call") do(call: EthCall, quantityTag: string) -> HexDataStr: + ## 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. + discard + + rpcsrv.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> HexDataStr: # TODO: Int or U/Int256? + ## 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. + ## + ## call: 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. + discard + + rpcsrv.rpc("eth_getBlockByHash") do(data: HexDataStr, 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. + discard + + rpcsrv.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> BlockObject: + ## Returns information about a block by block number. + ## + ## quantityTag: 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. + discard + + rpcsrv.rpc("eth_getTransactionByHash") do(data: HexDataStr) -> TransactionObject: + ## Returns the information about a transaction requested by transaction hash. + ## + ## data: hash of a transaction. + ## Returns requested transaction information. + discard + + rpcsrv.rpc("eth_getTransactionByBlockHashAndIndex") do(data: HexDataStr, quantity: int) -> 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. + discard + + rpcsrv.rpc("eth_getTransactionByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> 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. + discard + + # Currently defined as a variant type so this might need rethinking + # See: https://github.com/status-im/nim-json-rpc/issues/29 + #[ + rpcsrv.rpc("eth_getTransactionReceipt") do(data: HexDataStr) -> ReceiptObject: + ## Returns the receipt of a transaction by transaction hash. + ## + ## data: hash of a transaction. + ## Returns transaction receipt. + discard + ]# + + rpcsrv.rpc("eth_getUncleByBlockHashAndIndex") do(data: HexDataStr, quantity: int64) -> BlockObject: + ## Returns information about a uncle of a block by hash and uncle index position. + ## + ## data: hash a block. + ## quantity: the uncle's index position. + ## Returns BlockObject or nil when no block was found. + discard + + rpcsrv.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: string, quantity: int64) -> 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. + discard + + # FilterOptions requires more layout planning. + # See: https://github.com/status-im/nim-json-rpc/issues/29 + #[ + rpcsrv.rpc("eth_newFilter") do(filterOptions: FilterOptions) -> int: + ## Creates a filter object, based on filter options, to notify when the state changes (logs). + ## To check if the state has changed, call eth_getFilterChanges. + ## Topics are order-dependent. A transaction with a log with topics [A, B] will be matched by the following topic filters: + ## [] "anything" + ## [A] "A in first position (and anything after)" + ## [null, B] "anything in first position AND B in second position (and anything after)" + ## [A, B] "A in first position AND B in second position (and anything after)" + ## [[A, B], [A, B]] "(A OR B) in first position AND (A OR B) in second position (and anything after)" + ## + ## filterOptions: settings for this filter. + ## Returns integer filter id. + discard + ]# + rpcsrv.rpc("eth_newBlockFilter") do() -> int: + ## Creates a filter in the node, to notify when a new block arrives. + ## To check if the state has changed, call eth_getFilterChanges. + ## + ## Returns integer filter id. + discard + + rpcsrv.rpc("eth_newPendingTransactionFilter") do() -> int: + ## Creates a filter in the node, to notify when a new block arrives. + ## To check if the state has changed, call eth_getFilterChanges. + ## + ## Returns integer filter id. + discard + + rpcsrv.rpc("eth_uninstallFilter") do(filterId: int) -> bool: + ## Uninstalls a filter with given id. Should always be called when watch is no longer needed. + ## Additonally Filters timeout when they aren't requested with eth_getFilterChanges for a period of time. + ## + ## filterId: The filter id. + ## Returns true if the filter was successfully uninstalled, otherwise false. + discard + + #[ + rpcsrv.rpc("eth_getFilterChanges") do(filterId: int) -> seq[LogObject]: + ## Polling method for a filter, which returns an list of logs which occurred since last poll. + ## + ## filterId: the filter id. + result = @[] + + rpcsrv.rpc("eth_getFilterLogs") do(filterId: int) -> seq[LogObject]: + ## filterId: the filter id. + ## Returns a list of all logs matching filter with given id. + result = @[] + + rpcsrv.rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[LogObject]: + ## filterOptions: settings for this filter. + ## Returns a list of all logs matching a given filter object. + result = @[] + ]# + + rpcsrv.rpc("eth_getWork") do() -> seq[HexDataStr]: + ## Returns the hash of the current block, the seedHash, and the boundary condition to be met ("target"). + ## Returned list has the following properties: + ## DATA, 32 Bytes - current block header pow-hash. + ## DATA, 32 Bytes - the seed hash used for the DAG. + ## DATA, 32 Bytes - the boundary condition ("target"), 2^256 / difficulty. + result = @[] + + rpcsrv.rpc("eth_submitWork") do(nonce: int64, powHash: HexDataStr, mixDigest: HexDataStr) -> bool: + ## Used for submitting a proof-of-work solution. + ## + ## nonce: the nonce found. + ## headerPow: the header's pow-hash. + ## mixDigest: the mix digest. + ## Returns true if the provided solution is valid, otherwise false. + discard + + rpcsrv.rpc("eth_submitHashrate") do(hashRate: HexDataStr, id: HexDataStr) -> bool: + ## Used for submitting mining hashrate. + ## + ## hashRate: a hexadecimal string representation (32 bytes) of the hash rate. + ## id: a random hexadecimal(32 bytes) ID identifying the client. + ## Returns true if submitting went through succesfully and false otherwise. + discard + + From 404d4d883dbf40ef316bd124486f906e6c2d2d44 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 14 Aug 2018 16:52:30 +0100 Subject: [PATCH 09/20] eth_getTransactionCount --- nimbus/rpc/p2p.nim | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 277b19db4..3fc89967d 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -28,12 +28,12 @@ func headerFromTag(chain:BaseChainDB, blockTag: string): BlockHeader = proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = template chain: untyped = BaseChainDB(node.chain) # TODO: Sensible casting - proc accountDbFromTag(tag: string): AccountStateDb = + proc accountDbFromTag(tag: string, readOnly = true): AccountStateDb = # Note: This is a read only account let header = chain.headerFromTag(tag) vmState = newBaseVMState(header, chain) - result = vmState.readOnlyStateDb + result = vmState.chaindb.getStateDb(vmState.blockHeader.stateRoot, readOnly) rpcsrv.rpc("net_version") do() -> uint: let conf = getConfiguration() @@ -107,13 +107,16 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = result = ("0x" & storage[0].toHex).HexDataStr - rpcsrv.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string): + rpcsrv.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> int: ## Returns the number of transactions sent from an address. ## ## data: address. ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns integer of the number of transactions send from this address. - discard + let + header = chain.headerFromTag(quantityTag) + body = chain.getBlockBody(header.stateRoot) + result = body.transactions.len rpcsrv.rpc("eth_getBlockTransactionCountByHash") do(data: HexDataStr) -> int: ## Returns the number of transactions in a block from a block matching the given block hash. From 1871a7b09067d37c95239def5fc1dbd280661675 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 14 Aug 2018 16:53:45 +0100 Subject: [PATCH 10/20] eth_getBlockTransactionCountByHash --- nimbus/rpc/p2p.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 3fc89967d..ea952523c 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -123,7 +123,10 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## ## data: hash of a block ## Returns integer of the number of transactions in this block. - discard + var hashData: Hash256 + hashData.data = hexToPaddedByteArray[32](data.string) + let body = chain.getBlockBody(hashData) + result = body.transactions.len rpcsrv.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> int: ## Returns the number of transactions in a block matching the given block number. From b9906fb26300ded2788499a00c5dee0160efc148 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 14 Aug 2018 16:54:59 +0100 Subject: [PATCH 11/20] eth_getBlockTransactionCountByNumber --- nimbus/rpc/p2p.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index ea952523c..7f33761bc 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -133,7 +133,10 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## ## data: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. ## Returns integer of the number of transactions in this block. - discard + let + header = chain.headerFromTag(quantityTag) + body = chain.getBlockBody(header.stateRoot) + result = body.transactions.len rpcsrv.rpc("eth_getUncleCountByBlockHash") do(data: HexDataStr): ## Returns the number of uncles in a block from a block matching the given block hash. From 0e9edf80325eeea87e9e065d86b2e90e04af1517 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 14 Aug 2018 16:55:28 +0100 Subject: [PATCH 12/20] eth_getUncleCountByBlockHash --- nimbus/rpc/p2p.nim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 7f33761bc..69bfdccae 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -138,12 +138,15 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = body = chain.getBlockBody(header.stateRoot) result = body.transactions.len - rpcsrv.rpc("eth_getUncleCountByBlockHash") do(data: HexDataStr): + rpcsrv.rpc("eth_getUncleCountByBlockHash") do(data: HexDataStr) -> int: ## 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. - discard + var hashData: Hash256 + hashData.data = hexToPaddedByteArray[32](data.string) + let body = chain.getBlockBody(hashData) + result = body.uncles.len rpcsrv.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string): ## Returns the number of uncles in a block from a block matching the given block number. From 25f8123c6f472dcdf2565d5dd263d1b42d2a302e Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 14 Aug 2018 16:55:57 +0100 Subject: [PATCH 13/20] eth_getUncleCountByBlockNumber --- nimbus/rpc/p2p.nim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 69bfdccae..47832c934 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -148,12 +148,15 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = let body = chain.getBlockBody(hashData) result = body.uncles.len - rpcsrv.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string): + rpcsrv.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string) -> int: ## Returns the number of uncles in a block from a block matching the given block number. ## ## quantityTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns integer of uncles in this block. - discard + let + header = chain.headerFromTag(quantityTag) + body = chain.getBlockBody(header.stateRoot) + result = body.uncles.len rpcsrv.rpc("eth_getCode") do(data: EthAddressStr, quantityTag: string) -> HexDataStr: ## Returns code at a given address. From ee15f4a9950eacc4af538c9d4c1e5a873cd6f3a6 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 14 Aug 2018 18:22:31 +0100 Subject: [PATCH 14/20] eth_getCode and helper func strToAddress --- nimbus/rpc/p2p.nim | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 47832c934..40738e6b2 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -9,7 +9,17 @@ import nimcrypto, json_rpc/rpcserver, eth_p2p, hexstrings, strutils, stint, ../config, ../vm_state, ../constants, eth_trie/[memdb, types], - ../db/[db_chain, state_db], eth_common, rpc_types, byteutils + ../db/[db_chain, state_db], eth_common, rpc_types, byteutils, + ranges/typedranges + +#[ + Note: + * Hexstring types (HexQuantitySt, HexDataStr, EthAddressStr, EthHashStr) + are parsed to check format before the RPC blocks are executed and will + raise an exception if invalid. +]# + +func strToAddress(value: string): EthAddress = hexToPaddedByteArray[20](value) func headerFromTag(chain:BaseChainDB, blockTag: string): BlockHeader = let tag = blockTag.toLowerAscii @@ -87,7 +97,7 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns integer of the current balance in wei. let account_db = accountDbFromTag(quantityTag) - addrBytes = hexToPaddedByteArray[20](data.string) + addrBytes = strToAddress(data.string) balance = account_db.get_balance(addrBytes) result = balance.toInt @@ -101,12 +111,11 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns: the value at this storage position. let account_db = accountDbFromTag(quantityTag) - addrBytes = hexToPaddedByteArray[20](data.string) + addrBytes = strToAddress(data.string) storage = account_db.getStorage(addrBytes, quantity.u256) if storage[1]: result = ("0x" & storage[0].toHex).HexDataStr - rpcsrv.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> int: ## Returns the number of transactions sent from an address. ## @@ -164,7 +173,17 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## data: address ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the code from the given address. - discard + let + account_db = accountDbFromTag(quantityTag) + addrBytes = strToAddress(data.string) + storage = account_db.getCode(addrBytes) + var + idx = 2 + res = newString(storage.len + 2) + res[0..1] = "0x" + for b in storage: + res[idx] = b.char + result = hexDataStr(res) rpcsrv.rpc("eth_sign") do(data: EthAddressStr, message: HexDataStr) -> HexDataStr: ## The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))). From 88ab097a61990173ec01417e515e0b6095de8436 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 14 Aug 2018 20:22:04 +0100 Subject: [PATCH 15/20] Add json converters for byte array and UInt256 types to hex string --- nimbus/rpc/hexstrings.nim | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index 1fd089589..e80da1d78 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -10,6 +10,8 @@ ## This module implements the Ethereum hexadecimal string formats for JSON ## See: https://github.com/ethereum/wiki/wiki/JSON-RPC#hex-value-encoding +import eth_common/eth_types, stint, byteutils + type HexQuantityStr* = distinct string HexDataStr* = distinct string @@ -135,6 +137,17 @@ proc `%`*(value: EthAddressStr): JsonNode = proc `%`*(value: EthHashStr): JsonNode = result = %(value.string) +# Overloads to support expected representation of hex data + +proc `%`*(value: EthAddress): JsonNode = + result = %("0x" & value.toHex) + +proc `%`*(value: UInt256): JsonNode = + result = %("0x" & value.toString) + +proc `%`*(value: openArray[seq]): JsonNode = + result = %("0x" & value.toHex) + proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) = # Note that '0x' is stripped after validation n.kind.expect(JString, argName) From 4b03f6d4db990dcf27f17244b671826471a4aa84 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 15 Aug 2018 13:13:30 +0100 Subject: [PATCH 16/20] Updated hexstrings to translate common Nimbus types to hex strings --- nimbus/rpc/hexstrings.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index e80da1d78..039e34d56 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -10,7 +10,7 @@ ## This module implements the Ethereum hexadecimal string formats for JSON ## See: https://github.com/ethereum/wiki/wiki/JSON-RPC#hex-value-encoding -import eth_common/eth_types, stint, byteutils +import eth_common/eth_types, stint, byteutils, nimcrypto type HexQuantityStr* = distinct string @@ -142,6 +142,9 @@ proc `%`*(value: EthHashStr): JsonNode = proc `%`*(value: EthAddress): JsonNode = result = %("0x" & value.toHex) +proc `%`*(value: Hash256): JsonNode = + result = %("0x" & $value) + proc `%`*(value: UInt256): JsonNode = result = %("0x" & value.toString) From b6a73327c3ef20e4fef8891f99321cdda76caa01 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 15 Aug 2018 13:14:32 +0100 Subject: [PATCH 17/20] Converted rpc types to Nimbus types in line with changes to hexstrings --- nimbus/rpc/rpc_types.nim | 79 +++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/nimbus/rpc/rpc_types.nim b/nimbus/rpc/rpc_types.nim index e6e4aef04..ce86d7c2a 100644 --- a/nimbus/rpc/rpc_types.nim +++ b/nimbus/rpc/rpc_types.nim @@ -4,15 +4,22 @@ import eth_common, hexstrings Notes: * Some of the types suppose 'null' when there is no appropriate value. To allow for this, currently these values are refs so the JSON transform can convert to `JNull`. + * Parameter objects from users must have their data verified so will use EthAddressStr instead of EthAddres, for example + * Objects returned to the user can use native Nimbus types, where hexstrings provides converters to hex strings. + This is because returned arrays in JSON is + a) not an efficient use of space + b) not the format the user expects (for example addresses are expected to be hex strings prefixed by "0x") ]# type SyncState* = object - startingBlock*: HexDataStr - currentBlock*: HexDataStr - highestBlock*: HexDataStr + # Returned to user + startingBlock*: BlockNumber + currentBlock*: BlockNumber + highestBlock*: BlockNumber EthSend* = object + # Parameter from user source*: EthAddressStr # the address the transaction is send from. to*: EthAddressStr # (optional when creating new contract) the address the transaction is directed to. gas*: GasInt # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas. @@ -22,6 +29,7 @@ type nonce*: int # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce EthCall* = object + # Parameter from user source*: EthAddressStr # (optional) The address the transaction is send from. to*: EthAddressStr # The address the transaction is directed to. gas*: GasInt # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. @@ -32,48 +40,51 @@ type ## A block object, or null when no block was found ## Note that this includes slightly different information from eth_common.BlockHeader BlockObject* = ref object - number*: int # the block number. null when its pending block. - hash*: EthHashStr # hash of the block. null when its pending block. - parentHash*: EthHashStr # hash of the parent block. - nonce*: int64 # hash of the generated proof-of-work. null when its pending block. - sha3Uncles*: EthHashStr # SHA3 of the uncles data in the block. - logsBloom*: HexDataStr # the bloom filter for the logs of the block. null when its pending block. - transactionsRoot*: EthHashStr # the root of the transaction trie of the block. - stateRoot*: EthHashStr # the root of the final state trie of the block. - receiptsRoot*: EthHashStr # the root of the receipts trie of the block. - miner*: EthAddressStr # the address of the beneficiary to whom the mining rewards were given. - difficulty*: int # integer of the difficulty for this block. - totalDifficulty*: int # integer of the total difficulty of the chain until this block. - extraData*: string # the "extra data" field of this block. + # Returned to user + number*: ref BlockNumber # the block number. null when its pending block. + hash*: ref Hash256 # hash of the block. null when its pending block. + parentHash*: Hash256 # hash of the parent block. + nonce*: uint64 # hash of the generated proof-of-work. null when its pending block. + sha3Uncles*: Hash256 # SHA3 of the uncles data in the block. + logsBloom*: ref BloomFilter # the bloom filter for the logs of the block. null when its pending block. + transactionsRoot*: Hash256 # the root of the transaction trie of the block. + stateRoot*: Hash256 # the root of the final state trie of the block. + receiptsRoot*: Hash256 # the root of the receipts trie of the block. + miner*: EthAddress # the address of the beneficiary to whom the mining rewards were given. + difficulty*: UInt256 # integer of the difficulty for this block. + totalDifficulty*: UInt256 # integer of the total difficulty of the chain until this block. + extraData*: Blob # the "extra data" field of this block. size*: int # integer the size of this block in bytes. - gasLimit*: int # the maximum gas allowed in this block. - gasUsed*: int # the total used gas by all transactions in this block. - timestamp*: int # the unix timestamp for when the block was collated. - transactions*: seq[EthHashStr] # list of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter. - uncles*: seq[EthHashStr] # list of uncle hashes. + gasLimit*: GasInt # the maximum gas allowed in this block. + gasUsed*: GasInt # the total used gas by all transactions in this block. + timestamp*: EthTime # the unix timestamp for when the block was collated. + transactions*: seq[Transaction] # list of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter. + uncles*: seq[BlockHeader] # list of uncle hashes. TransactionObject* = object # A transaction object, or null when no transaction was found: - hash*: EthHashStr # hash of the transaction. + # Returned to user + hash*: Hash256 # hash of the transaction. nonce*: int64 # TODO: Is int? the number of transactions made by the sender prior to this one. - blockHash*: EthHashStr # hash of the block where this transaction was in. null when its pending. - blockNumber*: HexQuantityStr # block number where this transaction was in. null when its pending. - transactionIndex*: int64 # integer of the transactions index position in the block. null when its pending. - source*: EthAddressStr # address of the sender. - to*: EthAddressStr # address of the receiver. null when its a contract creation transaction. + blockHash*: ref Hash256 # hash of the block where this transaction was in. null when its pending. + blockNumber*: ref BlockNumber # block number where this transaction was in. null when its pending. + transactionIndex*: ref int64 # integer of the transactions index position in the block. null when its pending. + source*: EthAddress # address of the sender. + to*: ref EthAddress # address of the receiver. null when its a contract creation transaction. value*: int64 # value transferred in Wei. gasPrice*: GasInt # gas price provided by the sender in Wei. gas*: GasInt # gas provided by the sender. input*: HexDataStr # the data send along with the transaction. LogObject* = object + # Returned to user removed*: bool # true when the log was removed, due to a chain reorganization. false if its a valid log. - logIndex*: int # integer of the log index position in the block. null when its pending log. + logIndex*: ref int # integer of the log index position in the block. null when its pending log. transactionIndex*: ref int # integer of the transactions index position log was created from. null when its pending log. - transactionHash*: EthHashStr # hash of the transactions this log was created from. null when its pending log. - blockHash*: ref EthHashStr # hash of the block where this log was in. null when its pending. null when its pending log. - blockNumber*: ref HexDataStr # the block number where this log was in. null when its pending. null when its pending log. - address*: EthAddressStr # address from which this log originated. - data*: seq[EthHashStr] # contains one or more 32 Bytes non-indexed arguments of the log. - topics*: array[4, EthHashStr] # array of 0 to 4 32 Bytes DATA of indexed log arguments. + transactionHash*: ref Hash256 # hash of the transactions this log was created from. null when its pending log. + blockHash*: ref Hash256 # hash of the block where this log was in. null when its pending. null when its pending log. + blockNumber*: ref BlockNumber # the block number where this log was in. null when its pending. null when its pending log. + address*: EthAddress # address from which this log originated. + data*: seq[Hash256] # contains one or more 32 Bytes non-indexed arguments of the log. + topics*: array[4, Hash256] # array of 0 to 4 32 Bytes DATA of indexed log arguments. # (In solidity: The first topic is the hash of the signature of the event. # (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.) From 1826c0ce92b9ebe3610fe57efb9a1997e47aa194 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 15 Aug 2018 14:07:06 +0100 Subject: [PATCH 18/20] Add JSON transform for bloom filter --- nimbus/rpc/hexstrings.nim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index 039e34d56..7e411aff9 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -151,6 +151,11 @@ proc `%`*(value: UInt256): JsonNode = proc `%`*(value: openArray[seq]): JsonNode = result = %("0x" & value.toHex) +proc `%`*(value: ref BloomFilter): JsonNode = + result = %("0x" & toHex[256](value[])) + +# Marshalling from JSON to Nim types that includes format checking + proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) = # Note that '0x' is stripped after validation n.kind.expect(JString, argName) From 845866fc97b74301ba5dfaac0b4af8a61b30da5d Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 15 Aug 2018 14:08:40 +0100 Subject: [PATCH 19/20] eth_getBlockByHash, changes to support returning marshalled Nim types --- nimbus/rpc/p2p.nim | 66 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 40738e6b2..b98e31d27 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -10,15 +10,23 @@ import nimcrypto, json_rpc/rpcserver, eth_p2p, hexstrings, strutils, stint, ../config, ../vm_state, ../constants, eth_trie/[memdb, types], ../db/[db_chain, state_db], eth_common, rpc_types, byteutils, - ranges/typedranges + ranges/typedranges, times, ../utils/header #[ Note: * Hexstring types (HexQuantitySt, HexDataStr, EthAddressStr, EthHashStr) are parsed to check format before the RPC blocks are executed and will raise an exception if invalid. + * Many of the RPC calls do not validate hex string types when output, only + type cast to avoid extra processing. ]# +# TODO: Don't use stateRoot for block hash. + +# Work around for https://github.com/nim-lang/Nim/issues/8645 +proc `%`*(value: Time): JsonNode = + result = %value.toSeconds + func strToAddress(value: string): EthAddress = hexToPaddedByteArray[20](value) func headerFromTag(chain:BaseChainDB, blockTag: string): BlockHeader = @@ -58,9 +66,9 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = sync: SyncState if true: # TODO: Populate sync state, this is a placeholder - sync.startingBlock = GENESIS_BLOCK_NUMBER.toHex.HexDataStr - sync.currentBlock = chain.getCanonicalHead().blockNumber.toHex.HexDataStr - sync.highestBlock = chain.getCanonicalHead().blockNumber.toHex.HexDataStr + sync.startingBlock = GENESIS_BLOCK_NUMBER + sync.currentBlock = chain.getCanonicalHead().blockNumber + sync.highestBlock = chain.getCanonicalHead().blockNumber result = %sync else: result = newJBool(false) @@ -102,7 +110,7 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = result = balance.toInt - rpcsrv.rpc("eth_getStorageAt") do(data: EthAddressStr, quantity: int, quantityTag: string) -> HexDataStr: + rpcsrv.rpc("eth_getStorageAt") do(data: EthAddressStr, quantity: int, quantityTag: string) -> UInt256: ## Returns the value from a storage position at a given address. ## ## data: address of the storage. @@ -114,7 +122,7 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = addrBytes = strToAddress(data.string) storage = account_db.getStorage(addrBytes, quantity.u256) if storage[1]: - result = ("0x" & storage[0].toHex).HexDataStr + result = storage[0] rpcsrv.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> int: ## Returns the number of transactions sent from an address. @@ -177,13 +185,8 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = account_db = accountDbFromTag(quantityTag) addrBytes = strToAddress(data.string) storage = account_db.getCode(addrBytes) - var - idx = 2 - res = newString(storage.len + 2) - res[0..1] = "0x" - for b in storage: - res[idx] = b.char - result = hexDataStr(res) + # Easier to return the string manually here rather than expect ByteRange to be marshalled + result = byteutils.toHex(storage.toOpenArray).HexDataStr rpcsrv.rpc("eth_sign") do(data: EthAddressStr, message: HexDataStr) -> HexDataStr: ## The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))). @@ -230,13 +233,48 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns the amount of gas used. discard + func populateBlockObject(header: BlockHeader, blockBody: BlockBodyRef): BlockObject = + result.number = new BlockNumber + result.number[] = header.blockNumber + result.hash = new Hash256 + result.hash[] = header.hash + result.parentHash = header.parentHash + result.nonce = header.nonce.toUint + + # Calculate hash for all uncle headers + var + rawdata = newSeq[byte](blockBody.uncles.len * 32) + startIdx = 0 + for i in 0 ..< blockBody.uncles.len: + rawData[startIdx .. startIdx + 32] = blockBody.uncles[i].hash.data + startIdx += 32 + result.sha3Uncles = keccak256.digest(rawData) + + result.logsBloom = nil # TODO: Create bloom filter for logs + result.transactionsRoot = header.txRoot + result.stateRoot = header.stateRoot + result.receiptsRoot = header.receiptRoot + result.miner = ZERO_ADDRESS # TODO: Get miner address + result.difficulty = header.difficulty + result.totalDifficulty = header.difficulty # TODO: Calculate + result.extraData = header.extraData + result.size = 0 # TODO: Calculate block size + result.gasLimit = header.gasLimit + result.gasUsed = header.gasUsed + result.timestamp = header.timeStamp + result.transactions = blockBody.transactions + result.uncles = blockBody.uncles + rpcsrv.rpc("eth_getBlockByHash") do(data: HexDataStr, 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. - discard + let + header = chain.getCanonicalHead() + body = chain.getBlockBody(header.stateRoot) + populateBlockObject(header, body) rpcsrv.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> BlockObject: ## Returns information about a block by block number. From 22590dea32c5f950cdb757a2d9a30ea1adab4d90 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 15 Aug 2018 14:12:49 +0100 Subject: [PATCH 20/20] eth_getBlockByNumber, using actual BlockHeader hash now --- nimbus/rpc/p2p.nim | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index b98e31d27..e716d66d6 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -21,8 +21,6 @@ import type cast to avoid extra processing. ]# -# TODO: Don't use stateRoot for block hash. - # Work around for https://github.com/nim-lang/Nim/issues/8645 proc `%`*(value: Time): JsonNode = result = %value.toSeconds @@ -51,7 +49,7 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = let header = chain.headerFromTag(tag) vmState = newBaseVMState(header, chain) - result = vmState.chaindb.getStateDb(vmState.blockHeader.stateRoot, readOnly) + result = vmState.chaindb.getStateDb(vmState.blockHeader.hash, readOnly) rpcsrv.rpc("net_version") do() -> uint: let conf = getConfiguration() @@ -132,7 +130,7 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns integer of the number of transactions send from this address. let header = chain.headerFromTag(quantityTag) - body = chain.getBlockBody(header.stateRoot) + body = chain.getBlockBody(header.hash) result = body.transactions.len rpcsrv.rpc("eth_getBlockTransactionCountByHash") do(data: HexDataStr) -> int: @@ -152,7 +150,7 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns integer of the number of transactions in this block. let header = chain.headerFromTag(quantityTag) - body = chain.getBlockBody(header.stateRoot) + body = chain.getBlockBody(header.hash) result = body.transactions.len rpcsrv.rpc("eth_getUncleCountByBlockHash") do(data: HexDataStr) -> int: @@ -172,7 +170,7 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns integer of uncles in this block. let header = chain.headerFromTag(quantityTag) - body = chain.getBlockBody(header.stateRoot) + body = chain.getBlockBody(header.hash) result = body.uncles.len rpcsrv.rpc("eth_getCode") do(data: EthAddressStr, quantityTag: string) -> HexDataStr: @@ -273,7 +271,7 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns BlockObject or nil when no block was found. let header = chain.getCanonicalHead() - body = chain.getBlockBody(header.stateRoot) + body = chain.getBlockBody(header.hash) populateBlockObject(header, body) rpcsrv.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> BlockObject: @@ -282,7 +280,10 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## quantityTag: 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. - discard + let + header = chain.headerFromTag(quantityTag) + body = chain.getBlockBody(header.hash) + populateBlockObject(header, body) rpcsrv.rpc("eth_getTransactionByHash") do(data: HexDataStr) -> TransactionObject: ## Returns the information about a transaction requested by transaction hash.