nimbus-eth1/tests/test_rpc.nim
Jordan Hrycaj 261c0b51a7
Redesign of BaseVMState descriptor (#923)
* Redesign of BaseVMState descriptor

why:
  BaseVMState provides an environment for executing transactions. The
  current descriptor also provides data that cannot generally be known
  within the execution environment, e.g. the total gasUsed which is
  available not before after all transactions have finished.

  Also, the BaseVMState constructor has been replaced by a constructor
  that does not need pre-initialised input of the account database.

also:
  Previous constructor and some fields are provided with a deprecated
  annotation (producing a lot of noise.)

* Replace legacy directives in production sources

* Replace legacy directives in unit test sources

* fix CI (missing premix update)

* Remove legacy directives

* chase CI problem

* rebased

* Re-introduce 'AccountsCache' constructor optimisation for 'BaseVmState' re-initialisation

why:
  Constructing a new 'AccountsCache' descriptor can be avoided sometimes
  when the current state root is properly positioned already. Such a
  feature existed already as the update function 'initStateDB()' for the
  'BaseChanDB' where the accounts cache was linked into this desctiptor.

  The function 'initStateDB()' was removed and re-implemented into the
  'BaseVmState' constructor without optimisation. The old version was of
  restricted use as a wrong accounts cache state would unconditionally
  throw an exception rather than conceptually ask for a remedy.

  The optimised 'BaseVmState' re-initialisation has been implemented for
  the 'persistBlocks()' function.

also:
  moved some test helpers to 'test/replay' folder

* Remove unused & undocumented fields from Chain descriptor

why:
  Reduces attack surface in general & improves reading the code.
2022-01-18 16:19:32 +00:00

433 lines
15 KiB
Nim

# 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
asynctest, json, strformat, strutils, options, tables, os,
nimcrypto, stew/byteutils, times,
json_rpc/[rpcserver, rpcclient], eth/common as eth_common,
eth/[rlp, keys, trie/db, p2p/private/p2p_types],
../nimbus/rpc/[common, p2p, hexstrings, rpc_types, rpc_utils],
../nimbus/[constants, config, genesis, utils, transaction,
vm_state, vm_types],
../nimbus/db/[accounts_cache, db_chain],
../nimbus/p2p/[chain, executor, executor/executor_helpers],
../nimbus/sync/protocol_eth65,
../nimbus/utils/difficulty,
../nimbus/[context, chain_config],
./test_helpers, ./macro_assembler
# Perform checks for hex string validation
#doHexStrTests()
from os import getCurrentDir, DirSep
from strutils import rsplit
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
## Generate client convenience marshalling wrappers from forward declarations
## For testing, ethcallsigs needs to be kept in sync with ../nimbus/rpc/[common, p2p]
const sigPath = &"{sourceDir}{DirSep}rpcclient{DirSep}ethcallsigs.nim"
createRpcSigs(RpcSocketClient, sigPath)
const
zeroAddress = block:
var rc: EthAddress
rc
type
TestEnv = object
txHash: Hash256
blockHash: HAsh256
proc setupEnv(chainDB: BaseChainDB, signer, ks2: EthAddress, ctx: EthContext): TestEnv =
var
parent = chainDB.getCanonicalHead()
acc = ctx.am.getAccount(signer).tryGet()
blockNumber = 1.toBlockNumber
parentHash = parent.blockHash
const code = evmByteCode:
PUSH4 "0xDEADBEEF" # PUSH
PUSH1 "0x00" # MSTORE AT 0x00
MSTORE
PUSH1 "0x04" # RETURN LEN
PUSH1 "0x1C" # RETURN OFFSET at 28
RETURN
let
vmHeader = BlockHeader(parentHash: parentHash)
vmState = BaseVMState.new(
parent = BlockHeader(stateRoot: parent.stateRoot),
header = vmHeader,
chainDB = chainDB,
pruneTrie = chainDB.pruneTrie)
vmState.stateDB.setCode(ks2, code)
vmState.stateDB.addBalance(signer, 9_000_000_000.u256)
let
unsignedTx1 = Transaction(
txType : TxLegacy,
nonce : 0,
gasPrice: 1_100,
gasLimit: 70_000,
value : 1.u256,
to : some(zeroAddress)
)
unsignedTx2 = Transaction(
txType : TxLegacy,
nonce : 0,
gasPrice: 1_200,
gasLimit: 70_000,
value : 2.u256,
to : some(zeroAddress)
)
eip155 = chainDB.currentBlock >= chainDB.config.eip155Block
signedTx1 = signTransaction(unsignedTx1, acc.privateKey, chainDB.config.chainId, eip155)
signedTx2 = signTransaction(unsignedTx2, acc.privateKey, chainDB.config.chainId, eip155)
txs = [signedTx1, signedTx2]
txRoot = chainDB.persistTransactions(blockNumber, txs)
vmState.receipts = newSeq[Receipt](txs.len)
vmState.cumulativeGasUsed = 0
for txIndex, tx in txs:
let sender = tx.getSender()
discard vmState.processTransaction(tx, sender, vmHeader)
vmState.receipts[txIndex] = makeReceipt(vmState, tx.txType)
let
receiptRoot = chainDB.persistReceipts(vmState.receipts)
date = initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc())
timeStamp = date.toTime
difficulty = calcDifficulty(chainDB.config, timeStamp, parent)
# call persist() before we get the rootHash
vmState.stateDB.persist()
var header = BlockHeader(
parentHash : parentHash,
#coinbase*: EthAddress
stateRoot : vmState.stateDB.rootHash,
txRoot : txRoot,
receiptRoot : receiptRoot,
bloom : createBloom(vmState.receipts),
difficulty : difficulty,
blockNumber : blockNumber,
gasLimit : vmState.cumulativeGasUsed + 1_000_000,
gasUsed : vmState.cumulativeGasUsed,
timestamp : timeStamp
#extraData: Blob
#mixDigest: Hash256
#nonce: BlockNonce
)
let uncles = [header]
header.ommersHash = chainDB.persistUncles(uncles)
discard chainDB.persistHeaderToDb(header)
result = TestEnv(
txHash: signedTx1.rlpHash,
blockHash: header.hash
)
proc rpcMain*() =
suite "Remote Procedure Calls":
# TODO: Include other transports such as Http
let
conf = makeTestConfig()
ctx = newEthContext()
ethNode = setupEthNode(conf, ctx, eth)
chain = newBaseChainDB(
newMemoryDb(),
conf.pruneMode == PruneMode.Full,
conf.networkId,
conf.networkParams
)
signer: EthAddress = hexToByteArray[20]("0x0e69cde81b1aa07a45c32c6cd85d67229d36bb1b")
ks2: EthAddress = hexToByteArray[20]("0xa3b2222afa5c987da6ef773fde8d01b9f23d481f")
ks3: EthAddress = hexToByteArray[20]("0x597176e9a64aad0845d83afdaf698fbeff77703b")
ethNode.chain = newChain(chain)
let keyStore = "tests" / "keystore"
let res = ctx.am.loadKeystores(keyStore)
if res.isErr:
debugEcho res.error
doAssert(res.isOk)
let acc1 = ctx.am.getAccount(signer).tryGet()
let unlock = ctx.am.unlockAccount(signer, acc1.keystore["password"].getStr())
if unlock.isErr:
debugEcho unlock.error
doAssert(unlock.isOk)
initializeEmptyDb(chain)
let env = setupEnv(chain, signer, ks2, ctx)
# Create Ethereum RPCs
let RPC_PORT = 8545
var
rpcServer = newRpcSocketServer(["localhost:" & $RPC_PORT])
client = newRpcSocketClient()
setupCommonRpc(ethNode, conf, rpcServer)
setupEthRpc(ethNode, ctx, chain, rpcServer)
# Begin tests
rpcServer.start()
waitFor client.connect("localhost", Port(RPC_PORT))
# TODO: add more tests here
test "web3_clientVersion":
let res = await client.web3_clientVersion()
check res == NimbusIdent
test "web3_sha3":
expect ValueError:
discard await client.web3_sha3(NimbusName.HexDataStr)
let data = "0x" & byteutils.toHex(NimbusName.toOpenArrayByte(0, NimbusName.len-1))
let res = await client.web3_sha3(data.hexDataStr)
let rawdata = nimcrypto.fromHex(data[2 .. ^1])
let hash = "0x" & $keccak_256.digest(rawdata)
check hash == res
test "net_version":
let res = await client.net_version()
check res == $conf.networkId
test "net_listening":
let res = await client.net_listening()
let listening = ethNode.peerPool.connectedNodes.len < conf.maxPeers
check res == listening
test "net_peerCount":
let res = await client.net_peerCount()
let peerCount = ethNode.peerPool.connectedNodes.len
check isValidHexQuantity res.string
check res == encodeQuantity(peerCount.uint)
test "eth_protocolVersion":
let res = await client.eth_protocolVersion()
# Use a hard-coded number instead of the same expression as the client,
# so that bugs introduced via that expression are detected. Using the
# same expression as the client can hide issues when the value is wrong
# in both places. When the expected value genuinely changes, it'll be
# obvious. Just change this number.
check res == "65"
test "eth_syncing":
let res = await client.eth_syncing()
if res.kind == JBool:
let syncing = ethNode.peerPool.connectedNodes.len > 0
check res.getBool() == syncing
else:
check res.kind == JObject
check chain.startingBlock == UInt256.fromHex(res["startingBlock"].getStr())
check chain.currentBlock == UInt256.fromHex(res["currentBlock"].getStr())
check chain.highestBlock == UInt256.fromHex(res["highestBlock"].getStr())
test "eth_coinbase":
let res = await client.eth_coinbase()
# currently we don't have miner
check isValidEthAddress(res.string)
check res == ethAddressStr(EthAddress.default)
test "eth_mining":
let res = await client.eth_mining()
# currently we don't have miner
check res == false
test "eth_hashrate":
let res = await client.eth_hashrate()
# currently we don't have miner
check res == encodeQuantity(0.uint)
test "eth_gasPrice":
let res = await client.eth_gasPrice()
check res.string == "0x47E"
test "eth_accounts":
let res = await client.eth_accounts()
check signer.ethAddressStr in res
check ks2.ethAddressStr in res
check ks3.ethAddressStr in res
test "eth_blockNumber":
let res = await client.eth_blockNumber()
check res.string == "0x1"
test "eth_getBalance":
let a = await client.eth_getBalance(ethAddressStr("0xfff33a3bd36abdbd412707b8e310d6011454a7ae"), "0x0")
check a.string == "0x1b1ae4d6e2ef5000000"
let b = await client.eth_getBalance(ethAddressStr("0xfff4bad596633479a2a29f9a8b3f78eefd07e6ee"), "0x0")
check b.string == "0x56bc75e2d63100000"
let c = await client.eth_getBalance(ethAddressStr("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), "0x0")
check c.string == "0x3635c9adc5dea00000"
test "eth_getStorageAt":
let res = await client.eth_getStorageAt(ethAddressStr("0xfff33a3bd36abdbd412707b8e310d6011454a7ae"), hexQuantityStr "0x0", "0x0")
check hexDataStr(0.u256).string == hexDataStr(res).string
test "eth_getTransactionCount":
let res = await client.eth_getTransactionCount(ethAddressStr("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), "0x0")
check res.string == "0x0"
test "eth_getBlockTransactionCountByHash":
let hash = chain.getBlockHash(0.toBlockNumber)
let res = await client.eth_getBlockTransactionCountByHash(hash)
check res.string == "0x0"
test "eth_getBlockTransactionCountByNumber":
let res = await client.eth_getBlockTransactionCountByNumber("0x0")
check res.string == "0x0"
test "eth_getUncleCountByBlockHash":
let hash = chain.getBlockHash(0.toBlockNumber)
let res = await client.eth_getUncleCountByBlockHash(hash)
check res.string == "0x0"
test "eth_getUncleCountByBlockNumber":
let res = await client.eth_getUncleCountByBlockNumber("0x0")
check res.string == "0x0"
test "eth_getCode":
let res = await client.eth_getCode(ethAddressStr("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), "0x0")
check res.string == "0x"
test "eth_sign":
let msg = "hello world"
let msgHex = hexDataStr(msg.toOpenArrayByte(0, msg.len-1))
expect ValueError:
discard await client.eth_sign(ethAddressStr(ks2), msgHex)
let res = await client.eth_sign(ethAddressStr(signer), msgHex)
let sig = Signature.fromHex(res.string).tryGet()
# now let us try to verify signature
let msgData = "\x19Ethereum Signed Message:\n" & $msg.len & msg
let msgDataHex = hexDataStr(msgData.toOpenArrayByte(0, msgData.len-1))
let sha3Data = await client.web3_sha3(msgDataHex)
let msgHash = hexToByteArray[32](sha3Data)
let pubkey = recover(sig, SkMessage(msgHash)).tryGet()
let recoveredAddr = pubkey.toCanonicalAddress()
check recoveredAddr == signer # verified
test "eth_signTransaction, eth_sendTransaction, eth_sendRawTransaction":
var unsignedTx = TxSend(
source: ethAddressStr(signer),
to: ethAddressStr(ks2).some,
gas: encodeQuantity(100000'u).some,
gasPrice: none(HexQuantityStr),
value: encodeQuantity(100'u).some,
data: HexDataStr("0x"),
nonce: none(HexQuantityStr)
)
let signedTxHex = await client.eth_signTransaction(unsignedTx)
let signedTx = rlp.decode(hexToSeqByte(signedTxHex.string), Transaction)
check signer == signedTx.getSender() # verified
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
)
let res = await client.eth_call(ec, "latest")
check hexToByteArray[4](res.string) == hexToByteArray[4]("deadbeef")
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
test "eth_getBlockByHash":
let res = await client.eth_getBlockByHash(env.blockHash, true)
check res.isSome
check res.get().hash.get() == env.blockHash
let res2 = await client.eth_getBlockByHash(env.txHash, true)
check res2.isNone
test "eth_getBlockByNumber":
let res = await client.eth_getBlockByNumber("latest", true)
check res.isSome
check res.get().hash.get() == env.blockHash
let res2 = await client.eth_getBlockByNumber($1, true)
check res2.isNone
test "eth_getTransactionByHash":
let res = await client.eth_getTransactionByHash(env.txHash)
check res.isSome
check res.get().blockNumber.get().string.hexToInt(int) == 1
let res2 = await client.eth_getTransactionByHash(env.blockHash)
check res2.isNone
test "eth_getTransactionByBlockHashAndIndex":
let res = await client.eth_getTransactionByBlockHashAndIndex(env.blockHash, encodeQuantity(0))
check res.isSome
check res.get().blockNumber.get().string.hexToInt(int) == 1
let res2 = await client.eth_getTransactionByBlockHashAndIndex(env.blockHash, encodeQuantity(3))
check res2.isNone
let res3 = await client.eth_getTransactionByBlockHashAndIndex(env.txHash, encodeQuantity(3))
check res3.isNone
test "eth_getTransactionByBlockNumberAndIndex":
let res = await client.eth_getTransactionByBlockNumberAndIndex("latest", encodeQuantity(1))
check res.isSome
check res.get().blockNumber.get().string.hexToInt(int) == 1
let res2 = await client.eth_getTransactionByBlockNumberAndIndex("latest", encodeQuantity(3))
check res2.isNone
test "eth_getTransactionReceipt":
let res = await client.eth_getTransactionReceipt(env.txHash)
check res.isSome
check res.get().blockNumber.string.hexToInt(int) == 1
let res2 = await client.eth_getTransactionReceipt(env.blockHash)
check res2.isNone
test "eth_getUncleByBlockHashAndIndex":
let res = await client.eth_getUncleByBlockHashAndIndex(env.blockHash, encodeQuantity(0))
check res.isSome
check res.get().number.get().string.hexToInt(int) == 1
let res2 = await client.eth_getUncleByBlockHashAndIndex(env.blockHash, encodeQuantity(1))
check res2.isNone
let res3 = await client.eth_getUncleByBlockHashAndIndex(env.txHash, encodeQuantity(0))
check res3.isNone
test "eth_getUncleByBlockNumberAndIndex":
let res = await client.eth_getUncleByBlockNumberAndIndex("latest", encodeQuantity(0))
check res.isSome
check res.get().number.get().string.hexToInt(int) == 1
let res2 = await client.eth_getUncleByBlockNumberAndIndex("latest", encodeQuantity(1))
check res2.isNone
rpcServer.stop()
rpcServer.close()
when isMainModule:
rpcMain()