Merge branch 'eth_estimateGas'

This commit is contained in:
Ștefan Talpalaru 2019-01-06 21:20:41 +01:00
commit 6d234b70d7
No known key found for this signature in database
GPG Key ID: CBF7934204F1B6F9
8 changed files with 145 additions and 39 deletions

View File

@ -1,5 +1,5 @@
# Nimbus # Nimbus
# Copyright (c) 2018 Status Research & Development GmbH # Copyright (c) 2018-2019 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT)) # * MIT license ([LICENSE-MIT](LICENSE-MIT))
@ -14,7 +14,7 @@ import
../utils/header, ../transaction, ../config, ../vm_state, ../constants, ../vm_types, ../utils/header, ../transaction, ../config, ../vm_state, ../constants, ../vm_types,
../vm_state_transactions, ../utils/addresses, ../vm_state_transactions, ../utils/addresses,
../db/[db_chain, state_db, storage_types], ../db/[db_chain, state_db, storage_types],
rpc_types, rpc_utils, ../vm/[message, computation] rpc_types, rpc_utils, ../vm/[message, computation, interpreter_dispatch]
#[ #[
Note: Note:
@ -33,6 +33,51 @@ template balance(addressDb: ReadOnlyStateDb, address: EthAddress): GasInt =
# TODO: Account balance u256 but GasInt is int64? # TODO: Account balance u256 but GasInt is int64?
addressDb.getBalance(address).truncate(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,
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) = proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
func getAccountDb(header: BlockHeader): ReadOnlyStateDB = func getAccountDb(header: BlockHeader): ReadOnlyStateDB =
@ -100,7 +145,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
let let
accountDb = accountDbFromTag(quantityTag) accountDb = accountDbFromTag(quantityTag)
addrBytes = data.toAddress addrBytes = data.toAddress
balance = accountDb.get_balance(addrBytes) balance = accountDb.getBalance(addrBytes)
result = balance result = balance
@ -283,7 +328,37 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: 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.
discard var
header = chain.headerFromTag(quantityTag)
vmState = newBaseVMState(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)
func populateBlockObject(header: BlockHeader, blockBody: BlockBody): BlockObject = func populateBlockObject(header: BlockHeader, blockBody: BlockBody): BlockObject =
result.number = some(header.blockNumber) result.number = some(header.blockNumber)

View File

