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
|
# Work around for https://github.com/nim-lang/Nim/issues/8645
|
||||||
#[proc `%`*(value: Time): JsonNode =
|
# proc `%`*(value: Time): JsonNode =
|
||||||
result = %value.toUnix
|
# 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 setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
|
|
||||||
proc getAccountDb(header: BlockHeader): ReadOnlyStateDB =
|
proc getAccountDb(header: BlockHeader): ReadOnlyStateDB =
|
||||||
|
@ -318,11 +264,10 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
## Returns the return value of executed contract.
|
## Returns the return value of executed contract.
|
||||||
let
|
let
|
||||||
header = headerFromTag(chain, quantityTag)
|
header = headerFromTag(chain, quantityTag)
|
||||||
callData = callData(call, true)
|
callData = callData(call, true, chain)
|
||||||
result = doCall(callData, header, chain)
|
result = doCall(callData, header, chain)
|
||||||
|
|
||||||
#[
|
server.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> HexQuantityStr:
|
||||||
server.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.
|
## 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 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.
|
## 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.
|
## call: the transaction call object.
|
||||||
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
||||||
## Returns the amount of gas used.
|
## Returns the amount of gas used.
|
||||||
var
|
|
||||||
header = chain.headerFromTag(quantityTag)
|
|
||||||
# TODO: header.stateRoot to prevStateRoot?
|
|
||||||
vmState = newBaseVMState(header.stateRoot, header, chain)
|
|
||||||
let
|
let
|
||||||
gasLimit = if
|
header = chain.headerFromTag(quantityTag)
|
||||||
call.gas.isSome and call.gas.get > 0.GasInt: call.gas.get
|
callData = callData(call, false, chain)
|
||||||
else: header.gasLimit
|
result = estimateGas(callData, header, chain, call.gas.isSome)
|
||||||
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)
|
|
||||||
|
|
||||||
|
#[
|
||||||
func populateBlockObject(header: BlockHeader, blockBody: BlockBody): BlockObject =
|
func populateBlockObject(header: BlockHeader, blockBody: BlockBody): BlockObject =
|
||||||
result.number = some(header.blockNumber)
|
result.number = some(header.blockNumber)
|
||||||
result.hash = some(header.hash)
|
result.hash = some(header.hash)
|
||||||
|
|
|
@ -34,7 +34,7 @@ type
|
||||||
# Parameter from user
|
# Parameter from user
|
||||||
source*: Option[EthAddressStr] # (optional) The address the transaction is send from.
|
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.
|
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.
|
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.
|
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.
|
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
|
# This file may not be copied, modified, or distributed except according to
|
||||||
# those terms.
|
# those terms.
|
||||||
|
|
||||||
import hexstrings, eth/[common, rlp, keys], stew/byteutils, nimcrypto,
|
import hexstrings, eth/[common, rlp, keys, trie/db], stew/byteutils, nimcrypto,
|
||||||
../db/[db_chain], strutils, algorithm, options,
|
../db/[db_chain, accounts_cache], strutils, algorithm, options,
|
||||||
../constants, stint, hexstrings, rpc_types, ../config,
|
../constants, stint, hexstrings, rpc_types, ../config,
|
||||||
../vm_state_transactions, ../vm_state, ../vm_types, ../vm/interpreter/vm_forks,
|
../vm_state_transactions, ../vm_state, ../vm_types, ../vm/interpreter/vm_forks,
|
||||||
../vm/computation
|
../vm/computation, ../p2p/executor
|
||||||
|
|
||||||
type
|
type
|
||||||
UnsignedTx* = object
|
UnsignedTx* = object
|
||||||
|
@ -30,6 +30,7 @@ type
|
||||||
gasPrice: GasInt
|
gasPrice: GasInt
|
||||||
value: UInt256
|
value: UInt256
|
||||||
data: seq[byte]
|
data: seq[byte]
|
||||||
|
contractCreation: bool
|
||||||
|
|
||||||
proc read(rlp: var Rlp, t: var UnsignedTx, _: type EthAddress): EthAddress {.inline.} =
|
proc read(rlp: var Rlp, t: var UnsignedTx, _: type EthAddress): EthAddress {.inline.} =
|
||||||
if rlp.blobLen != 0:
|
if rlp.blobLen != 0:
|
||||||
|
@ -160,7 +161,7 @@ proc signTransaction*(tx: UnsignedTx, chain: BaseChainDB, privateKey: PrivateKey
|
||||||
S: Uint256.fromBytesBE(sig[32..63])
|
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:
|
if call.source.isSome:
|
||||||
result.source = toAddress(call.source.get)
|
result.source = toAddress(call.source.get)
|
||||||
|
|
||||||
|
@ -169,12 +170,17 @@ proc callData*(call: EthCall, callMode: bool = true): CallData =
|
||||||
else:
|
else:
|
||||||
if callMode:
|
if callMode:
|
||||||
raise newException(ValueError, "call.to required for eth_call operation")
|
raise newException(ValueError, "call.to required for eth_call operation")
|
||||||
|
else:
|
||||||
|
result.contractCreation = true
|
||||||
|
|
||||||
if call.gas.isSome:
|
if call.gas.isSome:
|
||||||
result.gas = hexToInt(call.gas.get.string, GasInt)
|
result.gas = hexToInt(call.gas.get.string, GasInt)
|
||||||
|
|
||||||
if call.gasPrice.isSome:
|
if call.gasPrice.isSome:
|
||||||
result.gasPrice = hexToInt(call.gasPrice.get.string, GasInt)
|
result.gasPrice = hexToInt(call.gasPrice.get.string, GasInt)
|
||||||
|
else:
|
||||||
|
if not callMode:
|
||||||
|
result.gasPrice = calculateMedianGasPrice(chain)
|
||||||
|
|
||||||
if call.value.isSome:
|
if call.value.isSome:
|
||||||
result.value = UInt256.fromHex(call.value.get.string)
|
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)
|
result = newComputation(vmState, msg)
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
proc doCall*(call: CallData, header: BlockHeader, chain: BaseChainDB): HexDataStr =
|
proc doCall*(call: CallData, header: BlockHeader, chain: BaseChainDB): HexDataStr =
|
||||||
var
|
var
|
||||||
# we use current header stateRoot, unlike block validation
|
# we use current header stateRoot, unlike block validation
|
||||||
# which use previous block stateRoot
|
# which use previous block stateRoot
|
||||||
vmState = newBaseVMState(header.stateRoot, header, chain, {EnableTracing})
|
vmState = newBaseVMState(header.stateRoot, header, chain)
|
||||||
fork = toFork(chain.config, header.blockNumber)
|
fork = toFork(chain.config, header.blockNumber)
|
||||||
comp = setupComputation(vmState, call, fork)
|
comp = setupComputation(vmState, call, fork)
|
||||||
|
|
||||||
|
@ -216,3 +220,26 @@ proc doCall*(call: CallData, header: BlockHeader, chain: BaseChainDB): HexDataSt
|
||||||
result = hexDataStr(comp.output)
|
result = hexDataStr(comp.output)
|
||||||
# TODO: handle revert and error
|
# TODO: handle revert and error
|
||||||
# TODO: handle contract ABI
|
# 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 = newBaseChainDB(memoryDB, false)
|
||||||
|
|
||||||
chainDB.setHead(parent, true)
|
chainDB.setHead(parent, true)
|
||||||
chainDB.persistTransactions(blockNumber, body.transactions)
|
discard chainDB.persistTransactions(blockNumber, body.transactions)
|
||||||
discard chainDB.persistUncles(body.uncles)
|
discard chainDB.persistUncles(body.uncles)
|
||||||
|
|
||||||
memoryDB.put(genericHashKey(headerHash).toOpenArray, rlp.encode(header))
|
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_sendTransaction(data: TxSend): EthHashStr
|
||||||
proc eth_sendRawTransaction(data: HexDataStr): EthHashStr
|
proc eth_sendRawTransaction(data: HexDataStr): EthHashStr
|
||||||
proc eth_call(call: EthCall, quantityTag: string): HexDataStr
|
proc eth_call(call: EthCall, quantityTag: string): HexDataStr
|
||||||
|
|
||||||
proc eth_estimateGas(call: EthCall, quantityTag: string): HexQuantityStr
|
proc eth_estimateGas(call: EthCall, quantityTag: string): HexQuantityStr
|
||||||
|
|
||||||
proc eth_getBlockByHash(data: Hash256, fullTransactions: bool): BlockObject
|
proc eth_getBlockByHash(data: Hash256, fullTransactions: bool): BlockObject
|
||||||
proc eth_getBlockByNumber(quantityTag: string, fullTransactions: bool): BlockObject
|
proc eth_getBlockByNumber(quantityTag: string, fullTransactions: bool): BlockObject
|
||||||
proc eth_getTransactionByHash(data: Hash256): TransactionObject
|
proc eth_getTransactionByHash(data: Hash256): TransactionObject
|
||||||
|
|
|
@ -48,7 +48,7 @@ proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, conf: NimbusConfigura
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
ac.setCode(ks2, code)
|
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)
|
var vmState = newBaseVMState(ac.rootHash, BlockHeader(parentHash: parentHash), chain)
|
||||||
|
|
||||||
let
|
let
|
||||||
|
@ -94,7 +94,7 @@ proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, conf: NimbusConfigura
|
||||||
bloom : createBloom(vmState.receipts),
|
bloom : createBloom(vmState.receipts),
|
||||||
difficulty : difficulty,
|
difficulty : difficulty,
|
||||||
blockNumber : blockNumber,
|
blockNumber : blockNumber,
|
||||||
gasLimit : vmState.cumulativeGasUsed + 1000,
|
gasLimit : vmState.cumulativeGasUsed + 1_000_000,
|
||||||
gasUsed : vmState.cumulativeGasUsed,
|
gasUsed : vmState.cumulativeGasUsed,
|
||||||
timestamp : timeStamp
|
timestamp : timeStamp
|
||||||
#extraData: Blob
|
#extraData: Blob
|
||||||
|
@ -309,12 +309,17 @@ proc doTests {.async.} =
|
||||||
let res = await client.eth_call(ec, "latest")
|
let res = await client.eth_call(ec, "latest")
|
||||||
check hexToByteArray[4](res.string) == hexToByteArray[4]("deadbeef")
|
check hexToByteArray[4](res.string) == hexToByteArray[4]("deadbeef")
|
||||||
|
|
||||||
#test "eth_estimateGas":
|
test "eth_estimateGas":
|
||||||
# let
|
var ec = EthCall(
|
||||||
# call = EthCall()
|
source: ethAddressStr(signer).some,
|
||||||
# blockNum = state.blockheader.blockNumber
|
to: ethAddressStr(ks3).some,
|
||||||
# r4 = await client.eth_estimateGas(call, "0x" & blockNum.toHex)
|
gas: encodeQuantity(42000'u).some,
|
||||||
# check r4 == 21_000
|
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.stop()
|
||||||
rpcServer.close()
|
rpcServer.close()
|
||||||
|
|
Loading…
Reference in New Issue