mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 05:14:14 +00:00
implement eth_signTransaction, eth_sendTransaction, eth_sendRawTransaction, eth_call
This commit is contained in:
parent
7819dae7ce
commit
bb89a296dd
@ -188,6 +188,9 @@ proc ethHashStr*(value: string): EthHashStr {.inline.} =
|
||||
value.validateHashStr
|
||||
result = value.EthHashStr
|
||||
|
||||
func ethHashStr*(value: Hash256): EthHashStr {.inline.} =
|
||||
result = EthHashStr("0x" & value.data.toHex)
|
||||
|
||||
# Converters for use in RPC
|
||||
|
||||
import json
|
||||
|
@ -13,8 +13,7 @@ import
|
||||
eth/[common, keys, rlp, p2p], nimcrypto,
|
||||
eth/p2p/rlpx_protocols/eth_protocol,
|
||||
../transaction, ../config, ../vm_state, ../constants, ../vm_types,
|
||||
../vm_state_transactions, ../utils,
|
||||
../db/[db_chain, state_db],
|
||||
../utils, ../db/[db_chain, state_db],
|
||||
rpc_types, rpc_utils, ../vm/[message, computation],
|
||||
../vm/interpreter/vm_forks
|
||||
|
||||
@ -260,6 +259,8 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||
result = ("0x" & sign(acc.privateKey, cast[string](msg))).HexDataStr
|
||||
|
||||
server.rpc("eth_signTransaction") do(data: TxSend) -> HexDataStr:
|
||||
## Signs a transaction that can be submitted to the network at a later time using with
|
||||
## eth_sendRawTransaction
|
||||
let
|
||||
address = data.source.toAddress
|
||||
conf = getConfiguration()
|
||||
@ -273,62 +274,41 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||
tx = unsignedTx(data, chain, accDB.getNonce(address) + 1)
|
||||
signedTx = signTransaction(tx, chain, acc.privateKey)
|
||||
rlpTx = rlp.encode(signedTx)
|
||||
|
||||
result = hexDataStr(rlpTx)
|
||||
#[
|
||||
# proc setupTransaction(send: EthSend): Transaction =
|
||||
# let
|
||||
# source = send.source.toAddress
|
||||
# destination = send.to.toAddress
|
||||
# data = nimcrypto.utils.fromHex(send.data.string)
|
||||
# contractCreation = false # TODO: Check if has code
|
||||
# v = 0.byte # TODO
|
||||
# r = 0.u256
|
||||
# s = 0.u256
|
||||
# result = initTransaction(send.nonce, send.gasPrice, send.gas, destination, send.value, data, v, r, s, contractCreation)
|
||||
|
||||
server.rpc("eth_sendTransaction") do(obj: EthSend) -> HexDataStr:
|
||||
result = hexDataStr(rlpTx)
|
||||
|
||||
server.rpc("eth_sendTransaction") do(data: TxSend) -> EthHashStr:
|
||||
## Creates new message call transaction or a contract creation, if the data field contains code.
|
||||
##
|
||||
## obj: the transaction object.
|
||||
## Returns the transaction hash, or the zero hash if the transaction is not yet available.
|
||||
## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract.
|
||||
# TODO: Relies on pending pool implementation
|
||||
discard
|
||||
let
|
||||
address = data.source.toAddress
|
||||
conf = getConfiguration()
|
||||
acc = conf.getAccount(address).tryGet()
|
||||
|
||||
server.rpc("eth_sendRawTransaction") do(data: string, quantityTag: int) -> HexDataStr:
|
||||
if not acc.unlocked:
|
||||
raise newException(ValueError, "Account locked, please unlock it first")
|
||||
|
||||
let
|
||||
accDB = accountDbFromTag("latest")
|
||||
tx = unsignedTx(data, chain, accDB.getNonce(address) + 1)
|
||||
signedTx = signTransaction(tx, chain, acc.privateKey)
|
||||
rlpTx = rlp.encode(signedTx)
|
||||
|
||||
result = keccak_256.digest(rlpTx).ethHashStr
|
||||
|
||||
server.rpc("eth_sendRawTransaction") do(data: HexDataStr) -> EthHashStr:
|
||||
## Creates new message call transaction or a contract creation for signed transactions.
|
||||
##
|
||||
## data: the signed transaction data.
|
||||
## Returns the transaction hash, or the zero hash if the transaction is not yet available.
|
||||
## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract.
|
||||
# TODO: Relies on pending pool implementation
|
||||
discard
|
||||
|
||||
proc setupComputation(vmState: BaseVMState,
|
||||
value: UInt256, data: seq[byte],
|
||||
sender, destination: EthAddress,
|
||||
gasLimit, gasPrice: GasInt,
|
||||
contractCreation: bool): Computation =
|
||||
let
|
||||
# Handle optional defaults.
|
||||
message = Message(
|
||||
kind: if contractCreation: evmcCreate else: evmcCall,
|
||||
depth: 0,
|
||||
gas: gasLimit,
|
||||
sender: sender,
|
||||
contractAddress: destination,
|
||||
codeAddress: CREATE_CONTRACT_ADDRESS,
|
||||
value: value,
|
||||
data: data
|
||||
)
|
||||
|
||||
vmState.setupTxContext(
|
||||
origin = sender,
|
||||
gasPrice = gasPrice
|
||||
)
|
||||
|
||||
result = newComputation(vmState, message)
|
||||
let rlpBytes = hexToSeqByte(data.string)
|
||||
result = keccak_256.digest(rlpBytes).ethHashStr
|
||||
|
||||
server.rpc("eth_call") do(call: EthCall, quantityTag: string) -> HexDataStr:
|
||||
## Executes a new message call immediately without creating a transaction on the block chain.
|
||||
@ -336,36 +316,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 header = headerFromTag(chain, quantityTag)
|
||||
var
|
||||
# TODO: header.stateRoot to prevStateRoot
|
||||
vmState = newBaseVMState(header.stateRoot, header, chain)
|
||||
gasLimit =
|
||||
if call.gas.isSome: call.gas.get
|
||||
else: 0.GasInt
|
||||
gasPrice =
|
||||
if call.gasPrice.isSome: call.gasPrice.get
|
||||
else: 0.GasInt
|
||||
|
||||
# Set defaults for gas limit if required
|
||||
# Price remains zero by default
|
||||
if gaslimit == 0.GasInt:
|
||||
gasLimit = header.gasLimit
|
||||
|
||||
var
|
||||
sender = if call.source.isSome: call.source.get.toAddress else: ZERO_ADDRESS
|
||||
# Note that destination is a required parameter for call.
|
||||
# In geth if it's zero they use the first wallet address,
|
||||
# if no wallets, remains as ZERO_ADDRESS
|
||||
# TODO: Wallets
|
||||
destination = if call.to.isSome: call.to.get.toAddress else: ZERO_ADDRESS
|
||||
data = if call.data.isSome: nimcrypto.utils.fromHex(call.data.get.string) else: @[]
|
||||
value = if call.value.isSome: call.value.get else: 0.u256
|
||||
comp = setupComputation(vmState, value, data, sender, destination, gasLimit, gasPrice, call.to.isNone)
|
||||
|
||||
comp.execComputation
|
||||
result = ("0x" & nimcrypto.toHex(comp.output)).HexDataStr
|
||||
let
|
||||
header = headerFromTag(chain, quantityTag)
|
||||
callData = callData(call, true)
|
||||
result = doCall(callData, header, chain)
|
||||
|
||||
#[
|
||||
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.
|
||||
## The transaction will not be added to the blockchain. Note that the estimate may be significantly more than
|
||||
|
@ -32,12 +32,12 @@ type
|
||||
|
||||
EthCall* = object
|
||||
# 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[GasInt] # (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[GasInt] # (optional) Integer of the gasPrice used for each paid gas.
|
||||
value*: Option[UInt256] # (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.
|
||||
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.
|
||||
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[HexDataStr] # (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI.
|
||||
|
||||
## A block object, or null when no block was found
|
||||
## Note that this includes slightly different information from eth/common.BlockHeader
|
||||
|
@ -9,7 +9,9 @@
|
||||
|
||||
import hexstrings, eth/[common, rlp, keys], stew/byteutils, nimcrypto,
|
||||
../db/[db_chain], strutils, algorithm, options,
|
||||
../constants, stint, hexstrings, rpc_types
|
||||
../constants, stint, hexstrings, rpc_types, ../config,
|
||||
../vm_state_transactions, ../vm_state, ../vm_types, ../vm/interpreter/vm_forks,
|
||||
../vm/computation
|
||||
|
||||
type
|
||||
UnsignedTx* = object
|
||||
@ -21,6 +23,14 @@ type
|
||||
payload : Blob
|
||||
contractCreation {.rlpIgnore.}: bool
|
||||
|
||||
CallData* = object
|
||||
source: EthAddress
|
||||
to: EthAddress
|
||||
gas: GasInt
|
||||
gasPrice: GasInt
|
||||
value: UInt256
|
||||
data: seq[byte]
|
||||
|
||||
proc read(rlp: var Rlp, t: var UnsignedTx, _: type EthAddress): EthAddress {.inline.} =
|
||||
if rlp.blobLen != 0:
|
||||
result = rlp.read(EthAddress)
|
||||
@ -149,3 +159,56 @@ proc signTransaction*(tx: UnsignedTx, chain: BaseChainDB, privateKey: PrivateKey
|
||||
R: Uint256.fromBytesBE(sig[0..31]),
|
||||
S: Uint256.fromBytesBE(sig[32..63])
|
||||
)
|
||||
|
||||
proc callData*(call: EthCall, callMode: bool = true): CallData =
|
||||
if call.source.isSome:
|
||||
result.source = toAddress(call.source.get)
|
||||
|
||||
if call.to.isSome:
|
||||
result.to = toAddress(call.to.get)
|
||||
else:
|
||||
if callMode:
|
||||
raise newException(ValueError, "call.to required for eth_call operation")
|
||||
|
||||
if call.gas.isSome:
|
||||
result.gas = hexToInt(call.gas.get.string, GasInt)
|
||||
|
||||
if call.gasPrice.isSome:
|
||||
result.gasPrice = hexToInt(call.gasPrice.get.string, GasInt)
|
||||
|
||||
if call.value.isSome:
|
||||
result.value = UInt256.fromHex(call.value.get.string)
|
||||
|
||||
if call.data.isSome:
|
||||
result.data = hexToSeqByte(call.data.get.string)
|
||||
|
||||
proc setupComputation(vmState: BaseVMState, call: CallData, fork: Fork) : Computation =
|
||||
vmState.setupTxContext(
|
||||
origin = call.source,
|
||||
gasPrice = call.gasPrice,
|
||||
forkOverride = some(fork)
|
||||
)
|
||||
|
||||
let msg = Message(
|
||||
kind: evmcCall,
|
||||
depth: 0,
|
||||
gas: call.gas,
|
||||
sender: call.source,
|
||||
contractAddress: call.to,
|
||||
codeAddress: call.to,
|
||||
value: call.value,
|
||||
data: call.data
|
||||
)
|
||||
|
||||
result = newComputation(vmState, msg)
|
||||
|
||||
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)
|
||||
fork = toFork(chain.config, header.blockNumber)
|
||||
comp = setupComputation(vmState, call, fork)
|
||||
|
||||
comp.execComputation()
|
||||
result = hexDataStr(comp.returnData)
|
||||
|
@ -36,21 +36,21 @@ proc eth_getUncleCountByBlockNumber(quantityTag: string): HexQuantityStr
|
||||
proc eth_getCode(data: EthAddressStr, quantityTag: string): HexDataStr
|
||||
proc eth_sign(data: EthAddressStr, message: HexDataStr): HexDataStr
|
||||
proc eth_signTransaction(data: TxSend): HexDataStr
|
||||
#proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256
|
||||
proc eth_call(call: EthCall, quantityTag: string): string
|
||||
proc eth_estimateGas(call: EthCall, quantityTag: string): GasInt
|
||||
proc eth_sendTransaction(data: TxSend): EthHashStr
|
||||
proc eth_sendRawTransaction(data: HexDataStr): EthHashStr
|
||||
proc eth_call(call: EthCall, quantityTag: string): HexDataStr
|
||||
|
||||
# TODO: Use eth/common types
|
||||
|
||||
#[proc eth_sendTransaction(obj: EthSend): UInt256
|
||||
proc eth_getBlockByHash(data: array[32, byte], fullTransactions: bool): BlockObject
|
||||
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: Uint256): TransactionObject
|
||||
proc eth_getTransactionByBlockHashAndIndex(data: UInt256, quantity: int): TransactionObject
|
||||
proc eth_getTransactionByHash(data: Hash256): TransactionObject
|
||||
proc eth_getTransactionByBlockHashAndIndex(data: Hash256, quantity: int): TransactionObject
|
||||
proc eth_getTransactionByBlockNumberAndIndex(quantityTag: string, quantity: int): TransactionObject
|
||||
proc eth_getTransactionReceipt(data: UInt256): ReceiptObject
|
||||
proc eth_getUncleByBlockHashAndIndex(data: UInt256, quantity: int64): BlockObject
|
||||
proc eth_getTransactionReceipt(data: Hash256): ReceiptObject
|
||||
proc eth_getUncleByBlockHashAndIndex(data: Hash256, quantity: int64): BlockObject
|
||||
proc eth_getUncleByBlockNumberAndIndex(quantityTag: string, quantity: int64): BlockObject
|
||||
|
||||
#[
|
||||
proc eth_getCompilers(): seq[string]
|
||||
proc eth_compileLLL(): seq[byte]
|
||||
proc eth_compileSolidity(): seq[byte]
|
||||
|
@ -215,7 +215,7 @@ proc doTests {.async.} =
|
||||
let recoveredAddr = pubkey.toCanonicalAddress()
|
||||
check recoveredAddr == signer # verified
|
||||
|
||||
test "eth_signTransaction":
|
||||
test "eth_signTransaction, eth_sendTransaction, eth_sendRawTransaction":
|
||||
var unsignedTx = TxSend(
|
||||
source: ethAddressStr(signer),
|
||||
to: ethAddressStr(ks2).some,
|
||||
@ -226,23 +226,26 @@ proc doTests {.async.} =
|
||||
nonce: none(HexQuantityStr)
|
||||
)
|
||||
|
||||
let res = await client.eth_signTransaction(unsignedTx)
|
||||
let signedTx = rlp.decode(hexToSeqByte(res.string), Transaction)
|
||||
let signedTxHex = await client.eth_signTransaction(unsignedTx)
|
||||
let signedTx = rlp.decode(hexToSeqByte(signedTxHex.string), Transaction)
|
||||
check signer == signedTx.getSender() # verified
|
||||
|
||||
#test "eth_call":
|
||||
# let
|
||||
# blockNum = state.blockheader.blockNumber
|
||||
# callParams = EthCall(value: some(100.u256))
|
||||
# r1 = await client.eth_call(callParams, "0x" & blockNum.toHex)
|
||||
# check r1 == "0x"
|
||||
#test "eth_getBalance":
|
||||
# let r2 = await client.eth_getBalance(ZERO_ADDRESS.toEthAddressStr, "0x0")
|
||||
# check r2 == 0
|
||||
#
|
||||
# let blockNum = state.blockheader.blockNumber
|
||||
# let r3 = await client.eth_getBalance(address.toEthAddressStr, "0x" & blockNum.toHex)
|
||||
# check r3 == 0
|
||||
let hashAhex = await client.eth_sendTransaction(unsignedTx)
|
||||
let hashBhex = await client.eth_sendRawTransaction(signedTxHex)
|
||||
check hashAhex.string == hashBhex.string
|
||||
|
||||
test "eth_call":
|
||||
var ec = EthCall(
|
||||
source: ethAddressStr(signer).some,
|
||||
to: ethAddressStr(ks2).some,
|
||||
gas: encodeQuantity(100000'u).some,
|
||||
gasPrice: none(HexQuantityStr),
|
||||
value: encodeQuantity(100'u).some,
|
||||
data: HexDataStr("0x").some,
|
||||
)
|
||||
|
||||
let res = await client.eth_call(ec, "latest")
|
||||
|
||||
#test "eth_estimateGas":
|
||||
# let
|
||||
# call = EthCall()
|
||||
|
Loading…
x
Reference in New Issue
Block a user