From ac9fb3746517936ef8da809bd270425e7e84dda6 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 27 Nov 2018 18:21:28 +0000 Subject: [PATCH] Initial commit for eth_estimateGas --- nimbus/rpc/p2p.nim | 73 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index afe6500fa..e37524a49 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -13,7 +13,7 @@ import eth_common, eth_p2p, eth_keys, eth_trie/db, rlp, ../utils/header, ../transaction, ../config, ../vm_state, ../constants, ../vm_types, ../vm_state_transactions, ../utils/addresses, - ../db/[db_chain, state_db, storage_types], + ../vm/[interpreter_dispatch, computation], rpc_types, rpc_utils, ../vm/[message, computation] #[ @@ -33,6 +33,52 @@ 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): BaseComputation = + # Note that vmState may be altered + setupComputation( + vmState.blockHeader, + vmState, + transaction, + sender) + + proc dummyTransaction(gasLimit, gasPrice: GasInt, destination: EthAddress, value: UInt256): Transaction = + Transaction( + accountNonce: 0.AccountNonce, + gasPrice: gasPrice, + gasLimit: gasLimit, + to: destination, + value: value + ) + var + hiGas = vmState.gasLimit + loGas = transaction.intrinsicGas + 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 + 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, rpcsrv: RpcServer) = func getAccountDb(header: BlockHeader): ReadOnlyStateDB = @@ -274,7 +320,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = 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. ## The transaction will not be added to the blockchain. Note that the estimate may be significantly more than @@ -283,7 +329,28 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: 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. - discard + # TODO: Use optional fields with EthCall + var + header = chain.headerFromTag(quantityTag) + vmState = newBaseVMState(header, chain) + let + gasLimit = if call.gas > 0.GasInt: call.gas else: header.gasLimit + gasPrice = if call.gasPrice > 0: call.gasPrice else: 0.GasInt + curState = chain.getStateDb(header.stateRoot, true) + sender = call.source.string.strToAddress + destination = call.to.string.strToAddress + nonce = curState.getNonce(sender) + value = call.value + # TODO: Use initTransaction when merged + transaction = Transaction( + accountNonce: nonce, + gasPrice: gasPrice, + gasLimit: gasLimit, + to: destination, + value: value.u256, + payload: @[] + ) + result = vmState.binarySearchGas(transaction, sender, gasPrice) func populateBlockObject(header: BlockHeader, blockBody: BlockBody): BlockObject = result.number = some(header.blockNumber)