mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 05:14:14 +00:00
7d3616e3d9
* Refactor TxPool: leaner and simpler * Rewrite test_txpool Reduce number of tables used, from 5 to 2. Reduce number of files. If need to modify the price rule or other filters, now is far more easier because only one table to work with(sender/nonce). And the other table is just a map from txHash to TxItemRef. Removing transactions from txPool either because of producing new block or syncing became much easier. Removing expired transactions also simple. Explicit Tx Pending, Staged, or Packed status is removed. The status of the transactions can be inferred implicitly. Developer new to TxPool can easily follow the logic. But the most important is we can revive the test_txpool without dirty trick and remove usage of getCanonicalHead furthermore to prepare for better integration with ForkedChain.
723 lines
24 KiB
Nim
723 lines
24 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2018-2024 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
|
|
chronicles,
|
|
std/[json, typetraits, sequtils],
|
|
asynctest,
|
|
web3/eth_api,
|
|
stew/byteutils,
|
|
json_rpc/[rpcserver, rpcclient],
|
|
eth/[p2p, rlp, trie/hexary_proof_verification],
|
|
eth/common/[transaction_utils, addresses],
|
|
../hive_integration/nodocker/engine/engine_client,
|
|
../nimbus/[constants, transaction, config, version],
|
|
../nimbus/db/[ledger, storage_types],
|
|
../nimbus/sync/protocol,
|
|
../nimbus/core/[tx_pool, chain, pow/difficulty, casper],
|
|
../nimbus/utils/utils,
|
|
../nimbus/[common, rpc],
|
|
../nimbus/rpc/rpc_types,
|
|
../nimbus/beacon/web3_eth_conv,
|
|
./test_helpers,
|
|
./macro_assembler,
|
|
./test_block_fixture
|
|
|
|
type
|
|
Hash32 = common.Hash32
|
|
|
|
TestEnv = object
|
|
conf : NimbusConf
|
|
com : CommonRef
|
|
txPool : TxPoolRef
|
|
server : RpcHttpServer
|
|
client : RpcHttpClient
|
|
chain : ForkedChainRef
|
|
ctx : EthContext
|
|
node : EthereumNode
|
|
txHash : Hash32
|
|
blockHash: Hash32
|
|
nonce : uint64
|
|
chainId : ChainId
|
|
|
|
const
|
|
zeroHash = hash32"0x0000000000000000000000000000000000000000000000000000000000000000"
|
|
emptyCodeHash = hash32"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
|
emptyStorageHash = hash32"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
|
|
genesisFile = "tests/customgenesis/cancun123.json"
|
|
contractCode = evmByteCode:
|
|
Push4 "0xDEADBEEF" # PUSH
|
|
Push1 "0x00" # MSTORE AT 0x00
|
|
Mstore
|
|
Push1 "0x04" # RETURN LEN
|
|
Push1 "0x1C" # RETURN OFFSET at 28
|
|
Return
|
|
keyStore = "tests/keystore"
|
|
signer = address"0x0e69cde81b1aa07a45c32c6cd85d67229d36bb1b"
|
|
contractAddress = address"0xa3b2222afa5c987da6ef773fde8d01b9f23d481f"
|
|
extraAddress = address"0x597176e9a64aad0845d83afdaf698fbeff77703b"
|
|
regularAcc = address"0x0000000000000000000000000000000000000001"
|
|
contractAccWithStorage = address"0x0000000000000000000000000000000000000002"
|
|
contractAccNoStorage = address"0x0000000000000000000000000000000000000003"
|
|
feeRecipient = address"0000000000000000000000000000000000000212"
|
|
prevRandao = Bytes32 EMPTY_UNCLE_HASH # it can be any valid hash
|
|
oneETH = 1.u256 * 1_000_000_000.u256 * 1_000_000_000.u256
|
|
|
|
proc verifyAccountProof(trustedStateRoot: Hash32, res: ProofResponse): MptProofVerificationResult =
|
|
let
|
|
key = toSeq(keccak256(res.address.data).data)
|
|
value = rlp.encode(Account(
|
|
nonce: res.nonce.uint64,
|
|
balance: res.balance,
|
|
storageRoot: res.storageHash,
|
|
codeHash: res.codeHash))
|
|
|
|
verifyMptProof(
|
|
seq[seq[byte]](res.accountProof),
|
|
trustedStateRoot,
|
|
key,
|
|
value)
|
|
|
|
proc verifySlotProof(trustedStorageRoot: Hash32, slot: StorageProof): MptProofVerificationResult =
|
|
let
|
|
key = toSeq(keccak256(toBytesBE(slot.key)).data)
|
|
value = rlp.encode(slot.value)
|
|
|
|
verifyMptProof(
|
|
seq[seq[byte]](slot.proof),
|
|
trustedStorageRoot,
|
|
key,
|
|
value)
|
|
|
|
proc persistFixtureBlock(chainDB: CoreDbRef) =
|
|
let header = getBlockHeader4514995()
|
|
# Manually inserting header to avoid any parent checks
|
|
discard chainDB.ctx.getKvt.put(genericHashKey(header.blockHash).toOpenArray, rlp.encode(header))
|
|
chainDB.addBlockNumberToHashLookup(header.number, header.blockHash)
|
|
chainDB.persistTransactions(header.number, header.txRoot, getBlockBody4514995().transactions)
|
|
chainDB.persistReceipts(header.receiptsRoot, getReceipts4514995())
|
|
|
|
proc setupConfig(): NimbusConf =
|
|
makeConfig(@[
|
|
"--custom-network:" & genesisFile
|
|
])
|
|
|
|
proc setupCom(conf: NimbusConf): CommonRef =
|
|
CommonRef.new(
|
|
newCoreDbRef DefaultDbMemory,
|
|
nil,
|
|
conf.networkId,
|
|
conf.networkParams
|
|
)
|
|
|
|
proc setupClient(port: Port): RpcHttpClient =
|
|
let client = newRpcHttpClient()
|
|
waitFor client.connect("127.0.0.1", port, false)
|
|
return client
|
|
|
|
proc close(env: TestEnv) =
|
|
waitFor env.client.close()
|
|
waitFor env.server.closeWait()
|
|
|
|
func makeTx(
|
|
env: var TestEnv,
|
|
signerKey: PrivateKey,
|
|
recipient: addresses.Address,
|
|
amount: UInt256,
|
|
gasPrice: GasInt,
|
|
payload: openArray[byte] = []
|
|
): Transaction =
|
|
const
|
|
gasLimit = 70000.GasInt
|
|
|
|
let tx = Transaction(
|
|
txType: TxLegacy,
|
|
chainId: env.chainId,
|
|
nonce: AccountNonce(env.nonce),
|
|
gasPrice: gasPrice,
|
|
gasLimit: gasLimit,
|
|
to: Opt.some(recipient),
|
|
value: amount,
|
|
payload: @payload,
|
|
)
|
|
|
|
inc env.nonce
|
|
signTransaction(tx, signerKey, eip155 = true)
|
|
|
|
proc setupEnv(envFork: HardFork = MergeFork): TestEnv =
|
|
doAssert(envFork >= MergeFork)
|
|
|
|
let
|
|
conf = setupConfig()
|
|
|
|
conf.networkParams.genesis.alloc[contractAddress] = GenesisAccount(code: contractCode)
|
|
conf.networkParams.genesis.alloc[signer] = GenesisAccount(balance: oneETH)
|
|
|
|
# Test data created for eth_getProof tests
|
|
conf.networkParams.genesis.alloc[regularAcc] = GenesisAccount(
|
|
balance: 2_000_000_000.u256,
|
|
nonce: 1.uint64)
|
|
|
|
conf.networkParams.genesis.alloc[contractAccWithStorage] = GenesisAccount(
|
|
balance: 1_000_000_000.u256,
|
|
nonce: 2.uint64,
|
|
code: contractCode,
|
|
storage: {
|
|
0.u256: 1234.u256,
|
|
1.u256: 2345.u256,
|
|
}.toTable)
|
|
|
|
conf.networkParams.genesis.alloc[contractAccNoStorage] = GenesisAccount(code: contractCode)
|
|
|
|
if envFork >= Shanghai:
|
|
conf.networkParams.config.shanghaiTime = Opt.some(0.EthTime)
|
|
|
|
if envFork >= Cancun:
|
|
conf.networkParams.config.cancunTime = Opt.some(0.EthTime)
|
|
|
|
if envFork >= Prague:
|
|
conf.networkParams.config.pragueTime = Opt.some(0.EthTime)
|
|
|
|
let
|
|
com = setupCom(conf)
|
|
chain = ForkedChainRef.init(com)
|
|
txPool = TxPoolRef.new(chain)
|
|
|
|
let
|
|
server = newRpcHttpServerWithParams("127.0.0.1:0").valueOr:
|
|
echo "Failed to create rpc server: ", error
|
|
quit(QuitFailure)
|
|
serverApi = newServerAPI(txPool)
|
|
client = setupClient(server.localAddress[0].port)
|
|
ctx = newEthContext()
|
|
node = setupEthNode(conf, ctx, eth)
|
|
|
|
ctx.am.loadKeystores(keyStore).isOkOr:
|
|
debugEcho error
|
|
quit(QuitFailure)
|
|
|
|
let acc1 = ctx.am.getAccount(signer).tryGet()
|
|
ctx.am.unlockAccount(signer, acc1.keystore["password"].getStr()).isOkOr:
|
|
debugEcho error
|
|
quit(QuitFailure)
|
|
|
|
setupServerAPI(serverApi, server, ctx)
|
|
setupCommonRpc(node, conf, server)
|
|
server.start()
|
|
|
|
TestEnv(
|
|
conf : conf,
|
|
com : com,
|
|
txPool : txPool,
|
|
server : server,
|
|
client : client,
|
|
chain : chain,
|
|
ctx : ctx,
|
|
node : node,
|
|
chainId: conf.networkParams.config.chainId,
|
|
)
|
|
|
|
proc generateBlock(env: var TestEnv) =
|
|
let
|
|
com = env.com
|
|
xp = env.txPool
|
|
ctx = env.ctx
|
|
acc = ctx.am.getAccount(signer).tryGet()
|
|
tx1 = env.makeTx(acc.privateKey, zeroAddress, 1.u256, 30_000_000_000'u64)
|
|
tx2 = env.makeTx(acc.privateKey, zeroAddress, 2.u256, 30_000_000_100'u64)
|
|
chain = env.chain
|
|
|
|
doAssert xp.addTx(tx1).isOk
|
|
doAssert xp.addTx(tx2).isOk
|
|
doAssert(xp.len == 2)
|
|
|
|
# generate block
|
|
com.pos.prevRandao = prevRandao
|
|
com.pos.feeRecipient = feeRecipient
|
|
com.pos.timestamp = EthTime.now()
|
|
|
|
let bundle = xp.assembleBlock().valueOr:
|
|
debugEcho error
|
|
quit(QuitFailure)
|
|
|
|
let blk = bundle.blk
|
|
doAssert(blk.transactions.len == 2)
|
|
|
|
# import block
|
|
chain.importBlock(blk).isOkOr:
|
|
debugEcho error
|
|
quit(QuitFailure)
|
|
|
|
xp.removeNewBlockTxs(blk)
|
|
|
|
com.db.persistFixtureBlock()
|
|
|
|
env.txHash = tx1.rlpHash
|
|
env.blockHash = blk.header.blockHash
|
|
|
|
createRpcSigsFromNim(RpcClient):
|
|
proc web3_clientVersion(): string
|
|
proc web3_sha3(data: seq[byte]): Hash32
|
|
proc net_version(): string
|
|
proc net_listening(): bool
|
|
proc net_peerCount(): Quantity
|
|
|
|
proc rpcMain*() =
|
|
suite "Remote Procedure Calls":
|
|
var env = setupEnv()
|
|
env.generateBlock()
|
|
let
|
|
client = env.client
|
|
node = env.node
|
|
com = env.com
|
|
|
|
test "web3_clientVersion":
|
|
let res = await client.web3_clientVersion()
|
|
check res == ClientId
|
|
|
|
test "web3_sha3":
|
|
let data = @(NimbusName.toOpenArrayByte(0, NimbusName.len-1))
|
|
let res = await client.web3_sha3(data)
|
|
let hash = keccak256(data)
|
|
check hash == res
|
|
|
|
test "net_version":
|
|
let res = await client.net_version()
|
|
check res == $env.conf.networkId
|
|
|
|
test "net_listening":
|
|
let res = await client.net_listening()
|
|
let listening = node.peerPool.connectedNodes.len < env.conf.maxPeers
|
|
check res == listening
|
|
|
|
test "net_peerCount":
|
|
let res = await client.net_peerCount()
|
|
let peerCount = node.peerPool.connectedNodes.len
|
|
check res == w3Qty(peerCount)
|
|
|
|
test "eth_chainId":
|
|
let res = await client.eth_chainId()
|
|
check res == w3Qty(distinctBase(com.chainId))
|
|
|
|
test "eth_syncing":
|
|
let res = await client.eth_syncing()
|
|
if res.syncing == false:
|
|
let syncing = node.peerPool.connectedNodes.len > 0
|
|
check syncing == false
|
|
else:
|
|
check com.syncStart == res.syncObject.startingBlock.uint64
|
|
check com.syncCurrent == res.syncObject.currentBlock.uint64
|
|
check com.syncHighest == res.syncObject.highestBlock.uint64
|
|
|
|
test "eth_gasPrice":
|
|
let res = await client.eth_gasPrice()
|
|
check res == w3Qty(30_000_000_050) # Avg of `unsignedTx1` / `unsignedTx2`
|
|
|
|
test "eth_accounts":
|
|
let res = await client.eth_accounts()
|
|
check signer in res
|
|
check contractAddress in res
|
|
check extraAddress in res
|
|
|
|
test "eth_blockNumber":
|
|
let res = await client.eth_blockNumber()
|
|
check res == w3Qty(0x1'u64)
|
|
|
|
test "eth_getBalance":
|
|
let a = await client.eth_getBalance(signer, blockId(1'u64))
|
|
check a == 998739999997899997'u256
|
|
let b = await client.eth_getBalance(regularAcc, blockId(1'u64))
|
|
check b == 2_000_000_000.u256
|
|
let c = await client.eth_getBalance(contractAccWithStorage, blockId(1'u64))
|
|
check c == 1_000_000_000.u256
|
|
|
|
test "eth_getStorageAt":
|
|
let res = await client.eth_getStorageAt(contractAccWithStorage, 1.u256, blockId(1'u64))
|
|
check FixedBytes[32](2345.u256.toBytesBE) == res
|
|
|
|
test "eth_getTransactionCount":
|
|
let res = await client.eth_getTransactionCount(signer, blockId(1'u64))
|
|
check res == w3Qty(2'u64)
|
|
|
|
test "eth_getBlockTransactionCountByHash":
|
|
let res = await client.eth_getBlockTransactionCountByHash(env.blockHash)
|
|
check res == w3Qty(2'u64)
|
|
|
|
test "eth_getBlockTransactionCountByNumber":
|
|
let res = await client.eth_getBlockTransactionCountByNumber(blockId(1'u64))
|
|
check res == w3Qty(2'u64)
|
|
|
|
test "eth_getUncleCountByBlockHash":
|
|
let res = await client.eth_getUncleCountByBlockHash(env.blockHash)
|
|
check res == w3Qty(0'u64)
|
|
|
|
test "eth_getUncleCountByBlockNumber":
|
|
let res = await client.eth_getUncleCountByBlockNumber(blockId(0'u64))
|
|
check res == w3Qty(0'u64)
|
|
|
|
test "eth_getCode":
|
|
let res = await client.eth_getCode(contractAddress, blockId(1'u64))
|
|
check res.len == contractCode.len
|
|
|
|
test "eth_sign":
|
|
let msg = "hello world"
|
|
let msgBytes = @(msg.toOpenArrayByte(0, msg.len-1))
|
|
|
|
expect JsonRpcError:
|
|
discard await client.eth_sign(contractAddress, msgBytes)
|
|
|
|
let res = await client.eth_sign(signer, msgBytes)
|
|
let sig = Signature.fromRaw(res).tryGet()
|
|
|
|
# now let us try to verify signature
|
|
let msgData = "\x19Ethereum Signed Message:\n" & $msg.len & msg
|
|
let msgDataBytes = @(msgData.toOpenArrayByte(0, msgData.len-1))
|
|
let msgHash = await client.web3_sha3(msgDataBytes)
|
|
let pubkey = recover(sig, SkMessage(msgHash.data)).tryGet()
|
|
let recoveredAddr = pubkey.toCanonicalAddress()
|
|
check recoveredAddr == signer # verified
|
|
|
|
test "eth_signTransaction, eth_sendTransaction":
|
|
let unsignedTx = TransactionArgs(
|
|
`from`: Opt.some(signer),
|
|
to: Opt.some(contractAddress),
|
|
gas: Opt.some(w3Qty(100000'u)),
|
|
gasPrice: Opt.none(Quantity),
|
|
value: Opt.some(100.u256),
|
|
nonce: Opt.some(2.Quantity)
|
|
)
|
|
|
|
let signedTxBytes = await client.eth_signTransaction(unsignedTx)
|
|
let signedTx = rlp.decode(signedTxBytes, Transaction)
|
|
check signer == signedTx.recoverSender().expect("valid signature") # verified
|
|
|
|
let txHash = await client.eth_sendTransaction(unsignedTx)
|
|
const expHash = hash32"0x929d48788096f26cfff70296b16c9974e6b1bf693c0121742e8527bb92b6d074"
|
|
check txHash == expHash
|
|
|
|
test "eth_sendRawTransaction":
|
|
let unsignedTx = TransactionArgs(
|
|
`from`: Opt.some(signer),
|
|
to: Opt.some(contractAddress),
|
|
gas: Opt.some(w3Qty(100001'u)),
|
|
gasPrice: Opt.none(Quantity),
|
|
value: Opt.some(100.u256),
|
|
nonce: Opt.some(3.Quantity)
|
|
)
|
|
|
|
let signedTxBytes = await client.eth_signTransaction(unsignedTx)
|
|
let signedTx = rlp.decode(signedTxBytes, Transaction)
|
|
check signer == signedTx.recoverSender().expect("valid signature") # verified
|
|
|
|
let txHash = await client.eth_sendRawTransaction(signedTxBytes)
|
|
const expHash = hash32"0xeea79669dd904921d203fb720c7228f5c7854e5a768248f494f36fa68c83c191"
|
|
check txHash == expHash
|
|
|
|
test "eth_call":
|
|
let ec = TransactionArgs(
|
|
`from`: Opt.some(signer),
|
|
to: Opt.some(contractAddress),
|
|
gas: Opt.some(w3Qty(100000'u)),
|
|
gasPrice: Opt.none(Quantity),
|
|
value: Opt.some(100.u256)
|
|
)
|
|
|
|
let res = await client.eth_call(ec, "latest")
|
|
check res == hexToSeqByte("deadbeef")
|
|
|
|
test "eth_estimateGas":
|
|
let ec = TransactionArgs(
|
|
`from`: Opt.some(signer),
|
|
to: Opt.some(extraAddress),
|
|
gas: Opt.some(w3Qty(42000'u)),
|
|
gasPrice: Opt.some(w3Qty(100'u)),
|
|
value: Opt.some(100.u256)
|
|
)
|
|
|
|
let res = await client.eth_estimateGas(ec)
|
|
check res == w3Qty(21000'u64)
|
|
|
|
test "eth_getBlockByHash":
|
|
let res = await client.eth_getBlockByHash(env.blockHash, true)
|
|
check res.isNil.not
|
|
check res.hash == env.blockHash
|
|
let res2 = await client.eth_getBlockByHash(env.txHash, true)
|
|
check res2.isNil
|
|
|
|
test "eth_getBlockByNumber":
|
|
let res = await client.eth_getBlockByNumber("latest", true)
|
|
check res.isNil.not
|
|
check res.hash == env.blockHash
|
|
let res2 = await client.eth_getBlockByNumber($1, true)
|
|
check res2.isNil
|
|
|
|
test "eth_getTransactionByHash":
|
|
let res = await client.eth_getTransactionByHash(env.txHash)
|
|
check res.isNil.not
|
|
check res.blockNumber.get() == w3Qty(1'u64)
|
|
let res2 = await client.eth_getTransactionByHash(env.blockHash)
|
|
check res2.isNil
|
|
|
|
test "eth_getTransactionByBlockHashAndIndex":
|
|
let res = await client.eth_getTransactionByBlockHashAndIndex(env.blockHash, w3Qty(0'u64))
|
|
check res.isNil.not
|
|
check res.blockNumber.get() == w3Qty(1'u64)
|
|
|
|
let res2 = await client.eth_getTransactionByBlockHashAndIndex(env.blockHash, w3Qty(3'u64))
|
|
check res2.isNil
|
|
|
|
let res3 = await client.eth_getTransactionByBlockHashAndIndex(env.txHash, w3Qty(3'u64))
|
|
check res3.isNil
|
|
|
|
test "eth_getTransactionByBlockNumberAndIndex":
|
|
let res = await client.eth_getTransactionByBlockNumberAndIndex("latest", w3Qty(1'u64))
|
|
check res.isNil.not
|
|
check res.blockNumber.get() == w3Qty(1'u64)
|
|
|
|
let res2 = await client.eth_getTransactionByBlockNumberAndIndex("latest", w3Qty(3'u64))
|
|
check res2.isNil
|
|
|
|
test "eth_getBlockReceipts":
|
|
let recs = await client.eth_getBlockReceipts(blockId(1'u64))
|
|
check recs.isSome
|
|
if recs.isSome:
|
|
let receipts = recs.get
|
|
check receipts.len == 2
|
|
check receipts[0].transactionIndex == 0.Quantity
|
|
check receipts[1].transactionIndex == 1.Quantity
|
|
|
|
test "eth_getTransactionReceipt":
|
|
let res = await client.eth_getTransactionReceipt(env.txHash)
|
|
check res.isNil.not
|
|
check res.blockNumber == w3Qty(1'u64)
|
|
|
|
let res2 = await client.eth_getTransactionReceipt(env.blockHash)
|
|
check res2.isNil
|
|
|
|
test "eth_getUncleByBlockHashAndIndex":
|
|
let res = await client.eth_getUncleByBlockHashAndIndex(env.blockHash, w3Qty(0'u64))
|
|
check res.isNil
|
|
|
|
let res2 = await client.eth_getUncleByBlockHashAndIndex(env.blockHash, w3Qty(1'u64))
|
|
check res2.isNil
|
|
|
|
let res3 = await client.eth_getUncleByBlockHashAndIndex(env.txHash, w3Qty(0'u64))
|
|
check res3.isNil
|
|
|
|
test "eth_getUncleByBlockNumberAndIndex":
|
|
let res = await client.eth_getUncleByBlockNumberAndIndex("latest", w3Qty(0'u64))
|
|
check res.isNil
|
|
|
|
let res2 = await client.eth_getUncleByBlockNumberAndIndex("latest", w3Qty(1'u64))
|
|
check res2.isNil
|
|
|
|
test "eth_getLogs by blockhash, no filters":
|
|
let testHeader = getBlockHeader4514995()
|
|
let testHash = testHeader.blockHash
|
|
let filterOptions = FilterOptions(
|
|
blockHash: Opt.some(testHash),
|
|
topics: @[]
|
|
)
|
|
let logs = await client.eth_getLogs(filterOptions)
|
|
|
|
check:
|
|
len(logs) == 54
|
|
|
|
var i = 0
|
|
for l in logs:
|
|
check:
|
|
l.blockHash.isSome()
|
|
l.blockHash.get() == testHash
|
|
l.logIndex.get() == w3Qty(i.uint64)
|
|
inc i
|
|
|
|
test "eth_getLogs by blockhash, filter logs at specific positions":
|
|
let testHeader = getBlockHeader4514995()
|
|
let testHash = testHeader.blockHash
|
|
|
|
let topic = bytes32"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
|
let topic1 = bytes32"0x000000000000000000000000fdc183d01a793613736cd40a5a578f49add1772b"
|
|
|
|
let filterOptions = FilterOptions(
|
|
blockHash: Opt.some(testHash),
|
|
topics: @[
|
|
TopicOrList(kind: slkList, list: @[topic]),
|
|
TopicOrList(kind: slkNull),
|
|
TopicOrList(kind: slkList, list: @[topic1])
|
|
]
|
|
)
|
|
|
|
let logs = await client.eth_getLogs(filterOptions)
|
|
|
|
check:
|
|
len(logs) == 1
|
|
|
|
|
|
test "eth_getLogs by blockhash, filter logs at specific postions with or options":
|
|
let testHeader = getBlockHeader4514995()
|
|
let testHash = testHeader.blockHash
|
|
|
|
let topic = bytes32"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
|
let topic1 = bytes32"0xa64da754fccf55aa65a1f0128a648633fade3884b236e879ee9f64c78df5d5d7"
|
|
|
|
let topic2 = bytes32"0x000000000000000000000000e16c02eac87920033ac72fc55ee1df3151c75786"
|
|
let topic3 = bytes32"0x000000000000000000000000b626a5facc4de1c813f5293ec3be31979f1d1c78"
|
|
|
|
|
|
|
|
let filterOptions = FilterOptions(
|
|
blockHash: Opt.some(testHash),
|
|
topics: @[
|
|
TopicOrList(kind: slkList, list: @[topic, topic1]),
|
|
TopicOrList(kind: slkList, list: @[topic2, topic3])
|
|
]
|
|
)
|
|
|
|
let logs = await client.eth_getLogs(filterOptions)
|
|
|
|
check:
|
|
len(logs) == 2
|
|
|
|
test "eth_getProof - Non existent account and storage slots":
|
|
let blockData = await client.eth_getBlockByNumber("latest", true)
|
|
|
|
block:
|
|
# account doesn't exist
|
|
let
|
|
address = address"0x0000000000000000000000000000000000000004"
|
|
proofResponse = await client.eth_getProof(address, @[], blockId(1'u64))
|
|
storageProof = proofResponse.storageProof
|
|
|
|
check:
|
|
proofResponse.address == address
|
|
verifyAccountProof(blockData.stateRoot, proofResponse).isMissing()
|
|
proofResponse.balance == 0.u256
|
|
proofResponse.codeHash == zeroHash
|
|
proofResponse.nonce == w3Qty(0.uint64)
|
|
proofResponse.storageHash == zeroHash
|
|
storageProof.len() == 0
|
|
|
|
block:
|
|
# account exists but requested slots don't exist
|
|
let
|
|
address = regularAcc
|
|
slot1Key = 0.u256
|
|
slot2Key = 1.u256
|
|
proofResponse = await client.eth_getProof(address, @[slot1Key, slot2Key], blockId(1'u64))
|
|
storageProof = proofResponse.storageProof
|
|
|
|
check:
|
|
proofResponse.address == address
|
|
verifyAccountProof(blockData.stateRoot, proofResponse).isValid()
|
|
proofResponse.balance == 2_000_000_000.u256
|
|
proofResponse.codeHash == emptyCodeHash
|
|
proofResponse.nonce == w3Qty(1.uint64)
|
|
proofResponse.storageHash == emptyStorageHash
|
|
storageProof.len() == 2
|
|
storageProof[0].key == slot1Key
|
|
storageProof[0].proof.len() == 0
|
|
storageProof[0].value == 0.u256
|
|
storageProof[1].key == slot2Key
|
|
storageProof[1].proof.len() == 0
|
|
storageProof[1].value == 0.u256
|
|
|
|
block:
|
|
# contract account with no storage slots
|
|
let
|
|
address = contractAccNoStorage
|
|
slot1Key = 0.u256 # Doesn't exist
|
|
proofResponse = await client.eth_getProof(address, @[slot1Key], blockId(1'u64))
|
|
storageProof = proofResponse.storageProof
|
|
|
|
check:
|
|
proofResponse.address == address
|
|
verifyAccountProof(blockData.stateRoot, proofResponse).isValid()
|
|
proofResponse.balance == 0.u256
|
|
proofResponse.codeHash == hash32"0x09044b55d7aba83cb8ac3d2c9c8d8bcadbfc33f06f1be65e8cc1e4ddab5f3074"
|
|
proofResponse.nonce == w3Qty(0.uint64)
|
|
proofResponse.storageHash == emptyStorageHash
|
|
storageProof.len() == 1
|
|
storageProof[0].key == slot1Key
|
|
storageProof[0].proof.len() == 0
|
|
storageProof[0].value == 0.u256
|
|
|
|
test "eth_getProof - Existing accounts and storage slots":
|
|
let blockData = await client.eth_getBlockByNumber("latest", true)
|
|
|
|
block:
|
|
# contract account with storage slots
|
|
let
|
|
address = contractAccWithStorage
|
|
slot1Key = 0.u256
|
|
slot2Key = 1.u256
|
|
slot3Key = 2.u256 # Doesn't exist
|
|
proofResponse = await client.eth_getProof(address, @[slot1Key, slot2Key, slot3Key], blockId(1'u64))
|
|
storageProof = proofResponse.storageProof
|
|
|
|
check:
|
|
proofResponse.address == address
|
|
verifyAccountProof(blockData.stateRoot, proofResponse).isValid()
|
|
proofResponse.balance == 1_000_000_000.u256
|
|
proofResponse.codeHash == hash32"0x09044b55d7aba83cb8ac3d2c9c8d8bcadbfc33f06f1be65e8cc1e4ddab5f3074"
|
|
proofResponse.nonce == w3Qty(2.uint64)
|
|
proofResponse.storageHash == hash32"0x2ed06ec37dad4cd8c8fc1a1172d633a8973987fa6995b14a7c0a50c0e8d1a9c3"
|
|
storageProof.len() == 3
|
|
storageProof[0].key == slot1Key
|
|
storageProof[0].proof.len() > 0
|
|
storageProof[0].value == 1234.u256
|
|
storageProof[1].key == slot2Key
|
|
storageProof[1].proof.len() > 0
|
|
storageProof[1].value == 2345.u256
|
|
storageProof[2].key == slot3Key
|
|
storageProof[2].proof.len() > 0
|
|
storageProof[2].value == 0.u256
|
|
verifySlotProof(proofResponse.storageHash, storageProof[0]).isValid()
|
|
verifySlotProof(proofResponse.storageHash, storageProof[1]).isValid()
|
|
verifySlotProof(proofResponse.storageHash, storageProof[2]).isMissing()
|
|
|
|
block:
|
|
# externally owned account
|
|
let
|
|
address = regularAcc
|
|
proofResponse = await client.eth_getProof(address, @[], blockId(1'u64))
|
|
storageProof = proofResponse.storageProof
|
|
|
|
check:
|
|
proofResponse.address == address
|
|
verifyAccountProof(blockData.stateRoot, proofResponse).isValid()
|
|
proofResponse.balance == 2_000_000_000.u256
|
|
proofResponse.codeHash == emptyCodeHash
|
|
proofResponse.nonce == w3Qty(1.uint64)
|
|
proofResponse.storageHash == emptyStorageHash
|
|
storageProof.len() == 0
|
|
|
|
test "eth_getProof - Multiple blocks":
|
|
let blockData = await client.eth_getBlockByNumber("latest", true)
|
|
|
|
block:
|
|
# block 1 - account has balance, code and storage
|
|
let
|
|
address = contractAccWithStorage
|
|
slot2Key = 1.u256
|
|
proofResponse = await client.eth_getProof(address, @[slot2Key], blockId(1'u64))
|
|
storageProof = proofResponse.storageProof
|
|
|
|
check:
|
|
proofResponse.address == address
|
|
verifyAccountProof(blockData.stateRoot, proofResponse).isValid()
|
|
proofResponse.balance == 1_000_000_000.u256
|
|
proofResponse.codeHash == hash32"0x09044b55d7aba83cb8ac3d2c9c8d8bcadbfc33f06f1be65e8cc1e4ddab5f3074"
|
|
proofResponse.nonce == w3Qty(2.uint64)
|
|
proofResponse.storageHash == hash32"0x2ed06ec37dad4cd8c8fc1a1172d633a8973987fa6995b14a7c0a50c0e8d1a9c3"
|
|
storageProof.len() == 1
|
|
verifySlotProof(proofResponse.storageHash, storageProof[0]).isValid()
|
|
|
|
env.close()
|
|
|
|
when isMainModule:
|
|
rpcMain()
|