implement eth_estimateGas
This commit is contained in:
parent
c9802edfce
commit
9c38266ba7
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue