diff --git a/.github/workflows/kurtosis.yml b/.github/workflows/kurtosis.yml index 3eaf3ec59..8f7974d4c 100644 --- a/.github/workflows/kurtosis.yml +++ b/.github/workflows/kurtosis.yml @@ -83,7 +83,7 @@ jobs: cat kurtosis-network-params.yml | envsubst > assertoor.yaml sed -i "s/el_image: .*/el_image: localtestnet/" assertoor.yaml - kurtosis run github.com/ethpandaops/ethereum-package@4.3.0 --enclave assertoor-${{ github.run_id }} --args-file assertoor.yaml + kurtosis run github.com/ethpandaops/ethereum-package --enclave assertoor-${{ github.run_id }} --args-file assertoor.yaml enclave_dump=$(kurtosis enclave inspect assertoor-${{ github.run_id }}) diff --git a/hive_integration/nodocker/engine/engine_env.nim b/hive_integration/nodocker/engine/engine_env.nim index 1b6d1e6dc..e3a95cc66 100644 --- a/hive_integration/nodocker/engine/engine_env.nim +++ b/hive_integration/nodocker/engine/engine_env.nim @@ -109,7 +109,7 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E beaconEngine = BeaconEngineRef.new(txPool, chain) serverApi = newServerAPI(chain, txPool) - setupServerAPI(serverApi, server) + setupServerAPI(serverApi, server, ctx) setupEngineAPI(beaconEngine, server) # temporary disabled #setupDebugRpc(com, txPool, server) diff --git a/hive_integration/nodocker/pyspec/test_env.nim b/hive_integration/nodocker/pyspec/test_env.nim index e8dd7012f..9e41881de 100644 --- a/hive_integration/nodocker/pyspec/test_env.nim +++ b/hive_integration/nodocker/pyspec/test_env.nim @@ -59,7 +59,7 @@ proc setupELClient*(conf: ChainConfig, node: JsonNode): TestEnv = rpcServer = newRpcHttpServer(["127.0.0.1:0"]) rpcClient = newRpcHttpClient() - setupServerAPI(serverApi, rpcServer) + setupServerAPI(serverApi, rpcServer, newEthContext()) setupEngineAPI(beaconEngine, rpcServer) rpcServer.start() diff --git a/hive_integration/nodocker/rpc/test_env.nim b/hive_integration/nodocker/rpc/test_env.nim index 519f114d2..dc22fb78a 100644 --- a/hive_integration/nodocker/rpc/test_env.nim +++ b/hive_integration/nodocker/rpc/test_env.nim @@ -16,8 +16,7 @@ import ../../../nimbus/common, ../../../nimbus/config, ../../../nimbus/rpc, - ../../../nimbus/rpc/oracle, - ../../../nimbus/rpc/p2p, + ../../../nimbus/rpc/server_api, ../../../nimbus/utils/utils, ../../../nimbus/core/[chain, tx_pool], ../../../tests/test_helpers, @@ -46,11 +45,14 @@ proc manageAccounts(ctx: EthContext, conf: NimbusConf) = proc setupRpcServer(ctx: EthContext, com: CommonRef, ethNode: EthereumNode, txPool: TxPoolRef, - conf: NimbusConf): RpcServer = - let rpcServer = newRpcHttpServer([initTAddress(conf.httpAddress, conf.httpPort)]) - let oracle = Oracle.new(com) + conf: NimbusConf, chain: ForkedChainRef): RpcServer = + let + rpcServer = newRpcHttpServer([initTAddress(conf.httpAddress, conf.httpPort)]) + serverApi = newServerAPI(chain, txPool) + + setupCommonRpc(ethNode, conf, rpcServer) - setupEthRpc(ethNode, ctx, com, txPool, oracle, rpcServer) + setupServerAPI(serverApi, rpcServer, ctx) rpcServer.start() rpcServer @@ -92,7 +94,7 @@ proc setupEnv*(): TestEnv = # so it can know the latest account state doAssert txPool.smartHead(head, chainRef) - let rpcServer = setupRpcServer(ethCtx, com, ethNode, txPool, conf) + let rpcServer = setupRpcServer(ethCtx, com, ethNode, txPool, conf, chainRef) let rpcClient = newRpcHttpClient() waitFor rpcClient.connect("127.0.0.1", Port(8545), false) let stopServer = stopRpcHttpServer diff --git a/kurtosis-network-params.yml b/kurtosis-network-params.yml index af4052d4f..61d2455c7 100644 --- a/kurtosis-network-params.yml +++ b/kurtosis-network-params.yml @@ -14,7 +14,7 @@ participants: el_extra_params: ["--log-level=DEBUG"] cl_type: nimbus cl_image: statusim/nimbus-eth2:multiarch-latest - cl_extra_params: ["--log-level=DEBUG;INFO:gossip_eth2,attpool,libp2p,gossipsub,pubsubpeer,pubsub,switch,networking,sync,dialer,identify,syncman,connmanager,beacnde,lightcl,requman,gossip_lc,clearance,lpstream,mplexchannel,nodes-verification,tcptransport,chaindag,noise,eth,p2p,discv5,muxedupgrade,multistream,connection,secure,fee_recipient,mplex,syncpool,multiaddress,peer_proto;WARN:message_router"] + cl_extra_params: ["--log-level=DEBUG"] use_separate_vc: false additional_services: - tx_spammer @@ -23,7 +23,7 @@ additional_services: - blob_spammer mev_type: null assertoor_params: - image: "ethpandaops/assertoor:latest" + image: "ethpandaops/assertoor" run_stability_check: false run_block_proposal_check: true run_transaction_test: true diff --git a/nimbus/beacon/api_handler/api_newpayload.nim b/nimbus/beacon/api_handler/api_newpayload.nim index 7a50b5ab6..e06e73db3 100644 --- a/nimbus/beacon/api_handler/api_newpayload.nim +++ b/nimbus/beacon/api_handler/api_newpayload.nim @@ -192,6 +192,11 @@ proc newPayload*(ben: BeaconEngineRef, hash = blockHash, number = header.number let vres = ben.chain.importBlock(blk) if vres.isErr: + warn "Error importing block", + number = header.number, + hash = blockHash.short, + parent = header.parentHash.short, + error = vres.error() ben.setInvalidAncestor(header, blockHash) let blockHash = latestValidHash(db, parent, ttd) return invalidStatus(blockHash, vres.error()) diff --git a/nimbus/core/chain/forked_chain.nim b/nimbus/core/chain/forked_chain.nim index 19fa57585..b3142dac0 100644 --- a/nimbus/core/chain/forked_chain.nim +++ b/nimbus/core/chain/forked_chain.nim @@ -164,7 +164,7 @@ proc validateBlock(c: ForkedChainRef, ok() -proc replaySegment(c: ForkedChainRef, target: Hash32) = +proc replaySegment*(c: ForkedChainRef, target: Hash32) = # Replay from base+1 to target block var prevHash = target @@ -635,9 +635,18 @@ func baseHash*(c: ForkedChainRef): Hash32 = func txRecords*(c: ForkedChainRef, txHash: Hash32): (Hash32, uint64) = c.txRecords.getOrDefault(txHash, (Hash32.default, 0'u64)) +func isInMemory*(c: ForkedChainRef, blockHash: Hash32): bool = + c.blocks.hasKey(blockHash) + func memoryBlock*(c: ForkedChainRef, blockHash: Hash32): BlockDesc = c.blocks.getOrDefault(blockHash) +func memoryTransaction*(c: ForkedChainRef, txHash: Hash32): Opt[Transaction] = + let (blockHash, index) = c.txRecords.getOrDefault(txHash, (Hash32.default, 0'u64)) + c.blocks.withValue(blockHash, val) do: + return Opt.some(val.blk.transactions[index]) + return Opt.none(Transaction) + proc latestBlock*(c: ForkedChainRef): Block = c.blocks.withValue(c.cursorHash, val) do: return val.blk diff --git a/nimbus/rpc.nim b/nimbus/rpc.nim index 791341d14..c329b693e 100644 --- a/nimbus/rpc.nim +++ b/nimbus/rpc.nim @@ -53,7 +53,7 @@ func installRPC(server: RpcServer, setupCommonRpc(nimbus.ethNode, conf, server) if RpcFlag.Eth in flags: - setupServerAPI(serverApi, server) + setupServerAPI(serverApi, server, nimbus.ctx) # # Tracer is currently disabled # if RpcFlag.Debug in flags: diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim deleted file mode 100644 index 7588787a7..000000000 --- a/nimbus/rpc/p2p.nim +++ /dev/null @@ -1,607 +0,0 @@ -# Nimbus -# Copyright (c) 2018-2024 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) -# * MIT license ([LICENSE-MIT](LICENSE-MIT)) -# at your option. -# This file may not be copied, modified, or distributed except according to -# those terms. - -{.push raises: [].} - -import - std/[sequtils, times, tables, typetraits], - json_rpc/rpcserver, - stint, - stew/byteutils, - json_serialization, - web3/conversions, - json_serialization/stew/results, - eth/common/eth_types_json_serialization, - eth/[rlp, p2p], - ".."/[transaction, evm/state, constants], - ../db/ledger, - ./rpc_types, ./rpc_utils, ./oracle, - ../transaction/call_evm, - ../core/tx_pool, - ../core/eip4844, - ../common/[common, context], - ../utils/utils, - ../beacon/web3_eth_conv, - ../evm/evm_errors, - ./filters - -type - Header = eth_types.Header - Hash32 = eth_types.Hash32 - -proc getProof*( - accDB: LedgerRef, - address: eth_types.Address, - slots: seq[UInt256]): ProofResponse = - let - acc = accDB.getEthAccount(address) - accExists = accDB.accountExists(address) - accountProof = accDB.getAccountProof(address) - slotProofs = accDB.getStorageProof(address, slots) - - var storage = newSeqOfCap[StorageProof](slots.len) - - for i, slotKey in slots: - let slotValue = accDB.getStorage(address, slotKey) - storage.add(StorageProof( - key: slotKey, - value: slotValue, - proof: seq[RlpEncodedBytes](slotProofs[i]))) - - if accExists: - ProofResponse( - address: address, - accountProof: seq[RlpEncodedBytes](accountProof), - balance: acc.balance, - nonce: w3Qty(acc.nonce), - codeHash: acc.codeHash, - storageHash: acc.storageRoot, - storageProof: storage) - else: - ProofResponse( - address: address, - accountProof: seq[RlpEncodedBytes](accountProof), - storageProof: storage) - -proc setupEthRpc*( - node: EthereumNode, ctx: EthContext, com: CommonRef, - txPool: TxPoolRef, oracle: Oracle, server: RpcServer) = - - let chainDB = com.db - proc getStateDB(header:Header): LedgerRef = - ## Retrieves the account db from canonical head - # we don't use accounst_cache here because it's only read operations - LedgerRef.init(chainDB) - - proc stateDBFromTag(quantityTag: BlockTag, readOnly = true): LedgerRef - {.gcsafe, raises: [CatchableError].} = - getStateDB(chainDB.headerFromTag(quantityTag)) - - server.rpc("eth_chainId") do() -> Web3Quantity: - return w3Qty(distinctBase(com.chainId)) - - server.rpc("eth_syncing") do() -> SyncingStatus: - ## Returns SyncObject or false when not syncing. - if com.syncState != Waiting: - let sync = SyncObject( - startingBlock: w3Qty com.syncStart, - currentBlock : w3Qty com.syncCurrent, - highestBlock : w3Qty com.syncHighest - ) - return SyncingStatus(syncing: true, syncObject: sync) - else: - return SyncingStatus(syncing: false) - - server.rpc("eth_gasPrice") do() -> Web3Quantity: - ## Returns an integer of the current gas price in wei. - w3Qty(calculateMedianGasPrice(chainDB).uint64) - - server.rpc("eth_accounts") do() -> seq[eth_types.Address]: - ## Returns a list of addresses owned by client. - result = newSeqOfCap[eth_types.Address](ctx.am.numAccounts) - for k in ctx.am.addresses: - result.add k - - server.rpc("eth_blockNumber") do() -> Web3Quantity: - ## Returns integer of the current block number the client is on. - w3Qty(chainDB.getCanonicalHead().number) - - server.rpc("eth_getBalance") do(data: eth_types.Address, quantityTag: BlockTag) -> UInt256: - ## Returns the balance of the account of given address. - ## - ## data: address to check for balance. - ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns integer of the current balance in wei. - let - accDB = stateDBFromTag(quantityTag) - address = data - accDB.getBalance(address) - - server.rpc("eth_getStorageAt") do(data: eth_types.Address, slot: UInt256, quantityTag: BlockTag) -> eth_types.FixedBytes[32]: - ## Returns the value from a storage position at a given address. - ## - ## data: address of the storage. - ## slot: integer of the position in the storage. - ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns: the value at this storage position. - let - accDB = stateDBFromTag(quantityTag) - address = data - data = accDB.getStorage(address, slot) - data.to(Bytes32) - - server.rpc("eth_getTransactionCount") do(data: eth_types.Address, quantityTag: BlockTag) -> Web3Quantity: - ## Returns the number of transactions sent from an address. - ## - ## data: address. - ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns integer of the number of transactions send from this address. - let - address = data - accDB = stateDBFromTag(quantityTag) - w3Qty(accDB.getNonce(address)) - - server.rpc("eth_getBlockTransactionCountByHash") do(data: Hash32) -> Web3Quantity: - ## Returns the number of transactions in a block from a block matching the given block hash. - ## - ## data: hash of a block - ## Returns integer of the number of transactions in this block. - let - blockHash = data - header = chainDB.getBlockHeader(blockHash) - txCount = chainDB.getTransactionCount(header.txRoot) - Web3Quantity(txCount) - - server.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: BlockTag) -> Web3Quantity: - ## Returns the number of transactions in a block matching the given block number. - ## - ## data: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. - ## Returns integer of the number of transactions in this block. - let - header = chainDB.headerFromTag(quantityTag) - txCount = chainDB.getTransactionCount(header.txRoot) - Web3Quantity(txCount) - - server.rpc("eth_getUncleCountByBlockHash") do(data: Hash32) -> Web3Quantity: - ## Returns the number of uncles in a block from a block matching the given block hash. - ## - ## data: hash of a block. - ## Returns integer of the number of uncles in this block. - let - blockHash = data - header = chainDB.getBlockHeader(blockHash) - unclesCount = chainDB.getUnclesCount(header.ommersHash) - Web3Quantity(unclesCount) - - server.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: BlockTag) -> Web3Quantity: - ## Returns the number of uncles in a block from a block matching the given block number. - ## - ## quantityTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns integer of uncles in this block. - let - header = chainDB.headerFromTag(quantityTag) - unclesCount = chainDB.getUnclesCount(header.ommersHash) - Web3Quantity(unclesCount) - - server.rpc("eth_getCode") do(data: eth_types.Address, quantityTag: BlockTag) -> seq[byte]: - ## Returns code at a given address. - ## - ## data: address - ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns the code from the given address. - let - accDB = stateDBFromTag(quantityTag) - address = data - accDB.getCode(address).bytes() - - template sign(privateKey: PrivateKey, message: string): seq[byte] = - # message length encoded as ASCII representation of decimal - let msgData = "\x19Ethereum Signed Message:\n" & $message.len & message - @(sign(privateKey, msgData.toBytes()).toRaw()) - - server.rpc("eth_sign") do(data: eth_types.Address, message: seq[byte]) -> seq[byte]: - ## The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))). - ## By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature. - ## This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim. - ## Note the address to sign with must be unlocked. - ## - ## data: address. - ## message: message to sign. - ## Returns signature. - let - address = data - acc = ctx.am.getAccount(address).tryGet() - - if not acc.unlocked: - raise newException(ValueError, "Account locked, please unlock it first") - sign(acc.privateKey, cast[string](message)) - - server.rpc("eth_signTransaction") do(data: TransactionArgs) -> seq[byte]: - ## Signs a transaction that can be submitted to the network at a later time using with - ## eth_sendRawTransaction - let - address = data.`from`.get() - acc = ctx.am.getAccount(address).tryGet() - - if not acc.unlocked: - raise newException(ValueError, "Account locked, please unlock it first") - - let - accDB = stateDBFromTag(blockId("latest")) - tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1, com.chainId) - eip155 = com.isEIP155(com.syncCurrent) - signedTx = signTransaction(tx, acc.privateKey, eip155) - result = rlp.encode(signedTx) - - server.rpc("eth_sendTransaction") do(data: TransactionArgs) -> Hash32: - ## 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. - let - address = data.`from`.get() - acc = ctx.am.getAccount(address).tryGet() - - if not acc.unlocked: - raise newException(ValueError, "Account locked, please unlock it first") - - let - accDB = stateDBFromTag(blockId("latest")) - tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1, com.chainId) - eip155 = com.isEIP155(com.syncCurrent) - signedTx = signTransaction(tx, acc.privateKey, eip155) - networkPayload = - if signedTx.txType == TxEip4844: - if data.blobs.isNone or data.commitments.isNone or data.proofs.isNone: - raise newException(ValueError, "EIP-4844 transaction needs blobs") - if data.blobs.get.len != signedTx.versionedHashes.len: - raise newException(ValueError, "Incorrect number of blobs") - if data.commitments.get.len != signedTx.versionedHashes.len: - raise newException(ValueError, "Incorrect number of commitments") - if data.proofs.get.len != signedTx.versionedHashes.len: - raise newException(ValueError, "Incorrect number of proofs") - NetworkPayload( - blobs: data.blobs.get.mapIt it.NetworkBlob, - commitments: data.commitments.get, - proofs: data.proofs.get) - else: - if data.blobs.isSome or data.commitments.isSome or data.proofs.isSome: - raise newException(ValueError, "Blobs require EIP-4844 transaction") - nil - pooledTx = PooledTransaction(tx: signedTx, networkPayload: networkPayload) - - txPool.add(pooledTx) - rlpHash(signedTx) - - server.rpc("eth_sendRawTransaction") do(txBytes: seq[byte]) -> Hash32: - ## 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. - let - pooledTx = decodePooledTx(txBytes) - txHash = rlpHash(pooledTx) - - txPool.add(pooledTx) - let res = txPool.inPoolAndReason(txHash) - if res.isErr: - raise newException(ValueError, res.error) - txHash - - server.rpc("eth_call") do(args: TransactionArgs, quantityTag: BlockTag) -> seq[byte]: - ## Executes a new message call immediately without creating a transaction on the block chain. - ## - ## 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(chainDB, quantityTag) - res = rpcCallEvm(args, header, com).valueOr: - raise newException(ValueError, "rpcCallEvm error: " & $error.code) - res.output - - server.rpc("eth_estimateGas") do(args: TransactionArgs) -> Web3Quantity: - ## 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 - ## the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance. - ## - ## args: the transaction call object. - ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns the amount of gas used. - let - header = chainDB.headerFromTag(blockId("latest")) - # TODO: DEFAULT_RPC_GAS_CAP should configurable - gasUsed = rpcEstimateGas(args, header, com, DEFAULT_RPC_GAS_CAP).valueOr: - raise newException(ValueError, "rpcEstimateGas error: " & $error.code) - w3Qty(gasUsed) - - server.rpc("eth_getBlockByHash") do(data: Hash32, fullTransactions: bool) -> BlockObject: - ## Returns information about a block by hash. - ## - ## data: Hash of a block. - ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. - ## Returns BlockObject or nil when no block was found. - var - header:Header - hash = data - - if chainDB.getBlockHeader(hash, header): - populateBlockObject(header, chainDB, fullTransactions) - else: - nil - - server.rpc("eth_getBlockByNumber") do(quantityTag: BlockTag, fullTransactions: bool) -> BlockObject: - ## Returns information about a block by block number. - ## - ## quantityTag: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. - ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. - ## Returns BlockObject or nil when no block was found. - try: - let header = chainDB.headerFromTag(quantityTag) - populateBlockObject(header, chainDB, fullTransactions) - except CatchableError: - nil - - server.rpc("eth_getTransactionByHash") do(data: Hash32) -> TransactionObject: - ## Returns the information about a transaction requested by transaction hash. - ## - ## data: hash of a transaction. - ## Returns requested transaction information. - let txHash = data - let res = txPool.getItem(txHash) - if res.isOk: - return populateTransactionObject(res.get().tx) - - let txDetails = chainDB.getTransactionKey(txHash) - if txDetails.index < 0: - return nil - - let header = chainDB.getBlockHeader(txDetails.blockNumber) - var tx: Transaction - if chainDB.getTransactionByIndex(header.txRoot, uint16(txDetails.index), tx): - result = populateTransactionObject(tx, Opt.some(header), Opt.some(txDetails.index)) - - server.rpc("eth_getTransactionByBlockHashAndIndex") do(data: Hash32, quantity: Web3Quantity) -> TransactionObject: - ## Returns information about a transaction by block hash and transaction index position. - ## - ## data: hash of a block. - ## quantity: integer of the transaction index position. - ## Returns requested transaction information. - let index = uint64(quantity) - var header:Header - if not chainDB.getBlockHeader(data, header): - return nil - - var tx: Transaction - if chainDB.getTransactionByIndex(header.txRoot, uint16(index), tx): - populateTransactionObject(tx, Opt.some(header), Opt.some(index)) - else: - nil - - server.rpc("eth_getTransactionByBlockNumberAndIndex") do(quantityTag: BlockTag, quantity: Web3Quantity) -> TransactionObject: - ## Returns information about a transaction by block number and transaction index position. - ## - ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. - ## quantity: the transaction index position. - let - header = chainDB.headerFromTag(quantityTag) - index = uint64(quantity) - - var tx: Transaction - if chainDB.getTransactionByIndex(header.txRoot, uint16(index), tx): - populateTransactionObject(tx, Opt.some(header), Opt.some(index)) - else: - nil - - server.rpc("eth_getTransactionReceipt") do(data: Hash32) -> ReceiptObject: - ## Returns the receipt of a transaction by transaction hash. - ## - ## data: hash of a transaction. - ## Returns transaction receipt. - - let txDetails = chainDB.getTransactionKey(data) - if txDetails.index < 0: - return nil - - let header = chainDB.getBlockHeader(txDetails.blockNumber) - var tx: Transaction - if not chainDB.getTransactionByIndex(header.txRoot, uint16(txDetails.index), tx): - return nil - - var - idx = 0'u64 - prevGasUsed = GasInt(0) - - for receipt in chainDB.getReceipts(header.receiptsRoot): - let gasUsed = receipt.cumulativeGasUsed - prevGasUsed - prevGasUsed = receipt.cumulativeGasUsed - if idx == txDetails.index: - return populateReceipt(receipt, gasUsed, tx, txDetails.index, header) - idx.inc - - server.rpc("eth_getUncleByBlockHashAndIndex") do(data: Hash32, quantity: Web3Quantity) -> BlockObject: - ## Returns information about a uncle of a block by hash and uncle index position. - ## - ## data: hash of block. - ## quantity: the uncle's index position. - ## Returns BlockObject or nil when no block was found. - let index = uint64(quantity) - var header:Header - if not chainDB.getBlockHeader(data, header): - return nil - - let uncles = chainDB.getUncles(header.ommersHash) - if index < 0 or index >= uncles.len.uint64: - return nil - - result = populateBlockObject(uncles[index], chainDB, false, true) - result.totalDifficulty = chainDB.getScore(header.blockHash).valueOr(0.u256) - - server.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: BlockTag, quantity: Web3Quantity) -> BlockObject: - # Returns information about a uncle of a block by number and uncle index position. - ## - ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. - ## quantity: the uncle's index position. - ## Returns BlockObject or nil when no block was found. - let - index = uint64(quantity) - header = chainDB.headerFromTag(quantityTag) - uncles = chainDB.getUncles(header.ommersHash) - - if index < 0 or index >= uncles.len.uint64: - return nil - - result = populateBlockObject(uncles[index], chainDB, false, true) - result.totalDifficulty = chainDB.getScore(header.blockHash).valueOr(0.u256) - - proc getLogsForBlock( - chain: CoreDbRef, - hash: Hash32, - header:Header, - opts: FilterOptions): seq[FilterLog] - {.gcsafe, raises: [RlpError,BlockNotFound].} = - if headerBloomFilter(header, opts.address, opts.topics): - let blockBody = chain.getBlockBody(hash) - let receipts = chain.getReceipts(header.receiptsRoot) - # Note: this will hit assertion error if number of block transactions - # do not match block receipts. - # Although this is fine as number of receipts should always match number - # of transactions - let logs = deriveLogs(header, blockBody.transactions, receipts) - let filteredLogs = filterLogs(logs, opts.address, opts.topics) - return filteredLogs - else: - return @[] - - proc getLogsForRange( - chain: CoreDbRef, - start: common.BlockNumber, - finish: common.BlockNumber, - opts: FilterOptions): seq[FilterLog] - {.gcsafe, raises: [RlpError,BlockNotFound].} = - var logs = newSeq[FilterLog]() - var i = start - while i <= finish: - let res = chain.getBlockHeaderWithHash(i) - if res.isSome(): - let (hash, header)= res.unsafeGet() - let filtered = chain.getLogsForBlock(header, hash, opts) - logs.add(filtered) - else: - # - return logs - i = i + 1 - return logs - - server.rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[FilterLog]: - ## filterOptions: settings for this filter. - ## Returns a list of all logs matching a given filter object. - ## TODO: Current implementation is pretty naive and not efficient - ## as it requires to fetch all transactions and all receipts from database. - ## Other clients (Geth): - ## - Store logs related data in receipts. - ## - Have separate indexes for Logs in given block - ## Both of those changes require improvements to the way how we keep our data - ## in Nimbus. - if filterOptions.blockHash.isSome(): - let hash = filterOptions.blockHash.unsafeGet() - let header = chainDB.getBlockHeader(hash) - return getLogsForBlock(chainDB, hash, header, filterOptions) - else: - # TODO: do something smarter with tags. It would be the best if - # tag would be an enum (Earliest, Latest, Pending, Number), and all operations - # would operate on this enum instead of raw strings. This change would need - # to be done on every endpoint to be consistent. - let fromHeader = chainDB.headerFromTag(filterOptions.fromBlock) - let toHeader = chainDB.headerFromTag(filterOptions.toBlock) - - # Note: if fromHeader.number > toHeader.number, no logs will be - # returned. This is consistent with, what other ethereum clients return - let logs = chainDB.getLogsForRange( - fromHeader.number, - toHeader.number, - filterOptions - ) - return logs - - server.rpc("eth_getProof") do(data: eth_types.Address, slots: seq[UInt256], quantityTag: BlockTag) -> ProofResponse: - ## Returns information about an account and storage slots (if the account is a contract - ## and the slots are requested) along with account and storage proofs which prove the - ## existence of the values in the state. - ## See spec here: https://eips.ethereum.org/EIPS/eip-1186 - ## - ## data: address of the account. - ## slots: integers of the positions in the storage to return with storage proofs. - ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. - ## Returns: the proof response containing the account, account proof and storage proof - - let - accDB = stateDBFromTag(quantityTag) - address = data - - getProof(accDB, address, slots) - - server.rpc("eth_getBlockReceipts") do(quantityTag: BlockTag) -> Opt[seq[ReceiptObject]]: - ## Returns the receipts of a block. - try: - let header = chainDB.headerFromTag(quantityTag) - var - prevGasUsed = GasInt(0) - recs: seq[ReceiptObject] - txs: seq[Transaction] - index = 0'u64 - - for tx in chainDB.getBlockTransactions(header): - txs.add tx - - for receipt in chainDB.getReceipts(header.receiptsRoot): - let gasUsed = receipt.cumulativeGasUsed - prevGasUsed - prevGasUsed = receipt.cumulativeGasUsed - recs.add populateReceipt(receipt, gasUsed, txs[index], index, header) - inc index - - return Opt.some(recs) - except CatchableError: - return Opt.none(seq[ReceiptObject]) - - server.rpc("eth_createAccessList") do(args: TransactionArgs, quantityTag: BlockTag) -> AccessListResult: - ## Generates an access list for a transaction. - try: - let - header = chainDB.headerFromTag(quantityTag) - return createAccessList(header, com, args) - except CatchableError as exc: - return AccessListResult( - error: Opt.some("createAccessList error: " & exc.msg), - ) - - server.rpc("eth_blobBaseFee") do() -> Web3Quantity: - ## Returns the base fee per blob gas in wei. - let header = chainDB.headerFromTag(blockId("latest")) - if header.blobGasUsed.isNone: - raise newException(ValueError, "blobGasUsed missing from latest header") - if header.excessBlobGas.isNone: - raise newException(ValueError, "excessBlobGas missing from latest header") - let blobBaseFee = getBlobBaseFee(header.excessBlobGas.get) * header.blobGasUsed.get.u256 - if blobBaseFee > high(uint64).u256: - raise newException(ValueError, "blobBaseFee is bigger than uint64.max") - return w3Qty blobBaseFee.truncate(uint64) - - server.rpc("eth_feeHistory") do(blockCount: Quantity, - newestBlock: BlockTag, - rewardPercentiles: Opt[seq[float64]]) -> FeeHistoryResult: - let - blocks = blockCount.uint64 - percentiles = rewardPercentiles.get(newSeq[float64]()) - res = feeHistory(oracle, blocks, newestBlock, percentiles) - if res.isErr: - raise newException(ValueError, res.error) - return res.get diff --git a/nimbus/rpc/rpc_utils.nim b/nimbus/rpc/rpc_utils.nim index 73d275ce9..2e49a6b14 100644 --- a/nimbus/rpc/rpc_utils.nim +++ b/nimbus/rpc/rpc_utils.nim @@ -10,7 +10,7 @@ {.push raises: [].} import - std/[strutils, algorithm], + std/[sequtils, algorithm], ./rpc_types, ./params, ../db/core_db, @@ -29,37 +29,6 @@ import ../common/common, web3/eth_api_types -const - defaultTag = blockId("latest") - -proc headerFromTag*(chain: CoreDbRef, blockId: BlockTag): Header - {.gcsafe, raises: [CatchableError].} = - - if blockId.kind == bidAlias: - let tag = blockId.alias.toLowerAscii - case tag - of "latest": result = chain.getCanonicalHead() - of "earliest": result = chain.getBlockHeader(GENESIS_BLOCK_NUMBER) - of "safe": result = chain.safeHeader() - of "finalized": result = chain.finalizedHeader() - of "pending": - #TODO: Implement get pending block - # We currently fall back to `latest` so that the `tx-spammer` in - # `ethpandaops/ethereum-package` can make progress. A real - # implementation is still required that takes into account any - # pending transactions that have not yet been bundled into a block. - result = chain.getCanonicalHead() - else: - raise newException(ValueError, "Unsupported block tag " & tag) - else: - let blockNum = blockId.number.uint64 - result = chain.getBlockHeader(blockNum) - -proc headerFromTag*(chain: CoreDbRef, blockTag: Opt[BlockTag]): Header - {.gcsafe, raises: [CatchableError].} = - let blockId = blockTag.get(defaultTag) - chain.headerFromTag(blockId) - proc calculateMedianGasPrice*(chain: CoreDbRef): GasInt {.gcsafe, raises: [CatchableError].} = var prices = newSeqOfCap[GasInt](64) @@ -90,31 +59,36 @@ proc calculateMedianGasPrice*(chain: CoreDbRef): GasInt proc unsignedTx*(tx: TransactionArgs, chain: CoreDbRef, defaultNonce: AccountNonce, chainId: ChainId): Transaction {.gcsafe, raises: [CatchableError].} = + + var res: Transaction + if tx.to.isSome: - result.to = Opt.some(tx.to.get) + res.to = Opt.some(tx.to.get) if tx.gas.isSome: - result.gasLimit = tx.gas.get.GasInt + res.gasLimit = tx.gas.get.GasInt else: - result.gasLimit = 90000.GasInt + res.gasLimit = 90000.GasInt if tx.gasPrice.isSome: - result.gasPrice = tx.gasPrice.get.GasInt + res.gasPrice = tx.gasPrice.get.GasInt else: - result.gasPrice = calculateMedianGasPrice(chain) + res.gasPrice = calculateMedianGasPrice(chain) if tx.value.isSome: - result.value = tx.value.get + res.value = tx.value.get else: - result.value = 0.u256 + res.value = 0.u256 if tx.nonce.isSome: - result.nonce = tx.nonce.get.AccountNonce + res.nonce = tx.nonce.get.AccountNonce else: - result.nonce = defaultNonce + res.nonce = defaultNonce - result.payload = tx.payload - result.chainId = chainId + res.payload = tx.payload + res.chainId = chainId + + return res proc toWd(wd: Withdrawal): WithdrawalObject = WithdrawalObject( @@ -125,116 +99,123 @@ proc toWd(wd: Withdrawal): WithdrawalObject = ) proc toWdList(list: openArray[Withdrawal]): seq[WithdrawalObject] = - result = newSeqOfCap[WithdrawalObject](list.len) + var res = newSeqOfCap[WithdrawalObject](list.len) for x in list: - result.add toWd(x) + res.add toWd(x) + return res + +func toWdList(x: Opt[seq[eth_types.Withdrawal]]): + Opt[seq[WithdrawalObject]] = + if x.isNone: Opt.none(seq[WithdrawalObject]) + else: Opt.some(toWdList x.get) proc populateTransactionObject*(tx: Transaction, - optionalHeader: Opt[Header] = Opt.none(Header), + optionalHash: Opt[eth_types.Hash32] = Opt.none(eth_types.Hash32), + optionalNumber: Opt[eth_types.BlockNumber] = Opt.none(eth_types.BlockNumber), txIndex: Opt[uint64] = Opt.none(uint64)): TransactionObject = - result = TransactionObject() - result.`type` = Opt.some Quantity(tx.txType) - if optionalHeader.isSome: - let header = optionalHeader.get - result.blockHash = Opt.some(header.blockHash) - result.blockNumber = Opt.some(Quantity(header.number)) + var res = TransactionObject() + res.`type` = Opt.some Quantity(tx.txType) + res.blockHash = optionalHash + res.blockNumber = w3Qty(optionalNumber) if (let sender = tx.recoverSender(); sender.isOk): - result.`from` = sender[] - result.gas = Quantity(tx.gasLimit) - result.gasPrice = Quantity(tx.gasPrice) - result.hash = tx.rlpHash - result.input = tx.payload - result.nonce = Quantity(tx.nonce) - result.to = Opt.some(tx.destination) + res.`from` = sender[] + res.gas = Quantity(tx.gasLimit) + res.gasPrice = Quantity(tx.gasPrice) + res.hash = tx.rlpHash + res.input = tx.payload + res.nonce = Quantity(tx.nonce) + res.to = Opt.some(tx.destination) if txIndex.isSome: - result.transactionIndex = Opt.some(Quantity(txIndex.get)) - result.value = tx.value - result.v = Quantity(tx.V) - result.r = tx.R - result.s = tx.S - result.maxFeePerGas = Opt.some Quantity(tx.maxFeePerGas) - result.maxPriorityFeePerGas = Opt.some Quantity(tx.maxPriorityFeePerGas) + res.transactionIndex = Opt.some(Quantity(txIndex.get)) + res.value = tx.value + res.v = Quantity(tx.V) + res.r = tx.R + res.s = tx.S + res.maxFeePerGas = Opt.some Quantity(tx.maxFeePerGas) + res.maxPriorityFeePerGas = Opt.some Quantity(tx.maxPriorityFeePerGas) if tx.txType >= TxEip2930: - result.chainId = Opt.some(Quantity(tx.chainId)) - result.accessList = Opt.some(tx.accessList) + res.chainId = Opt.some(Quantity(tx.chainId)) + res.accessList = Opt.some(tx.accessList) if tx.txType >= TxEip4844: - result.maxFeePerBlobGas = Opt.some(tx.maxFeePerBlobGas) - result.blobVersionedHashes = Opt.some(tx.versionedHashes) + res.maxFeePerBlobGas = Opt.some(tx.maxFeePerBlobGas) + res.blobVersionedHashes = Opt.some(tx.versionedHashes) -proc populateBlockObject*(header: Header, chain: CoreDbRef, fullTx: bool, isUncle = false): BlockObject - {.gcsafe, raises: [RlpError].} = - let blockHash = header.blockHash - result = BlockObject() + return res - result.number = Quantity(header.number) - result.hash = blockHash - result.parentHash = header.parentHash - result.nonce = Opt.some(FixedBytes[8] header.nonce) - result.sha3Uncles = header.ommersHash - result.logsBloom = FixedBytes[256] header.logsBloom - result.transactionsRoot = header.txRoot - result.stateRoot = header.stateRoot - result.receiptsRoot = header.receiptsRoot - result.miner = header.coinbase - result.difficulty = header.difficulty - result.extraData = HistoricExtraData header.extraData - result.mixHash = Hash32 header.mixHash +proc populateBlockObject*(blockHash: Hash32, + blk: Block, + totalDifficulty: UInt256, + fullTx: bool, + isUncle = false): BlockObject = + template header: auto = blk.header + + var res = BlockObject() + res.number = Quantity(header.number) + res.hash = blockHash + res.parentHash = header.parentHash + res.nonce = Opt.some(header.nonce) + res.sha3Uncles = header.ommersHash + res.logsBloom = header.logsBloom + res.transactionsRoot = header.txRoot + res.stateRoot = header.stateRoot + res.receiptsRoot = header.receiptsRoot + res.miner = header.coinbase + res.difficulty = header.difficulty + res.extraData = HistoricExtraData header.extraData + res.mixHash = Hash32 header.mixHash # discard sizeof(seq[byte]) of extraData and use actual length let size = sizeof(Header) - sizeof(seq[byte]) + header.extraData.len - result.size = Quantity(size) + res.size = Quantity(size) - result.gasLimit = Quantity(header.gasLimit) - result.gasUsed = Quantity(header.gasUsed) - result.timestamp = Quantity(header.timestamp) - result.baseFeePerGas = header.baseFeePerGas + res.gasLimit = Quantity(header.gasLimit) + res.gasUsed = Quantity(header.gasUsed) + res.timestamp = Quantity(header.timestamp) + res.baseFeePerGas = header.baseFeePerGas + res.totalDifficulty = totalDifficulty if not isUncle: - result.totalDifficulty = chain.getScore(blockHash).valueOr(0.u256) - result.uncles = chain.getUncleHashes(header) + res.uncles = blk.uncles.mapIt(it.blockHash) if fullTx: - var i = 0'u64 - for tx in chain.getBlockTransactions(header): - result.transactions.add txOrHash(populateTransactionObject(tx, Opt.some(header), Opt.some(i))) - inc i + for i, tx in blk.transactions: + let txObj = populateTransactionObject(tx, + Opt.some(blockHash), + Opt.some(header.number), Opt.some(i.uint64)) + res.transactions.add txOrHash(txObj) else: - for x in chain.getBlockTransactionHashes(header): - result.transactions.add txOrHash(x) + for i, tx in blk.transactions: + let txHash = rlpHash(tx) + res.transactions.add txOrHash(txHash) - if header.withdrawalsRoot.isSome: - result.withdrawalsRoot = Opt.some(header.withdrawalsRoot.get) - result.withdrawals = Opt.some(toWdList(chain.getWithdrawals(header.withdrawalsRoot.get))) + res.withdrawalsRoot = header.withdrawalsRoot + res.withdrawals = toWdList blk.withdrawals + res.parentBeaconBlockRoot = header.parentBeaconBlockRoot + res.blobGasUsed = w3Qty(header.blobGasUsed) + res.excessBlobGas = w3Qty(header.excessBlobGas) - if header.blobGasUsed.isSome: - result.blobGasUsed = Opt.some(Quantity(header.blobGasUsed.get)) - - if header.excessBlobGas.isSome: - result.excessBlobGas = Opt.some(Quantity(header.excessBlobGas.get)) - - if header.parentBeaconBlockRoot.isSome: - result.parentBeaconBlockRoot = Opt.some(header.parentBeaconBlockRoot.get) + return res proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction, txIndex: uint64, header: Header): ReceiptObject = let sender = tx.recoverSender() - result = ReceiptObject() - result.transactionHash = tx.rlpHash - result.transactionIndex = Quantity(txIndex) - result.blockHash = header.blockHash - result.blockNumber = Quantity(header.number) + var res = ReceiptObject() + res.transactionHash = tx.rlpHash + res.transactionIndex = Quantity(txIndex) + res.blockHash = header.blockHash + res.blockNumber = Quantity(header.number) if sender.isSome(): - result.`from` = sender.get() - result.to = Opt.some(tx.destination) - result.cumulativeGasUsed = Quantity(receipt.cumulativeGasUsed) - result.gasUsed = Quantity(gasUsed) - result.`type` = Opt.some Quantity(receipt.receiptType) + res.`from` = sender.get() + res.to = Opt.some(tx.destination) + res.cumulativeGasUsed = Quantity(receipt.cumulativeGasUsed) + res.gasUsed = Quantity(gasUsed) + res.`type` = Opt.some Quantity(receipt.receiptType) if tx.contractCreation and sender.isSome: - result.contractAddress = Opt.some(tx.creationAddress(sender[])) + res.contractAddress = Opt.some(tx.creationAddress(sender[])) for log in receipt.logs: # TODO: Work everywhere with either `Hash32` as topic or `array[32, byte]` @@ -246,36 +227,38 @@ proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction, removed: false, # TODO: Not sure what is difference between logIndex and TxIndex and how # to calculate it. - logIndex: Opt.some(result.transactionIndex), + logIndex: Opt.some(res.transactionIndex), # Note: the next 4 fields cause a lot of duplication of data, but the spec # is what it is. Not sure if other clients actually add this. - transactionIndex: Opt.some(result.transactionIndex), - transactionHash: Opt.some(result.transactionHash), - blockHash: Opt.some(result.blockHash), - blockNumber: Opt.some(result.blockNumber), + transactionIndex: Opt.some(res.transactionIndex), + transactionHash: Opt.some(res.transactionHash), + blockHash: Opt.some(res.blockHash), + blockNumber: Opt.some(res.blockNumber), # The actual fields address: log.address, data: log.data, topics: topics ) - result.logs.add(logObject) + res.logs.add(logObject) - result.logsBloom = FixedBytes[256] receipt.logsBloom + res.logsBloom = FixedBytes[256] receipt.logsBloom # post-transaction stateroot (pre Byzantium). if receipt.hasStateRoot: - result.root = Opt.some(receipt.stateRoot) + res.root = Opt.some(receipt.stateRoot) else: # 1 = success, 0 = failure. - result.status = Opt.some(Quantity(receipt.status.uint64)) + res.status = Opt.some(Quantity(receipt.status.uint64)) let baseFeePerGas = header.baseFeePerGas.get(0.u256) let normTx = eip1559TxNormalization(tx, baseFeePerGas.truncate(GasInt)) - result.effectiveGasPrice = Quantity(normTx.gasPrice) + res.effectiveGasPrice = Quantity(normTx.gasPrice) if tx.txType == TxEip4844: - result.blobGasUsed = Opt.some(Quantity(tx.versionedHashes.len.uint64 * GAS_PER_BLOB.uint64)) - result.blobGasPrice = Opt.some(getBlobBaseFee(header.excessBlobGas.get(0'u64))) + res.blobGasUsed = Opt.some(Quantity(tx.versionedHashes.len.uint64 * GAS_PER_BLOB.uint64)) + res.blobGasPrice = Opt.some(getBlobBaseFee(header.excessBlobGas.get(0'u64))) + + return res proc createAccessList*(header: Header, com: CommonRef, @@ -335,4 +318,4 @@ proc createAccessList*(header: Header, gasUsed: Quantity res.gasUsed, ) - prevTracer = tracer + prevTracer = tracer \ No newline at end of file diff --git a/nimbus/rpc/server_api.nim b/nimbus/rpc/server_api.nim index 7b2310c56..d05fbb15b 100644 --- a/nimbus/rpc/server_api.nim +++ b/nimbus/rpc/server_api.nim @@ -10,9 +10,12 @@ {.push raises: [].} import + chronicles, + std/[sequtils, strutils], stint, web3/[conversions, eth_api_types], eth/common/base, + stew/byteutils, ../common/common, json_rpc/rpcserver, ../db/ledger, @@ -22,32 +25,70 @@ import ../transaction, ../transaction/call_evm, ../evm/evm_errors, + ../core/eip4844, ./rpc_types, ./rpc_utils, - ./filters, - ./server_api_helpers + ./filters -type - ServerAPIRef* = ref object - com: CommonRef - chain: ForkedChainRef - txPool: TxPoolRef +type ServerAPIRef* = ref object + com: CommonRef + chain: ForkedChainRef + txPool: TxPoolRef -const - defaultTag = blockId("latest") +const defaultTag = blockId("latest") func newServerAPI*(c: ForkedChainRef, t: TxPoolRef): ServerAPIRef = - ServerAPIRef( - com: c.com, - chain: c, - txPool: t - ) + ServerAPIRef(com: c.com, chain: c, txPool: t) + +proc getTotalDifficulty*(api: ServerAPIRef, blockHash: Hash32): UInt256 = + var totalDifficulty: UInt256 + if api.com.db.getTd(blockHash, totalDifficulty): + return totalDifficulty + else: + return api.com.db.headTotalDifficulty() + +proc getProof*( + accDB: LedgerRef, address: eth_types.Address, slots: seq[UInt256] +): ProofResponse = + let + acc = accDB.getEthAccount(address) + accExists = accDB.accountExists(address) + accountProof = accDB.getAccountProof(address) + slotProofs = accDB.getStorageProof(address, slots) + + var storage = newSeqOfCap[StorageProof](slots.len) + + for i, slotKey in slots: + let slotValue = accDB.getStorage(address, slotKey) + storage.add( + StorageProof( + key: slotKey, value: slotValue, proof: seq[RlpEncodedBytes](slotProofs[i]) + ) + ) + + if accExists: + ProofResponse( + address: address, + accountProof: seq[RlpEncodedBytes](accountProof), + balance: acc.balance, + nonce: w3Qty(acc.nonce), + codeHash: acc.codeHash, + storageHash: acc.storageRoot, + storageProof: storage, + ) + else: + ProofResponse( + address: address, + accountProof: seq[RlpEncodedBytes](accountProof), + storageProof: storage, + ) proc headerFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[Header, string] = if blockTag.kind == bidAlias: let tag = blockTag.alias.toLowerAscii case tag - of "latest": return ok(api.chain.latestHeader) + of "latest": + return ok(api.chain.latestHeader) else: return err("Unsupported block tag " & tag) else: @@ -78,31 +119,35 @@ proc blockFromTag(api: ServerAPIRef, blockTag: BlockTag): Result[Block, string] let blockNum = base.BlockNumber blockTag.number return api.chain.blockByNumber(blockNum) -proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = +proc setupServerAPI*(api: ServerAPIRef, server: RpcServer, ctx: EthContext) = server.rpc("eth_getBalance") do(data: Address, blockTag: BlockTag) -> UInt256: ## Returns the balance of the account of given address. let - ledger = api.ledgerFromTag(blockTag).valueOr: + ledger = api.ledgerFromTag(blockTag).valueOr: raise newException(ValueError, error) address = data ledger.getBalance(address) - server.rpc("eth_getStorageAt") do(data: Address, slot: UInt256, blockTag: BlockTag) -> FixedBytes[32]: + server.rpc("eth_getStorageAt") do( + data: Address, slot: UInt256, blockTag: BlockTag + ) -> FixedBytes[32]: ## Returns the value from a storage position at a given address. let - ledger = api.ledgerFromTag(blockTag).valueOr: + ledger = api.ledgerFromTag(blockTag).valueOr: raise newException(ValueError, error) address = data - value = ledger.getStorage(address, slot) + value = ledger.getStorage(address, slot) value.to(Bytes32) - server.rpc("eth_getTransactionCount") do(data: Address, blockTag: BlockTag) -> Web3Quantity: + server.rpc("eth_getTransactionCount") do( + data: Address, blockTag: BlockTag + ) -> Web3Quantity: ## Returns the number of transactions ak.s. nonce sent from an address. let - ledger = api.ledgerFromTag(blockTag).valueOr: + ledger = api.ledgerFromTag(blockTag).valueOr: raise newException(ValueError, error) address = data - nonce = ledger.getNonce(address) + nonce = ledger.getNonce(address) Quantity(nonce) server.rpc("eth_blockNumber") do() -> Web3Quantity: @@ -119,12 +164,14 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = ## blockTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the code from the given address. let - ledger = api.ledgerFromTag(blockTag).valueOr: + ledger = api.ledgerFromTag(blockTag).valueOr: raise newException(ValueError, error) address = data ledger.getCode(address).bytes() - server.rpc("eth_getBlockByHash") do(data: Hash32, fullTransactions: bool) -> BlockObject: + server.rpc("eth_getBlockByHash") do( + data: Hash32, fullTransactions: bool + ) -> BlockObject: ## Returns information about a block by hash. ## ## data: Hash of a block. @@ -135,9 +182,13 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = let blk = api.chain.blockByHash(blockHash).valueOr: return nil - return populateBlockObject(blockHash, blk, fullTransactions) + return populateBlockObject( + blockHash, blk, api.getTotalDifficulty(blockHash), fullTransactions + ) - server.rpc("eth_getBlockByNumber") do(blockTag: BlockTag, fullTransactions: bool) -> BlockObject: + server.rpc("eth_getBlockByNumber") do( + blockTag: BlockTag, fullTransactions: bool + ) -> BlockObject: ## Returns information about a block by block number. ## ## blockTag: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. @@ -147,29 +198,35 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = return nil let blockHash = blk.header.blockHash - return populateBlockObject(blockHash, blk, fullTransactions) + return populateBlockObject( + blockHash, blk, api.getTotalDifficulty(blockHash), fullTransactions + ) server.rpc("eth_syncing") do() -> SyncingStatus: ## Returns SyncObject or false when not syncing. if api.com.syncState != Waiting: let sync = SyncObject( startingBlock: Quantity(api.com.syncStart), - currentBlock : Quantity(api.com.syncCurrent), - highestBlock : Quantity(api.com.syncHighest) + currentBlock: Quantity(api.com.syncCurrent), + highestBlock: Quantity(api.com.syncHighest), ) return SyncingStatus(syncing: true, syncObject: sync) else: return SyncingStatus(syncing: false) proc getLogsForBlock( - chain: ForkedChainRef, - header: Header, - opts: FilterOptions): seq[FilterLog] - {.gcsafe, raises: [RlpError].} = + chain: ForkedChainRef, header: Header, opts: FilterOptions + ): seq[FilterLog] {.gcsafe, raises: [RlpError].} = if headerBloomFilter(header, opts.address, opts.topics): - let - receipts = chain.db.getReceipts(header.receiptsRoot) - txs = chain.db.getTransactions(header.txRoot) + let (receipts, txs) = + if api.chain.isInMemory(header.blockHash): + let blk = api.chain.memoryBlock(header.blockHash) + (blk.receipts, blk.blk.transactions) + else: + ( + chain.db.getReceipts(header.receiptsRoot), + chain.db.getTransactions(header.txRoot), + ) # Note: this will hit assertion error if number of block transactions # do not match block receipts. # Although this is fine as number of receipts should always match number @@ -184,8 +241,8 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = chain: ForkedChainRef, start: base.BlockNumber, finish: base.BlockNumber, - opts: FilterOptions): seq[FilterLog] - {.gcsafe, raises: [RlpError].} = + opts: FilterOptions, + ): seq[FilterLog] {.gcsafe, raises: [RlpError].} = var logs = newSeq[FilterLog]() blockNum = start @@ -193,7 +250,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = while blockNum <= finish: let header = chain.headerByNumber(blockNum).valueOr: - return logs + return logs filtered = chain.getLogsForBlock(header, opts) logs.add(filtered) blockNum = blockNum + 1 @@ -228,11 +285,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = # Note: if fromHeader.number > toHeader.number, no logs will be # returned. This is consistent with, what other ethereum clients return - return api.chain.getLogsForRange( - blockFrom.number, - blockTo.number, - filterOptions - ) + return api.chain.getLogsForRange(blockFrom.number, blockTo.number, filterOptions) server.rpc("eth_sendRawTransaction") do(txBytes: seq[byte]) -> Hash32: ## Creates new message call transaction or a contract creation for signed transactions. @@ -242,7 +295,7 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = ## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract. let pooledTx = decodePooledTx(txBytes) - txHash = rlpHash(pooledTx) + txHash = rlpHash(pooledTx) api.txPool.add(pooledTx) let res = api.txPool.inPoolAndReason(txHash) @@ -258,9 +311,9 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = ## Returns the return value of executed contract. let header = api.headerFromTag(blockTag).valueOr: - raise newException(ValueError, "Block not found") - res = rpcCallEvm(args, header, api.com).valueOr: - raise newException(ValueError, "rpcCallEvm error: " & $error.code) + raise newException(ValueError, "Block not found") + res = rpcCallEvm(args, header, api.com).valueOr: + raise newException(ValueError, "rpcCallEvm error: " & $error.code) res.output server.rpc("eth_getTransactionReceipt") do(data: Hash32) -> ReceiptObject: @@ -278,14 +331,16 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = if blockhash == zeroHash32: # Receipt in database - let txDetails = api.chain.db.getTransactionKey(txHash) + let txDetails = api.chain.db.getTransactionKey(data) if txDetails.index < 0: return nil let header = api.chain.headerByNumber(txDetails.blockNumber).valueOr: raise newException(ValueError, "Block not found") var tx: Transaction - if not api.chain.db.getTransactionByIndex(header.txRoot, uint16(txDetails.index), tx): + if not api.chain.db.getTransactionByIndex( + header.txRoot, uint16(txDetails.index), tx + ): return nil for receipt in api.chain.db.getReceipts(header.receiptsRoot): @@ -304,7 +359,9 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = prevGasUsed = receipt.cumulativeGasUsed if txid == idx: - return populateReceipt(receipt, gasUsed, blkdesc.blk.transactions[txid], txid, blkdesc.blk.header) + return populateReceipt( + receipt, gasUsed, blkdesc.blk.transactions[txid], txid, blkdesc.blk.header + ) idx.inc @@ -317,8 +374,336 @@ proc setupServerAPI*(api: ServerAPIRef, server: RpcServer) = ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the amount of gas used. let - header = api.headerFromTag(blockId("latest")).valueOr: + header = api.headerFromTag(blockId("latest")).valueOr: raise newException(ValueError, "Block not found") - gasUsed = rpcEstimateGas(args, header, api.chain.com, DEFAULT_RPC_GAS_CAP).valueOr: + #TODO: change 0 to configureable gas cap + gasUsed = rpcEstimateGas(args, header, api.chain.com, DEFAULT_RPC_GAS_CAP).valueOr: raise newException(ValueError, "rpcEstimateGas error: " & $error.code) Quantity(gasUsed) + + server.rpc("eth_gasPrice") do() -> Web3Quantity: + ## Returns an integer of the current gas price in wei. + w3Qty(calculateMedianGasPrice(api.com.db).uint64) + + server.rpc("eth_accounts") do() -> seq[eth_types.Address]: + ## Returns a list of addresses owned by client. + result = newSeqOfCap[eth_types.Address](ctx.am.numAccounts) + for k in ctx.am.addresses: + result.add k + + server.rpc("eth_getBlockTransactionCountByHash") do(data: Hash32) -> Web3Quantity: + ## Returns the number of transactions in a block from a block matching the given block hash. + ## + ## data: hash of a block + ## Returns integer of the number of transactions in this block. + let blk = api.chain.blockByHash(data).valueOr: + raise newException(ValueError, "Block not found") + + Web3Quantity(blk.transactions.len) + + server.rpc("eth_getBlockTransactionCountByNumber") do( + blockTag: BlockTag + ) -> Web3Quantity: + ## Returns the number of transactions in a block from a block matching the given block number. + ## + ## blockTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns integer of the number of transactions in this block. + let blk = api.blockFromTag(blockTag).valueOr: + raise newException(ValueError, "Block not found") + + Web3Quantity(blk.transactions.len) + + server.rpc("eth_getUncleCountByBlockHash") do(data: Hash32) -> Web3Quantity: + ## Returns the number of uncles in a block from a block matching the given block hash. + ## + ## data: hash of a block. + ## Returns integer of the number of uncles in this block. + let blk = api.chain.blockByHash(data).valueOr: + raise newException(ValueError, "Block not found") + + Web3Quantity(blk.uncles.len) + + server.rpc("eth_getUncleCountByBlockNumber") do(blockTag: BlockTag) -> Web3Quantity: + ## Returns the number of uncles in a block from a block matching the given block number. + ## + ## blockTag: integer of a block number, or the string "latest", see the default block parameter. + ## Returns integer of the number of uncles in this block. + let blk = api.blockFromTag(blockTag).valueOr: + raise newException(ValueError, "Block not found") + + Web3Quantity(blk.uncles.len) + + template sign(privateKey: PrivateKey, message: string): seq[byte] = + # message length encoded as ASCII representation of decimal + let msgData = "\x19Ethereum Signed Message:\n" & $message.len & message + @(sign(privateKey, msgData.toBytes()).toRaw()) + + server.rpc("eth_sign") do(data: eth_types.Address, message: seq[byte]) -> seq[byte]: + ## The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))). + ## By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature. + ## This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim. + ## Note the address to sign with must be unlocked. + ## + ## data: address. + ## message: message to sign. + ## Returns signature. + let + address = data + acc = ctx.am.getAccount(address).tryGet() + + if not acc.unlocked: + raise newException(ValueError, "Account locked, please unlock it first") + sign(acc.privateKey, cast[string](message)) + + server.rpc("eth_signTransaction") do(data: TransactionArgs) -> seq[byte]: + ## Signs a transaction that can be submitted to the network at a later time using with + ## eth_sendRawTransaction + let + address = data.`from`.get() + acc = ctx.am.getAccount(address).tryGet() + + if not acc.unlocked: + raise newException(ValueError, "Account locked, please unlock it first") + + let + accDB = api.ledgerFromTag(blockId("latest")).valueOr: + raise newException(ValueError, "Latest Block not found") + tx = unsignedTx(data, api.chain.db, accDB.getNonce(address) + 1, api.com.chainId) + eip155 = api.com.isEIP155(api.chain.latestNumber) + signedTx = signTransaction(tx, acc.privateKey, eip155) + return rlp.encode(signedTx) + + server.rpc("eth_sendTransaction") do(data: TransactionArgs) -> Hash32: + ## 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. + let + address = data.`from`.get() + acc = ctx.am.getAccount(address).tryGet() + + if not acc.unlocked: + raise newException(ValueError, "Account locked, please unlock it first") + + let + accDB = api.ledgerFromTag(blockId("latest")).valueOr: + raise newException(ValueError, "Latest Block not found") + tx = unsignedTx(data, api.chain.db, accDB.getNonce(address) + 1, api.com.chainId) + eip155 = api.com.isEIP155(api.chain.latestNumber) + signedTx = signTransaction(tx, acc.privateKey, eip155) + networkPayload = + if signedTx.txType == TxEip4844: + if data.blobs.isNone or data.commitments.isNone or data.proofs.isNone: + raise newException(ValueError, "EIP-4844 transaction needs blobs") + if data.blobs.get.len != signedTx.versionedHashes.len: + raise newException(ValueError, "Incorrect number of blobs") + if data.commitments.get.len != signedTx.versionedHashes.len: + raise newException(ValueError, "Incorrect number of commitments") + if data.proofs.get.len != signedTx.versionedHashes.len: + raise newException(ValueError, "Incorrect number of proofs") + NetworkPayload( + blobs: data.blobs.get.mapIt it.NetworkBlob, + commitments: data.commitments.get, + proofs: data.proofs.get, + ) + else: + if data.blobs.isSome or data.commitments.isSome or data.proofs.isSome: + raise newException(ValueError, "Blobs require EIP-4844 transaction") + nil + pooledTx = PooledTransaction(tx: signedTx, networkPayload: networkPayload) + + api.txPool.add(pooledTx) + rlpHash(signedTx) + + server.rpc("eth_getTransactionByHash") do(data: Hash32) -> TransactionObject: + ## Returns the information about a transaction requested by transaction hash. + ## + ## data: hash of a transaction. + ## Returns requested transaction information. + let txHash = data + let res = api.txPool.getItem(txHash) + if res.isOk: + return populateTransactionObject(res.get().tx, Opt.none(Hash32), Opt.none(uint64)) + + let txDetails = api.chain.db.getTransactionKey(txHash) + if txDetails.index < 0: + let + (blockHash, txid) = api.chain.txRecords(txHash) + tx = api.chain.memoryTransaction(txHash).valueOr: + return nil + return populateTransactionObject(tx, Opt.some(blockHash), Opt.some(txid)) + # TODO: include block number + + let header = api.chain.db.getBlockHeader(txDetails.blockNumber) + var tx: Transaction + if api.chain.db.getTransactionByIndex(header.txRoot, uint16(txDetails.index), tx): + return populateTransactionObject( + tx, + Opt.some(header.blockHash), + Opt.some(header.number), + Opt.some(txDetails.index), + ) + + server.rpc("eth_getTransactionByBlockHashAndIndex") do( + data: Hash32, quantity: Web3Quantity + ) -> TransactionObject: + ## Returns information about a transaction by block hash and transaction index position. + ## + ## data: hash of a block. + ## quantity: integer of the transaction index position. + ## Returns requested transaction information. + let index = uint64(quantity) + let blk = api.chain.blockByHash(data).valueOr: + return nil + + if index >= uint64(blk.transactions.len): + return nil + + populateTransactionObject( + blk.transactions[index], Opt.some(data), Opt.some(blk.header.number), Opt.some(index) + ) + + server.rpc("eth_getTransactionByBlockNumberAndIndex") do( + quantityTag: BlockTag, quantity: Web3Quantity + ) -> TransactionObject: + ## Returns information about a transaction by block number and transaction index position. + ## + ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. + ## quantity: the transaction index position. + ## NOTE : "pending" blockTag is not supported. + let index = uint64(quantity) + let blk = api.blockFromTag(quantityTag).valueOr: + return nil + + if index >= uint64(blk.transactions.len): + return nil + + populateTransactionObject( + blk.transactions[index], Opt.some(blk.header.blockHash), Opt.some(blk.header.number), Opt.some(index) + ) + + server.rpc("eth_getProof") do( + data: eth_types.Address, slots: seq[UInt256], quantityTag: BlockTag + ) -> ProofResponse: + ## Returns information about an account and storage slots (if the account is a contract + ## and the slots are requested) along with account and storage proofs which prove the + ## existence of the values in the state. + ## See spec here: https://eips.ethereum.org/EIPS/eip-1186 + ## + ## data: address of the account. + ## slots: integers of the positions in the storage to return with storage proofs. + ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. + ## Returns: the proof response containing the account, account proof and storage proof + let accDB = api.ledgerFromTag(quantityTag).valueOr: + raise newException(ValueError, "Block not found") + + getProof(accDB, data, slots) + + server.rpc("eth_getBlockReceipts") do( + quantityTag: BlockTag + ) -> Opt[seq[ReceiptObject]]: + ## Returns the receipts of a block. + let + header = api.headerFromTag(quantityTag).valueOr: + raise newException(ValueError, "Block not found") + blkHash = header.blockHash + + var + prevGasUsed = GasInt(0) + receipts: seq[Receipt] + recs: seq[ReceiptObject] + txs: seq[Transaction] + index = 0'u64 + + if api.chain.haveBlockAndState(blkHash): + let blkdesc = api.chain.memoryBlock(blkHash) + receipts = blkdesc.receipts + txs = blkdesc.blk.transactions + else: + for receipt in api.chain.db.getReceipts(header.receiptsRoot): + receipts.add receipt + txs = api.chain.db.getTransactions(header.txRoot) + + try: + for receipt in receipts: + let gasUsed = receipt.cumulativeGasUsed - prevGasUsed + prevGasUsed = receipt.cumulativeGasUsed + recs.add populateReceipt(receipt, gasUsed, txs[index], index, header) + inc index + return Opt.some(recs) + except CatchableError: + return Opt.none(seq[ReceiptObject]) + + server.rpc("eth_createAccessList") do( + args: TransactionArgs, quantityTag: BlockTag + ) -> AccessListResult: + ## Generates an access list for a transaction. + try: + let header = api.headerFromTag(quantityTag).valueOr: + raise newException(ValueError, "Block not found") + return createAccessList(header, api.com, args) + except CatchableError as exc: + return AccessListResult(error: Opt.some("createAccessList error: " & exc.msg)) + + server.rpc("eth_blobBaseFee") do() -> Web3Quantity: + ## Returns the base fee per blob gas in wei. + let header = api.headerFromTag(blockId("latest")).valueOr: + raise newException(ValueError, "Block not found") + if header.blobGasUsed.isNone: + raise newException(ValueError, "blobGasUsed missing from latest header") + if header.excessBlobGas.isNone: + raise newException(ValueError, "excessBlobGas missing from latest header") + let blobBaseFee = + getBlobBaseFee(header.excessBlobGas.get) * header.blobGasUsed.get.u256 + if blobBaseFee > high(uint64).u256: + raise newException(ValueError, "blobBaseFee is bigger than uint64.max") + return w3Qty blobBaseFee.truncate(uint64) + + server.rpc("eth_getUncleByBlockHashAndIndex") do( + data: Hash32, quantity: Web3Quantity + ) -> BlockObject: + ## Returns information about a uncle of a block by hash and uncle index position. + ## + ## data: hash of block. + ## quantity: the uncle's index position. + ## Returns BlockObject or nil when no block was found. + let index = uint64(quantity) + let blk = api.chain.blockByHash(data).valueOr: + return nil + + if index < 0 or index >= blk.uncles.len.uint64: + return nil + + let + uncle = api.chain.blockByHash(blk.uncles[index].blockHash).valueOr: + return nil + uncleHash = uncle.header.blockHash + + return populateBlockObject( + uncleHash, uncle, api.getTotalDifficulty(uncleHash), false, true + ) + + server.rpc("eth_getUncleByBlockNumberAndIndex") do( + quantityTag: BlockTag, quantity: Web3Quantity + ) -> BlockObject: + # Returns information about a uncle of a block by number and uncle index position. + ## + ## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. + ## quantity: the uncle's index position. + ## Returns BlockObject or nil when no block was found. + let index = uint64(quantity) + let blk = api.blockFromTag(quantityTag).valueOr: + return nil + + if index < 0 or index >= blk.uncles.len.uint64: + return nil + + let + uncle = api.chain.blockByHash(blk.uncles[index].blockHash).valueOr: + return nil + uncleHash = uncle.header.blockHash + + return populateBlockObject( + uncleHash, uncle, api.getTotalDifficulty(uncleHash), false, true + ) diff --git a/run-kurtosis-check.sh b/run-kurtosis-check.sh index 2a7a442cd..b262554dd 100755 --- a/run-kurtosis-check.sh +++ b/run-kurtosis-check.sh @@ -87,7 +87,7 @@ sed -i "s/el_image: .*/el_image: $new_el_image/" assertoor.yaml sudo kurtosis run \ --enclave nimbus-localtestnet \ - github.com/ethpandaops/ethereum-package@4.3.0 \ + github.com/ethpandaops/ethereum-package \ --args-file assertoor.yaml enclave_dump=$(kurtosis enclave inspect nimbus-localtestnet) diff --git a/tests/all_tests.nim b/tests/all_tests.nim index d2060c922..30b2257ab 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -15,10 +15,10 @@ cliBuilder: ./test_evm_support, ./test_genesis, ./test_precompiles, + ./test_rpc, ./test_generalstate_json, ./test_tracer_json, #./test_persistblock_json, -- fails - #./test_rpc, -- fails ./test_filters, ./test_op_arith, ./test_op_bit, diff --git a/tests/test_engine_api.nim b/tests/test_engine_api.nim index a8f5b9cf8..a2db2133c 100644 --- a/tests/test_engine_api.nim +++ b/tests/test_engine_api.nim @@ -87,7 +87,7 @@ proc setupEnv(envFork: HardFork = MergeFork): TestEnv = beaconEngine = BeaconEngineRef.new(txPool, chain) serverApi = newServerAPI(chain, txPool) - setupServerAPI(serverApi, server) + setupServerAPI(serverApi, server, newEthContext()) setupEngineAPI(beaconEngine, server) server.start() diff --git a/tests/test_getproof_json.nim b/tests/test_getproof_json.nim index 22cd65c5b..a6d6c8b6b 100644 --- a/tests/test_getproof_json.nim +++ b/tests/test_getproof_json.nim @@ -15,7 +15,7 @@ import eth/[rlp, trie/trie_defs, trie/hexary_proof_verification], ../nimbus/db/[ledger, core_db], ../nimbus/common/chain_config, - ../nimbus/rpc/p2p + ../nimbus/rpc/server_api type Hash32 = eth_types.Hash32 diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index 5f3e83b71..bbca1d1fb 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -6,13 +6,14 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import + chronicles, std/[json, os, typetraits, times, sequtils], asynctest, web3/eth_api, stew/byteutils, json_rpc/[rpcserver, rpcclient], - nimcrypto/[keccak, hash], - eth/[rlp, keys, trie/hexary_proof_verification], - eth/common/transaction_utils, + eth/[rlp, trie/hexary_proof_verification], + eth/common/[transaction_utils, addresses], + ../hive_integration/nodocker/engine/engine_client, ../nimbus/[constants, transaction, config, evm/state, evm/types, version], ../nimbus/db/[ledger, storage_types], ../nimbus/sync/protocol, @@ -26,11 +27,6 @@ import ./macro_assembler, ./test_block_fixture -const - zeroAddress = block: - var rc: Address - rc - type Hash32 = common.Hash32 Header = common.Header @@ -50,7 +46,7 @@ func emptyStorageHash(): Hash32 = proc verifyAccountProof(trustedStateRoot: Hash32, res: ProofResponse): MptProofVerificationResult = let - key = toSeq(keccak256(res.address).data) + key = toSeq(keccak256(res.address.data).data) value = rlp.encode(Account( nonce: res.nonce.uint64, balance: res.balance, @@ -78,18 +74,34 @@ 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) + chainDB.addBlockNumberToHashLookup(header.number, header.blockHash) chainDB.persistTransactions(header.number, header.txRoot, getBlockBody4514995().transactions) chainDB.persistReceipts(header.receiptsRoot, getReceipts4514995()) -proc setupEnv(com: CommonRef, signer, ks2: Address, ctx: EthContext): TestEnv = +proc setupClient(port: Port): RpcHttpClient = + let client = newRpcHttpClient() + waitFor client.connect("127.0.0.1", port, false) + return client + +proc close(client: RpcHttpClient, server: RpcHttpServer) = + waitFor client.close() + waitFor server.closeWait() + + +# NOTE : The setup of the environment should have been done through the +# `ForkedChainRef`, however the `ForkedChainRef` is does not persist blocks to the db +# unless the base distance is reached. This is not the case for the tests, so we +# have to manually persist the blocks to the db. +# Main goal of the tests to check the RPC calls, can serve data persisted in the db +# as data from memory blocks are easily tested via kurtosis or other tests +proc setupEnv(signer, ks2: Address, ctx: EthContext, com: CommonRef): TestEnv = var - parent = com.db.getCanonicalHead() acc = ctx.am.getAccount(signer).tryGet() - blockNumber = 1.BlockNumber + blockNumber = 1'u64 + parent = com.db.getCanonicalHead() parentHash = parent.blockHash - const code = evmByteCode: + let code = evmByteCode: Push4 "0xDEADBEEF" # PUSH Push1 "0x00" # MSTORE AT 0x00 Mstore @@ -99,28 +111,26 @@ proc setupEnv(com: CommonRef, signer, ks2: Address, ctx: EthContext): TestEnv = let vmHeader = Header(parentHash: parentHash, gasLimit: 5_000_000) - vmState = BaseVMState.new( - parent = Header(stateRoot: parent.stateRoot), - header = vmHeader, - com = com) + vmState = BaseVMState() + vmState.init(parent, vmHeader, com) vmState.stateDB.setCode(ks2, code) vmState.stateDB.addBalance( signer, 1.u256 * 1_000_000_000.u256 * 1_000_000_000.u256) # 1 ETH # Test data created for eth_getProof tests - let regularAcc = Hash32.fromHex("0x0000000000000000000000000000000000000001") + let regularAcc = Address.fromHex("0x0000000000000000000000000000000000000001") vmState.stateDB.addBalance(regularAcc, 2_000_000_000.u256) vmState.stateDB.setNonce(regularAcc, 1.uint64) - let contractAccWithStorage = Hash32.fromHex("0x0000000000000000000000000000000000000002") + let contractAccWithStorage = Address.fromHex("0x0000000000000000000000000000000000000002") vmState.stateDB.addBalance(contractAccWithStorage, 1_000_000_000.u256) vmState.stateDB.setNonce(contractAccWithStorage, 2.uint64) vmState.stateDB.setCode(contractAccWithStorage, code) vmState.stateDB.setStorage(contractAccWithStorage, u256(0), u256(1234)) vmState.stateDB.setStorage(contractAccWithStorage, u256(1), u256(2345)) - let contractAccNoStorage = Hash32.fromHex("0x0000000000000000000000000000000000000003") + let contractAccNoStorage = Address.fromHex("0x0000000000000000000000000000000000000003") vmState.stateDB.setCode(contractAccNoStorage, code) @@ -128,19 +138,19 @@ proc setupEnv(com: CommonRef, signer, ks2: Address, ctx: EthContext): TestEnv = unsignedTx1 = Transaction( txType : TxLegacy, nonce : 0, - gasPrice: 30_000_000_000, + gasPrice: uint64(30_000_000_000), gasLimit: 70_000, value : 1.u256, - to : some(zeroAddress), + to : Opt.some(zeroAddress), chainId : com.chainId, ) unsignedTx2 = Transaction( txType : TxLegacy, nonce : 1, - gasPrice: 30_000_000_100, + gasPrice: uint64(30_000_000_100), gasLimit: 70_000, value : 2.u256, - to : some(zeroAddress), + to : Opt.some(zeroAddress), chainId : com.chainId, ) eip155 = com.isEIP155(com.syncCurrent) @@ -159,13 +169,10 @@ proc setupEnv(com: CommonRef, signer, ks2: Address, ctx: EthContext): TestEnv = doAssert(rc.isOk, "Invalid transaction: " & rc.error) vmState.receipts[txIndex] = makeReceipt(vmState, tx.txType) - com.db.persistReceipts(vmState.receipts) let # TODO: `getColumn(CtReceipts)` does not exists anymore. There s only the # generic `MPT` left that can be retrieved with `getGeneric()`, # optionally with argument `clearData=true` - # - receiptRoot = com.db.ctx.getColumn(CtReceipts).state(updateOk=true).valueOr(EMPTY_ROOT_HASH) date = dateTime(2017, mMar, 30) timeStamp = date.toTime.toUnix.EthTime difficulty = com.calcDifficulty(timeStamp, parent) @@ -175,37 +182,43 @@ proc setupEnv(com: CommonRef, signer, ks2: Address, ctx: EthContext): TestEnv = var header = Header( parentHash : parentHash, - #coinbase*: Address - stateRoot : vmState.stateDB.getStateRoot(), - txRoot : txRoot, - receiptsRoot : receiptsRoot, - bloom : createBloom(vmState.receipts), + stateRoot : vmState.stateDB.getStateRoot, + transactionsRoot : txRoot, + receiptsRoot : calcReceiptsRoot(vmState.receipts), + logsBloom : createBloom(vmState.receipts), difficulty : difficulty, - blockNumber : blockNumber, + number : blockNumber, gasLimit : vmState.cumulativeGasUsed + 1_000_000, gasUsed : vmState.cumulativeGasUsed, timestamp : timeStamp - #extraData: Blob - #mixHash: Hash32 - #nonce: BlockNonce ) + doAssert com.db.persistHeader(header, + com.pos.isNil, com.startOfHistory) + let uncles = [header] header.ommersHash = com.db.persistUncles(uncles) doAssert com.db.persistHeader(header, - com.consensus == ConsensusType.POS) + com.pos.isNil, com.startOfHistory) + com.db.persistFixtureBlock() + + com.db.persistent(header.number).isOkOr: + echo "Failed to save state: ", $error + quit(QuitFailure) + result = TestEnv( txHash: signedTx1.rlpHash, - blockHash: header.hash + blockHash: header.blockHash ) + proc rpcMain*() = suite "Remote Procedure Calls": # TODO: Include other transports such as Http let - conf = makeTestConfig() + conf = makeConfig(@[]) ctx = newEthContext() ethNode = setupEthNode(conf, ctx, eth) com = CommonRef.new( @@ -213,12 +226,9 @@ proc rpcMain*() = conf.networkId, conf.networkParams ) - signer = Hash32 bytes32"0x0e69cde81b1aa07a45c32c6cd85d67229d36bb1b" - ks2 = Hash32 bytes32"0xa3b2222afa5c987da6ef773fde8d01b9f23d481f" - ks3 = Hash32 bytes32"0x597176e9a64aad0845d83afdaf698fbeff77703b" - - # disable POS/post Merge feature - com.setTTD none(DifficultyInt) + signer = Address.fromHex "0x0e69cde81b1aa07a45c32c6cd85d67229d36bb1b" + ks2 = Address.fromHex "0xa3b2222afa5c987da6ef773fde8d01b9f23d481f" + ks3 = Address.fromHex "0x597176e9a64aad0845d83afdaf698fbeff77703b" let keyStore = "tests" / "keystore" let res = ctx.am.loadKeystores(keyStore) @@ -232,24 +242,30 @@ proc rpcMain*() = debugEcho unlock.error doAssert(unlock.isOk) - let env = setupEnv(com, signer, ks2, ctx) + let + env = setupEnv(signer, ks2, ctx, com) + chain = ForkedChainRef.init(com) + txPool = TxPoolRef.new(com) - # Create Ethereum RPCs - let RPC_PORT = 0 # let the OS choose a port - var - rpcServer = newRpcSocketServer(["127.0.0.1:" & $RPC_PORT]) - client = newRpcSocketClient() - txPool = TxPoolRef.new(com, conf.engineSigner) - oracle = Oracle.new(com) + # txPool must be informed of active head + # so it can know the latest account state + doAssert txPool.smartHead(chain.latestHeader, chain) - setupCommonRpc(ethNode, conf, rpcServer) - setupEthRpc(ethNode, ctx, com, txPool, oracle, rpcServer) + let + server = newRpcHttpServerWithParams("127.0.0.1:0").valueOr: + quit(QuitFailure) + serverApi = newServerAPI(chain, txPool) + + setupServerAPI(serverApi, server, ctx) + setupCommonRpc(ethNode, conf, server) + + server.start() + let client = setupClient(server.localAddress[0].port) + + # disable POS/post Merge feature + com.setTTD Opt.none(DifficultyInt) - # Begin tests - rpcServer.start() - waitFor client.connect("127.0.0.1", rpcServer.localAddress[0].port) - # TODO: add more tests here test "web3_clientVersion": let res = await client.web3_clientVersion() check res == ClientId @@ -274,15 +290,6 @@ proc rpcMain*() = let peerCount = ethNode.peerPool.connectedNodes.len check res == w3Qty(peerCount) - 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 == $ethVersion - test "eth_chainId": let res = await client.eth_chainId() check res == w3Qty(distinctBase(com.chainId)) @@ -293,24 +300,9 @@ proc rpcMain*() = let syncing = ethNode.peerPool.connectedNodes.len > 0 check syncing == false else: - check com.syncStart == res.syncObject.startingBlock.uint64.u256 - check com.syncCurrent == res.syncObject.currentBlock.uint64.u256 - check com.syncHighest == res.syncObject.highestBlock.uint64.u256 - - test "eth_coinbase": - let res = await client.eth_coinbase() - # currently we don't have miner - check res == default(Address) - - 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 == w3Qty(0'u64) + 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() @@ -327,23 +319,23 @@ proc rpcMain*() = check res == w3Qty(0x1'u64) test "eth_getBalance": - let a = await client.eth_getBalance(Hash32.fromHex("0xfff33a3bd36abdbd412707b8e310d6011454a7ae"), blockId(0'u64)) + let a = await client.eth_getBalance(Address.fromHex("0xfff33a3bd36abdbd412707b8e310d6011454a7ae"), blockId(1'u64)) check a == UInt256.fromHex("0x1b1ae4d6e2ef5000000") - let b = await client.eth_getBalance(Hash32.fromHex("0xfff4bad596633479a2a29f9a8b3f78eefd07e6ee"), blockId(0'u64)) + let b = await client.eth_getBalance(Address.fromHex("0xfff4bad596633479a2a29f9a8b3f78eefd07e6ee"), blockId(1'u64)) check b == UInt256.fromHex("0x56bc75e2d63100000") - let c = await client.eth_getBalance(Hash32.fromHex("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), blockId(0'u64)) + let c = await client.eth_getBalance(Address.fromHex("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), blockId(1'u64)) check c == UInt256.fromHex("0x3635c9adc5dea00000") test "eth_getStorageAt": - let res = await client.eth_getStorageAt(Hash32.fromHex("0xfff33a3bd36abdbd412707b8e310d6011454a7ae"), 0.u256, blockId(0'u64)) - check default(Hash32) == res + let res = await client.eth_getStorageAt(Address.fromHex("0xfff33a3bd36abdbd412707b8e310d6011454a7ae"), 0.u256, blockId(1'u64)) + check FixedBytes[32](zeroHash32.data) == res test "eth_getTransactionCount": - let res = await client.eth_getTransactionCount(Hash32.fromHex("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), blockId(0'u64)) + let res = await client.eth_getTransactionCount(Address.fromHex("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), blockId(1'u64)) check res == w3Qty(0'u64) test "eth_getBlockTransactionCountByHash": - let hash = com.db.getBlockHash(0.BlockNumber) + let hash = com.db.getBlockHash(0'u64) let res = await client.eth_getBlockTransactionCountByHash(hash) check res == w3Qty(0'u64) @@ -352,7 +344,7 @@ proc rpcMain*() = check res == w3Qty(0'u64) test "eth_getUncleCountByBlockHash": - let hash = com.db.getBlockHash(0.BlockNumber) + let hash = com.db.getBlockHash(0'u64) let res = await client.eth_getUncleCountByBlockHash(hash) check res == w3Qty(0'u64) @@ -361,7 +353,7 @@ proc rpcMain*() = check res == w3Qty(0'u64) test "eth_getCode": - let res = await client.eth_getCode(Hash32.fromHex("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), blockId(0'u64)) + let res = await client.eth_getCode(Address.fromHex("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), blockId(1'u64)) check res.len == 0 test "eth_sign": @@ -384,12 +376,12 @@ proc rpcMain*() = test "eth_signTransaction, eth_sendTransaction, eth_sendRawTransaction": var unsignedTx = TransactionArgs( - `from`: signer.some, - to: ks2.some, - gas: w3Qty(100000'u).some, - gasPrice: none(Quantity), - value: some 100.u256, - nonce: none(Quantity) + `from`: Opt.some(signer), + to: Opt.some(ks2), + gas: Opt.some(w3Qty(100000'u)), + gasPrice: Opt.none(Quantity), + value: Opt.some(100.u256), + nonce: Opt.none(Quantity) ) let signedTxBytes = await client.eth_signTransaction(unsignedTx) @@ -402,11 +394,11 @@ proc rpcMain*() = test "eth_call": var ec = TransactionArgs( - `from`: signer.some, - to: ks2.some, - gas: w3Qty(100000'u).some, - gasPrice: none(Quantity), - value: some 100.u256 + `from`: Opt.some(signer), + to: Opt.some(ks2), + gas: Opt.some(w3Qty(100000'u)), + gasPrice: Opt.none(Quantity), + value: Opt.some(100.u256) ) let res = await client.eth_call(ec, "latest") @@ -414,11 +406,11 @@ proc rpcMain*() = test "eth_estimateGas": var ec = TransactionArgs( - `from`: signer.some, - to: ks3.some, - gas: w3Qty(42000'u).some, - gasPrice: w3Qty(100'u).some, - value: some 100.u256 + `from`: Opt.some(signer), + to: Opt.some(ks3), + gas: Opt.some(w3Qty(42000'u)), + gasPrice: Opt.some(w3Qty(100'u)), + value: Opt.some(100.u256) ) let res = await client.eth_estimateGas(ec) @@ -441,14 +433,14 @@ proc rpcMain*() = test "eth_getTransactionByHash": let res = await client.eth_getTransactionByHash(env.txHash) check res.isNil.not - check res.number.get() == w3BlockNumber(1'u64) + check res.blockNumber.get() == w3BlockNumber(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.number.get() == w3BlockNumber(1'u64) + check res.blockNumber.get() == w3BlockNumber(1'u64) let res2 = await client.eth_getTransactionByBlockHashAndIndex(env.blockHash, w3Qty(3'u64)) check res2.isNil @@ -459,18 +451,29 @@ proc rpcMain*() = test "eth_getTransactionByBlockNumberAndIndex": let res = await client.eth_getTransactionByBlockNumberAndIndex("latest", w3Qty(1'u64)) check res.isNil.not - check res.number.get() == w3BlockNumber(1'u64) + check res.blockNumber.get() == w3BlockNumber(1'u64) let res2 = await client.eth_getTransactionByBlockNumberAndIndex("latest", w3Qty(3'u64)) check res2.isNil - test "eth_getTransactionReceipt": - let res = await client.eth_getTransactionReceipt(env.txHash) - check res.isNil.not - check res.number == w3BlockNumber(1'u64) + # TODO: Solved with Issue #2700 - let res2 = await client.eth_getTransactionReceipt(env.blockHash) - 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 == w3BlockNumber(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)) @@ -495,7 +498,7 @@ proc rpcMain*() = let testHeader = getBlockHeader4514995() let testHash = testHeader.blockHash let filterOptions = FilterOptions( - blockHash: some(testHash), + blockHash: Opt.some(testHash), topics: @[] ) let logs = await client.eth_getLogs(filterOptions) @@ -507,41 +510,19 @@ proc rpcMain*() = for l in logs: check: l.blockHash.isSome() - l.blockHash.unsafeGet() == testHash - l.logIndex.unsafeGet() == w3Qty(i.uint64) - inc i - - test "eth_getLogs by blockNumber, no filters": - let testHeader = getBlockHeader4514995() - let testHash = testHeader.blockHash - let fBlock = blockId(testHeader.number) - let tBlock = blockId(testHeader.number) - let filterOptions = FilterOptions( - fromBlock: some(fBlock), - toBlock: some(tBlock) - ) - let logs = await client.eth_getLogs(filterOptions) - - check: - len(logs) == 54 - - var i = 0 - for l in logs: - check: - l.blockHash.isSome() - l.blockHash.unsafeGet() == testHash - l.logIndex.unsafeGet() == w3Qty(i.uint64) + 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 = Hash32.fromHex("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") - let topic1 = Hash32.fromHex("0x000000000000000000000000fdc183d01a793613736cd40a5a578f49add1772b") + let topic = Bytes32.fromHex("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") + let topic1 = Bytes32.fromHex("0x000000000000000000000000fdc183d01a793613736cd40a5a578f49add1772b") let filterOptions = FilterOptions( - blockHash: some(testHash), + blockHash: Opt.some(testHash), topics: @[ TopicOrList(kind: slkList, list: @[topic]), TopicOrList(kind: slkNull), @@ -559,16 +540,16 @@ proc rpcMain*() = let testHeader = getBlockHeader4514995() let testHash = testHeader.blockHash - let topic = Hash32.fromHex("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") - let topic1 = Hash32.fromHex("0xa64da754fccf55aa65a1f0128a648633fade3884b236e879ee9f64c78df5d5d7") + let topic = Bytes32.fromHex("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") + let topic1 = Bytes32.fromHex("0xa64da754fccf55aa65a1f0128a648633fade3884b236e879ee9f64c78df5d5d7") - let topic2 = Hash32.fromHex("0x000000000000000000000000e16c02eac87920033ac72fc55ee1df3151c75786") - let topic3 = Hash32.fromHex("0x000000000000000000000000b626a5facc4de1c813f5293ec3be31979f1d1c78") + let topic2 = Bytes32.fromHex("0x000000000000000000000000e16c02eac87920033ac72fc55ee1df3151c75786") + let topic3 = Bytes32.fromHex("0x000000000000000000000000b626a5facc4de1c813f5293ec3be31979f1d1c78") let filterOptions = FilterOptions( - blockHash: some(testHash), + blockHash: Opt.some(testHash), topics: @[ TopicOrList(kind: slkList, list: @[topic, topic1]), TopicOrList(kind: slkList, list: @[topic2, topic3]) @@ -586,7 +567,7 @@ proc rpcMain*() = block: # account doesn't exist let - address = Hash32.fromHex("0x0000000000000000000000000000000000000004") + address = Address.fromHex("0x0000000000000000000000000000000000000004") proofResponse = await client.eth_getProof(address, @[], blockId(1'u64)) storageProof = proofResponse.storageProof @@ -602,7 +583,7 @@ proc rpcMain*() = block: # account exists but requested slots don't exist let - address = Hash32.fromHex("0x0000000000000000000000000000000000000001") + address = Address.fromHex("0x0000000000000000000000000000000000000001") slot1Key = 0.u256 slot2Key = 1.u256 proofResponse = await client.eth_getProof(address, @[slot1Key, slot2Key], blockId(1'u64)) @@ -626,7 +607,7 @@ proc rpcMain*() = block: # contract account with no storage slots let - address = Hash32.fromHex("0x0000000000000000000000000000000000000003") + address = Address.fromHex("0x0000000000000000000000000000000000000003") slot1Key = 0.u256 # Doesn't exist proofResponse = await client.eth_getProof(address, @[slot1Key], blockId(1'u64)) storageProof = proofResponse.storageProof @@ -649,7 +630,7 @@ proc rpcMain*() = block: # contract account with storage slots let - address = Hash32.fromHex("0x0000000000000000000000000000000000000002") + address = Address.fromHex("0x0000000000000000000000000000000000000002") slot1Key = 0.u256 slot2Key = 1.u256 slot3Key = 2.u256 # Doesn't exist @@ -680,7 +661,7 @@ proc rpcMain*() = block: # externally owned account let - address = Hash32.fromHex("0x0000000000000000000000000000000000000001") + address = Address.fromHex("0x0000000000000000000000000000000000000001") proofResponse = await client.eth_getProof(address, @[], blockId(1'u64)) storageProof = proofResponse.storageProof @@ -696,28 +677,10 @@ proc rpcMain*() = test "eth_getProof - Multiple blocks": let blockData = await client.eth_getBlockByNumber("latest", true) - block: - # block 0 - account doesn't exist yet - let - address = Hash32.fromHex("0x0000000000000000000000000000000000000002") - slot1Key = 100.u256 - proofResponse = await client.eth_getProof(address, @[slot1Key], blockId(0'u64)) - storageProof = proofResponse.storageProof - - check: - proofResponse.address == address - verifyAccountProof(blockData.stateRoot, proofResponse).kind == InvalidProof - proofResponse.balance == 0.u256 - proofResponse.codeHash == zeroHash() - proofResponse.nonce == w3Qty(0.uint64) - proofResponse.storageHash == zeroHash() - storageProof.len() == 1 - verifySlotProof(proofResponse.storageHash, storageProof[0]).kind == InvalidProof - block: # block 1 - account has balance, code and storage let - address = Hash32.fromHex("0x0000000000000000000000000000000000000002") + address = Address.fromHex("0x0000000000000000000000000000000000000002") slot2Key = 1.u256 proofResponse = await client.eth_getProof(address, @[slot2Key], blockId(1'u64)) storageProof = proofResponse.storageProof @@ -732,17 +695,13 @@ proc rpcMain*() = storageProof.len() == 1 verifySlotProof(proofResponse.storageHash, storageProof[0]).isValid() - test "eth_getBlockReceipts": - let recs = await client.eth_getBlockReceipts(blockId("latest")) - 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 + close(client, server) - rpcServer.stop() - rpcServer.close() +proc setErrorLevel* = + discard + when defined(chronicles_runtime_filtering) and loggingEnabled: + setLogLevel(LogLevel.ERROR) when isMainModule: + setErrorLevel() rpcMain()