From e51a4632cd5c567b4703c5824140145b4b72edbc Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 20 Nov 2018 17:27:22 +0000 Subject: [PATCH 01/14] Update RPC types to be inline with internal expectations and stay to spec --- nimbus/rpc/rpc_types.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nimbus/rpc/rpc_types.nim b/nimbus/rpc/rpc_types.nim index f715cb20b..3bf6002b9 100644 --- a/nimbus/rpc/rpc_types.nim +++ b/nimbus/rpc/rpc_types.nim @@ -24,9 +24,9 @@ type 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. + value*: UInt256 # (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 + nonce*: AccountNonce # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce EthCall* = object # Parameter from user @@ -34,7 +34,7 @@ type 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. + value*: UInt256 # (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 From 553605875b6ec9c613a4930fbf528e16377c484e Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 20 Nov 2018 17:30:04 +0000 Subject: [PATCH 02/14] Update types to avoid early casting to strings and keep type strictness --- nimbus/rpc/p2p.nim | 49 +++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 8a835fd70..f262c7f40 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -11,9 +11,10 @@ import strutils, times, options, nimcrypto, json_rpc/rpcserver, hexstrings, stint, byteutils, ranges/typedranges, eth_common, eth_p2p, eth_keys, eth_trie/db, rlp, - ../utils/header, ../transaction, ../config, ../vm_state, ../constants, + ../utils/header, ../transaction, ../config, ../vm_state, ../constants, ../vm_types, + ../vm_state_transactions, ../db/[db_chain, state_db, storage_types], - rpc_types + ../vm/[message, computation], rpc_types #[ Note: @@ -28,17 +29,17 @@ import proc `%`*(value: Time): JsonNode = result = %value.toSeconds -func strToAddress(value: string): EthAddress = hexToPaddedByteArray[20](value) +func toAddress(value: EthAddressStr): EthAddress = hexToPaddedByteArray[20](value.string) func toHash(value: array[32, byte]): Hash256 {.inline.} = result.data = value -func strToHash(value: string): Hash256 {.inline.} = - result = hexToPaddedByteArray[32](value).toHash +func toHash(value: EthHashStr): Hash256 {.inline.} = + result = hexToPaddedByteArray[32](value.string).toHash template balance(addressDb: AccountStateDb, address: EthAddress): GasInt = # TODO: Account balance u256 but GasInt is int64? - cast[GasInt](addressDb.get_balance(address).data.lo) + addressDb.getBalance(address).truncate(int64) func headerFromTag(chain:BaseChainDB, blockTag: string): BlockHeader = let tag = blockTag.toLowerAscii @@ -120,7 +121,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## Returns integer of the current balance in wei. let accountDb = accountDbFromTag(quantityTag) - addrBytes = strToAddress(data.string) + addrBytes = data.toAddress balance = accountDb.get_balance(addrBytes) result = balance.toInt @@ -134,7 +135,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## Returns: the value at this storage position. let accountDb = accountDbFromTag(quantityTag) - addrBytes = strToAddress(data.string) + addrBytes = data.toAddress storage = accountDb.getStorage(addrBytes, quantity.u256) if storage[1]: result = storage[0] @@ -146,16 +147,16 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## 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. let - addrBytes = data.string.strToAddress() + addrBytes = data.toAddress accountDb = accountDbFromTag(quantityTag) result = accountDb.getNonce(addrBytes) - rpcsrv.rpc("eth_getBlockTransactionCountByHash") do(data: HexDataStr) -> int: + rpcsrv.rpc("eth_getBlockTransactionCountByHash") do(data: EthHashStr) -> 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. - var hashData = strToHash(data.string) + var hashData = data.toHash result = getBlockBody(hashData).transactions.len rpcsrv.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> int: @@ -166,12 +167,12 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = let header = chain.headerFromTag(quantityTag) result = getBlockBody(header.hash).transactions.len - rpcsrv.rpc("eth_getUncleCountByBlockHash") do(data: HexDataStr) -> int: + rpcsrv.rpc("eth_getUncleCountByBlockHash") do(data: EthHashStr) -> 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. - var hashData = strToHash(data.string) + var hashData = data.toHash result = getBlockBody(hashData).uncles.len rpcsrv.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string) -> int: @@ -190,7 +191,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## Returns the code from the given address. let accountDb = accountDbFromTag(quantityTag) - addrBytes = strToAddress(data.string) + addrBytes = toAddress(data) storage = accountDb.getCode(addrBytes) # Easier to return the string manually here rather than expect ByteRange to be marshalled result = byteutils.toHex(storage.toOpenArray).HexDataStr @@ -279,14 +280,14 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = for i in 0 ..< blockBody.uncles.len: result.uncles[i] = blockBody.uncles[i].hash - rpcsrv.rpc("eth_getBlockByHash") do(data: HexDataStr, fullTransactions: bool) -> Option[BlockObject]: + rpcsrv.rpc("eth_getBlockByHash") do(data: EthHashStr, fullTransactions: bool) -> Option[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 - h = data.string.strToHash + h = data.toHash header = chain.getBlockHeader(h) result = some(populateBlockObject(header, getBlockBody(h))) @@ -321,27 +322,27 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = result.gas = accountGas result.input = transaction.payload - rpcsrv.rpc("eth_getTransactionByHash") do(data: HexDataStr) -> TransactionObject: + rpcsrv.rpc("eth_getTransactionByHash") do(data: EthHashStr) -> TransactionObject: ## Returns the information about a transaction requested by transaction hash. ## ## data: hash of a transaction. ## Returns requested transaction information. let - h = data.string.strToHash() + h = data.toHash() txDetails = chain.getTransactionKey(h) header = chain.getBlockHeader(txDetails.blockNumber) blockHash = chain.getBlockHash(txDetails.blockNumber) transaction = getBlockBody(blockHash).transactions[txDetails.index] populateTransactionObject(transaction, txDetails.index, header, blockHash) - rpcsrv.rpc("eth_getTransactionByBlockHashAndIndex") do(data: HexDataStr, quantity: int) -> TransactionObject: + rpcsrv.rpc("eth_getTransactionByBlockHashAndIndex") do(data: EthHashStr, 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. let - blockHash = data.string.strToHash() + blockHash = data.toHash() header = chain.getBlockHeader(blockHash) transaction = getBlockBody(blockHash).transactions[quantity] populateTransactionObject(transaction, quantity, header, blockHash) @@ -376,13 +377,13 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = # 1 = success, 0 = failure. result.status = 1 - rpcsrv.rpc("eth_getTransactionReceipt") do(data: HexDataStr) -> ReceiptObject: + rpcsrv.rpc("eth_getTransactionReceipt") do(data: EthHashStr) -> ReceiptObject: ## Returns the receipt of a transaction by transaction hash. ## ## data: hash of a transaction. ## Returns transaction receipt. let - h = data.string.strToHash() + h = data.toHash txDetails = chain.getTransactionKey(h) header = chain.getBlockHeader(txDetails.blockNumber) body = getBlockBody(header.hash) @@ -395,14 +396,14 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = return populateReceipt(receipt, cumulativeGas, body.transactions[txDetails.index], txDetails.index, header) idx.inc - rpcsrv.rpc("eth_getUncleByBlockHashAndIndex") do(data: HexDataStr, quantity: int) -> Option[BlockObject]: + rpcsrv.rpc("eth_getUncleByBlockHashAndIndex") do(data: EthHashStr, quantity: int) -> Option[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 - blockHash = data.string.strToHash() + blockHash = data.toHash() body = getBlockBody(blockHash) if quantity < 0 or quantity >= body.uncles.len: raise newException(ValueError, "Uncle index out of range") From 826206d054c9309712c6fd04790dbd876c8532b2 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 20 Nov 2018 17:31:17 +0000 Subject: [PATCH 03/14] Fill in eth_call --- nimbus/rpc/p2p.nim | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index f262c7f40..488897d8d 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -214,12 +214,24 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = var privateKey: PrivateKey # TODO: Get from key store result = ("0x" & sign(privateKey, message.string)).HexDataStr + proc setupTransaction(send: EthSend): Transaction = + let + source = send.source.toAddress + destination = send.to.toAddress + data = send.data.string.fromHex + contractCreation = false # TODO: Check if has code + v = 0.byte # TODO + r = 0.u256 + s = 0.u256 + result = initTransaction(send.nonce, send.gasPrice, send.gas, destination, send.value, data, v, r, s, contractCreation) + 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. + # TODO: Relies on pending pool implementation discard rpcsrv.rpc("eth_sendRawTransaction") do(data: string, quantityTag: int) -> HexDataStr: @@ -228,15 +240,38 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## 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. + # TODO: Relies on pending pool implementation discard + proc setupComputation(call: EthCall, vmState: BaseVMState, blockNumber: BlockNumber, sender: EthAddress): BaseComputation = + let + destination = call.to.toAddress + message = newMessage( + gas = call.gas, + gasPrice = call.gasPrice, + to = destination, + sender = sender, + value = call.value, + data = call.data.string.fromHex, + code = vmState.readOnlyStateDB.getCode(destination).toSeq, + options = newMessageOptions(origin = sender, + createAddress = destination)) + + result = newBaseComputation(vmState, blockNumber, message) + 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 + let header = headerFromTag(chain, quantityTag) + var + vmState = newBaseVMState(header, chain) + sender = call.source.toAddress + comp = call.setupComputation(vmState, header.blockNumber, sender) + discard comp.execComputation + result = ("0x" & nimcrypto.toHex(comp.output)).HexDataStr rpcsrv.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> GasInt: ## Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. From 1f0766c5d438ec26b057d8ba46da140d13791f01 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 20 Nov 2018 17:35:11 +0000 Subject: [PATCH 04/14] Add initTransaction --- nimbus/transaction.nim | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nimbus/transaction.nim b/nimbus/transaction.nim index 089a69106..699d6e039 100644 --- a/nimbus/transaction.nim +++ b/nimbus/transaction.nim @@ -8,6 +8,18 @@ import constants, errors, eth_common, eth_keys, nimcrypto, rlp +proc initTransaction*(nonce: AccountNonce, gasPrice, gasLimit: GasInt, to: EthAddress, + value: UInt256, payload: Blob, V: byte, R, S: UInt256, isContractCreation = false): Transaction = + result.accountNonce = nonce + result.gasPrice = gasPrice + result.gasLimit = gasLimit + result.to = to + result.value = value + result.V = V + result.R = R + result.S = S + result.isContractCreation = isContractCreation + proc intrinsicGas*(t: Transaction): GasInt = # Compute the baseline gas cost for this transaction. This is the amount # of gas needed to send this transaction (but that is not actually used From dc5e62951a219b1191dbd94cd6bf33981af7828d Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 23 Nov 2018 17:20:20 +0000 Subject: [PATCH 05/14] Make eth_call use optional params --- nimbus/rpc/p2p.nim | 31 +++++++++++++++++++++++-------- nimbus/rpc/rpc_types.nim | 12 ++++++------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 488897d8d..2c5b06b49 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -243,16 +243,20 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = # TODO: Relies on pending pool implementation discard - proc setupComputation(call: EthCall, vmState: BaseVMState, blockNumber: BlockNumber, sender: EthAddress): BaseComputation = + proc setupComputation(vmState: BaseVMState, blockNumber: BlockNumber, + value: UInt256, data: seq[byte], + sender, destination: EthAddress, + gasLimit, gasPrice: GasInt): BaseComputation = let - destination = call.to.toAddress + # Handle optional defaults. + # Note in eth_call (but not eth_estimateGas), the `to` field is required. message = newMessage( - gas = call.gas, - gasPrice = call.gasPrice, + gas = gasLimit, + gasPrice = gasPrice, to = destination, sender = sender, - value = call.value, - data = call.data.string.fromHex, + value = value, + data = data, code = vmState.readOnlyStateDB.getCode(destination).toSeq, options = newMessageOptions(origin = sender, createAddress = destination)) @@ -268,8 +272,19 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = let header = headerFromTag(chain, quantityTag) var vmState = newBaseVMState(header, chain) - sender = call.source.toAddress - comp = call.setupComputation(vmState, header.blockNumber, sender) + sender = if call.source.isSome: call.source.get.toAddress else: ZERO_ADDRESS + # destination is a required parameter for call. In geth if it's zero they use the first wallet address, + # if no wallets, remains as ZERO_ADDRESS + # TODO: Update to use wallets + destination = if call.to.isSome: call.to.get.toAddress else: ZERO_ADDRESS + pendingBlock = vmState.chaindb.getCanonicalHead() # TODO: Needs to fetch from a pending block, not head + gasLimit = if call.gas.isSome: call.gas.get else: pendingBlock.gasLimit + gGasPrice = 1.GasInt + gasPrice = if call.gasPrice.isSome: call.gasPrice.get else: gGasPrice + data = if call.data.isSome: call.data.get.string.fromHex else: @[] + value = if call.value.isSome: call.value.get else: 0.u256 + + comp = setupComputation(vmState, header.blockNumber, value, data, sender, destination, gasLimit, gasPrice) discard comp.execComputation result = ("0x" & nimcrypto.toHex(comp.output)).HexDataStr diff --git a/nimbus/rpc/rpc_types.nim b/nimbus/rpc/rpc_types.nim index 3bf6002b9..249fec466 100644 --- a/nimbus/rpc/rpc_types.nim +++ b/nimbus/rpc/rpc_types.nim @@ -30,12 +30,12 @@ type 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. - gasPrice*: GasInt # (optional) Integer of the gasPrice used for each paid gas. - value*: UInt256 # (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. + source*: Option[EthAddressStr] # (optional) The address the transaction is send from. + to*: Option[EthAddressStr] # (optional in eth_estimateGas, not in eth_call) The address the transaction is directed to. + gas*: Option[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*: Option[GasInt] # (optional) Integer of the gasPrice used for each paid gas. + value*: Option[UInt256] # (optional) Integer of the value sent with this transaction. + data*: Option[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 From a20a18f8e8b83a4391a408d5773a40c38bb76139 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 23 Nov 2018 17:21:03 +0000 Subject: [PATCH 06/14] Add converter from Json to UInt256 --- nimbus/rpc/hexstrings.nim | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index c02d17627..e03f3f000 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -210,3 +210,10 @@ proc fromJson*(n: JsonNode, argName: string, result: var WhisperIdentityStr) = raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as a Whisper identity \"" & hexStr & "\"") result = hexStr.WhisperIdentityStr +proc fromJson*(n: JsonNode, argName: string, result: var UInt256) = + n.kind.expect(JString, argName) + let hexStr = n.getStr() + if not hexStr.isValidEthHash: # Same format as hash + raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as a UInt256 \"" & hexStr & "\"") + result = readUintBE[256](hexToPaddedByteArray[32](hexStr)) + From a279915175a082a2baa086a14d69e91531cdbe92 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 23 Nov 2018 18:24:42 +0000 Subject: [PATCH 07/14] Refactored variable setup to ensure sensible defaults --- nimbus/rpc/p2p.nim | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 2c5b06b49..8f5fc8f1f 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -249,7 +249,6 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = gasLimit, gasPrice: GasInt): BaseComputation = let # Handle optional defaults. - # Note in eth_call (but not eth_estimateGas), the `to` field is required. message = newMessage( gas = gasLimit, gasPrice = gasPrice, @@ -272,19 +271,30 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = let header = headerFromTag(chain, quantityTag) var vmState = newBaseVMState(header, chain) + gasLimit = + if call.gas.isSome: call.gas.get + else: 0.GasInt + gasPrice = + if call.gasPrice.isSome: call.gasPrice.get + else: 0.GasInt + + # Set defaults for gas limit and price if required + if gaslimit == 0.GasInt: + gasLimit = (int64.high div 2).GasInt # Note this is `uint64.high div 2` in Geth + if gasPrice == 0.GasInt: + gasPrice = 1e9.GasInt # Geth's default gas price + + var sender = if call.source.isSome: call.source.get.toAddress else: ZERO_ADDRESS - # destination is a required parameter for call. In geth if it's zero they use the first wallet address, + # Note that destination is a required parameter for call. + # In geth if it's zero they use the first wallet address, # if no wallets, remains as ZERO_ADDRESS - # TODO: Update to use wallets + # TODO: Wallets destination = if call.to.isSome: call.to.get.toAddress else: ZERO_ADDRESS - pendingBlock = vmState.chaindb.getCanonicalHead() # TODO: Needs to fetch from a pending block, not head - gasLimit = if call.gas.isSome: call.gas.get else: pendingBlock.gasLimit - gGasPrice = 1.GasInt - gasPrice = if call.gasPrice.isSome: call.gasPrice.get else: gGasPrice data = if call.data.isSome: call.data.get.string.fromHex else: @[] value = if call.value.isSome: call.value.get else: 0.u256 - comp = setupComputation(vmState, header.blockNumber, value, data, sender, destination, gasLimit, gasPrice) + discard comp.execComputation result = ("0x" & nimcrypto.toHex(comp.output)).HexDataStr From d269bd07c523974a3ca7d15e3e38aa086aa154ff Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 26 Nov 2018 11:28:38 +0000 Subject: [PATCH 08/14] Remove default gas price (is zero instead) and use gasLimit from header --- nimbus/rpc/p2p.nim | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 8f5fc8f1f..34aaf709c 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -280,9 +280,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = # Set defaults for gas limit and price if required if gaslimit == 0.GasInt: - gasLimit = (int64.high div 2).GasInt # Note this is `uint64.high div 2` in Geth - if gasPrice == 0.GasInt: - gasPrice = 1e9.GasInt # Geth's default gas price + gasLimit = header.gasLimit var sender = if call.source.isSome: call.source.get.toAddress else: ZERO_ADDRESS From 0899634b6d61226c11046fd4d55b9975f9468bb3 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 26 Nov 2018 18:13:24 +0000 Subject: [PATCH 09/14] Update signatures for eth_call --- tests/rpcclient/ethcallsigs.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rpcclient/ethcallsigs.nim b/tests/rpcclient/ethcallsigs.nim index 9527c36d1..7c7ab48ce 100644 --- a/tests/rpcclient/ethcallsigs.nim +++ b/tests/rpcclient/ethcallsigs.nim @@ -25,12 +25,12 @@ proc eth_getUncleCountByBlockHash(data: array[32, byte]) proc eth_getUncleCountByBlockNumber(quantityTag: string) proc eth_getCode(data: EthAddressStr, quantityTag: string): HexDataStr proc eth_sign(data:EthAddressStr, message: HexDataStr): HexDataStr +#proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256 +proc eth_call(call: EthCall, quantityTag: string): UInt256 # TODO: Use eth_common types #[proc eth_sendTransaction(obj: EthSend): UInt256 -proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256 -proc eth_call(call: EthCall, quantityTag: string): UInt256 proc eth_estimateGas(call: EthCall, quantityTag: string): UInt256 proc eth_getBlockByHash(data: array[32, byte], fullTransactions: bool): BlockObject proc eth_getBlockByNumber(quantityTag: string, fullTransactions: bool): BlockObject From 3557567a1b015e9a4aa23d7c5cd28694b2cadfc4 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 26 Nov 2018 18:16:58 +0000 Subject: [PATCH 10/14] Moch test for eth_call but needs working header fetching --- tests/test_rpc.nim | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index d73da724a..8ef1c9b60 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -1,7 +1,7 @@ import - unittest, json, strformat, nimcrypto, rlp, + unittest, json, strformat, nimcrypto, rlp, options, json_rpc/[rpcserver, rpcclient], - ../nimbus/rpc/[common, p2p, hexstrings], + ../nimbus/rpc/[common, p2p, hexstrings, rpc_types], ../nimbus/constants, ../nimbus/nimbus/[vm_state, config], ../nimbus/db/[state_db, db_chain], eth_common, byteutils, @@ -68,6 +68,13 @@ proc doTests = waitFor client.connect("localhost", Port(8545)) suite "Remote Procedure Calls": + # TODO: Currently returning 'block not found' when fetching header in p2p, so cannot perform tests + test "eth_call": + let + blockNum = state.blockheader.blockNumber + callParams = EthCall(value: some(100.u256)) + var r = waitFor client.eth_call(callParams, "0x" & blockNum.toHex) + echo r test "eth_getBalance": expect ValueError: # check error is raised on null address From 5681c355ef60790b311b68d4c2a4f4ee7795c711 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 26 Nov 2018 19:40:29 +0000 Subject: [PATCH 11/14] Update comment --- nimbus/rpc/p2p.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 34aaf709c..0ac5c95ed 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -278,7 +278,8 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = if call.gasPrice.isSome: call.gasPrice.get else: 0.GasInt - # Set defaults for gas limit and price if required + # Set defaults for gas limit if required + # Price remains zero by default if gaslimit == 0.GasInt: gasLimit = header.gasLimit From b57427e3eed836ed24d7619c053044199a3c7d36 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 28 Nov 2018 21:57:10 +0000 Subject: [PATCH 12/14] Add genesis block --- tests/test_rpc.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index 8ef1c9b60..21b7e471b 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -6,6 +6,7 @@ import ../nimbus/nimbus/[vm_state, config], ../nimbus/db/[state_db, db_chain], eth_common, byteutils, ../nimbus/p2p/chain, + ../nimbus/genesis, eth_trie/db, eth_p2p, eth_keys import rpcclient/test_hexstrings @@ -53,9 +54,11 @@ proc doTests = let balance = 100.u256 address: EthAddress = hexToByteArray[20]("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6") + conf = getConfiguration() + defaultGenesisBlockForNetwork(conf.net.networkId.toPublicNetwork()).commit(chain) state.mutateStateDB: db.setBalance(address, balance) - + # Create Ethereum RPCs var rpcServer = newRpcSocketServer(["localhost:8545"]) From f6cd02ff3fecad7955329fb2cb6582b63d74e18a Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 28 Nov 2018 22:04:57 +0000 Subject: [PATCH 13/14] Relax JSON to UInt256 length constraint, refactor err msgs --- nimbus/rpc/hexstrings.nim | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index e03f3f000..58a21f251 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -175,45 +175,47 @@ proc `%`*(value: ref BloomFilter): JsonNode = # Marshalling from JSON to Nim types that includes format checking +func invalidMsg(name: string): string = "When marshalling from JSON, parameter \"" & name & "\" is not valid" + proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) = n.kind.expect(JString, argName) let hexStr = n.getStr() if not hexStr.isValidHexQuantity: - raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as an Ethereum hex quantity \"" & hexStr & "\"") + raise newException(ValueError, invalidMsg(argName) & " as an Ethereum hex quantity \"" & hexStr & "\"") result = hexStr.hexQuantityStr 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 Ethereum data \"" & hexStr & "\"") + raise newException(ValueError, invalidMsg(argName) & " as Ethereum data \"" & hexStr & "\"") result = hexStr.hexDataStr proc fromJson*(n: JsonNode, argName: string, result: var EthAddressStr) = 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 & "\"") + raise newException(ValueError, invalidMsg(argName) & "\" as an Ethereum address \"" & hexStr & "\"") result = hexStr.EthAddressStr proc fromJson*(n: JsonNode, argName: string, result: var EthHashStr) = 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 & "\"") + raise newException(ValueError, invalidMsg(argName) & " as an Ethereum hash \"" & hexStr & "\"") result = hexStr.EthHashStr proc fromJson*(n: JsonNode, argName: string, result: var WhisperIdentityStr) = n.kind.expect(JString, argName) let hexStr = n.getStr() if not hexStr.isValidWhisperIdentity: - raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as a Whisper identity \"" & hexStr & "\"") + raise newException(ValueError, invalidMsg(argName) & " as a Whisper identity \"" & hexStr & "\"") result = hexStr.WhisperIdentityStr proc fromJson*(n: JsonNode, argName: string, result: var UInt256) = n.kind.expect(JString, argName) let hexStr = n.getStr() - if not hexStr.isValidEthHash: # Same format as hash - raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as a UInt256 \"" & hexStr & "\"") + if hexStr.len <= 66 and hexStr.isValidHexData: + raise newException(ValueError, invalidMsg(argName) & " as a UInt256 \"" & hexStr & "\"") result = readUintBE[256](hexToPaddedByteArray[32](hexStr)) From 184143ba65049acd388731fe436eb32b2794ac7c Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 28 Nov 2018 22:27:15 +0000 Subject: [PATCH 14/14] Update return types --- nimbus/rpc/p2p.nim | 4 ++-- tests/rpcclient/ethcallsigs.nim | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 0ac5c95ed..ec626a24a 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -113,7 +113,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## Returns integer of the current block number the client is on. result = chain.getCanonicalHead().blockNumber - rpcsrv.rpc("eth_getBalance") do(data: EthAddressStr, quantityTag: string) -> GasInt: + rpcsrv.rpc("eth_getBalance") do(data: EthAddressStr, quantityTag: string) -> UInt256: ## Returns the balance of the account of given address. ## ## data: address to check for balance. @@ -124,7 +124,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = addrBytes = data.toAddress balance = accountDb.get_balance(addrBytes) - result = balance.toInt + result = balance rpcsrv.rpc("eth_getStorageAt") do(data: EthAddressStr, quantity: int, quantityTag: string) -> UInt256: ## Returns the value from a storage position at a given address. diff --git a/tests/rpcclient/ethcallsigs.nim b/tests/rpcclient/ethcallsigs.nim index 7c7ab48ce..c508bf6e5 100644 --- a/tests/rpcclient/ethcallsigs.nim +++ b/tests/rpcclient/ethcallsigs.nim @@ -16,7 +16,7 @@ proc eth_hashrate(): int proc eth_gasPrice(): GasInt proc eth_accounts(): seq[EthAddressStr] proc eth_blockNumber(): BlockNumber -proc eth_getBalance(data: EthAddressStr, quantityTag: string): int +proc eth_getBalance(data: EthAddressStr, quantityTag: string): UInt256 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]) @@ -26,7 +26,7 @@ proc eth_getUncleCountByBlockNumber(quantityTag: string) proc eth_getCode(data: EthAddressStr, quantityTag: string): HexDataStr proc eth_sign(data:EthAddressStr, message: HexDataStr): HexDataStr #proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256 -proc eth_call(call: EthCall, quantityTag: string): UInt256 +proc eth_call(call: EthCall, quantityTag: string): string # TODO: Use eth_common types