@ -20,11 +20,19 @@ proc initTransaction*(nonce: AccountNonce, gasPrice, gasLimit: GasInt, to: EthAd
result.S = S result.S = S
result.isContractCreation = isContractCreation result.isContractCreation = isContractCreation
func intrinsicGas*(data: openarray[byte]): GasInt =
result = 21_000 # GasTransaction
for i in data:
if i == 0:
result += 4 # GasTXDataZero
else:
result += 68 # GasTXDataNonZero
proc intrinsicGas*(t: Transaction): GasInt = proc intrinsicGas*(t: Transaction): GasInt =
# Compute the baseline gas cost for this transaction. This is the amount # Compute the baseline gas cost for this transaction. This is the amount
# of gas needed to send this transaction (but that is not actually used # of gas needed to send this transaction (but that is not actually used
# for computation) # for computation)
raise newException(ValueError, "not implemented intrinsicGas") result = t.payload.intrinsicGas
proc validate*(t: Transaction) = proc validate*(t: Transaction) =
# Hook called during instantiation to ensure that all transaction # Hook called during instantiation to ensure that all transaction

View File

@ -1,5 +1,5 @@
# Nimbus # Nimbus
# Copyright (c) 2018 Status Research & Development GmbH # Copyright (c) 2018-2019 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
@ -781,8 +781,8 @@ op selfDestruct, inline = false:
computation.vmState.mutateStateDB: computation.vmState.mutateStateDB:
let let
local_balance = db.get_balance(computation.msg.storage_address) local_balance = db.getBalance(computation.msg.storage_address)
beneficiary_balance = db.get_balance(beneficiary) beneficiary_balance = db.getBalance(beneficiary)
# Transfer to beneficiary # Transfer to beneficiary
db.setBalance(beneficiary, local_balance + beneficiary_balance) db.setBalance(beneficiary, local_balance + beneficiary_balance)

View File

@ -12,14 +12,6 @@ import
./transaction, ./vm_types, ./vm_state, ./block_types, ./db/[db_chain, state_db], ./utils/header, ./transaction, ./vm_types, ./vm_state, ./block_types, ./db/[db_chain, state_db], ./utils/header,
./vm/interpreter, ./vm/interpreter/gas_costs, ./utils/addresses ./vm/interpreter, ./vm/interpreter/gas_costs, ./utils/addresses
func intrinsicGas*(data: openarray[byte]): GasInt =
result = 21_000
for i in data:
if i == 0:
result += 4
else:
result += 68
proc validateTransaction*(vmState: BaseVMState, transaction: Transaction, sender: EthAddress): bool = proc validateTransaction*(vmState: BaseVMState, transaction: Transaction, sender: EthAddress): bool =
# XXX: https://github.com/status-im/nimbus/issues/35#issuecomment-391726518 # XXX: https://github.com/status-im/nimbus/issues/35#issuecomment-391726518
# XXX: lots of avoidable u256 construction # XXX: lots of avoidable u256 construction

View File

@ -1,5 +1,5 @@
# Nimbus # Nimbus
# Copyright (c) 2018 Status Research & Development GmbH # Copyright (c) 2018-2019 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
@ -17,4 +17,5 @@ import ./test_code_stream,
./test_precompiles, ./test_precompiles,
./test_generalstate_json, ./test_generalstate_json,
./test_tracer_json, ./test_tracer_json,
./test_persistblock_json ./test_persistblock_json,
./test_rpc

View File

@ -1,7 +1,17 @@
# Nimbus
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
## This module contains signatures for the Ethereum client RPCs. ## This module contains signatures for the Ethereum client RPCs.
## The signatures are not imported directly, but read and processed with parseStmt, ## The signatures are not imported directly, but read and processed with parseStmt,
## then a procedure body is generated to marshal native Nim parameters to json and visa versa. ## then a procedure body is generated to marshal native Nim parameters to json and visa versa.
import json, stint, eth_common, ../../nimbus/rpc/hexstrings import
json,
stint, eth_common,
../../nimbus/rpc/hexstrings, ../../nimbus/rpc/rpc_types
proc web3_clientVersion(): string proc web3_clientVersion(): string
proc web3_sha3(data: string): string proc web3_sha3(data: string): string
@ -27,11 +37,11 @@ proc eth_getCode(data: EthAddressStr, quantityTag: string): HexDataStr
proc eth_sign(data:EthAddressStr, message: HexDataStr): HexDataStr proc eth_sign(data:EthAddressStr, message: HexDataStr): HexDataStr
#proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256 #proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256
proc eth_call(call: EthCall, quantityTag: string): string proc eth_call(call: EthCall, quantityTag: string): string
proc eth_estimateGas(call: EthCall, quantityTag: string): GasInt
# TODO: Use eth_common types # TODO: Use eth_common types
#[proc eth_sendTransaction(obj: EthSend): UInt256 #[proc eth_sendTransaction(obj: EthSend): UInt256
proc eth_estimateGas(call: EthCall, quantityTag: string): UInt256
proc eth_getBlockByHash(data: array[32, byte], fullTransactions: bool): BlockObject proc eth_getBlockByHash(data: array[32, byte], fullTransactions: bool): BlockObject
proc eth_getBlockByNumber(quantityTag: string, fullTransactions: bool): BlockObject proc eth_getBlockByNumber(quantityTag: string, fullTransactions: bool): BlockObject
proc eth_getTransactionByHash(data: Uint256): TransactionObject proc eth_getTransactionByHash(data: Uint256): TransactionObject

View File

@ -1,3 +1,10 @@
# Nimbus
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# Separated from main tests for brevity # Separated from main tests for brevity
import unittest, ../../nimbus/rpc/hexstrings, json import unittest, ../../nimbus/rpc/hexstrings, json

View File

@ -1,15 +1,21 @@
# Nimbus
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
unittest, json, strformat, nimcrypto, rlp, options, unittest, json, strformat, options,
nimcrypto, rlp, eth_trie/db, eth_p2p, eth_keys,
json_rpc/[rpcserver, rpcclient], json_rpc/[rpcserver, rpcclient],
../nimbus/rpc/[common, p2p, hexstrings, rpc_types], ../nimbus/rpc/[common, p2p, hexstrings, rpc_types],
../nimbus/constants, ../nimbus/constants,
../nimbus/nimbus/[vm_state, config], ../nimbus/[vm_state, config],
../nimbus/db/[state_db, db_chain], eth_common, byteutils, ../nimbus/db/[state_db, db_chain, storage_types], eth_common, byteutils,
../nimbus/p2p/chain, ../nimbus/p2p/chain,
../nimbus/genesis, ../nimbus/genesis,
eth_trie/db, ./rpcclient/test_hexstrings
eth_p2p, eth_keys
import rpcclient/test_hexstrings
# Perform checks for hex string validation # Perform checks for hex string validation
doHexStrTests() doHexStrTests()
@ -58,34 +64,41 @@ proc doTests =
defaultGenesisBlockForNetwork(conf.net.networkId.toPublicNetwork()).commit(chain) defaultGenesisBlockForNetwork(conf.net.networkId.toPublicNetwork()).commit(chain)
state.mutateStateDB: state.mutateStateDB:
db.setBalance(address, balance) db.setBalance(address, balance)
doAssert(canonicalHeadHashKey().toOpenArray in state.chainDb.db)
# Create Ethereum RPCs # Create Ethereum RPCs
let RPC_PORT = 8545
var var
rpcServer = newRpcSocketServer(["localhost:8545"]) rpcServer = newRpcSocketServer(["localhost:" & $RPC_PORT])
client = newRpcSocketClient() client = newRpcSocketClient()
setupCommonRpc(rpcServer) setupCommonRpc(rpcServer)
setupEthRpc(ethNode, chain, rpcServer) setupEthRpc(ethNode, chain, rpcServer)
# Begin tests # Begin tests
rpcServer.start() rpcServer.start()
waitFor client.connect("localhost", Port(8545)) waitFor client.connect("localhost", Port(RPC_PORT))
# TODO: add more tests here
suite "Remote Procedure Calls": suite "Remote Procedure Calls":
# TODO: Currently returning 'block not found' when fetching header in p2p, so cannot perform tests
test "eth_call": test "eth_call":
let let
blockNum = state.blockheader.blockNumber blockNum = state.blockheader.blockNumber
callParams = EthCall(value: some(100.u256)) callParams = EthCall(value: some(100.u256))
var r = waitFor client.eth_call(callParams, "0x" & blockNum.toHex) r1 = waitFor client.eth_call(callParams, "0x" & blockNum.toHex)
echo r check r1 == "0x"
test "eth_getBalance": test "eth_getBalance":
expect ValueError: let r2 = waitFor client.eth_getBalance(ZERO_ADDRESS.toEthAddressStr, "0x0")
# check error is raised on null address check r2 == 0
var r = waitFor client.eth_getBalance(ZERO_ADDRESS.toEthAddressStr, "0x0")
let blockNum = state.blockheader.blockNumber let blockNum = state.blockheader.blockNumber
var r = waitFor client.eth_getBalance(address.toEthAddressStr, "0x" & blockNum.toHex) let r3 = waitFor client.eth_getBalance(address.toEthAddressStr, "0x" & blockNum.toHex)
echo r check r3 == 0
test "eth_estimateGas":
let
call = EthCall()
blockNum = state.blockheader.blockNumber
r4 = waitFor client.eth_estimateGas(call, "0x" & blockNum.toHex)
check r4 == 21_000
rpcServer.stop() rpcServer.stop()
rpcServer.close() rpcServer.close()