From 9c38266ba794d14b12696947148bf128be6cec7f Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 29 Jul 2020 12:42:32 +0700 Subject: [PATCH] implement eth_estimateGas --- nimbus/rpc/p2p.nim | 100 +++----------------------------- nimbus/rpc/rpc_types.nim | 2 +- nimbus/rpc/rpc_utils.nim | 41 ++++++++++--- premix/prestate.nim | 2 +- tests/rpcclient/ethcallsigs.nim | 2 +- tests/test_rpc.nim | 21 ++++--- 6 files changed, 59 insertions(+), 109 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index aefd577c3..fdd389e25 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -27,63 +27,9 @@ import ]# # Work around for https://github.com/nim-lang/Nim/issues/8645 -#[proc `%`*(value: Time): JsonNode = - result = %value.toUnix +# proc `%`*(value: Time): JsonNode = +# result = %value.toUnix -template balance(addressDb: ReadOnlyStateDb, address: EthAddress): GasInt = - # TODO: Account balance u256 but GasInt is int64? - addressDb.getBalance(address).truncate(int64) - -proc binarySearchGas(vmState: var BaseVMState, transaction: Transaction, sender: EthAddress, gasPrice: GasInt, tolerance = 1): GasInt = - proc dummyComputation(vmState: var BaseVMState, transaction: Transaction, sender: EthAddress): Computation = - # Note that vmState may be altered - var chainDB = vmState.chainDB - let fork = chainDB.config.toFork(vmState.blockNumber) - setupComputation( - vmState, - transaction, - sender, - fork) - - proc dummyTransaction(gasLimit, gasPrice: GasInt, destination: EthAddress, value: UInt256): Transaction = - Transaction( - accountNonce: 0.AccountNonce, - gasPrice: gasPrice, - gasLimit: gasLimit, - to: destination, - value: value - ) - var - chainDB = vmState.chainDB - fork = chainDB.config.toFork(vmState.blockNumber) - hiGas = vmState.gasLimit - loGas = transaction.intrinsicGas(fork) - gasPrice = transaction.gasPrice # TODO: Or zero? - - proc tryTransaction(vmState: var BaseVMState, gasLimit: GasInt): bool = - var - spoofTransaction = dummyTransaction(gasLimit, gasPrice, transaction.to, transaction.value) - computation = vmState.dummyComputation(spoofTransaction, sender) - computation.executeOpcodes - if not computation.isError: - return true - - if vmState.tryTransaction(loGas): - return loGas - if not vmState.tryTransaction(hiGas): - return 0.GasInt # TODO: Reraise error from computation - - var - minVal = vmState.gasLimit - maxVal = transaction.intrinsicGas(fork) - while loGas - hiGas > tolerance: - let midPoint = (loGas + hiGas) div 2 - if vmState.tryTransaction(midPoint): - minVal = midPoint - else: - maxVal = midPoint - result = minVal -]# proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = proc getAccountDb(header: BlockHeader): ReadOnlyStateDB = @@ -316,13 +262,12 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = ## call: the transaction call object. ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the return value of executed contract. - let + let header = headerFromTag(chain, quantityTag) - callData = callData(call, true) + callData = callData(call, true, chain) result = doCall(callData, header, chain) -#[ - server.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> GasInt: + server.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> HexQuantityStr: ## 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. @@ -330,39 +275,12 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = ## 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. - var - header = chain.headerFromTag(quantityTag) - # TODO: header.stateRoot to prevStateRoot? - vmState = newBaseVMState(header.stateRoot, header, chain) let - gasLimit = if - call.gas.isSome and call.gas.get > 0.GasInt: call.gas.get - else: header.gasLimit - gasPrice = if - call.gasPrice.isSome and call.gasPrice.get > 0: call.gasPrice.get - else: 0.GasInt - sender = if - call.source.isSome: call.source.get.toAddress - else: ZERO_ADDRESS - destination = if - call.to.isSome: call.to.get.toAddress - else: ZERO_ADDRESS - curState = vmState.readOnlyStateDb() - nonce = curState.getNonce(sender) - value = if - call.value.isSome: call.value.get - else: 0.u256 - - transaction = Transaction( - accountNonce: nonce, - gasPrice: gasPrice, - gasLimit: gasLimit, - to: destination, - value: value, - payload: @[] - ) - result = vmState.binarySearchGas(transaction, sender, gasPrice) + header = chain.headerFromTag(quantityTag) + callData = callData(call, false, chain) + result = estimateGas(callData, header, chain, call.gas.isSome) +#[ func populateBlockObject(header: BlockHeader, blockBody: BlockBody): BlockObject = result.number = some(header.blockNumber) result.hash = some(header.hash) diff --git a/nimbus/rpc/rpc_types.nim b/nimbus/rpc/rpc_types.nim index 366ba012c..231c423fa 100644 --- a/nimbus/rpc/rpc_types.nim +++ b/nimbus/rpc/rpc_types.nim @@ -34,7 +34,7 @@ type # Parameter from user 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[HexQuantityStr] # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. + gas*: Option[HexQuantityStr]# (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[HexQuantityStr]# (optional) Integer of the gasPrice used for each paid gas. value*: Option[HexQuantityStr] # (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. diff --git a/nimbus/rpc/rpc_utils.nim b/nimbus/rpc/rpc_utils.nim index 078641408..bf1918f67 100644 --- a/nimbus/rpc/rpc_utils.nim +++ b/nimbus/rpc/rpc_utils.nim @@ -7,11 +7,11 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import hexstrings, eth/[common, rlp, keys], stew/byteutils, nimcrypto, - ../db/[db_chain], strutils, algorithm, options, +import hexstrings, eth/[common, rlp, keys, trie/db], stew/byteutils, nimcrypto, + ../db/[db_chain, accounts_cache], strutils, algorithm, options, ../constants, stint, hexstrings, rpc_types, ../config, ../vm_state_transactions, ../vm_state, ../vm_types, ../vm/interpreter/vm_forks, - ../vm/computation + ../vm/computation, ../p2p/executor type UnsignedTx* = object @@ -30,6 +30,7 @@ type gasPrice: GasInt value: UInt256 data: seq[byte] + contractCreation: bool proc read(rlp: var Rlp, t: var UnsignedTx, _: type EthAddress): EthAddress {.inline.} = if rlp.blobLen != 0: @@ -160,7 +161,7 @@ proc signTransaction*(tx: UnsignedTx, chain: BaseChainDB, privateKey: PrivateKey S: Uint256.fromBytesBE(sig[32..63]) ) -proc callData*(call: EthCall, callMode: bool = true): CallData = +proc callData*(call: EthCall, callMode: bool = true, chain: BaseChainDB): CallData = if call.source.isSome: result.source = toAddress(call.source.get) @@ -169,12 +170,17 @@ proc callData*(call: EthCall, callMode: bool = true): CallData = else: if callMode: raise newException(ValueError, "call.to required for eth_call operation") + else: + result.contractCreation = true if call.gas.isSome: result.gas = hexToInt(call.gas.get.string, GasInt) if call.gasPrice.isSome: result.gasPrice = hexToInt(call.gasPrice.get.string, GasInt) + else: + if not callMode: + result.gasPrice = calculateMedianGasPrice(chain) if call.value.isSome: result.value = UInt256.fromHex(call.value.get.string) @@ -202,13 +208,11 @@ proc setupComputation(vmState: BaseVMState, call: CallData, fork: Fork) : Comput result = newComputation(vmState, msg) -import json - proc doCall*(call: CallData, header: BlockHeader, chain: BaseChainDB): HexDataStr = var # we use current header stateRoot, unlike block validation # which use previous block stateRoot - vmState = newBaseVMState(header.stateRoot, header, chain, {EnableTracing}) + vmState = newBaseVMState(header.stateRoot, header, chain) fork = toFork(chain.config, header.blockNumber) comp = setupComputation(vmState, call, fork) @@ -216,3 +220,26 @@ proc doCall*(call: CallData, header: BlockHeader, chain: BaseChainDB): HexDataSt result = hexDataStr(comp.output) # TODO: handle revert and error # TODO: handle contract ABI + +proc estimateGas*(call: CallData, header: BlockHeader, chain: BaseChainDB, haveGasLimit: bool): HexQuantityStr = + var + # we use current header stateRoot, unlike block validation + # which use previous block stateRoot + vmState = newBaseVMState(header.stateRoot, header, chain) + fork = toFork(chain.config, header.blockNumber) + tx = Transaction( + accountNonce: vmState.accountdb.getNonce(call.source), + gasPrice: call.gasPrice, + gasLimit: if haveGasLimit: call.gas else: header.gasLimit - vmState.cumulativeGasUsed, + to : call.to, + value : call.value, + payload : call.data, + isContractCreation: call.contractCreation + ) + + var dbTx = chain.db.beginTransaction() + defer: dbTx.dispose() + let gasUsed = processTransaction(tx, call.source, vmState, fork) + result = encodeQuantity(gasUsed.uint64) + dbTx.dispose() + # TODO: handle revert and error \ No newline at end of file diff --git a/premix/prestate.nim b/premix/prestate.nim index 0e3f8818a..b98da4c24 100644 --- a/premix/prestate.nim +++ b/premix/prestate.nim @@ -13,7 +13,7 @@ proc generatePrestate*(nimbus, geth: JsonNode, blockNumber: Uint256, parent, hea chainDB = newBaseChainDB(memoryDB, false) chainDB.setHead(parent, true) - chainDB.persistTransactions(blockNumber, body.transactions) + discard chainDB.persistTransactions(blockNumber, body.transactions) discard chainDB.persistUncles(body.uncles) memoryDB.put(genericHashKey(headerHash).toOpenArray, rlp.encode(header)) diff --git a/tests/rpcclient/ethcallsigs.nim b/tests/rpcclient/ethcallsigs.nim index f0f208b52..3186487ec 100644 --- a/tests/rpcclient/ethcallsigs.nim +++ b/tests/rpcclient/ethcallsigs.nim @@ -39,8 +39,8 @@ proc eth_signTransaction(data: TxSend): HexDataStr proc eth_sendTransaction(data: TxSend): EthHashStr proc eth_sendRawTransaction(data: HexDataStr): EthHashStr proc eth_call(call: EthCall, quantityTag: string): HexDataStr - proc eth_estimateGas(call: EthCall, quantityTag: string): HexQuantityStr + proc eth_getBlockByHash(data: Hash256, fullTransactions: bool): BlockObject proc eth_getBlockByNumber(quantityTag: string, fullTransactions: bool): BlockObject proc eth_getTransactionByHash(data: Hash256): TransactionObject diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index 1202a9316..932774418 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -48,7 +48,7 @@ proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, conf: NimbusConfigura RETURN ac.setCode(ks2, code) - ac.addBalance(signer, 1_000_000.u256) + ac.addBalance(signer, 9_000_000_000.u256) var vmState = newBaseVMState(ac.rootHash, BlockHeader(parentHash: parentHash), chain) let @@ -94,7 +94,7 @@ proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, conf: NimbusConfigura bloom : createBloom(vmState.receipts), difficulty : difficulty, blockNumber : blockNumber, - gasLimit : vmState.cumulativeGasUsed + 1000, + gasLimit : vmState.cumulativeGasUsed + 1_000_000, gasUsed : vmState.cumulativeGasUsed, timestamp : timeStamp #extraData: Blob @@ -309,12 +309,17 @@ proc doTests {.async.} = let res = await client.eth_call(ec, "latest") check hexToByteArray[4](res.string) == hexToByteArray[4]("deadbeef") - #test "eth_estimateGas": - # let - # call = EthCall() - # blockNum = state.blockheader.blockNumber - # r4 = await client.eth_estimateGas(call, "0x" & blockNum.toHex) - # check r4 == 21_000 + test "eth_estimateGas": + var ec = EthCall( + source: ethAddressStr(signer).some, + to: ethAddressStr(ks3).some, + gas: encodeQuantity(42000'u).some, + gasPrice: encodeQuantity(100'u).some, + value: encodeQuantity(100'u).some + ) + + let res = await client.eth_estimateGas(ec, "latest") + check hexToInt(res.string, int) == 21000 rpcServer.stop() rpcServer.close()