implement eth_signTransaction, eth_sendTransaction, eth_sendRawTransaction, eth_call

This commit is contained in:
jangko 2020-07-24 19:44:36 +07:00
parent 7819dae7ce
commit bb89a296dd
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
6 changed files with 132 additions and 107 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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