implement eth_estimateGas

This commit is contained in:
jangko 2020-07-29 12:42:32 +07:00
parent c9802edfce
commit 9c38266ba7
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
6 changed files with 59 additions and 109 deletions

View File

@ -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 =
@ -318,11 +264,10 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
## Returns the return value of executed contract.
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)

View File

@ -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.

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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()