From b0158c0619e349cab13f974111e15e02a3f55957 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 13 May 2024 11:00:13 +0300 Subject: [PATCH] X --- .gitmodules | 4 +- .../nodocker/consensus/consensus_sim.nim | 3 +- .../nodocker/engine/cancun/customizer.nim | 11 +- .../nodocker/engine/cancun/helpers.nim | 47 ++-- hive_integration/nodocker/engine/clmock.nim | 21 +- .../engine/engine/invalid_payload.nim | 9 +- .../nodocker/engine/engine/prev_randao.nim | 4 +- .../nodocker/engine/engine/reorg.nim | 41 ++- .../nodocker/engine/engine/rpc.nim | 5 +- .../engine/engine/suggested_fee_recipient.nim | 8 +- .../nodocker/engine/engine_client.nim | 132 +++++++--- .../nodocker/engine/engine_env.nim | 2 +- hive_integration/nodocker/engine/test_env.nim | 9 +- .../nodocker/engine/tx_sender.nim | 240 +++++++++++------- .../withdrawals/wd_block_value_spec.nim | 2 +- .../withdrawals/wd_max_init_code_spec.nim | 7 +- .../nodocker/graphql/graphql_sim.nim | 3 +- hive_integration/nodocker/rpc/client.nim | 8 +- hive_integration/nodocker/rpc/rpc_tests.nim | 13 +- hive_integration/nodocker/rpc/test_env.nim | 3 +- hive_integration/nodocker/rpc/vault.nim | 82 +++--- nimbus/beacon/api_handler/api_newpayload.nim | 14 +- nimbus/beacon/web3_eth_conv.nim | 12 +- nimbus/common/common.nim | 2 +- nimbus/common/genesis.nim | 5 +- nimbus/constants.nim | 3 - nimbus/core/eip4844.nim | 50 ++-- nimbus/core/executor/process_block.nim | 3 +- nimbus/core/executor/process_transaction.nim | 23 +- nimbus/core/tx_pool.nim | 15 +- nimbus/core/tx_pool/tx_chain/tx_basefee.nim | 2 +- nimbus/core/tx_pool/tx_desc.nim | 12 +- nimbus/core/tx_pool/tx_item.nim | 36 +-- nimbus/core/tx_pool/tx_tabs/tx_sender.nim | 26 +- nimbus/core/tx_pool/tx_tabs/tx_status.nim | 10 +- nimbus/core/tx_pool/tx_tasks/tx_add.nim | 13 +- nimbus/core/tx_pool/tx_tasks/tx_bucket.nim | 6 +- nimbus/core/tx_pool/tx_tasks/tx_classify.nim | 46 ++-- nimbus/core/tx_pool/tx_tasks/tx_dispose.nim | 4 +- nimbus/core/tx_pool/tx_tasks/tx_head.nim | 2 +- nimbus/core/tx_pool/tx_tasks/tx_packer.nim | 31 +-- nimbus/core/validate.nim | 100 ++++---- nimbus/db/access_list.nim | 8 +- nimbus/db/core_db/base/base_desc.nim | 3 +- nimbus/db/core_db/core_apps_newapi.nim | 10 +- nimbus/db/core_db/memory_only.nim | 23 +- nimbus/db/core_db/persistent.nim | 14 +- nimbus/graphql/ethapi.nim | 82 +++--- nimbus/nimbus.nim | 8 +- nimbus/rpc/oracle.nim | 6 +- nimbus/rpc/p2p.nim | 55 ++-- nimbus/rpc/rpc_utils.nim | 166 +++++++----- nimbus/sync/handlers/eth.nim | 24 +- nimbus/sync/protocol/eth/eth_types.nim | 1 + nimbus/sync/protocol/eth66.nim | 4 +- nimbus/sync/protocol/eth67.nim | 16 +- nimbus/sync/protocol/eth68.nim | 4 +- nimbus/tracer.nim | 1 + nimbus/transaction.nim | 199 +++------------ nimbus/transaction/call_common.nim | 3 +- nimbus/transaction/call_evm.nim | 38 +-- nimbus/utils/ec_recover.nim | 33 --- premix/debug.nim | 3 +- premix/downloader.nim | 6 +- premix/dumper.nim | 8 +- premix/hunter.nim | 23 +- premix/parser.nim | 152 +++++++---- premix/persist.nim | 16 +- premix/premix.nim | 8 +- premix/premixcore.nim | 11 +- premix/prestate.nim | 5 +- premix/regress.nim | 5 +- stateless/witness_verification.nim | 5 +- tests/persistBlockTestGen.nim | 9 +- tests/tracerTestGen.nim | 11 +- vendor/nim-eth | 2 +- vendor/nim-ssz-serialization | 2 +- vendor/nim-web3 | 2 +- vendor/nimbus-eth2 | 2 +- 79 files changed, 1121 insertions(+), 916 deletions(-) diff --git a/.gitmodules b/.gitmodules index 283c4db03..11b23cf8f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -102,7 +102,7 @@ path = vendor/nim-web3 url = https://github.com/status-im/nim-web3.git ignore = dirty - branch = master + branch = feat/eip-6493 [submodule "vendor/nim-snappy"] path = vendor/nim-snappy url = https://github.com/status-im/nim-snappy.git @@ -172,7 +172,7 @@ path = vendor/nim-ssz-serialization url = https://github.com/status-im/nim-ssz-serialization.git ignore = untracked - branch = master + branch = feat/eip-6493 [submodule "vendor/nimbus-eth2"] path = vendor/nimbus-eth2 url = https://github.com/status-im/nimbus-eth2.git diff --git a/hive_integration/nodocker/consensus/consensus_sim.nim b/hive_integration/nodocker/consensus/consensus_sim.nim index 0c1e140d6..611d800fe 100644 --- a/hive_integration/nodocker/consensus/consensus_sim.nim +++ b/hive_integration/nodocker/consensus/consensus_sim.nim @@ -21,7 +21,8 @@ import proc processChainData(cd: ChainData): TestStatus = let networkId = NetworkId(cd.params.config.chainId) - com = CommonRef.new(newCoreDbRef DefaultDbMemory, + com = CommonRef.new( + newCoreDbRef(DefaultDbMemory, cd.params.config.chainId), pruneTrie = false, networkId, cd.params diff --git a/hive_integration/nodocker/engine/cancun/customizer.nim b/hive_integration/nodocker/engine/cancun/customizer.nim index 4f874ba02..bfc19454a 100644 --- a/hive_integration/nodocker/engine/cancun/customizer.nim +++ b/hive_integration/nodocker/engine/cancun/customizer.nim @@ -11,6 +11,7 @@ import std/[options, strutils, typetraits], stew/byteutils, + eth/common/transaction, ./blobs, ../types, ../tx_sender, @@ -656,10 +657,11 @@ proc generateInvalidPayload*(sender: TxSender, data: ExecutableData, payloadFiel case payloadField of InvalidTransactionSignature: - var sig = CustSig(R: baseTx.R - 1.u256) + var sig = CustSig( + R: ecdsa_unpack_signature(baseTx.signature.ecdsa_signature).r - 1.u256) custTx.signature = some(sig) of InvalidTransactionNonce: - custTx.nonce = some(baseTx.nonce - 1) + custTx.nonce = some(baseTx.payload.nonce - 1) of InvalidTransactionGas: custTx.gas = some(0.GasInt) of InvalidTransactionGasPrice: @@ -670,11 +672,12 @@ proc generateInvalidPayload*(sender: TxSender, data: ExecutableData, payloadFiel # Vault account initially has 0x123450000000000000000, so this value should overflow custTx.value = some(UInt256.fromHex("0x123450000000000000001")) of InvalidTransactionChainID: - custTx.chainId = some(ChainId(baseTx.chainId.uint64 + 1)) + custTx.chainId = some(ChainId(sender.chainId.uint64 + 1)) else: discard let acc = sender.getNextAccount() - let modifiedTx = sender.customizeTransaction(acc, baseTx, custTx) + let modifiedTx = sender.customizeTransaction( + acc, baseTx, custTx, sender.chainId) customPayloadMod = CustomPayloadData( transactions: some(@[modifiedTx]), ) diff --git a/hive_integration/nodocker/engine/cancun/helpers.nim b/hive_integration/nodocker/engine/cancun/helpers.nim index 7ebabe312..a0647d88d 100644 --- a/hive_integration/nodocker/engine/cancun/helpers.nim +++ b/hive_integration/nodocker/engine/cancun/helpers.nim @@ -12,7 +12,7 @@ import std/[tables, strutils, typetraits], stint, eth/[common, rlp], - eth/common/eth_types_rlp, + eth/common/[eth_types_rlp, transaction], chronicles, stew/[results, byteutils], kzg4844/kzg_ex as kzg, @@ -53,8 +53,9 @@ func getMinExcessBlobGasForBlobGasPrice(data_gas_price: uint64): uint64 = func getMinExcessBlobsForBlobGasPrice*(data_gas_price: uint64): uint64 = return getMinExcessBlobGasForBlobGasPrice(data_gas_price) div GAS_PER_BLOB.uint64 -proc addBlobTransaction*(pool: TestBlobTxPool, tx: PooledTransaction) = - let txHash = rlpHash(tx) +proc addBlobTransaction*( + pool: TestBlobTxPool, tx: PooledTransaction, chainId: ChainId) = + let txHash = tx.tx.compute_tx_hash(chainId) pool.transactions[txHash] = tx proc `==`(a: openArray[AccessTuple], b: openArray[AccessPair]): bool = @@ -73,7 +74,10 @@ proc `==`(a: openArray[AccessTuple], b: openArray[AccessPair]): bool = return true # Test two different transactions with the same blob, and check the blob bundle. -proc verifyTransactionFromNode*(client: RpcClient, tx: Transaction): Result[void, string] = +proc verifyTransactionFromNode*( + client: RpcClient, + tx: Transaction, + chainId: ChainId): Result[void, string] = let txHash = tx.rlpHash let res = client.txByHash(txHash) if res.isErr: @@ -81,29 +85,34 @@ proc verifyTransactionFromNode*(client: RpcClient, tx: Transaction): Result[void let returnedTx = res.get() # Verify that the tx fields are all the same - if returnedTx.nonce != tx.nonce: - return err("nonce mismatch: $1 != $2" % [$returnedTx.nonce, $tx.nonce]) + if returnedTx.nonce != tx.payload.nonce: + return err("nonce mismatch: $1 != $2" % + [$returnedTx.nonce, $tx.payload.nonce]) - if returnedTx.gasLimit != tx.gasLimit: - return err("gas mismatch: $1 != $2" % [$returnedTx.gasLimit, $tx.gasLimit]) + if returnedTx.gasLimit != tx.payload.gas: + return err("gas mismatch: $1 != $2" % + [$returnedTx.gasLimit, $tx.payload.gas]) - if returnedTx.gasPrice != tx.gasPrice: - return err("gas price mismatch: $1 != $2" % [$returnedTx.gasPrice, $tx.gasPrice]) + if returnedTx.gasPrice != tx.payload.max_fee_per_gas: + return err("gas price mismatch: $1 != $2" % + [$returnedTx.gasPrice, $tx.payload.max_fee_per_gas]) - if returnedTx.value != tx.value: - return err("value mismatch: $1 != $2" % [$returnedTx.value, $tx.value]) + if returnedTx.value != tx.payload.value: + return err("value mismatch: $1 != $2" % + [$returnedTx.value, $tx.payload.value]) - if returnedTx.to != tx.to: - return err("to mismatch: $1 != $2" % [$returnedTx.to, $tx.to]) + if returnedTx.to != tx.payload.to: + return err("to mismatch: $1 != $2" % [$returnedTx.to, $tx.payload.to]) - if returnedTx.payload != tx.payload: - return err("data mismatch: $1 != $2" % [returnedTx.payload.toHex, tx.payload.toHex]) + if returnedTx.payload != tx.payload.input: + return err("data mismatch: $1 != $2" % + [returnedTx.payload.input.toHex, tx.payload.input.toHex]) - if returnedTx.accessList.isNone: + if returnedTx.payload.access_list.isNone: return err("expect accessList is some") - let ac = returnedTx.accessList.get - if ac != tx.accessList: + let ac = returnedTx.payload.access_list.get + if ac != tx.payload.access_list: return err("access list mismatch") if returnedTx.chainId.isNone: diff --git a/hive_integration/nodocker/engine/clmock.nim b/hive_integration/nodocker/engine/clmock.nim index 28e847feb..484fab8f2 100644 --- a/hive_integration/nodocker/engine/clmock.nim +++ b/hive_integration/nodocker/engine/clmock.nim @@ -12,7 +12,7 @@ import std/[tables], chronicles, stew/[byteutils], - eth/common, chronos, + eth/[common, common/transaction], chronos, json_rpc/rpcclient, web3/execution_types, ../../../nimbus/beacon/web3_eth_conv, @@ -94,8 +94,9 @@ type proc collectBlobHashes(list: openArray[Web3Tx]): seq[common.Hash256] = for w3tx in list: let tx = ethTx(w3tx) - for h in tx.versionedHashes: - result.add h + if tx.payload.blob_versioned_hashes.isSome: + for h in tx.payload.blob_versioned_hashes.unsafeGet: + result.add h func latestExecutableData*(cl: CLMocker): ExecutableData = ExecutableData( @@ -373,12 +374,14 @@ proc getNextPayload(cl: CLMocker): bool = return true -func versionedHashes(payload: ExecutionPayload): seq[Web3Hash] = +func versionedHashes(payload: ExecutionPayload, chainId: ChainId): seq[Web3Hash] = result = newSeqOfCap[BlockHash](payload.transactions.len) for x in payload.transactions: - let tx = rlp.decode(distinctBase(x), Transaction) - for vs in tx.versionedHashes: - result.add w3Hash vs + let tx = Transaction.fromBytes(distinctBase(x), chainId).valueOr: + raise (ref MalformedRlpError)(msg: "Invalid transaction in payload") + if tx.payload.blob_versioned_hashes.isSome: + for vs in tx.payload.blob_versioned_hashes.get: + result.add w3Hash vs proc broadcastNewPayload(cl: CLMocker, eng: EngineEnv, @@ -387,10 +390,10 @@ proc broadcastNewPayload(cl: CLMocker, of Version.V1: return eng.client.newPayloadV1(payload.V1) of Version.V2: return eng.client.newPayloadV2(payload.V2) of Version.V3: return eng.client.newPayloadV3(payload.V3, - versionedHashes(payload), + versionedHashes(payload, cl.com.chainId), cl.latestPayloadAttributes.parentBeaconBlockRoot.get) of Version.V4: return eng.client.newPayloadV4(payload.V4, - versionedHashes(payload), + versionedHashes(payload, cl.com.chainId), cl.latestPayloadAttributes.parentBeaconBlockRoot.get) proc broadcastNextNewPayload(cl: CLMocker): bool = diff --git a/hive_integration/nodocker/engine/engine/invalid_payload.nim b/hive_integration/nodocker/engine/engine/invalid_payload.nim index dfeaff7c9..5de3c40a3 100644 --- a/hive_integration/nodocker/engine/engine/invalid_payload.nim +++ b/hive_integration/nodocker/engine/engine/invalid_payload.nim @@ -10,6 +10,7 @@ import chronicles, + eth/common/transaction, ./engine_spec, ../helper, ../cancun/customizer, @@ -304,8 +305,9 @@ method getName(cs: PayloadBuildAfterInvalidPayloadTest): string = proc collectBlobHashes(list: openArray[Web3Tx]): seq[common.Hash256] = for w3tx in list: let tx = ethTx(w3tx) - for h in tx.versionedHashes: - result.add h + if tx.payload.blob_versioned_hashes.isSome: + for h in tx.payload.blob_versioned_hashes.unsafeGet: + result.add h method execute(cs: PayloadBuildAfterInvalidPayloadTest, env: TestEnv): bool = # Add a second client to build the invalid payload @@ -442,7 +444,8 @@ method execute(cs: InvalidTxChainIDTest, env: TestEnv): bool = testCond pbRes # Verify that the latest payload built does NOT contain the invalid chain Tx - let txHash = shadow.invalidTx.rlpHash + let txHash = shadow.invalidTx.tx.compute_tx_hash( + env.conf.networkParams.config.chainId) if txInPayload(env.clMock.latestPayloadBuilt, txHash): fatal "Invalid chain ID tx was included in payload" return false diff --git a/hive_integration/nodocker/engine/engine/prev_randao.nim b/hive_integration/nodocker/engine/engine/prev_randao.nim index 37b756cf7..609aa279a 100644 --- a/hive_integration/nodocker/engine/engine/prev_randao.nim +++ b/hive_integration/nodocker/engine/engine/prev_randao.nim @@ -70,7 +70,9 @@ method execute(cs: PrevRandaoTransactionTest, env: TestEnv): bool = onForkchoiceBroadcast: proc(): bool = # Check the transaction tracing, which is client specific let expectedPrevRandao = env.clMock.prevRandaoHistory[env.clMock.latestHeader.blockNumber.truncate(uint64)+1] - let res = debugPrevRandaoTransaction(env.engine.client, shadow.txs[shadow.currentTxIndex-1], expectedPrevRandao) + let res = debugPrevRandaoTransaction( + env.engine.client, shadow.txs[shadow.currentTxIndex-1], + expectedPrevRandao, env.conf.networkParams.config.chainId) testCond res.isOk: fatal "Error during transaction tracing", msg=res.error diff --git a/hive_integration/nodocker/engine/engine/reorg.nim b/hive_integration/nodocker/engine/engine/reorg.nim index a1c4b5e43..d70f742d6 100644 --- a/hive_integration/nodocker/engine/engine/reorg.nim +++ b/hive_integration/nodocker/engine/engine/reorg.nim @@ -9,7 +9,7 @@ # according to those terms. import - eth/common, + eth/[common, common/transaction], chronicles, stew/byteutils, ./engine_spec, @@ -134,11 +134,11 @@ method getName(cs: TransactionReOrgTest): string = name.add ", " & $cs.scenario return name -proc txHash(shadow: ShadowTx): common.Hash256 = +proc txHash(shadow: ShadowTx, chainId: ChainId): common.Hash256 = if shadow.tx.isNone: error "SHADOW TX IS NONE" return - shadow.tx.get.rlpHash + shadow.tx.get.tx.compute_tx_hash(chainId) # Test transaction status after a forkchoiceUpdated re-orgs to an alternative hash where a transaction is not present method execute(cs: TransactionReOrgTest, env: TestEnv): bool = @@ -204,7 +204,8 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool = shadow.tx = some(shadow.sendTransaction(i)) # Get the receipt - let receipt = env.engine.client.txReceipt(shadow.txHash) + let receipt = env.engine.client.txReceipt( + shadow.txHash(env.conf.networkParams.config.chainId)) testCond receipt.isErr: fatal "Receipt obtained before tx included in block" @@ -213,7 +214,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool = onGetpayload: proc(): bool = # Check that indeed the payload contains the transaction if shadow.tx.isSome: - testCond txInPayload(env.clMock.latestPayloadBuilt, shadow.txHash): + testCond txInPayload( + env.clMock.latestPayloadBuilt, + shadow.txHash(env.conf.networkParams.config.chainId)): fatal "Payload built does not contain the transaction" if cs.scenario in [TransactionReOrgScenarioReOrgDifferentBlock, TransactionReOrgScenarioNewPayloadOnRevert]: @@ -256,7 +259,8 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool = g.expectNoError() let payload = g.get.executionPayload - testCond txInPayload(payload, shadow.nextTx.rlpHash): + testCond txInPayload(payload, shadow.nextTx.tx.compute_tx_hash( + env.conf.networkParams.config.chainId)): fatal "Payload built does not contain the transaction" # Send the new payload and forkchoiceUpdated to it @@ -273,7 +277,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool = onNewPayloadBroadcast: proc(): bool = if shadow.tx.isSome: # Get the receipt - let receipt = env.engine.client.txReceipt(shadow.txHash) + let receipt = env.engine.client.txReceipt( + shadow.tx.unsafeGet.tx.compute_tx_hash( + env.conf.networkParams.config.chainId)) testCond receipt.isErr: fatal "Receipt obtained before tx included in block (NewPayload)" return true @@ -282,7 +288,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool = if cs.scenario != TransactionReOrgScenarioReOrgBackIn: # Transaction is now in the head of the canonical chain, re-org and verify it's removed # Get the receipt - var txt = env.engine.client.txReceipt(shadow.txHash) + var txt = env.engine.client.txReceipt( + shadow.tx.get.tx.compute_tx_hash( + env.conf.networkParams.config.chainId)) txt.expectBlockHash(ethHash env.clMock.latestForkchoice.headBlockHash) testCond shadow.payload.parentHash == env.clMock.latestPayloadBuilt.parentHash: @@ -311,7 +319,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool = let p = env.engine.client.namedHeader(Head) p.expectHash(ethHash shadow.payload.blockHash) - txt = env.engine.client.txReceipt(shadow.txHash) + txt = env.engine.client.txReceipt( + shadow.tx.get.tx.compute_tx_hash( + env.conf.networkParams.config.chainId)) if cs.scenario == TransactionReOrgScenarioReOrgOut: testCond txt.isErr: fatal "Receipt was obtained when the tx had been re-org'd out" @@ -328,7 +338,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool = if shadow.tx.isSome: # Now it should be back with main payload - let txt = env.engine.client.txReceipt(shadow.txHash) + let txt = env.engine.client.txReceipt( + shadow.tx.get.tx.compute_tx_hash( + env.conf.networkParams.config.chainId)) txt.expectBlockHash(ethHash env.clMock.latestForkchoice.headBlockHash) if cs.scenario != TransactionReOrgScenarioReOrgBackIn: @@ -347,11 +359,16 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool = # Produce one last block and verify that the block contains the transaction let pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks( onForkchoiceBroadcast: proc(): bool = - testCond txInPayload(env.clMock.latestPayloadBuilt, shadow.txHash): + testCond txInPayload( + env.clMock.latestPayloadBuilt, + shadow.tx.get.tx.compute_tx_hash( + env.conf.networkParams.config.chainId)): fatal "Payload built does not contain the transaction" # Get the receipt - let receipt = env.engine.client.txReceipt(shadow.txHash) + let receipt = env.engine.client.txReceipt( + shadow.tx.get.tx.compute_tx_hash( + env.conf.networkParams.config.chainId)) testCond receipt.isOk: fatal "Receipt not obtained after tx included in block" diff --git a/hive_integration/nodocker/engine/engine/rpc.nim b/hive_integration/nodocker/engine/engine/rpc.nim index 7c6749931..93fd223a6 100644 --- a/hive_integration/nodocker/engine/engine/rpc.nim +++ b/hive_integration/nodocker/engine/engine/rpc.nim @@ -9,7 +9,7 @@ # according to those terms. import - eth/common, + eth/[common, common/transaction], chronicles, ./engine_spec @@ -64,7 +64,8 @@ method execute(cs: BlockStatus, env: TestEnv): bool = ) let tx = env.makeNextTx(tc) - shadow.txHash = tx.rlpHash + shadow.txHash = tx.tx.compute_tx_hash( + env.conf.networkParams.config.chainId) let ok = env.sendTx(tx) testCond ok: fatal "Error trying to send transaction" diff --git a/hive_integration/nodocker/engine/engine/suggested_fee_recipient.nim b/hive_integration/nodocker/engine/engine/suggested_fee_recipient.nim index f5e38d7ec..df057ea15 100644 --- a/hive_integration/nodocker/engine/engine/suggested_fee_recipient.nim +++ b/hive_integration/nodocker/engine/engine/suggested_fee_recipient.nim @@ -56,7 +56,7 @@ method execute(cs: SuggestedFeeRecipientTest, env: TestEnv): bool = testCond env.clMock.produceSingleBlock(BlockProcessCallbacks()) # Calculate the fees and check that they match the balance of the fee recipient - let r = env.engine.client.latestBlock() + let r = env.engine.client.latestBlock(env.conf.networkParams.config.chainId) testCond r.isOk: error "cannot get latest header", msg=r.error @@ -72,14 +72,16 @@ method execute(cs: SuggestedFeeRecipientTest, env: TestEnv): bool = var feeRecipientFees = 0.u256 for tx in blockIncluded.txs: - let effGasTip = tx.effectiveGasTip(blockIncluded.header.fee) + let effGasTip = tx.effectiveGasTip( + blockIncluded.header.fee.get(UInt256.zero)) let r = env.engine.client.txReceipt(tx.rlpHash) testCond r.isOk: fatal "unable to obtain receipt", msg=r.error let receipt = r.get - feeRecipientFees = feeRecipientFees + effGasTip.u256 * receipt.gasUsed.u256 + feeRecipientFees = + feeRecipientFees + effGasTip.uint64.u256 * receipt.gasUsed.u256 var s = env.engine.client.balanceAt(feeRecipient) diff --git a/hive_integration/nodocker/engine/engine_client.nim b/hive_integration/nodocker/engine/engine_client.nim index b4b8609a7..293be0454 100644 --- a/hive_integration/nodocker/engine/engine_client.nim +++ b/hive_integration/nodocker/engine/engine_client.nim @@ -11,7 +11,7 @@ import std/[times, json, strutils], stew/byteutils, - eth/[common, common/eth_types, rlp], chronos, + eth/[common, common/eth_types, common/transaction], chronos, json_rpc/[rpcclient, errors, jsonmarshal], ../../../nimbus/beacon/web3_eth_conv, ./types @@ -206,8 +206,9 @@ proc newPayloadV4*(client: RpcClient, proc collectBlobHashes(list: openArray[Web3Tx]): seq[Web3Hash] = for w3tx in list: let tx = ethTx(w3tx) - for h in tx.versionedHashes: - result.add w3Hash(h) + if tx.payload.blob_versioned_hashes.isSome: + for h in tx.payload.blob_versioned_hashes.unsafeGet: + result.add w3Hash(h) proc newPayload*(client: RpcClient, payload: ExecutionPayload, @@ -308,30 +309,95 @@ func vHashes(x: Option[seq[Web3Hash]]): seq[common.Hash256] = if x.isNone: return else: ethHashes(x.get) -proc toTransaction(tx: TransactionObject): Transaction = - common.Transaction( - txType : tx.`type`.get(0.Web3Quantity).TxType, - chainId : tx.chainId.get(0.Web3Quantity).ChainId, - nonce : tx.nonce.AccountNonce, - gasPrice : tx.gasPrice.GasInt, - maxPriorityFee : tx.maxPriorityFeePerGas.get(0.Web3Quantity).GasInt, - maxFee : tx.maxFeePerGas.get(0.Web3Quantity).GasInt, - gasLimit : tx.gas.GasInt, - to : ethAddr tx.to, - value : tx.value, - payload : tx.input, - accessList : ethAccessList(tx.accessList), - maxFeePerBlobGas: tx.maxFeePerBlobGas.get(0.u256), - versionedHashes : vHashes(tx.blobVersionedHashes), - V : tx.v.int64, - R : tx.r, - S : tx.s, - ) +proc toTransaction(tx: TransactionObject, chain_id: ChainId): Transaction = + var + payload: TransactionPayload + gasPrice: Opt[UInt256] + txChainId: Opt[ChainId] + v: UInt256 + r: UInt256 + s: UInt256 + if tx.`type`.isSome: + payload.tx_type.ok tx.`type`.get.TxType + if tx.chainId.isSome: + txChainId.ok tx.chainId.get.ChainId + payload.nonce = tx.nonce.AccountNonce + payload.max_fee_per_gas = distinctBase(tx.gasPrice).u256 + if tx.maxPriorityFeePerGas.isSome: + payload.max_priority_fee_per_gas.ok( + distinctBase(tx.maxPriorityFeePerGas.get).u256) + if tx.maxFeePerGas.isSome: + gasPrice.ok distinctBase(tx.maxFeePerGas.get).u256 + payload.gas = distinctBase(tx.gas) + if tx.to.isSome: + payload.to.ok(ethAddr(tx.to).get) + payload.value = tx.value + if tx.input.len > payload.input.maxLen: + raise (ref ValueError)(msg: + "Input cannot fit " & $tx.input.len & " bytes") + payload.input = List[byte, Limit MAX_CALLDATA_SIZE].init tx.input + if tx.accessList.isSome: + payload.access_list.ok ethAccessList(tx.accessList) + if tx.maxFeePerBlobGas.isSome: + payload.max_fee_per_blob_gas.ok tx.maxFeePerBlobGas.get + if tx.blobVersionedHashes.isSome: + if tx.blobVersionedHashes.get.len > MAX_BLOB_COMMITMENTS_PER_BLOCK: + raise (ref ValueError)(msg: + "Access list cannot fit " & $tx.blobVersionedHashes.get.len & " bytes") + payload.blob_versioned_hashes.ok( + List[eth_types.VersionedHash, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init vHashes(tx.blobVersionedHashes)) + v = distinctBase(tx.v).u256 + r = tx.r + s = tx.s + if gasPrice.get(payload.max_fee_per_gas) != payload.max_fee_per_gas: + raise (ref ValueError)(msg: "`gasPrice` and `maxFeePerGas` don't match") + if payload.tx_type.get(TxLegacy) != TxLegacy and txChainId.isNone: + raise (ref ValueError)(msg: "`chainId` is required") + if payload.tx_type == Opt.some(TxLegacy) and txChainId.isNone: + payload.tx_type.reset() + if txChainId.get(chain_id) != chain_id: + raise (ref ValueError)(msg: "Unsupported `chainId`") + if r >= SECP256K1N: + raise (ref ValueError)(msg: "Invalid `r`") + if s < UInt256.one or s >= SECP256K1N: + raise (ref ValueError)(msg: "Invalid `s`") + let anyTx = AnyTransactionPayload.fromOneOfBase(payload).valueOr: + raise (ref ValueError)(msg: "Invalid combination of fields") + withTxPayloadVariant(anyTx): + let y_parity = + when txKind == TransactionKind.Replayable: + if v == 27.u256: + false + elif v == 28.u256: + true + else: + raise (ref ValueError)(msg: "Invalid `v`") + elif txKind == TransactionKind.Legacy: + let + res = v.isEven + expected_v = + distinctBase(chain_id).u256 * 2 + (if res: 36.u256 else: 35.u256) + if v != expected_v: + raise (ref ValueError)(msg: "Invalid `v`") + res + else: + if v > UInt256.one: + raise (ref ValueError)(msg: "Invalid `v`") + v.isOdd + var signature: TransactionSignature + signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s) + signature.from_address = ecdsa_recover_from_address( + signature.ecdsa_signature, + txPayloadVariant.compute_sig_hash(chain_id)).valueOr: + raise (ref ValueError)(msg: "Cannot compute `from` address") + Transaction(payload: payload, signature: signature) -proc toTransactions*(txs: openArray[TxOrHash]): seq[Transaction] = +proc toTransactions*( + txs: openArray[TxOrHash], chain_id: ChainId): seq[Transaction] = for x in txs: doAssert x.kind == tohTx - result.add toTransaction(x.tx) + result.add toTransaction(x.tx, chain_id) proc toWithdrawal(wd: WithdrawalObject): Withdrawal = Withdrawal( @@ -493,14 +559,15 @@ proc latestHeader*(client: RpcClient): Result[common.BlockHeader, string] = return err("failed to get latest blockHeader") return ok(res.toBlockHeader) -proc latestBlock*(client: RpcClient): Result[common.EthBlock, string] = +proc latestBlock*( + client: RpcClient, chainId: ChainId): Result[common.EthBlock, string] = wrapTry: let res = waitFor client.eth_getBlockByNumber(blockId("latest"), true) if res.isNil: return err("failed to get latest blockHeader") let output = EthBlock( header: toBlockHeader(res), - txs: toTransactions(res.transactions), + txs: toTransactions(res.transactions, chainId), withdrawals: toWithdrawals(res.withdrawals), ) return ok(output) @@ -513,11 +580,13 @@ proc namedHeader*(client: RpcClient, name: string): Result[common.BlockHeader, s return ok(res.toBlockHeader) proc sendTransaction*( - client: RpcClient, tx: common.PooledTransaction): Result[void, string] = + client: RpcClient, + tx: common.PooledTransaction, + chainId: ChainId): Result[void, string] = wrapTry: - let encodedTx = rlp.encode(tx) + let encodedTx = tx.toBytes(chainId) let res = waitFor client.eth_sendRawTransaction(encodedTx) - let txHash = rlpHash(tx) + let txHash = tx.tx.compute_tx_hash(chainId) let getHash = ethHash res if txHash != getHash: return err("sendTransaction: tx hash mismatch") @@ -607,9 +676,10 @@ createRpcSigsFromNim(RpcClient): proc debugPrevRandaoTransaction*( client: RpcClient, tx: PooledTransaction, - expectedPrevRandao: Hash256): Result[void, string] = + expectedPrevRandao: Hash256, + chain_id: ChainId): Result[void, string] = wrapTry: - let hash = w3Hash tx.rlpHash + let hash = tx.tx.compute_tx_hash(chain_id).data.TxHash # we only interested in stack, disable all other elems let opts = TraceOpts( disableStorage: true, diff --git a/hive_integration/nodocker/engine/engine_env.nim b/hive_integration/nodocker/engine/engine_env.nim index 8cd27f67c..d1300ce14 100644 --- a/hive_integration/nodocker/engine/engine_env.nim +++ b/hive_integration/nodocker/engine/engine_env.nim @@ -58,7 +58,7 @@ const proc makeCom*(conf: NimbusConf): CommonRef = CommonRef.new( - newCoreDbRef LegacyDbMemory, + newCoreDbRef(LegacyDbMemory, conf.networkParams.config.chainId), conf.chainDbMode == ChainDbMode.Prune, conf.networkId, conf.networkParams diff --git a/hive_integration/nodocker/engine/test_env.nim b/hive_integration/nodocker/engine/test_env.nim index 0af07ee0f..108c7df65 100644 --- a/hive_integration/nodocker/engine/test_env.nim +++ b/hive_integration/nodocker/engine/test_env.nim @@ -32,7 +32,7 @@ export type TestEnv* = ref object - conf : NimbusConf + conf* : NimbusConf chainFile : string enableAuth: bool port : int @@ -150,7 +150,7 @@ proc sendTx*(env: TestEnv, eng: EngineEnv, tc: BigInitcodeTx, nonce: AccountNonc proc sendTxs*( env: TestEnv, eng: EngineEnv, txs: openArray[PooledTransaction]): bool = for tx in txs: - if not sendTx(eng.client, tx): + if not sendTx(eng.client, tx, env.conf.networkParams.config.chainId): return false true @@ -168,7 +168,7 @@ proc sendTx*(env: TestEnv, tc: BigInitcodeTx, nonce: AccountNonce): bool = proc sendTx*(env: TestEnv, tx: PooledTransaction): bool = let client = env.engine.client - sendTx(client, tx) + sendTx(client, tx, env.conf.networkParams.config.chainId) proc sendTx*( env: TestEnv, @@ -195,7 +195,8 @@ proc customizeTransaction*(env: TestEnv, acc: TestAccount, baseTx: Transaction, custTx: CustomTransactionData): Transaction = - env.sender.customizeTransaction(acc, baseTx, custTx) + env.sender.customizeTransaction( + acc, baseTx, custTx, env.conf.networkParams.config.chainId) proc generateInvalidPayload*(env: TestEnv, data: ExecutableData, diff --git a/hive_integration/nodocker/engine/tx_sender.nim b/hive_integration/nodocker/engine/tx_sender.nim index bc6f7ffcf..aa94f73b4 100644 --- a/hive_integration/nodocker/engine/tx_sender.nim +++ b/hive_integration/nodocker/engine/tx_sender.nim @@ -10,8 +10,9 @@ import std/[tables], - eth/keys, + eth/[common/transaction, keys], stew/endians2, + stint, nimcrypto/sha2, chronicles, ./engine_client, @@ -50,7 +51,7 @@ type accounts: seq[TestAccount] nonceMap: Table[EthAddress, uint64] txSent : int - chainId : ChainID + chainId*: ChainID MakeTxParams* = object chainId*: ChainID @@ -142,29 +143,34 @@ proc makeTxOfType(params: MakeTxParams, tc: BaseTx): PooledTransaction = of TxLegacy: PooledTransaction( tx: Transaction( - txType : TxLegacy, - nonce : params.nonce, - to : tc.recipient, - value : tc.amount, - gasLimit: tc.gasLimit, - gasPrice: gasPrice, - payload : tc.payload - ) - ) + payload: TransactionPayload( + nonce: params.nonce, + to: + if tc.recipient.isSome: + Opt.some(tc.recipient.get) + else: + Opt.none(EthAddress), + value: tc.amount, + gas: tc.gasLimit.uint64, + max_fee_per_gas: gasPrice.uint64.u256, + input: List[byte, Limit MAX_CALLDATA_SIZE].init tc.payload))) + of TxEip1559: PooledTransaction( tx: Transaction( - txType : TxEIP1559, - nonce : params.nonce, - gasLimit: tc.gasLimit, - maxFee : gasFeeCap, - maxPriorityFee: gasTipCap, - to : tc.recipient, - value : tc.amount, - payload : tc.payload, - chainId : params.chainId - ) - ) + payload: TransactionPayload( + tx_type: Opt.some TxEip1559, + nonce: params.nonce, + gas: tc.gasLimit.uint64, + max_fee_per_gas: gasPrice.uint64.u256, + max_priority_fee_per_gas: Opt.some(gasTipCap.uint64.u256), + to: + if tc.recipient.isSome: + Opt.some(tc.recipient.get) + else: + Opt.none(EthAddress), + value: tc.amount, + input: List[byte, Limit MAX_CALLDATA_SIZE].init tc.payload))) of TxEip4844: doAssert(tc.recipient.isSome, "recipient must be some") let @@ -179,23 +185,33 @@ proc makeTxOfType(params: MakeTxParams, tc: BaseTx): PooledTransaction = PooledTransaction( tx: Transaction( - txType : TxEIP4844, - nonce : params.nonce, - chainId : params.chainId, - maxFee : gasFeeCap, - maxPriorityFee: gasTipCap, - gasLimit: tc.gasLimit, - to : tc.recipient, - value : tc.amount, - payload : tc.payload, - #AccessList: tc.AccessList, - maxFeePerBlobGas: blobFeeCap, - versionedHashes: system.move(blobData.hashes), - ), - networkPayload: NetworkPayload( - blobs: system.move(blobData.blobs), - commitments: system.move(blobData.commitments), - proofs: system.move(blobData.proofs), + payload: TransactionPayload( + tx_type: Opt.some TxEip4844, + nonce: params.nonce, + max_fee_per_gas: gasPrice.uint64.u256, + max_priority_fee_per_gas: Opt.some(gasTipCap.uint64.u256), + gas: tc.gasLimit.uint64, + to: + if tc.recipient.isSome: + Opt.some(tc.recipient.get) + else: + Opt.none(EthAddress), + value: tc.amount, + input: List[byte, Limit MAX_CALLDATA_SIZE].init tc.payload, + max_fee_per_blob_gas: Opt.some(blobFeeCap), + blob_versioned_hashes: Opt.some( + List[eth_types.VersionedHash, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(system.move(blobData.hashes))))), + blob_data: Opt.some NetworkPayload( + blobs: + List[NetworkBlob, MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(system.move(blobData.blobs)), + commitments: + List[eth_types.KzgCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(system.move(blobData.commitments)), + proofs: + List[eth_types.KzgProof, MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(system.move(blobData.proofs)), ) ) else: @@ -205,8 +221,8 @@ proc makeTx(params: MakeTxParams, tc: BaseTx): PooledTransaction = # Build the transaction depending on the specified type let tx = makeTxOfType(params, tc) PooledTransaction( - tx: signTransaction(tx.tx, params.key, params.chainId, eip155 = true), - networkPayload: tx.networkPayload) + tx: signTransaction(tx.tx.payload, params.key, params.chainId), + blob_data: tx.blob_data) proc makeTx(params: MakeTxParams, tc: BigInitcodeTx): PooledTransaction = var tx = tc @@ -257,7 +273,7 @@ proc makeNextTx*(sender: TxSender, tc: BaseTx): PooledTransaction = proc sendNextTx*(sender: TxSender, client: RpcClient, tc: BaseTx): bool = let tx = sender.makeNextTx(tc) - let rr = client.sendTransaction(tx) + let rr = client.sendTransaction(tx, sender.chainId) if rr.isErr: error "sendNextTx: Unable to send transaction", msg=rr.error return false @@ -275,7 +291,7 @@ proc sendTx*(sender: TxSender, client: RpcClient, tc: BaseTx, nonce: AccountNonc ) tx = params.makeTx(tc) - let rr = client.sendTransaction(tx) + let rr = client.sendTransaction(tx, sender.chainId) if rr.isErr: error "sendTx: Unable to send transaction", msg=rr.error return false @@ -293,7 +309,7 @@ proc sendTx*(sender: TxSender, client: RpcClient, tc: BigInitcodeTx, nonce: Acco ) tx = params.makeTx(tc) - let rr = client.sendTransaction(tx) + let rr = client.sendTransaction(tx, sender.chainId) if rr.isErr: error "Unable to send transaction", msg=rr.error return false @@ -301,8 +317,8 @@ proc sendTx*(sender: TxSender, client: RpcClient, tc: BigInitcodeTx, nonce: Acco inc sender.txSent return true -proc sendTx*(client: RpcClient, tx: PooledTransaction): bool = - let rr = client.sendTransaction(tx) +proc sendTx*(client: RpcClient, tx: PooledTransaction, chainId: ChainId): bool = + let rr = client.sendTransaction(tx, chainId) if rr.isErr: error "Unable to send transaction", msg=rr.error return false @@ -320,27 +336,36 @@ proc makeTx*(params: MakeTxParams, tc: BlobTx): PooledTransaction = else: gasTipPrice # Collect fields for transaction - let unsignedTx = Transaction( - txType : TxEip4844, - chainId : params.chainId, - nonce : params.nonce, - maxPriorityFee: gasTipCap, - maxFee : gasFeeCap, - gasLimit : tc.gasLimit, - to : tc.recipient, - value : tc.amount, - payload : tc.payload, - maxFeePerBlobGas: tc.blobGasFee, - versionedHashes: data.hashes, - ) - + let unsignedTx = TransactionPayload( + tx_type: Opt.some TxEip4844, + nonce: params.nonce, + max_priority_fee_per_gas: Opt.some(gasTipCap.uint64.u256), + max_fee_per_gas: gasFeeCap.uint64.u256, + gas: tc.gasLimit.uint64, + to: + if tc.recipient.isSome: + Opt.some(tc.recipient.get) + else: + Opt.none(EthAddress), + value: tc.amount, + input: List[byte, Limit MAX_CALLDATA_SIZE].init tc.payload, + max_fee_per_blob_gas: Opt.some(tc.blobGasFee), + blob_versioned_hashes: Opt.some( + List[eth_types.VersionedHash, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(data.hashes))) PooledTransaction( - tx: signTransaction(unsignedTx, params.key, params.chainId, eip155 = true), - networkPayload: NetworkPayload( - blobs : data.blobs, - commitments: data.commitments, - proofs : data.proofs, - ), + tx: signTransaction(unsignedTx, params.key, params.chainId), + blob_data: Opt.some NetworkPayload( + blobs: + List[NetworkBlob, MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(data.blobs), + commitments: + List[eth_types.KzgCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(data.commitments), + proofs: + List[eth_types.KzgProof, MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(data.proofs), + ) ) proc getAccount*(sender: TxSender, idx: int): TestAccount = @@ -359,7 +384,7 @@ proc sendTx*( ) tx = params.makeTx(tc) - let rr = client.sendTransaction(tx) + let rr = client.sendTransaction(tx, params.chainId) if rr.isErr: error "Unable to send transaction", msg=rr.error return err() @@ -380,7 +405,7 @@ proc replaceTx*( ) tx = params.makeTx(tc) - let rr = client.sendTransaction(tx) + let rr = client.sendTransaction(tx, params.chainId) if rr.isErr: error "Unable to send transaction", msg=rr.error return err() @@ -404,49 +429,86 @@ proc makeTx*( proc customizeTransaction*(sender: TxSender, acc: TestAccount, baseTx: Transaction, - custTx: CustomTransactionData): Transaction = + custTx: CustomTransactionData, + chainId: ChainId): Transaction = # Create a modified transaction base, from the base transaction and custTx mix var modTx = baseTx if custTx.nonce.isSome: - modTx.nonce = custTx.nonce.get.AccountNonce + modTx.payload.nonce = custTx.nonce.get.AccountNonce if custTx.gasPriceOrGasFeeCap.isSome: - modTx.gasPrice = custTx.gasPriceOrGasFeeCap.get.GasInt + modTx.payload.max_fee_per_gas = custTx.gasPriceOrGasFeeCap.get.u256 if custTx.gas.isSome: - modTx.gasLimit = custTx.gas.get.GasInt + modTx.payload.gas = custTx.gas.get.uint64 if custTx.to.isSome: - modTx.to = custTx.to + modTx.payload.to.ok custTx.to.get if custTx.value.isSome: - modTx.value = custTx.value.get + modTx.payload.value = custTx.value.get if custTx.data.isSome: - modTx.payload = custTx.data.get + modTx.payload.input = + List[byte, Limit MAX_CALLDATA_SIZE].init(custTx.data.get) - if custTx.signature.isSome: - let signature = custTx.signature.get - modTx.V = signature.V - modTx.R = signature.R - modTx.S = signature.S - - if baseTx.txType in {TxEip1559, TxEip4844}: + let custChainId = if custTx.chainId.isSome: - modTx.chainId = custTx.chainId.get + custTx.chainId.get + else: + chainId + if baseTx.payload.tx_type.get(TxLegacy) in {TxEip1559, TxEip4844}: if custTx.gasPriceOrGasFeeCap.isSome: - modTx.maxFee = custTx.gasPriceOrGasFeeCap.get.GasInt + modTx.payload.max_fee_per_gas = custTx.gasPriceOrGasFeeCap.get.u256 if custTx.gasTipCap.isSome: - modTx.maxPriorityFee = custTx.gasTipCap.get.GasInt + modTx.payload.max_priority_fee_per_gas.ok custTx.gasTipCap.get.u256 - if baseTx.txType == TxEip4844: - if modTx.to.isNone: + if baseTx.payload.tx_type.get(TxLegacy) == TxEip4844: + if modTx.payload.to.isNone: var address: EthAddress - modTx.to = some(address) + modTx.payload.to.ok(address) + + if custTx.signature.isSome: + let + signature = custTx.signature.get + v = signature.V.u256 + r = signature.R + s = signature.S + anyTx = AnyTransactionPayload.fromOneOfBase(modTx.payload).valueOr: + raise (ref ValueError)(msg: "Invalid combination of fields") + return withTxPayloadVariant(anyTx): + let y_parity = + when txKind == TransactionKind.Replayable: + if v == 27.u256: + false + elif v == 28.u256: + true + else: + raise (ref ValueError)(msg: "Invalid `v`") + elif txKind == TransactionKind.Legacy: + let + res = v.isEven + expected_v = + distinctBase(custChainId).u256 * 2 + + (if res: 36.u256 else: 35.u256) + if v != expected_v: + raise (ref ValueError)(msg: "Invalid `v`") + res + else: + if v > UInt256.one: + raise (ref ValueError)(msg: "Invalid `v`") + v.isOdd + var signature: TransactionSignature + signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s) + signature.from_address = ecdsa_recover_from_address( + signature.ecdsa_signature, + txPayloadVariant.compute_sig_hash(custChainId)).valueOr: + raise (ref ValueError)(msg: "Cannot compute `from` address") + Transaction(payload: modTx.payload, signature: signature) if custTx.signature.isNone: - return signTransaction(modTx, acc.key, modTx.chainId, eip155 = true) + return signTransaction(modTx.payload, acc.key, custChainId) return modTx diff --git a/hive_integration/nodocker/engine/withdrawals/wd_block_value_spec.nim b/hive_integration/nodocker/engine/withdrawals/wd_block_value_spec.nim index b314bacd1..9c47642ee 100644 --- a/hive_integration/nodocker/engine/withdrawals/wd_block_value_spec.nim +++ b/hive_integration/nodocker/engine/withdrawals/wd_block_value_spec.nim @@ -26,7 +26,7 @@ proc execute*(ws: BlockValueSpec, env: TestEnv): bool = testCond WDBaseSpec(ws).execute(env) # Get the latest block and the transactions included - let b = env.client.latestBlock() + let b = env.client.latestBlock(env.conf.networkParams.config.chainId) b.expectNoError() let blk = b.get diff --git a/hive_integration/nodocker/engine/withdrawals/wd_max_init_code_spec.nim b/hive_integration/nodocker/engine/withdrawals/wd_max_init_code_spec.nim index 4d2648022..acffda378 100644 --- a/hive_integration/nodocker/engine/withdrawals/wd_max_init_code_spec.nim +++ b/hive_integration/nodocker/engine/withdrawals/wd_max_init_code_spec.nim @@ -12,7 +12,7 @@ import std/typetraits, chronos, chronicles, - eth/common/eth_types_rlp, + eth/common/[eth_types_rlp, transaction], ./wd_base_spec, ../test_env, ../engine_client, @@ -87,7 +87,8 @@ proc execute*(ws: MaxInitcodeSizeSpec, env: TestEnv): bool = testCond not env.sendTx(tx): error "Client accepted tx exceeding the MAX_INITCODE_SIZE" - let res = env.client.txByHash(rlpHash(tx)) + let res = env.client.txByHash( + tx.tx.compute_tx_hash(env.conf.networkParams.config.chainId)) testCond res.isErr: error "Invalid tx was not unknown to the client" @@ -102,7 +103,7 @@ proc execute*(ws: MaxInitcodeSizeSpec, env: TestEnv): bool = return true , onGetPayload: proc(): bool = - let validTxBytes = rlp.encode(validTx) + let validTxBytes = validTx.toBytes(env.conf.networkParams.config.chainId) testCond env.clMock.latestPayloadBuilt.transactions.len == 1: error "Client did not include valid tx with MAX_INITCODE_SIZE" diff --git a/hive_integration/nodocker/graphql/graphql_sim.nim b/hive_integration/nodocker/graphql/graphql_sim.nim index eaac9ab72..ce74457cc 100644 --- a/hive_integration/nodocker/graphql/graphql_sim.nim +++ b/hive_integration/nodocker/graphql/graphql_sim.nim @@ -78,7 +78,8 @@ proc main() = conf = makeConfig(@["--custom-network:" & genesisFile]) ethCtx = newEthContext() ethNode = setupEthNode(conf, ethCtx, eth) - com = CommonRef.new(newCoreDbRef LegacyDbMemory, + com = CommonRef.new( + newCoreDbRef(LegacyDbMemory, conf.networkParams.config.chainId), pruneTrie = false, conf.networkId, conf.networkParams diff --git a/hive_integration/nodocker/rpc/client.nim b/hive_integration/nodocker/rpc/client.nim index 0d203540b..55cc4dc4e 100644 --- a/hive_integration/nodocker/rpc/client.nim +++ b/hive_integration/nodocker/rpc/client.nim @@ -8,7 +8,7 @@ # those terms. import - eth/[common, rlp], + eth/common, chronos, stint, json_rpc/[rpcclient], ../../../nimbus/transaction, @@ -19,8 +19,10 @@ import export eth_api proc sendTransaction*( - client: RpcClient, tx: PooledTransaction): Future[bool] {.async.} = - let data = rlp.encode(tx) + client: RpcClient, + tx: PooledTransaction, + chainId: ChainId): Future[bool] {.async.} = + let data = tx.toBytes(chainId) let txHash = keccakHash(data) let hex = await client.eth_sendRawTransaction(data) let decodedHash = ethHash(hex) diff --git a/hive_integration/nodocker/rpc/rpc_tests.nim b/hive_integration/nodocker/rpc/rpc_tests.nim index 5938fd8d4..9f23a5491 100644 --- a/hive_integration/nodocker/rpc/rpc_tests.nim +++ b/hive_integration/nodocker/rpc/rpc_tests.nim @@ -9,7 +9,7 @@ import std/strutils, - eth/[common], + eth/[common, common/transaction], stew/byteutils, stint, chronos, @@ -88,11 +88,11 @@ proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} = let tx = vault.signTx(sourceAddr, sourceNonce, targetAddr, amount, gasLimit, gasPrice) inc sourceNonce - let txHash = rlpHash(tx) + let txHash = tx.tx.compute_tx_hash(vault.chainId) echo "BalanceAt: send $1 wei from 0x$2 to 0x$3 in 0x$4" % [ - $tx.tx.value, sourceAddr.toHex, targetAddr.toHex, txHash.data.toHex] + $tx.tx.payload.value, sourceAddr.toHex, targetAddr.toHex, txHash.data.toHex] - let ok = await client.sendTransaction(tx) + let ok = await client.sendTransaction(tx, vault.chainId) if not ok: echo "failed to send transaction" return TestStatus.Failed @@ -118,7 +118,8 @@ proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} = # expected balance is previous balance - tx amount - tx fee (gasUsed * gasPrice) let exp = - sourceAddressBalanceBefore - amount - (gasUsed * tx.tx.gasPrice).u256 + sourceAddressBalanceBefore - amount - + gasUsed.u256 * tx.tx.payload.max_fee_per_gas if exp != accountBalanceAfter: echo "Expected sender account to have a balance of $1, got $2" % [$exp, $accountBalanceAfter] @@ -126,7 +127,7 @@ proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} = if balanceTargetAccountAfter != amount: echo "Expected new account to have a balance of $1, got $2" % [ - $tx.tx.value, $balanceTargetAccountAfter] + $tx.tx.payload.value, $balanceTargetAccountAfter] return TestStatus.Failed # ensure nonce is incremented by 1 diff --git a/hive_integration/nodocker/rpc/test_env.nim b/hive_integration/nodocker/rpc/test_env.nim index d601c43eb..c9465bf31 100644 --- a/hive_integration/nodocker/rpc/test_env.nim +++ b/hive_integration/nodocker/rpc/test_env.nim @@ -76,7 +76,8 @@ proc setupEnv*(): TestEnv = let ethCtx = newEthContext() ethNode = setupEthNode(conf, ethCtx, eth) - com = CommonRef.new(newCoreDbRef LegacyDbMemory, + com = CommonRef.new( + newCoreDbRef(LegacyDbMemory, conf.networkParams.config.chainId), conf.chainDbMode == ChainDbMode.Prune, conf.networkId, conf.networkParams diff --git a/hive_integration/nodocker/rpc/vault.nim b/hive_integration/nodocker/rpc/vault.nim index 17d3e1c59..9f769ed63 100644 --- a/hive_integration/nodocker/rpc/vault.nim +++ b/hive_integration/nodocker/rpc/vault.nim @@ -42,7 +42,7 @@ type accounts: Table[EthAddress, PrivateKey] rng: ref HmacDrbgContext - chainId: ChainID + chainId*: ChainID gasPrice: GasInt vaultKey: PrivateKey client: RpcClient @@ -68,57 +68,49 @@ proc nextNonce*(v: Vault): AccountNonce = inc(v.nonce) nonce -proc sendSome(address: EthAddress, amount: UInt256): seq[byte] = +proc sendSome( + address: EthAddress, amount: UInt256): List[byte, Limit MAX_CALLDATA_SIZE] = const padding = repeat('\0', 12).toBytes # makeshift contract ABI construction # https://docs.soliditylang.org/en/develop/abi-spec.html let h = keccakHash("sendSome(address,uint256)".toBytes) - result.add h.data[0..3] # first 4 bytes of hash - result.add padding # left pad address - result.add address - result.add amount.toBytesBE + doAssert result.add h.data[0..3] # first 4 bytes of hash + doAssert result.add padding # left pad address + doAssert result.add address + doAssert result.add amount.toBytesBE doAssert(result.len == 68) # 4 + 32 + 32 proc makeFundingTx*( v: Vault, recipient: EthAddress, amount: UInt256): PooledTransaction = + let unsignedTx = TransactionPayload( + nonce: v.nextNonce(), + max_fee_per_gas: v.gasPrice.uint64.u256, + gas: 75000, + to: Opt.some(predeployedVaultAddr), + value: 0.u256, + input: sendSome(recipient, amount), + tx_type: Opt.some TxLegacy) + PooledTransaction(tx: signTransaction(unsignedTx, v.vaultKey, v.chainId)) + +proc signTx*( + v: Vault, + sender: EthAddress, + nonce: AccountNonce, + recipient: EthAddress, + amount: UInt256, + gasLimit, gasPrice: GasInt, + payload = List[byte, Limit MAX_CALLDATA_SIZE] @[]): PooledTransaction = let - unsignedTx = Transaction( - txType : TxLegacy, - chainId : v.chainId, - nonce : v.nextNonce(), - gasPrice: v.gasPrice, - gasLimit: GasInt(75000), - to : some(predeployedVaultAddr), - value : 0.u256, - payload : sendSome(recipient, amount) - ) - - PooledTransaction( - tx: signTransaction(unsignedTx, v.vaultKey, v.chainId, eip155 = true)) - -proc signTx*(v: Vault, - sender: EthAddress, - nonce: AccountNonce, - recipient: EthAddress, - amount: UInt256, - gasLimit, gasPrice: GasInt, - payload: seq[byte] = @[]): PooledTransaction = - - let - unsignedTx = Transaction( - txType : TxLegacy, - chainId : v.chainId, - nonce : nonce, - gasPrice: gasPrice, - gasLimit: gasLimit, - to : some(recipient), - value : amount, - payload : payload - ) - - let key = v.accounts[sender] - PooledTransaction( - tx: signTransaction(unsignedTx, key, v.chainId, eip155 = true)) + unsignedTx = TransactionPayload( + nonce: nonce, + max_fee_per_gas: gasPrice.uint64.u256, + gas: gasLimit.uint64, + to: Opt.some(recipient), + value: amount, + input: payload, + tx_type: Opt.some TxLegacy) + key = v.accounts[sender] + PooledTransaction(tx: signTransaction(unsignedTx, key, v.chainId)) # createAccount creates a new account that is funded from the vault contract. # It will panic when the account could not be created and funded. @@ -127,7 +119,7 @@ proc createAccount*(v: Vault, amount: UInt256): Future[EthAddress] {.async.} = # order the vault to send some ether let tx = v.makeFundingTx(address, amount) - let res = await v.client.sendTransaction(tx) + let res = await v.client.sendTransaction(tx, v.chainId) if not res: raise newException(ValueError, "unable to send funding transaction") @@ -147,5 +139,5 @@ proc createAccount*(v: Vault, amount: UInt256): Future[EthAddress] {.async.} = let period = chronos.seconds(1) await sleepAsync(period) - let txHash = tx.rlpHash().data.toHex + let txHash = tx.tx.compute_tx_hash(v.chainId).data.toHex raise newException(ValueError, "could not fund account $2 in transaction $2" % [address.toHex, txHash]) diff --git a/nimbus/beacon/api_handler/api_newpayload.nim b/nimbus/beacon/api_handler/api_newpayload.nim index 200b6c485..c98503196 100644 --- a/nimbus/beacon/api_handler/api_newpayload.nim +++ b/nimbus/beacon/api_handler/api_newpayload.nim @@ -8,7 +8,7 @@ # those terms. import - eth/common, + eth/[common, common/transaction], stew/results, ../web3_eth_conv, ../beacon_engine, @@ -20,11 +20,15 @@ import {.push gcsafe, raises:[CatchableError].} func validateVersionedHashed(payload: ExecutionPayload, - expected: openArray[Web3Hash]): bool = + expected: openArray[Web3Hash], + chainId: ChainId): bool = var versionedHashes: seq[common.Hash256] for x in payload.transactions: - let tx = rlp.decode(distinctBase(x), Transaction) - versionedHashes.add tx.versionedHashes + let tx = Transaction.fromBytes(distinctBase(x), chainId).valueOr: + raise (ref MalformedRlpError)(msg: "Invalid transaction in payload") + if tx.payload.blob_versioned_hashes.isSome: + versionedHashes.add distinctBase( + tx.payload.blob_versioned_hashes.unsafeGet) if versionedHashes.len != expected.len: return false @@ -125,7 +129,7 @@ proc newPayload*(ben: BeaconEngineRef, if versionedHashes.isNone: raise invalidParams("newPayload" & $apiVersion & " expect blobVersionedHashes but got none") - if not validateVersionedHashed(payload, versionedHashes.get): + if not validateVersionedHashed(payload, versionedHashes.get, com.chainId): return invalidStatus(header.parentHash, "invalid blob versionedHashes") let blockHash = ethHash payload.blockHash diff --git a/nimbus/beacon/web3_eth_conv.nim b/nimbus/beacon/web3_eth_conv.nim index f231b332b..29c0d9454 100644 --- a/nimbus/beacon/web3_eth_conv.nim +++ b/nimbus/beacon/web3_eth_conv.nim @@ -157,16 +157,18 @@ func ethTxs*(list: openArray[Web3Tx]): for x in list: result.add ethTx(x) -func storageKeys(list: seq[FixedBytes[32]]): seq[StorageKey] = +func storageKeys(list: seq[FixedBytes[32]]): common.StorageKeys = for x in list: - result.add StorageKey(x) + let ok = result.add distinctBase(x) + doAssert ok, "StorageKeys capacity exceeded" func ethAccessList*(list: openArray[AccessTuple]): common.AccessList = for x in list: - result.add common.AccessPair( + let ok = result.add common.AccessPair( address : ethAddr x.address, storageKeys: storageKeys x.storageKeys, ) + doAssert ok, "AccessList capacity exceeded" func ethAccessList*(x: Option[seq[AccessTuple]]): common.AccessList = if x.isSome: @@ -286,10 +288,10 @@ func w3Txs*(list: openArray[common.Transaction]): seq[Web3Tx] = proc w3AccessTuple*(ac: AccessPair): AccessTuple = AccessTuple( address: w3Addr ac.address, - storageKeys: w3Hash(ac.storageKeys) + storageKeys: w3Hash(distinctBase(ac.storage_keys)) ) -proc w3AccessList*(list: openArray[AccessPair]): seq[AccessTuple] = +proc w3AccessList*(list: common.AccessList): seq[AccessTuple] = result = newSeqOfCap[AccessTuple](list.len) for x in list: result.add w3AccessTuple(x) diff --git a/nimbus/common/common.nim b/nimbus/common/common.nim index 3fcca7710..007ef5c9b 100644 --- a/nimbus/common/common.nim +++ b/nimbus/common/common.nim @@ -190,7 +190,7 @@ proc init(com : CommonRef, time: some(genesis.timestamp) )) com.genesisHeader = toGenesisHeader(genesis, - com.currentFork, com.db, com.ldgType) + com.currentFork, config.chainId, com.db, com.ldgType) com.setForkId(com.genesisHeader) com.pos.timestamp = genesis.timestamp else: diff --git a/nimbus/common/genesis.nim b/nimbus/common/genesis.nim index 3a3ba5d3d..027f0d7b2 100644 --- a/nimbus/common/genesis.nim +++ b/nimbus/common/genesis.nim @@ -225,6 +225,7 @@ proc toGenesisHeader*( proc toGenesisHeader*( genesis: Genesis; fork: HardFork; + chainId: ChainId; db = CoreDbRef(nil); ledgerType = GenesisLedgerTypeDefault; ): BlockHeader @@ -232,7 +233,7 @@ proc toGenesisHeader*( ## Generate the genesis block header from the `genesis` and `config` ## argument value. let - db = if db.isNil: newCoreDbRef LegacyDbMemory else: db + db = if db.isNil: newCoreDbRef LegacyDbMemory, chainId else: db sdb = newStateDB(db, pruneTrie = true, ledgerType) toGenesisHeader(genesis, sdb, fork) @@ -246,7 +247,7 @@ proc toGenesisHeader*( ## argument value. let map = toForkTransitionTable(params.config) let fork = map.toHardFork(forkDeterminationInfo(0.toBlockNumber, params.genesis.timestamp)) - toGenesisHeader(params.genesis, fork, db, ledgerType) + toGenesisHeader(params.genesis, fork, params.config.chainId, db, ledgerType) # ------------------------------------------------------------------------------ # End diff --git a/nimbus/constants.nim b/nimbus/constants.nim index ca43ea4e6..12340cbf8 100644 --- a/nimbus/constants.nim +++ b/nimbus/constants.nim @@ -83,9 +83,6 @@ const DEFAULT_RPC_GAS_CAP* = 50_000_000.GasInt # EIP-4844 constants - MAX_CALLDATA_SIZE* = 1 shl 24 # 2^24 - MAX_ACCESS_LIST_SIZE* = 1 shl 24 # 2^24 - MAX_ACCESS_LIST_STORAGE_KEYS* = 1 shl 24 # 2^24 MAX_TX_WRAP_COMMITMENTS* = 1 shl 12 # 2^12 VERSIONED_HASH_VERSION_KZG* = 0x01.byte FIELD_ELEMENTS_PER_BLOB* = 4096 diff --git a/nimbus/core/eip4844.nim b/nimbus/core/eip4844.nim index 1ab12f8c2..8ad7a2b2a 100644 --- a/nimbus/core/eip4844.nim +++ b/nimbus/core/eip4844.nim @@ -9,7 +9,7 @@ # according to those terms. import - std/[os, strutils], + std/[os, strutils, typetraits], nimcrypto/sha2, kzg4844/kzg_ex as kzg, stew/results, @@ -108,7 +108,9 @@ func fakeExponential*(factor, numerator, denominator: UInt256): UInt256 = output div denominator proc getTotalBlobGas*(tx: Transaction): uint64 = - GAS_PER_BLOB * tx.versionedHashes.len.uint64 + let vhs = tx.payload.blob_versioned_hashes.valueOr: + return 0 + GAS_PER_BLOB * vhs.len.uint64 proc getTotalBlobGas*(versionedHashesLen: int): uint64 = GAS_PER_BLOB * versionedHashesLen.uint64 @@ -169,38 +171,44 @@ func validateEip4844Header*( proc validateBlobTransactionWrapper*(tx: PooledTransaction): Result[void, string] {.raises: [].} = - if tx.networkPayload.isNil: + if tx.tx.payload.blob_versioned_hashes.isNone: + if tx.blob_data.isSome: + return err("tx wrapper contains unexpected blobs") + return ok() + if tx.blob_data.isNone: return err("tx wrapper is none") + template blob_versioned_hashes: untyped = + tx.tx.payload.blob_versioned_hashes.unsafeGet + template blob_data: untyped = + tx.blob_data.unsafeGet + # note: assert blobs are not malformatted - let goodFormatted = tx.tx.versionedHashes.len == - tx.networkPayload.commitments.len and - tx.tx.versionedHashes.len == - tx.networkPayload.blobs.len and - tx.tx.versionedHashes.len == - tx.networkPayload.proofs.len + let goodFormatted = blob_versioned_hashes.len == + blob_data.commitments.len and + blob_versioned_hashes.len == + blob_data.blobs.len and + blob_versioned_hashes.len == + blob_data.proofs.len if not goodFormatted: return err("tx wrapper is ill formatted") # Verify that commitments match the blobs by checking the KZG proof - let res = kzg.verifyBlobKzgProofBatch(tx.networkPayload.blobs, - tx.networkPayload.commitments, tx.networkPayload.proofs) - if res.isErr: - return err(res.error) - - # Actual verification result - if not res.get(): + if not(? kzg.verifyBlobKzgProofBatch( + distinctBase(blob_data.blobs), + distinctBase(blob_data.commitments), + distinctBase(blob_data.proofs))): return err("Failed to verify network payload of a transaction") - # Now that all commitments have been verified, check that versionedHashes matches the commitments - for i in 0 ..< tx.tx.versionedHashes.len: + # Now that all commitments have been verified, check that versionedHashes + # matches the commitments + for i in 0 ..< blob_versioned_hashes.len: # this additional check also done in tx validation - if tx.tx.versionedHashes[i].data[0] != VERSIONED_HASH_VERSION_KZG: + if blob_versioned_hashes[i].data[0] != VERSIONED_HASH_VERSION_KZG: return err("wrong kzg version in versioned hash at index " & $i) - if tx.tx.versionedHashes[i] != - kzgToVersionedHash(tx.networkPayload.commitments[i]): + if blob_versioned_hashes[i] != kzgToVersionedHash(blob_data.commitments[i]): return err("tx versioned hash not match commitments at index " & $i) ok() diff --git a/nimbus/core/executor/process_block.nim b/nimbus/core/executor/process_block.nim index d175f6534..022313683 100644 --- a/nimbus/core/executor/process_block.nim +++ b/nimbus/core/executor/process_block.nim @@ -42,7 +42,8 @@ proc processTransactions*(vmState: BaseVMState; let rc = vmState.processTransaction(tx, sender, header) if rc.isErr: return err("Error processing tx with index " & $(txIndex) & ":" & rc.error) - vmState.receipts[txIndex] = vmState.makeReceipt(tx.txType) + vmState.receipts[txIndex] = + vmState.makeReceipt(tx.payload.tx_type.get(TxLegacy)) ok() proc procBlkPreamble(vmState: BaseVMState; diff --git a/nimbus/core/executor/process_transaction.nim b/nimbus/core/executor/process_transaction.nim index 28b92e118..fc9d783c8 100644 --- a/nimbus/core/executor/process_transaction.nim +++ b/nimbus/core/executor/process_transaction.nim @@ -60,7 +60,7 @@ proc commitOrRollbackDependingOnGasUsed( # Return remaining gas to the block gas counter so it is # available for the next transaction. - vmState.gasPool += tx.gasLimit - gasBurned + vmState.gasPool += tx.payload.gas.GasInt - gasBurned return ok(gasBurned) proc asyncProcessTransactionImpl( @@ -77,24 +77,25 @@ proc asyncProcessTransactionImpl( let roDB = vmState.readOnlyStateDB baseFee256 = header.eip1559BaseFee(fork) - baseFee = baseFee256.truncate(GasInt) - tx = eip1559TxNormalization(tx, baseFee) - priorityFee = min(tx.maxPriorityFee, tx.maxFee - baseFee) + baseFee = baseFee256 + priorityFee = min( + tx.payload.max_priority_fee_per_gas.get(tx.payload.max_fee_per_gas), + tx.payload.max_fee_per_gas - baseFee).truncate(int64) excessBlobGas = header.excessBlobGas.get(0'u64) # Return failure unless explicitely set `ok()` var res: Result[GasInt, string] = err("") await ifNecessaryGetAccounts(vmState, @[sender, vmState.coinbase()]) - if tx.to.isSome: - await ifNecessaryGetCode(vmState, tx.to.get) + if tx.payload.to.isSome: + await ifNecessaryGetCode(vmState, tx.payload.to.unsafeGet) # buy gas, then the gas goes into gasMeter - if vmState.gasPool < tx.gasLimit: + if vmState.gasPool < tx.payload.gas.GasInt: return err("gas limit reached. gasLimit=$1, gasNeeded=$2" % [ - $vmState.gasPool, $tx.gasLimit]) + $vmState.gasPool, $tx.payload.gas]) - vmState.gasPool -= tx.gasLimit + vmState.gasPool -= tx.payload.gas.GasInt # Actually, the eip-1559 reference does not mention an early exit. # @@ -109,11 +110,11 @@ proc asyncProcessTransactionImpl( vmState.stateDB.clearTransientStorage() # Execute the transaction. - vmState.captureTxStart(tx.gasLimit) + vmState.captureTxStart(tx.payload.gas.GasInt) let accTx = vmState.stateDB.beginSavepoint gasBurned = tx.txCallEvm(sender, vmState, fork) - vmState.captureTxEnd(tx.gasLimit - gasBurned) + vmState.captureTxEnd(tx.payload.gas.GasInt - gasBurned) res = commitOrRollbackDependingOnGasUsed(vmState, accTx, header, tx, gasBurned, priorityFee) else: diff --git a/nimbus/core/tx_pool.nim b/nimbus/core/tx_pool.nim index b4f176505..e74d80856 100644 --- a/nimbus/core/tx_pool.nim +++ b/nimbus/core/tx_pool.nim @@ -452,7 +452,6 @@ export tx_item.GasPrice, tx_item.`<=`, tx_item.`<`, - tx_item.effectiveGasTip, tx_item.info, tx_item.itemID, tx_item.sender, @@ -630,6 +629,7 @@ proc assembleBlock*( xp.packerVmExec().isOkOr: # updates vmState return err(error) + let com = xp.chain.com var blk = EthBlock( header: xp.chain.getHeader # uses updated vmState ) @@ -639,20 +639,19 @@ proc assembleBlock*( for item in nonceList.incNonce: let tx = item.pooledTx blk.txs.add tx.tx - if tx.networkPayload != nil: - for k in tx.networkPayload.commitments: + if tx.blob_data.isSome: + if not com.forkGTE(Cancun): + return err("PooledTransaction contains blobs prior to Cancun") + for k in tx.blob_data.unsafeGet.commitments: blobsBundle.commitments.add k - for p in tx.networkPayload.proofs: + for p in tx.blob_data.unsafeGet.proofs: blobsBundle.proofs.add p - for blob in tx.networkPayload.blobs: + for blob in tx.blob_data.unsafeGet.blobs: blobsBundle.blobs.add blob - let com = xp.chain.com if com.forkGTE(Shanghai): blk.withdrawals = some(com.pos.withdrawals) - if not com.forkGTE(Cancun) and blobsBundle.commitments.len > 0: - return err("PooledTransaction contains blobs prior to Cancun") let blobsBundleOpt = if com.forkGTE(Cancun): doAssert blobsBundle.commitments.len == blobsBundle.blobs.len diff --git a/nimbus/core/tx_pool/tx_chain/tx_basefee.nim b/nimbus/core/tx_pool/tx_chain/tx_basefee.nim index 029fbdc9f..494d08bbb 100644 --- a/nimbus/core/tx_pool/tx_chain/tx_basefee.nim +++ b/nimbus/core/tx_pool/tx_chain/tx_basefee.nim @@ -16,7 +16,7 @@ import ../../../common/common, ../../../constants, ../tx_item, - eth/eip1559 + eth/[common/transaction, eip1559] {.push raises: [].} diff --git a/nimbus/core/tx_pool/tx_desc.nim b/nimbus/core/tx_pool/tx_desc.nim index 44e934aac..e41b5cf21 100644 --- a/nimbus/core/tx_pool/tx_desc.nim +++ b/nimbus/core/tx_pool/tx_desc.nim @@ -242,10 +242,10 @@ proc verify*(xp: TxPoolRef): Result[void,TxInfo] if not initOk or lastSender != item.sender: initOk = true lastSender = item.sender - lastNonce = item.tx.nonce + lastNonce = item.tx.payload.nonce lastSublist = xp.txDB.bySender.eq(item.sender).value.data - elif lastNonce + 1 == item.tx.nonce: - lastNonce = item.tx.nonce + elif lastNonce + 1 == item.tx.payload.nonce: + lastNonce = item.tx.payload.nonce else: return err(txInfoVfyNonceChain) @@ -254,12 +254,12 @@ proc verify*(xp: TxPoolRef): Result[void,TxInfo] of txItemPending: discard of txItemStaged: - if lastSublist.eq(txItemPending).eq(item.tx.nonce - 1).isOk: + if lastSublist.eq(txItemPending).eq(item.tx.payload.nonce - 1).isOk: return err(txInfoVfyNonceChain) of txItemPacked: - if lastSublist.eq(txItemPending).eq(item.tx.nonce - 1).isOk: + if lastSublist.eq(txItemPending).eq(item.tx.payload.nonce - 1).isOk: return err(txInfoVfyNonceChain) - if lastSublist.eq(txItemStaged).eq(item.tx.nonce - 1).isOk: + if lastSublist.eq(txItemStaged).eq(item.tx.payload.nonce - 1).isOk: return err(txInfoVfyNonceChain) ok() diff --git a/nimbus/core/tx_pool/tx_item.nim b/nimbus/core/tx_pool/tx_item.nim index 2d1cb2e01..55c809c45 100644 --- a/nimbus/core/tx_pool/tx_item.nim +++ b/nimbus/core/tx_pool/tx_item.nim @@ -17,22 +17,14 @@ import ../../utils/ec_recover, ../../utils/utils, ./tx_info, - eth/[common, keys], + eth/[common, common/transaction, keys], stew/results +export transaction.GasPrice, transaction.GasPriceEx + {.push raises: [].} type - GasPrice* = ##| - ## Handy definition distinct from `GasInt` which is a commodity unit while - ## the `GasPrice` is the commodity valuation per unit of gas, similar to a - ## kind of currency. - distinct uint64 - - GasPriceEx* = ##\ - ## Similar to `GasPrice` but is allowed to be negative. - distinct int64 - TxItemStatus* = enum ##\ ## Current status of a transaction as seen by the pool. txItemPending = 0 @@ -115,12 +107,9 @@ proc init*(item: TxItemRef; status: TxItemStatus; info: string) = proc new*(T: type TxItemRef; tx: PooledTransaction; itemID: Hash256; status: TxItemStatus; info: string): Result[T,void] {.gcsafe,raises: [].} = ## Create item descriptor. - let rc = tx.tx.ecRecover - if rc.isErr: - return err() ok(T(itemID: itemID, tx: tx, - sender: rc.value, + sender: tx.tx.signature.from_address, timeStamp: utcTime(), info: info, status: status)) @@ -157,22 +146,7 @@ proc itemID*(tx: PooledTransaction): Hash256 = # core/types/transaction.go(297): func (tx *Transaction) Cost() *big.Int { proc cost*(tx: Transaction): UInt256 = ## Getter (go/ref compat): gas * gasPrice + value. - (tx.gasPrice * tx.gasLimit).u256 + tx.value - -# core/types/transaction.go(332): .. *Transaction) EffectiveGasTip(baseFee .. -# core/types/transaction.go(346): .. EffectiveGasTipValue(baseFee .. -proc effectiveGasTip*(tx: Transaction; baseFee: GasPrice): GasPriceEx = - ## The effective miner gas tip for the globally argument `baseFee`. The - ## result (which is a price per gas) might well be negative. - if tx.txType < TxEip1559: - (tx.gasPrice - baseFee.int64).GasPriceEx - else: - # London, EIP1559 - min(tx.maxPriorityFee, tx.maxFee - baseFee.int64).GasPriceEx - -proc effectiveGasTip*(tx: Transaction; baseFee: UInt256): GasPriceEx = - ## Variant of `effectiveGasTip()` - tx.effectiveGasTip(baseFee.truncate(uint64).GasPrice) + tx.payload.max_fee_per_gas * tx.payload.gas.u256 + tx.payload.value # ------------------------------------------------------------------------------ # Public functions, item getters diff --git a/nimbus/core/tx_pool/tx_tabs/tx_sender.nim b/nimbus/core/tx_pool/tx_tabs/tx_sender.nim index d4cfc5f3b..3dfa718dd 100644 --- a/nimbus/core/tx_pool/tx_tabs/tx_sender.nim +++ b/nimbus/core/tx_pool/tx_tabs/tx_sender.nim @@ -16,7 +16,7 @@ import std/[math], ../tx_info, ../tx_item, - eth/[common], + eth/[common, common/transaction], stew/[results, keyed_queue, keyed_queue/kq_debug, sorted_set] {.push raises: [].} @@ -120,7 +120,7 @@ proc getRank(schedData: TxSenderSchedRef): int64 = proc maxProfit(item: TxItemRef; baseFee: GasPrice): float64 = ## Profit calculator - item.tx.gasLimit.float64 * item.tx.effectiveGasTip(baseFee).float64 + item.tx.payload.gas.float64 * item.tx.effectiveGasTip(baseFee).float64 proc recalcProfit(nonceData: TxSenderNonceRef; baseFee: GasPrice) = ## Re-calculate profit value depending on `baseFee` @@ -129,7 +129,7 @@ proc recalcProfit(nonceData: TxSenderNonceRef; baseFee: GasPrice) = while rc.isOk: let item = rc.value.data nonceData.profit += item.maxProfit(baseFee) - rc = nonceData.nonceList.gt(item.tx.nonce) + rc = nonceData.nonceList.gt(item.tx.payload.nonce) # ------------------------------------------------------------------------------ # Private functions @@ -152,7 +152,7 @@ proc mkInxImpl(gt: var TxSenderTab; item: TxItemRef): Result[TxSenderInx,void] inxData.schedData.allList = inxData.allNonce else: inxData.allNonce = inxData.schedData.allList - let rc = inxData.allNonce.nonceList.insert(item.tx.nonce) + let rc = inxData.allNonce.nonceList.insert(item.tx.payload.nonce) if rc.isErr: return err() rc.value.data = item @@ -165,7 +165,7 @@ proc mkInxImpl(gt: var TxSenderTab; item: TxItemRef): Result[TxSenderInx,void] else: inxData.statusNonce = inxData.schedData.statusList[item.status] # this is a new item, checked at `all items sub-list` above - inxData.statusNonce.nonceList.insert(item.tx.nonce).value.data = item + inxData.statusNonce.nonceList.insert(item.tx.payload.nonce).value.data = item return ok(inxData) @@ -214,10 +214,10 @@ proc insert*(gt: var TxSenderTab; item: TxItemRef): bool inx.schedData.size.inc - inx.statusNonce.gasLimits += item.tx.gasLimit + inx.statusNonce.gasLimits += item.tx.payload.gas.GasInt inx.statusNonce.profit += tip - inx.allNonce.gasLimits += item.tx.gasLimit + inx.allNonce.gasLimits += item.tx.payload.gas.GasInt inx.allNonce.profit += tip return true @@ -233,21 +233,21 @@ proc delete*(gt: var TxSenderTab; item: TxItemRef): bool inx.schedData.size.dec - discard inx.allNonce.nonceList.delete(item.tx.nonce) + discard inx.allNonce.nonceList.delete(item.tx.payload.nonce) if inx.allNonce.nonceList.len == 0: # this was the last nonce for that sender account discard gt.addrList.delete(item.sender) return true - inx.allNonce.gasLimits -= item.tx.gasLimit + inx.allNonce.gasLimits -= item.tx.payload.gas.GasInt inx.allNonce.profit -= tip - discard inx.statusNonce.nonceList.delete(item.tx.nonce) + discard inx.statusNonce.nonceList.delete(item.tx.payload.nonce) if inx.statusNonce.nonceList.len == 0: inx.schedData.statusList[item.status] = nil return true - inx.statusNonce.gasLimits -= item.tx.gasLimit + inx.statusNonce.gasLimits -= item.tx.payload.gas.GasInt inx.statusNonce.profit -= tip return true @@ -293,7 +293,7 @@ proc verify*(gt: var TxSenderTab): Result[void,TxInfo] let (nonceKey, item) = (rcNonce.value.key, rcNonce.value.data) rcNonce = statusData.nonceList.gt(nonceKey) - statusGas += item.tx.gasLimit + statusGas += item.tx.payload.gas.GasInt statusCount.inc bucketProfit += item.maxProfit(gt.baseFee) @@ -330,7 +330,7 @@ proc verify*(gt: var TxSenderTab): Result[void,TxInfo] rcNonce = allData.nonceList.gt(nonceKey) allProfit += item.maxProfit(gt.baseFee) - allGas += item.tx.gasLimit + allGas += item.tx.payload.gas.GasInt allCount.inc if differs(allData.profit, allProfit): diff --git a/nimbus/core/tx_pool/tx_tabs/tx_status.nim b/nimbus/core/tx_pool/tx_tabs/tx_status.nim index 90bc42630..c3f8842de 100644 --- a/nimbus/core/tx_pool/tx_tabs/tx_status.nim +++ b/nimbus/core/tx_pool/tx_tabs/tx_status.nim @@ -77,7 +77,7 @@ proc mkInxImpl(sq: var TxStatusTab; item: TxItemRef): Result[TxStatusInx,void] inx.addrData.addrList[item.sender] = inx.nonceData # nonce sublist - let rc = inx.nonceData.nonceList.insert(item.tx.nonce) + let rc = inx.nonceData.nonceList.insert(item.tx.payload.nonce) if rc.isErr: return err() rc.value.data = item @@ -120,7 +120,7 @@ proc insert*(sq: var TxStatusTab; item: TxItemRef): bool let inx = rc.value sq.size.inc inx.addrData.size.inc - inx.addrData.gasLimits += item.tx.gasLimit + inx.addrData.gasLimits += item.tx.payload.gas.GasInt return true @@ -132,9 +132,9 @@ proc delete*(sq: var TxStatusTab; item: TxItemRef): bool sq.size.dec inx.addrData.size.dec - inx.addrData.gasLimits -= item.tx.gasLimit + inx.addrData.gasLimits -= item.tx.payload.gas.GasInt - discard inx.nonceData.nonceList.delete(item.tx.nonce) + discard inx.nonceData.nonceList.delete(item.tx.payload.nonce) if inx.nonceData.nonceList.len == 0: discard inx.addrData.addrList.delete(item.sender) @@ -174,7 +174,7 @@ proc verify*(sq: var TxStatusTab): Result[void,TxInfo] let (nonceKey, item) = (rcNonce.value.key, rcNonce.value.data) rcNonce = nonceData.nonceList.gt(nonceKey) - gasLimits += item.tx.gasLimit + gasLimits += item.tx.payload.gas.GasInt addrCount.inc if addrCount != addrData.size: diff --git a/nimbus/core/tx_pool/tx_tasks/tx_add.nim b/nimbus/core/tx_pool/tx_tasks/tx_add.nim index 6c5dbedf0..ab5370153 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_add.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_add.nim @@ -72,14 +72,17 @@ proc supersede(xp: TxPoolRef; item: TxItemRef): Result[void,TxInfo] var current: TxItemRef block: - let rc = xp.txDB.bySender.eq(item.sender).sub.eq(item.tx.nonce) + let rc = xp.txDB.bySender.eq(item.sender).sub.eq(item.tx.payload.nonce) if rc.isErr: return err(txInfoErrUnspecified) current = rc.value.data # verify whether replacing is allowed, at all - let bumpPrice = (current.tx.gasPrice * xp.priceBump.GasInt + 99) div 100 - if item.tx.gasPrice < current.tx.gasPrice + bumpPrice: + let bumpPrice = ( + current.tx.payload.max_fee_per_gas.truncate(int64) * + xp.priceBump.GasInt + 99) div 100 + if item.tx.payload.max_fee_per_gas.truncate(int64) < + current.tx.payload.max_fee_per_gas.truncate(int64) + bumpPrice: discard # return err(txInfoErrReplaceUnderpriced) # make space, delete item @@ -181,7 +184,7 @@ proc addTxs*(xp: TxPoolRef; for tx in txs.items: var reason: TxInfo - if tx.tx.txType == TxEip4844: + if tx.blob_data.isSome: let res = tx.validateBlobTransactionWrapper() if res.isErr: # move item to waste basket @@ -197,7 +200,7 @@ proc addTxs*(xp: TxPoolRef; else: let item = rcTx.value - rcInsert = accTab.getItemList(item.sender).insert(item.tx.nonce) + rcInsert = accTab.getItemList(item.sender).insert(item.tx.payload.nonce) if rcInsert.isErr: reason = txInfoErrSenderNonceIndex else: diff --git a/nimbus/core/tx_pool/tx_tasks/tx_bucket.nim b/nimbus/core/tx_pool/tx_tasks/tx_bucket.nim index cceec5304..505b20f78 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_bucket.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_bucket.nim @@ -53,7 +53,7 @@ proc bucketItemsReassignPending*(xp: TxPoolRef; labelFrom: TxItemStatus; proc bucketItemsReassignPending*(xp: TxPoolRef; item: TxItemRef) {.gcsafe,raises: [CatchableError].} = ## Variant of `bucketItemsReassignPending()` - xp.bucketItemsReassignPending(item.status, item.sender, item.tx.nonce) + xp.bucketItemsReassignPending(item.status, item.sender, item.tx.payload.nonce) proc bucketUpdateAll*(xp: TxPoolRef): bool @@ -71,10 +71,10 @@ proc bucketUpdateAll*(xp: TxPoolRef): bool for item in xp.pDoubleCheck: if item.reject == txInfoOk: # Check whether there was a gap when the head was moved backwards. - let rc = xp.txDB.bySender.eq(item.sender).sub.gt(item.tx.nonce) + let rc = xp.txDB.bySender.eq(item.sender).sub.gt(item.tx.payload.nonce) if rc.isOk: let nextItem = rc.value.data - if item.tx.nonce + 1 < nextItem.tx.nonce: + if item.tx.payload.nonce + 1 < nextItem.tx.payload.nonce: discard xp.disposeItemAndHigherNonces( item, txInfoErrNonceGap, txInfoErrImpliedNonceGap) else: diff --git a/nimbus/core/tx_pool/tx_tasks/tx_classify.nim b/nimbus/core/tx_pool/tx_tasks/tx_classify.nim index dedb6a240..ff88be2da 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_classify.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_classify.nim @@ -57,19 +57,19 @@ proc checkTxNonce(xp: TxPoolRef; item: TxItemRef): bool # get the next applicable nonce as registered on the account database let accountNonce = xp.chain.getNonce(item.sender) - if item.tx.nonce < accountNonce: + if item.tx.payload.nonce < accountNonce: debug "invalid tx: account nonce too small", - txNonce = item.tx.nonce, + txNonce = item.tx.payload.nonce, accountNonce return false - elif accountNonce < item.tx.nonce: + elif accountNonce < item.tx.payload.nonce: # for an existing account, nonces must come in increasing consecutive order let rc = xp.txDB.bySender.eq(item.sender) if rc.isOk: - if rc.value.data.sub.eq(item.tx.nonce - 1).isErr: + if rc.value.data.sub.eq(item.tx.payload.nonce - 1).isErr: debug "invalid tx: account nonces gap", - txNonce = item.tx.nonce, + txNonce = item.tx.payload.nonce, accountNonce return false @@ -87,7 +87,7 @@ proc txNonceActive(xp: TxPoolRef; item: TxItemRef): bool if rc.isErr: return true # Must not be in the `pending` bucket. - if rc.value.data.eq(txItemPending).eq(item.tx.nonce - 1).isOk: + if rc.value.data.eq(txItemPending).eq(item.tx.payload.nonce - 1).isOk: return false true @@ -96,30 +96,31 @@ proc txGasCovered(xp: TxPoolRef; item: TxItemRef): bool = ## Check whether the max gas consumption is within the gas limit (aka block ## size). let trgLimit = xp.chain.limits.trgLimit - if trgLimit < item.tx.gasLimit: + if trgLimit < item.tx.payload.gas.GasInt: debug "invalid tx: gasLimit exceeded", maxLimit = trgLimit, - gasLimit = item.tx.gasLimit + gasLimit = item.tx.payload.gas return false true proc txFeesCovered(xp: TxPoolRef; item: TxItemRef): bool = ## Ensure that the user was willing to at least pay the base fee ## And to at least pay the current data gasprice - if item.tx.txType >= TxEip1559: - if item.tx.maxFee.GasPriceEx < xp.chain.baseFee: + if item.tx.payload.tx_type.get(TxLegacy) >= TxEip1559: + if item.tx.payload.max_fee_per_gas.truncate(int64).GasPriceEx < + xp.chain.baseFee: debug "invalid tx: maxFee is smaller than baseFee", - maxFee = item.tx.maxFee, + maxFee = item.tx.payload.max_fee_per_gas, baseFee = xp.chain.baseFee return false - if item.tx.txType >= TxEip4844: + if item.tx.payload.max_fee_per_blob_gas.isSome: let excessBlobGas = xp.chain.excessBlobGas blobGasPrice = getBlobBaseFee(excessBlobGas) - if item.tx.maxFeePerBlobGas < blobGasPrice: + if item.tx.payload.max_fee_per_blob_gas.unsafeGet < blobGasPrice: debug "invalid tx: maxFeePerBlobGas smaller than blobGasPrice", - maxFeePerBlobGas=item.tx.maxFeePerBlobGas, + maxFeePerBlobGas=item.tx.payload.max_fee_per_blob_gas.unsafeGet, blobGasPrice=blobGasPrice return false true @@ -135,11 +136,11 @@ proc txCostInBudget(xp: TxPoolRef; item: TxItemRef): bool = require = gasCost return false let balanceOffGasCost = balance - gasCost - if balanceOffGasCost < item.tx.value: + if balanceOffGasCost < item.tx.payload.value: debug "invalid tx: not enough cash to send", available = balance, availableMinusGas = balanceOffGasCost, - require = item.tx.value + require = item.tx.payload.value return false true @@ -147,10 +148,11 @@ proc txCostInBudget(xp: TxPoolRef; item: TxItemRef): bool = proc txPreLondonAcceptableGasPrice(xp: TxPoolRef; item: TxItemRef): bool = ## For legacy transactions check whether minimum gas price and tip are ## high enough. These checks are optional. - if item.tx.txType < TxEip1559: + if item.tx.payload.tx_type.get(TxLegacy) < TxEip1559: if stageItemsPlMinPrice in xp.pFlags: - if item.tx.gasPrice.GasPriceEx < xp.pMinPlGasPrice: + if item.tx.payload.max_fee_per_gas.truncate(int64).GasPriceEx < + xp.pMinPlGasPrice: return false elif stageItems1559MinTip in xp.pFlags: @@ -161,14 +163,15 @@ proc txPreLondonAcceptableGasPrice(xp: TxPoolRef; item: TxItemRef): bool = proc txPostLondonAcceptableTipAndFees(xp: TxPoolRef; item: TxItemRef): bool = ## Helper for `classifyTxPacked()` - if item.tx.txType >= TxEip1559: + if item.tx.payload.tx_type.get(TxLegacy) >= TxEip1559: if stageItems1559MinTip in xp.pFlags: if item.tx.effectiveGasTip(xp.chain.baseFee) < xp.pMinTipPrice: return false if stageItems1559MinFee in xp.pFlags: - if item.tx.maxFee.GasPriceEx < xp.pMinFeePrice: + if item.tx.payload.max_fee_per_gas.truncate(int64).GasPriceEx < + xp.pMinFeePrice: return false true @@ -231,11 +234,10 @@ proc classifyValidatePacked*(xp: TxPoolRef; xp.chain.limits.maxLimit else: xp.chain.limits.trgLimit - tx = item.tx.eip1559TxNormalization(xp.chain.baseFee.GasInt) excessBlobGas = calcExcessBlobGas(vmState.parent) roDB.validateTransaction( - tx, item.sender, gasLimit, baseFee, excessBlobGas, fork).isOk + item.tx, item.sender, gasLimit, baseFee, excessBlobGas, fork).isOk proc classifyPacked*(xp: TxPoolRef; gasBurned, moreBurned: GasInt): bool = ## Classifier for *packing* (i.e. adding up `gasUsed` values after executing diff --git a/nimbus/core/tx_pool/tx_tasks/tx_dispose.nim b/nimbus/core/tx_pool/tx_tasks/tx_dispose.nim index f98018ada..f32dc3c5b 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_dispose.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_dispose.nim @@ -46,7 +46,7 @@ proc deleteOtherNonces(xp: TxPoolRef; item: TxItemRef; newerThan: Time): bool {.gcsafe,raises: [KeyError].} = let rc = xp.txDB.bySender.eq(item.sender).sub if rc.isOk: - for other in rc.value.data.incNonce(item.tx.nonce): + for other in rc.value.data.incNonce(item.tx.payload.nonce): # only delete non-expired items if newerThan < other.timeStamp: discard xp.txDB.dispose(other, txInfoErrTxExpiredImplied) @@ -105,7 +105,7 @@ proc disposeItemAndHigherNonces*(xp: TxPoolRef; item: TxItemRef; if rc.isOk: let nonceList = rc.value.data - for otherItem in nonceList.incNonce(item.tx.nonce): + for otherItem in nonceList.incNonce(item.tx.payload.nonce): if xp.txDB.dispose(otherItem, otherReason): result.inc diff --git a/nimbus/core/tx_pool/tx_tasks/tx_head.nim b/nimbus/core/tx_pool/tx_tasks/tx_head.nim index 6d5db80e7..5442b5c71 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_head.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_head.nim @@ -50,7 +50,7 @@ proc insert(xp: TxPoolRef; kq: TxHeadDiffRef; blockHash: Hash256) {.gcsafe,raises: [CatchableError].} = let db = xp.chain.com.db for tx in db.getBlockBody(blockHash).transactions: - if tx.versionedHashes.len > 0: + if tx.payload.blob_versioned_hashes.isSome: # EIP-4844 blobs are not persisted and cannot be re-broadcasted. # Note that it is also not possible to crete a cache in all cases, # as we may have never seen the actual blob sidecar while syncing. diff --git a/nimbus/core/tx_pool/tx_tasks/tx_packer.nim b/nimbus/core/tx_pool/tx_tasks/tx_packer.nim index c021d0387..488e9769d 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_packer.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_packer.nim @@ -27,7 +27,7 @@ import ../../../transaction, ../../../vm_state, ../../../vm_types, - ".."/[tx_chain, tx_desc, tx_item, tx_tabs, tx_tabs/tx_status, tx_info], + ".."/[tx_chain, tx_desc, tx_item, tx_tabs, tx_tabs/tx_status], "."/[tx_bucket, tx_classify] type @@ -87,11 +87,10 @@ proc runTx(pst: TxPackerStateRef; item: TxItemRef): GasInt let fork = pst.xp.chain.nextFork baseFee = pst.xp.chain.baseFee - tx = item.tx.eip1559TxNormalization(baseFee.GasInt) #safeExecutor "tx_packer.runTx": # # Execute transaction, may return a wildcard `Exception` - result = tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork) + result = item.tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork) pst.cleanState = false doAssert 0 <= result @@ -130,14 +129,15 @@ proc runTxCommit(pst: TxPackerStateRef; item: TxItemRef; gasBurned: GasInt) # Return remaining gas to the block gas counter so it is # available for the next transaction. - vmState.gasPool += item.tx.gasLimit - gasBurned + vmState.gasPool += item.tx.payload.gas.GasInt - gasBurned # gasUsed accounting vmState.cumulativeGasUsed += gasBurned - vmState.receipts[inx] = vmState.makeReceipt(item.tx.txType) + vmState.receipts[inx] = + vmState.makeReceipt(item.tx.payload.tx_type.get(TxLegacy)) # EIP-4844, count blobGasUsed - if item.tx.txType >= TxEip4844: + if item.tx.payload.max_fee_per_blob_gas.isSome: pst.blobGasUsed += item.tx.getTotalBlobGas # Update txRoot @@ -175,7 +175,7 @@ proc vmExecInit(xp: TxPoolRef): Result[TxPackerStateRef, string] let packer = TxPackerStateRef( # return value xp: xp, - tr: newCoreDbRef(LegacyDbMemory).mptPrune, + tr: newCoreDbRef(LegacyDbMemory, xp.chain.com.chainId).mptPrune, balance: xp.chain.vmState.readOnlyStateDB.getBalance(xp.chain.feeRecipient), numBlobPerBlock: 0, ) @@ -189,22 +189,23 @@ proc vmExecGrabItem(pst: TxPackerStateRef; item: TxItemRef): Result[bool,void] xp = pst.xp vmState = xp.chain.vmState - if not item.tx.validateChainId(xp.chain.com.chainId): - discard xp.txDB.dispose(item, txInfoChainIdMismatch) - return ok(false) # continue with next account - # EIP-4844 - if pst.numBlobPerBlock + item.tx.versionedHashes.len > MAX_BLOBS_PER_BLOCK: + numBlobVersionedHashes = + if item.tx.payload.blob_versioned_hashes.isSome: + item.tx.payload.blob_versioned_hashes.unsafeGet.len + else: + 0 + if pst.numBlobPerBlock + numBlobVersionedHashes > MAX_BLOBS_PER_BLOCK: return err() # stop collecting - pst.numBlobPerBlock += item.tx.versionedHashes.len + pst.numBlobPerBlock += numBlobVersionedHashes # Verify we have enough gas in gasPool - if vmState.gasPool < item.tx.gasLimit: + if vmState.gasPool < item.tx.payload.gas.GasInt: # skip this transaction and # continue with next account # if we don't have enough gas return ok(false) - vmState.gasPool -= item.tx.gasLimit + vmState.gasPool -= item.tx.payload.gas.GasInt # Validate transaction relative to the current vmState if not xp.classifyValidatePacked(vmState, item): diff --git a/nimbus/core/validate.nim b/nimbus/core/validate.nim index 5fba80dcf..9aeb130d8 100644 --- a/nimbus/core/validate.nim +++ b/nimbus/core/validate.nim @@ -217,12 +217,8 @@ proc validateUncles(com: CommonRef; header: BlockHeader; # ------------------------------------------------------------------------------ func gasCost*(tx: Transaction): UInt256 = - if tx.txType >= TxEip4844: - tx.gasLimit.u256 * tx.maxFee.u256 + tx.getTotalBlobGas.u256 * tx.maxFeePerBlobGas.u256 - elif tx.txType >= TxEip1559: - tx.gasLimit.u256 * tx.maxFee.u256 - else: - tx.gasLimit.u256 * tx.gasPrice.u256 + tx.payload.gas.u256 * tx.payload.max_fee_per_gas + + tx.getTotalBlobGas.u256 * tx.payload.max_fee_per_blob_gas.get(UInt256.zero) proc validateTxBasic*( tx: Transaction; ## tx to validate @@ -230,54 +226,63 @@ proc validateTxBasic*( validateFork: bool = true): Result[void, string] = if validateFork: - if tx.txType == TxEip2930 and fork < FkBerlin: - return err("invalid tx: Eip2930 Tx type detected before Berlin") + case tx.payload.tx_type.get(TxLegacy) + of TxEip4844: + if fork < FkCancun: + return err("invalid tx: Eip4844 Tx type detected before Cancun") + of TxEip1559: + if fork < FkLondon: + return err("invalid tx: Eip1559 Tx type detected before London") + of TxEip2930: + if fork < FkBerlin: + return err("invalid tx: Eip2930 Tx type detected before Berlin") + of TxLegacy: + if tx.payload.tx_type.isSome and fork < FkHomestead: + return err("invalid tx: Tx with chain ID detected before Homestead") - if tx.txType == TxEip1559 and fork < FkLondon: - return err("invalid tx: Eip1559 Tx type detected before London") - - if tx.txType == TxEip4844 and fork < FkCancun: - return err("invalid tx: Eip4844 Tx type detected before Cancun") - - if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE: + if fork >= FkShanghai and + tx.contractCreation and tx.payload.input.len > EIP3860_MAX_INITCODE_SIZE: return err("invalid tx: initcode size exceeds maximum") try: # The total must be the larger of the two - if tx.maxFee < tx.maxPriorityFee: - return err("invalid tx: maxFee is smaller than maPriorityFee. maxFee=$1, maxPriorityFee=$2" % [ - $tx.maxFee, $tx.maxPriorityFee]) + if tx.payload.max_fee_per_gas < + tx.payload.max_priority_fee_per_gas.get(tx.payload.max_fee_per_gas): + return err("invalid tx: maxFee is smaller than maxPriorityFee. maxFee=$1, maxPriorityFee=$2" % [ + $tx.payload.max_fee_per_gas, $tx.payload.max_priority_fee_per_gas]) - if tx.gasLimit < tx.intrinsicGas(fork): + if tx.payload.gas.int64 < tx.intrinsicGas(fork): return err("invalid tx: not enough gas to perform calculation. avail=$1, require=$2" % [ - $tx.gasLimit, $tx.intrinsicGas(fork)]) + $tx.payload.gas, $tx.intrinsicGas(fork)]) if fork >= FkCancun: - if tx.payload.len > MAX_CALLDATA_SIZE: + if tx.payload.input.len > MAX_CALLDATA_SIZE: return err("invalid tx: payload len exceeds MAX_CALLDATA_SIZE. len=" & - $tx.payload.len) + $tx.payload.input.len) - if tx.accessList.len > MAX_ACCESS_LIST_SIZE: - return err("invalid tx: access list len exceeds MAX_ACCESS_LIST_SIZE. len=" & - $tx.accessList.len) + if tx.payload.access_list.isSome: + if tx.payload.access_list.unsafeGet.len > MAX_ACCESS_LIST_SIZE: + return err("invalid tx: access list len exceeds MAX_ACCESS_LIST_SIZE. len=" & + $tx.payload.access_list.unsafeGet.len) - for i, acl in tx.accessList: - if acl.storageKeys.len > MAX_ACCESS_LIST_STORAGE_KEYS: - return err("invalid tx: access list storage keys len exceeds MAX_ACCESS_LIST_STORAGE_KEYS. " & - "index=$1, len=$2" % [$i, $acl.storageKeys.len]) + for i, acl in tx.payload.access_list.unsafeGet: + if acl.storage_keys.len > MAX_ACCESS_LIST_STORAGE_KEYS: + return err("invalid tx: access list storage keys len exceeds MAX_ACCESS_LIST_STORAGE_KEYS. " & + "index=$1, len=$2" % [$i, $acl.storage_keys.len]) - if tx.txType >= TxEip4844: - if tx.to.isNone: + if tx.payload.tx_type == Opt.some TxEip4844: + if tx.payload.to.isNone: return err("invalid tx: destination must be not empty") - if tx.versionedHashes.len == 0: + if tx.payload.blob_versioned_hashes.isNone or + tx.payload.blob_versioned_hashes.unsafeGet.len == 0: return err("invalid tx: there must be at least one blob") - if tx.versionedHashes.len > MAX_BLOBS_PER_BLOCK: + if tx.payload.blob_versioned_hashes.unsafeGet.len > MAX_BLOBS_PER_BLOCK: return err("invalid tx: versioned hashes len exceeds MAX_BLOBS_PER_BLOCK=" & $MAX_BLOBS_PER_BLOCK & - ". get=" & $tx.versionedHashes.len) + ". get=" & $tx.payload.blob_versioned_hashes.unsafeGet.len) - for i, bv in tx.versionedHashes: + for i, bv in tx.payload.blob_versioned_hashes.unsafeGet: if bv.data[0] != VERSIONED_HASH_VERSION_KZG: return err("invalid tx: one of blobVersionedHash has invalid version. " & "get=$1, expect=$2" % [$bv.data[0].int, $VERSIONED_HASH_VERSION_KZG.int]) @@ -320,14 +325,14 @@ proc validateTransaction*( # The parallel lowGasLimit.json test never triggers the case checked below # as the paricular transaction is omitted (the txs list is just set empty.) try: - if maxLimit < tx.gasLimit: + if maxLimit < tx.payload.gas.int64: return err("invalid tx: block header gasLimit exceeded. maxLimit=$1, gasLimit=$2" % [ - $maxLimit, $tx.gasLimit]) + $maxLimit, $tx.payload.gas]) # ensure that the user was willing to at least pay the base fee - if tx.maxFee < baseFee.truncate(int64): + if tx.payload.max_fee_per_gas < baseFee: return err("invalid tx: maxFee is smaller than baseFee. maxFee=$1, baseFee=$2" % [ - $tx.maxFee, $baseFee]) + $tx.payload.max_fee_per_gas, $baseFee]) # the signer must be able to fully afford the transaction let gasCost = tx.gasCost() @@ -336,15 +341,15 @@ proc validateTransaction*( return err("invalid tx: not enough cash for gas. avail=$1, require=$2" % [ $balance, $gasCost]) - if balance - gasCost < tx.value: + if balance - gasCost < tx.payload.value: return err("invalid tx: not enough cash to send. avail=$1, availMinusGas=$2, require=$3" % [ - $balance, $(balance-gasCost), $tx.value]) + $balance, $(balance-gasCost), $tx.payload.value]) - if tx.nonce != nonce: + if tx.payload.nonce != nonce: return err("invalid tx: account nonce mismatch. txNonce=$1, accNonce=$2" % [ - $tx.nonce, $nonce]) + $tx.payload.nonce, $nonce]) - if tx.nonce == high(uint64): + if tx.payload.nonce == high(uint64): return err("invalid tx: nonce at maximum") # EIP-3607 Reject transactions from senders with deployed code @@ -357,12 +362,13 @@ proc validateTransaction*( return err("invalid tx: sender is not an EOA. sender=$1, codeHash=$2" % [ sender.toHex, codeHash.data.toHex]) - if tx.txType >= TxEip4844: + if tx.payload.max_fee_per_blob_gas.isSome: # ensure that the user was willing to at least pay the current data gasprice let blobGasPrice = getBlobBaseFee(excessBlobGas) - if tx.maxFeePerBlobGas < blobGasPrice: + if tx.payload.max_fee_per_blob_gas.unsafeGet < blobGasPrice: return err("invalid tx: maxFeePerBlobGas smaller than blobGasPrice. " & - "maxFeePerBlobGas=$1, blobGasPrice=$2" % [$tx.maxFeePerBlobGas, $blobGasPrice]) + "maxFeePerBlobGas=$1, blobGasPrice=$2" % + [$tx.payload.max_fee_per_blob_gas, $blobGasPrice]) except CatchableError as ex: return err(ex.msg) diff --git a/nimbus/db/access_list.nim b/nimbus/db/access_list.nim index 12b35a106..1790bce37 100644 --- a/nimbus/db/access_list.nim +++ b/nimbus/db/access_list.nim @@ -23,9 +23,10 @@ type # Private helpers # ------------------------------------------------------------------------------ -func toStorageKeys(slots: SlotSet): seq[StorageKey] = +func toStorageKeys(slots: SlotSet): StorageKeys = for slot in slots: - result.add slot.toBytesBE + let ok = result.add slot.toBytesBE() + doAssert ok, "StorageKeys capacity exceeded" # ------------------------------------------------------------------------------ # Public constructors @@ -71,10 +72,11 @@ proc clear*(ac: var AccessList) {.inline.} = func getAccessList*(ac: AccessList): common.AccessList = for address, slots in ac.slots: - result.add common.AccessPair( + let ok = result.add common.AccessPair( address : address, storageKeys: slots.toStorageKeys, ) + doAssert ok, "AccessList capacity exceeded" func equal*(ac: AccessList, other: var AccessList): bool = if ac.slots.len != other.slots.len: diff --git a/nimbus/db/core_db/base/base_desc.nim b/nimbus/db/core_db/base/base_desc.nim index ff8006950..935cc7021 100644 --- a/nimbus/db/core_db/base/base_desc.nim +++ b/nimbus/db/core_db/base/base_desc.nim @@ -148,7 +148,7 @@ type # Sub-descriptor: KVT methods # -------------------------------------------------- CoreDbKvtBackendFn* = proc(): CoreDbKvtBackendRef {.noRaise.} - CoreDbKvtGetFn* = proc(k: openArray[byte]): CoreDbRc[Blob] {.noRaise.} + CoreDbKvtGetFn* = proc(k: openArray[byte]): CoreDbRc[Blob] {.noRaise.} CoreDbKvtDelFn* = proc(k: openArray[byte]): CoreDbRc[void] {.noRaise.} CoreDbKvtPutFn* = proc(k: openArray[byte]; v: openArray[byte]): CoreDbRc[void] {.noRaise.} @@ -287,6 +287,7 @@ type profTab*: CoreDbProfListRef ## Profiling data (if any) ledgerHook*: RootRef ## Debugging/profiling, to be used by ledger methods*: CoreDbBaseFns + chainId*: ChainId CoreDbErrorRef* = ref object of RootRef ## Generic error object diff --git a/nimbus/db/core_db/core_apps_newapi.nim b/nimbus/db/core_db/core_apps_newapi.nim index 209646f28..c1ae0fa82 100644 --- a/nimbus/db/core_db/core_apps_newapi.nim +++ b/nimbus/db/core_db/core_apps_newapi.nim @@ -16,7 +16,7 @@ import std/[algorithm, options, sequtils], chronicles, - eth/[common, rlp], + eth/[common, common/transaction, rlp], results, stew/byteutils, "../.."/[errors, constants], @@ -150,7 +150,8 @@ iterator getBlockTransactions*( ): Transaction {.gcsafe, raises: [RlpError].} = for encodedTx in db.getBlockTransactionData(header.txRoot): - yield rlp.decode(encodedTx, Transaction) + yield Transaction.fromBytes(encodedTx, db.chainId).valueOr: + raise (ref MalformedRlpError)(msg: "Invalid transaction in block") iterator getBlockTransactionHashes*( @@ -161,8 +162,9 @@ iterator getBlockTransactionHashes*( ## Returns an iterable of the transaction hashes from th block specified ## by the given block header. for encodedTx in db.getBlockTransactionData(blockHeader.txRoot): - let tx = rlp.decode(encodedTx, Transaction) - yield rlpHash(tx) # beware EIP-4844 + let tx = Transaction.fromBytes(encodedTx, db.chainId).valueOr: + raise (ref MalformedRlpError)(msg: "Invalid transaction in block") + yield tx.compute_tx_hash(db.chainId) iterator getWithdrawalsData*( diff --git a/nimbus/db/core_db/memory_only.nim b/nimbus/db/core_db/memory_only.nim index 9977347b8..07172d3b2 100644 --- a/nimbus/db/core_db/memory_only.nim +++ b/nimbus/db/core_db/memory_only.nim @@ -53,6 +53,7 @@ export proc newCoreDbRef*( db: TrieDatabaseRef; + chainId: ChainId; ): CoreDbRef {.gcsafe, deprecated: "use newCoreDbRef(LegacyDbPersistent,)".} = ## Legacy constructor. @@ -60,10 +61,13 @@ proc newCoreDbRef*( ## Note: Using legacy notation `newCoreDbRef()` rather than ## `CoreDbRef.init()` because of compiler coughing. ## - db.newLegacyPersistentCoreDbRef() + let res = db.newLegacyPersistentCoreDbRef() + res.chainId = chainId + res proc newCoreDbRef*( dbType: static[CoreDbType]; # Database type symbol + chainId: ChainId; ): CoreDbRef = ## Constructor for volatile/memory type DB ## @@ -71,19 +75,23 @@ proc newCoreDbRef*( ## `CoreDbRef.init()` because of compiler coughing. ## when dbType == LegacyDbMemory: - newLegacyMemoryCoreDbRef() + let res = newLegacyMemoryCoreDbRef() elif dbType == AristoDbMemory: - newAristoMemoryCoreDbRef() + let res = newAristoMemoryCoreDbRef() elif dbType == AristoDbVoid: - newAristoVoidCoreDbRef() + let res = newAristoVoidCoreDbRef() else: {.error: "Unsupported constructor " & $dbType & ".newCoreDbRef()".} + res.chainId = chainId + res + proc newCoreDbRef*( dbType: static[CoreDbType]; # Database type symbol + chainId: ChainId; qidLayout: QidLayoutRef; # `Aristo` only ): CoreDbRef = ## Constructor for volatile/memory type DB @@ -92,15 +100,18 @@ proc newCoreDbRef*( ## `CoreDbRef.init()` because of compiler coughing. ## when dbType == AristoDbMemory: - newAristoMemoryCoreDbRef(DefaultQidLayoutRef) + let res = newAristoMemoryCoreDbRef(DefaultQidLayoutRef) elif dbType == AristoDbVoid: - newAristoVoidCoreDbRef() + let res = newAristoVoidCoreDbRef() else: {.error: "Unsupported constructor " & $dbType & ".newCoreDbRef()" & " with qidLayout argument".} + res.chainId = chainId + res + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/nimbus/db/core_db/persistent.nim b/nimbus/db/core_db/persistent.nim index d91b1bd5c..35d845da4 100644 --- a/nimbus/db/core_db/persistent.nim +++ b/nimbus/db/core_db/persistent.nim @@ -35,23 +35,28 @@ export proc newCoreDbRef*( dbType: static[CoreDbType]; # Database type symbol path: string; # Storage path for database + chainId: ChainId; ): CoreDbRef = ## Constructor for persistent type DB ## ## Note: Using legacy notation `newCoreDbRef()` rather than ## `CoreDbRef.init()` because of compiler coughing. when dbType == LegacyDbPersistent: - newLegacyPersistentCoreDbRef path + let res = newLegacyPersistentCoreDbRef path elif dbType == AristoDbRocks: - newAristoRocksDbCoreDbRef path + let res = newAristoRocksDbCoreDbRef path else: {.error: "Unsupported dbType for persistent newCoreDbRef()".} + res.chainId = chainId + res + proc newCoreDbRef*( dbType: static[CoreDbType]; # Database type symbol path: string; # Storage path for database + chainId: ChainId; qidLayout: QidLayoutRef; # Optional for `Aristo`, ignored by others ): CoreDbRef = ## Constructor for persistent type DB @@ -59,10 +64,13 @@ proc newCoreDbRef*( ## Note: Using legacy notation `newCoreDbRef()` rather than ## `CoreDbRef.init()` because of compiler coughing. when dbType == AristoDbRocks: - newAristoRocksDbCoreDbRef(path, qlr) + let res = newAristoRocksDbCoreDbRef(path, qlr) else: {.error: "Unsupported dbType for persistent newCoreDbRef()" & " with qidLayout argument".} + res.chainId = chainId + res + # End diff --git a/nimbus/graphql/ethapi.nim b/nimbus/graphql/ethapi.nim index fd5d77ed6..8918364f7 100644 --- a/nimbus/graphql/ethapi.nim +++ b/nimbus/graphql/ethapi.nim @@ -49,6 +49,7 @@ type db: ReadOnlyStateDB TxNode = ref object of Node + chainId: ChainId tx: Transaction index: int blockNumber: common.BlockNumber @@ -113,6 +114,7 @@ proc txNode(ctx: GraphqlContextRef, tx: Transaction, index: int, blockNumber: co kind: nkMap, typeName: ctx.ids[ethTransaction], pos: Pos(), + chainId: ctx.com.chainId, tx: tx, index: index, blockNumber: blockNumber, @@ -282,7 +284,8 @@ proc getTxs(ctx: GraphqlContextRef, header: common.BlockHeader): RespResult = var list = respList() var index = 0 for n in getBlockTransactionData(ctx.chainDB, header.txRoot): - let tx = decodeTx(n) + let tx = Transaction.fromBytes(n, ctx.com.chainId).valueOr: + raise (ref MalformedRlpError)(msg: "Invalid transaction in block") list.add txNode(ctx, tx, index, header.blockNumber, header.fee) inc index @@ -614,7 +617,7 @@ proc txHash(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = proc txNonce(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - longNode(tx.tx.nonce) + longNode(tx.tx.payload.nonce) proc txIndex(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) @@ -653,61 +656,65 @@ proc txTo(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = if hres.isErr: return hres let h = HeaderNode(hres.get()) - ctx.accountNode(h.header, tx.tx.to.get()) + ctx.accountNode(h.header, tx.tx.payload.to.get()) proc txValue(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - bigIntNode(tx.tx.value) + bigIntNode(tx.tx.payload.value) proc txGasPrice(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - if tx.tx.txType == TxEip1559: + if tx.tx.payload.max_priority_fee_per_gas.isSome: if tx.baseFee.isNone: - return bigIntNode(tx.tx.gasPrice) + return bigIntNode(tx.tx.payload.max_fee_per_gas) let baseFee = tx.baseFee.get().truncate(GasInt) - let priorityFee = min(tx.tx.maxPriorityFee, tx.tx.maxFee - baseFee) + let priorityFee = min( + tx.tx.payload.max_priority_fee_per_gas.unsafeGet.truncate(int64), + tx.tx.payload.max_fee_per_gas.truncate(int64) - baseFee) bigIntNode(priorityFee + baseFee) else: - bigIntNode(tx.tx.gasPrice) + bigIntNode(tx.tx.payload.max_fee_per_gas) proc txMaxFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - if tx.tx.txType == TxEip1559: - bigIntNode(tx.tx.maxFee) + if tx.tx.payload.max_priority_fee_per_gas.isSome: + bigIntNode(tx.tx.payload.max_fee_per_gas) else: ok(respNull()) proc txMaxPriorityFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - if tx.tx.txType == TxEip1559: - bigIntNode(tx.tx.maxPriorityFee) + if tx.tx.payload.max_priority_fee_per_gas.isSome: + bigIntNode(tx.tx.payload.max_priority_fee_per_gas.unsafeGet) else: ok(respNull()) proc txEffectiveGasPrice(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) if tx.baseFee.isNone: - return bigIntNode(tx.tx.gasPrice) + return bigIntNode(tx.tx.payload.max_fee_per_gas.truncate(int64)) let baseFee = tx.baseFee.get().truncate(GasInt) - let priorityFee = min(tx.tx.maxPriorityFee, tx.tx.maxFee - baseFee) + let priorityFee = min( + tx.tx.payload.max_priority_fee_per_gas.unsafeGet.truncate(int64), + tx.tx.payload.max_fee_per_gas.truncate(int64) - baseFee) bigIntNode(priorityFee + baseFee) proc txChainId(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - if tx.tx.txType == TxLegacy: + if tx.tx.payload.tx_type.isNone: ok(respNull()) else: - longNode(tx.tx.chainId.uint64) + longNode(tx.chainId.uint64) proc txGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - longNode(tx.tx.gasLimit) + longNode(tx.tx.payload.gas) proc txInputData(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - resp(tx.tx.payload) + resp(distinctBase(tx.tx.payload.input)) proc txBlock(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let ctx = GraphqlContextRef(ud) @@ -743,7 +750,7 @@ proc txCreatedContract(ud: RootRef, params: Args, parent: Node): RespResult {.ap if hres.isErr: return hres let h = HeaderNode(hres.get()) - let contractAddress = generateAddress(sender, tx.tx.nonce) + let contractAddress = generateAddress(sender, tx.tx.payload.nonce) ctx.accountNode(h.header, contractAddress) proc txLogs(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = @@ -756,46 +763,55 @@ proc txLogs(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = proc txR(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - bigIntNode(tx.tx.R) + bigIntNode(ecdsa_unpack_signature(tx.tx.signature.ecdsa_signature).r) proc txS(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - bigIntNode(tx.tx.S) + bigIntNode(ecdsa_unpack_signature(tx.tx.signature.ecdsa_signature).s) proc txV(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = - let tx = TxNode(parent) - bigIntNode(tx.tx.V) + let + tx = TxNode(parent) + yParity = ecdsa_unpack_signature(tx.tx.signature.ecdsa_signature).y_parity + v = + if tx.tx.payload.tx_type.isNone: + if yParity: 28.u256 else: 27.u256 + elif tx.tx.payload.tx_type == Opt.some TxLegacy: + distinctBase(tx.chainId).u256 * 2 + (if yParity: 36.u256 else: 35.u256) + else: + if yParity: UInt256.one else: UInt256.zero + bigIntNode(v) proc txType(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - let typ = resp(ord(tx.tx.txType)) + let typ = resp(ord(tx.tx.payload.tx_type.get(TxLegacy))) ok(typ) proc txAccessList(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let ctx = GraphqlContextRef(ud) let tx = TxNode(parent) - if tx.tx.txType == TxLegacy: + if tx.tx.payload.access_list.isNone: ok(respNull()) else: var list = respList() - for x in tx.tx.accessList: + for x in tx.tx.payload.access_list.unsafeGet: list.add aclNode(ctx, x) ok(list) proc txMaxFeePerBlobGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - if tx.tx.txType < TxEIP4844: + if tx.tx.payload.max_fee_per_blob_gas.isNone: ok(respNull()) else: - longNode(tx.tx.maxFeePerBlobGas) + longNode(tx.tx.payload.max_fee_per_blob_gas.unsafeGet) proc txVersionedHashes(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - if tx.tx.txType < TxEIP4844: + if tx.tx.payload.blob_versioned_hashes.isNone: ok(respNull()) else: var list = respList() - for hs in tx.tx.versionedHashes: + for hs in tx.tx.payload.blob_versioned_hashes.unsafeGet: list.add resp("0x" & hs.data.toHex) ok(list) @@ -1364,8 +1380,10 @@ proc sendRawTransaction(ud: RootRef, params: Args, parent: Node): RespResult {.a let ctx = GraphqlContextRef(ud) try: let data = hexToSeqByte(params[0].val.stringVal) - let tx = decodePooledTx(data) # we want to know if it is a valid tx blob - let txHash = rlpHash(tx) + # we want to know if it is a valid tx blob + let tx = PooledTransaction.fromBytes(data, ctx.com.chainId).valueOr: + return err("Invalid `PooledTransaction`") + let txHash = tx.tx.compute_tx_hash(ctx.com.chainId) ctx.txPool.add(tx) diff --git a/nimbus/nimbus.nim b/nimbus/nimbus.nim index cbdd02948..e45751198 100644 --- a/nimbus/nimbus.nim +++ b/nimbus/nimbus.nim @@ -283,8 +283,12 @@ proc start(nimbus: NimbusNode, conf: NimbusConf) = let coreDB = # Resolve statically for database type case conf.chainDbMode: - of Prune,Archive: LegacyDbPersistent.newCoreDbRef(string conf.dataDir) - of Aristo: AristoDbRocks.newCoreDbRef(string conf.dataDir) + of Prune,Archive: + LegacyDbPersistent.newCoreDbRef( + string conf.dataDir, conf.networkParams.config.chainId) + of Aristo: + AristoDbRocks.newCoreDbRef( + string conf.dataDir, conf.networkParams.config.chainId) let com = CommonRef.new( db = coreDB, pruneTrie = (conf.chainDbMode == ChainDbMode.Prune), diff --git a/nimbus/rpc/oracle.nim b/nimbus/rpc/oracle.nim index 11cd3346c..255146f16 100644 --- a/nimbus/rpc/oracle.nim +++ b/nimbus/rpc/oracle.nim @@ -8,7 +8,7 @@ # those terms. import - std/[hashes, algorithm, strutils], + std/[hashes, algorithm, strutils, typetraits], eth/eip1559, stew/keyed_queue, stew/endians2, @@ -125,11 +125,11 @@ proc processBlock(oracle: Oracle, bc: BlockContent, percentiles: openArray[float for i, tx in bc.txs: let - reward = tx.effectiveGasTip(bc.header.fee) + reward = tx.effectiveGasTip(bc.header.fee.get(UInt256.zero)) gasUsed = bc.receipts[i].cumulativeGasUsed - prevUsed sorter[i] = TxGasAndReward( gasUsed: gasUsed.uint64, - reward: reward.u256 + reward: distinctBase(reward).u256 ) prevUsed = bc.receipts[i].cumulativeGasUsed diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index ca56e15f4..341345afa 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -259,9 +259,9 @@ proc setupEthRpc*( let accDB = stateDBFromTag(blockId("latest")) - tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1) eip155 = com.isEIP155(com.syncCurrent) - signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155) + tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1, eip155) + signedTx = signTransaction(tx, acc.privateKey, com.chainId) result = rlp.encode(signedTx) server.rpc("eth_sendTransaction") do(data: TransactionArgs) -> Web3Hash: @@ -279,31 +279,40 @@ proc setupEthRpc*( let accDB = stateDBFromTag(blockId("latest")) - tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1) eip155 = com.isEIP155(com.syncCurrent) - signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155) + tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1, eip155) + signedTx = signTransaction(tx, acc.privateKey, com.chainId) networkPayload = - if signedTx.txType == TxEip4844: + if signedTx.payload.blob_versioned_hashes.isSome: + template vhs: untyped = + signedTx.payload.blob_versioned_hashes.unsafeGet 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: + if data.blobs.get.len != vhs.len: raise newException(ValueError, "Incorrect number of blobs") - if data.commitments.get.len != signedTx.versionedHashes.len: + if data.commitments.get.len != vhs.len: raise newException(ValueError, "Incorrect number of commitments") - if data.proofs.get.len != signedTx.versionedHashes.len: + if data.proofs.get.len != vhs.len: raise newException(ValueError, "Incorrect number of proofs") - NetworkPayload( - blobs: data.blobs.get.mapIt it.NetworkBlob, - commitments: data.commitments.get.mapIt eth_types.KzgCommitment(it), - proofs: data.proofs.get.mapIt eth_types.KzgProof(it)) + + Opt.some NetworkPayload( + blobs: + List[NetworkBlob, MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(data.blobs.get.mapIt it.NetworkBlob), + commitments: + List[eth_types.KzgCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(data.commitments.get.mapIt eth_types.KzgCommitment(it)), + proofs: + List[eth_types.KzgProof, MAX_BLOB_COMMITMENTS_PER_BLOCK] + .init(data.proofs.get.mapIt eth_types.KzgProof(it))) 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) + Opt.none NetworkPayload + pooledTx = PooledTransaction(tx: signedTx, blob_data: networkPayload) txPool.add(pooledTx) - result = rlpHash(signedTx).w3Hash + result = signedTx.compute_tx_hash(com.chainId).w3Hash server.rpc("eth_sendRawTransaction") do(txBytes: seq[byte]) -> Web3Hash: ## Creates new message call transaction or a contract creation for signed transactions. @@ -312,8 +321,9 @@ proc setupEthRpc*( ## 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) + pooledTx = PooledTransaction.fromBytes(txBytes, com.chainId).valueOr: + raise (ref MalformedRlpError)(msg: "Invalid `PooledTransaction`") + txHash = pooledTx.tx.compute_tx_hash(com.chainId) txPool.add(pooledTx) let res = txPool.inPoolAndReason(txHash) @@ -381,7 +391,7 @@ proc setupEthRpc*( let txHash = data.ethHash() let res = txPool.getItem(txHash) if res.isOk: - return populateTransactionObject(res.get().tx) + return populateTransactionObject(res.get().tx, com.chainId) let txDetails = chainDB.getTransactionKey(txHash) if txDetails.index < 0: @@ -390,7 +400,8 @@ proc setupEthRpc*( let header = chainDB.getBlockHeader(txDetails.blockNumber) var tx: Transaction if chainDB.getTransaction(header.txRoot, txDetails.index, tx): - result = populateTransactionObject(tx, some(header), some(txDetails.index)) + result = populateTransactionObject( + tx, com.chainId, some(header), some(txDetails.index)) server.rpc("eth_getTransactionByBlockHashAndIndex") do(data: Web3Hash, quantity: Web3Quantity) -> TransactionObject: ## Returns information about a transaction by block hash and transaction index position. @@ -405,7 +416,8 @@ proc setupEthRpc*( var tx: Transaction if chainDB.getTransaction(header.txRoot, index, tx): - result = populateTransactionObject(tx, some(header), some(index)) + result = populateTransactionObject( + tx, com.chainId, some(header), some(index)) else: result = nil @@ -420,7 +432,8 @@ proc setupEthRpc*( var tx: Transaction if chainDB.getTransaction(header.txRoot, index, tx): - result = populateTransactionObject(tx, some(header), some(index)) + result = populateTransactionObject( + tx, com.chainId, some(header), some(index)) else: result = nil diff --git a/nimbus/rpc/rpc_utils.nim b/nimbus/rpc/rpc_utils.nim index 533f9b617..179c110dd 100644 --- a/nimbus/rpc/rpc_utils.nim +++ b/nimbus/rpc/rpc_utils.nim @@ -62,9 +62,8 @@ proc calculateMedianGasPrice*(chain: CoreDbRef): GasInt {.gcsafe, raises: [CatchableError].} = var prices = newSeqOfCap[GasInt](64) let header = chain.getCanonicalHead() - for encodedTx in chain.getBlockTransactionData(header.txRoot): - let tx = decodeTx(encodedTx) - prices.add(tx.gasPrice) + for tx in chain.getBlockTransactions(header): + prices.add(tx.payload.max_fee_per_gas.truncate(int64)) if prices.len > 0: sort(prices) @@ -79,32 +78,48 @@ proc calculateMedianGasPrice*(chain: CoreDbRef): GasInt const minGasPrice = 30_000_000_000.GasInt result = max(result, minGasPrice) -proc unsignedTx*(tx: TransactionArgs, chain: CoreDbRef, defaultNonce: AccountNonce): Transaction - {.gcsafe, raises: [CatchableError].} = - if tx.to.isSome: - result.to = some(ethAddr(tx.to.get)) - - if tx.gas.isSome: - result.gasLimit = tx.gas.get.GasInt - else: - result.gasLimit = 90000.GasInt - - if tx.gasPrice.isSome: - result.gasPrice = tx.gasPrice.get.GasInt - else: - result.gasPrice = calculateMedianGasPrice(chain) - - if tx.value.isSome: - result.value = tx.value.get - else: - result.value = 0.u256 - - if tx.nonce.isSome: - result.nonce = tx.nonce.get.AccountNonce - else: - result.nonce = defaultNonce - - result.payload = tx.payload +proc unsignedTx*( + tx: TransactionArgs, + chain: CoreDbRef, + defaultNonce: AccountNonce, + eip155 = true +): TransactionPayload {.gcsafe, raises: [CatchableError].} = + TransactionPayload( + nonce: + if tx.nonce.isSome: + tx.nonce.get.AccountNonce + else: + defaultNonce, + max_fee_per_gas: + if tx.gasPrice.isSome: + distinctBase(tx.gasPrice.get).u256 + else: + calculateMedianGasPrice(chain).uint64.u256, + gas: + if tx.gas.isSome: + distinctBase(tx.gas.get) + else: + 90000, + to: + if tx.to.isSome: + Opt.some ethAddr(tx.to.get) + else: + Opt.none(EthAddress), + value: + if tx.value.isSome: + tx.value.get + else: + UInt256.zero, + input: + if tx.payload.len > MAX_CALL_DATA_SIZE: + raise (ref ValueError)(msg: "tx.payload exceeds MAX_CALL_DATA_SIZE") + else: + List[byte, Limit MAX_CALL_DATA_SIZE].init(tx.payload), + tx_type: + if eip155: + Opt.some TxLegacy + else: + Opt.none TxType) proc toWd(wd: Withdrawal): WithdrawalObject = WithdrawalObject( @@ -120,39 +135,61 @@ proc toWdList(list: openArray[Withdrawal]): seq[WithdrawalObject] = result.add toWd(x) proc populateTransactionObject*(tx: Transaction, + chainId: ChainId, optionalHeader: Option[BlockHeader] = none(BlockHeader), txIndex: Option[int] = none(int)): TransactionObject {.gcsafe, raises: [ValidationError].} = - result = TransactionObject() - result.`type` = some w3Qty(tx.txType.ord) - if optionalHeader.isSome: - let header = optionalHeader.get - result.blockHash = some(w3Hash header.hash) - result.blockNumber = some(w3BlockNumber(header.blockNumber)) + let anyTx = AnyTransaction.fromOneOfBase(tx).valueOr: + raiseAssert "Cannot convert invalid `Transaction`: " & $tx + withTxVariant(anyTx): + result = TransactionObject() + when txKind >= TransactionKind.Legacy: + result.`type` = options.some w3Qty(txVariant.payload.tx_type.ord) + if optionalHeader.isSome: + let header = optionalHeader.get + result.blockHash = some(w3Hash header.hash) + result.blockNumber = some(w3BlockNumber(header.blockNumber)) - result.`from` = w3Addr tx.getSender() - result.gas = w3Qty(tx.gasLimit) - result.gasPrice = w3Qty(tx.gasPrice) - result.hash = w3Hash tx.rlpHash - result.input = tx.payload - result.nonce = w3Qty(tx.nonce) - result.to = some(w3Addr tx.destination) - if txIndex.isSome: - result.transactionIndex = some(w3Qty(txIndex.get)) - result.value = tx.value - result.v = w3Qty(tx.V) - result.r = u256(tx.R) - result.s = u256(tx.S) - result.maxFeePerGas = some w3Qty(tx.maxFee) - result.maxPriorityFeePerGas = some w3Qty(tx.maxPriorityFee) + result.`from` = w3Addr txVariant.signature.from_address + result.gas = w3Qty(txVariant.payload.gas) + result.gasPrice = w3Qty(txVariant.payload.max_fee_per_gas) + result.hash = w3Hash txVariant.payload.compute_sig_hash(chainId) + result.input = distinctBase(txVariant.payload.input) + result.nonce = w3Qty(txVariant.payload.nonce) + when txKind == TransactionKind.Eip4844: + result.to = some(w3Addr txVariant.payload.to) + else: + if txVariant.payload.to.isSome: + result.to = some(w3Addr txVariant.payload.to.unsafeGet) + if txIndex.isSome: + result.transactionIndex = some(w3Qty(txIndex.get)) + result.value = txVariant.payload.value + let + (yParity, r, s) = ecdsa_unpack_signature( + txVariant.signature.ecdsa_signature) + v = + when txKind == TransactionKind.Replayable: + if yParity: 28.u256 else: 27.u256 + elif txKind == TransactionKind.Legacy: + distinctBase(chainId).u256 * 2 + (if yParity: 36.u256 else: 35.u256) + else: + if yParity: UInt256.one else: UInt256.zero + result.v = w3Qty(v) + result.r = u256(r) + result.s = u256(s) + when txKind >= TransactionKind.Eip1559: + result.maxFeePerGas = some w3Qty(txVariant.payload.max_fee_per_gas) + result.maxPriorityFeePerGas = some w3Qty( + txVariant.payload.max_priority_fee_per_gas) - if tx.txType >= TxEip2930: - result.chainId = some(Web3Quantity(tx.chainId)) - result.accessList = some(w3AccessList(tx.accessList)) + when txKind >= TransactionKind.Eip2930: + result.chainId = some(Web3Quantity(chainId)) + result.accessList = some(w3AccessList(txVariant.payload.access_list)) - if tx.txType >= TxEIP4844: - result.maxFeePerBlobGas = some(tx.maxFeePerBlobGas) - result.blobVersionedHashes = some(w3Hashes tx.versionedHashes) + when txKind == TransactionKind.Eip4844: + result.maxFeePerBlobGas = some(txVariant.payload.max_fee_per_blob_gas) + result.blobVersionedHashes = + some(w3Hashes distinctBase(txVariant.payload.blob_versioned_hashes)) proc populateBlockObject*(header: BlockHeader, chain: CoreDbRef, fullTx: bool, isUncle = false): BlockObject {.gcsafe, raises: [CatchableError].} = @@ -191,7 +228,8 @@ proc populateBlockObject*(header: BlockHeader, chain: CoreDbRef, fullTx: bool, i if fullTx: var i = 0 for tx in chain.getBlockTransactions(header): - result.transactions.add txOrHash(populateTransactionObject(tx, some(header), some(i))) + result.transactions.add txOrHash( + populateTransactionObject(tx, chain.chainId, some(header), some(i))) inc i else: for x in chain.getBlockTransactionHashes(header): @@ -227,7 +265,7 @@ proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction, if tx.contractCreation: var sender: EthAddress if tx.getSender(sender): - let contractAddress = generateAddress(sender, tx.nonce) + let contractAddress = generateAddress(sender, tx.payload.nonce) result.contractAddress = some(w3Addr contractAddress) for log in receipt.logs: @@ -263,11 +301,15 @@ proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction, # 1 = success, 0 = failure. result.status = some(w3Qty(receipt.status.uint64)) - let normTx = eip1559TxNormalization(tx, header.baseFee.truncate(GasInt)) - result.effectiveGasPrice = w3Qty(normTx.gasPrice) + result.effectiveGasPrice = w3Qty( + (header.baseFee + min( + tx.payload.max_priority_fee_per_gas.get(tx.payload.max_fee_per_gas), + tx.payload.max_fee_per_gas - header.baseFee)).truncate(int64)) - if tx.txType == TxEip4844: - result.blobGasUsed = some(w3Qty(tx.versionedHashes.len.uint64 * GAS_PER_BLOB.uint64)) + if tx.payload.blob_versioned_hashes.isSome: + result.blobGasUsed = some(w3Qty( + tx.payload.blob_versioned_hashes.unsafeGet.len.uint64 * + GAS_PER_BLOB.uint64)) result.blobGasPrice = some(getBlobBaseFee(header.excessBlobGas.get(0'u64))) proc createAccessList*(header: BlockHeader, diff --git a/nimbus/sync/handlers/eth.nim b/nimbus/sync/handlers/eth.nim index bbc83de21..8188c3c7b 100644 --- a/nimbus/sync/handlers/eth.nim +++ b/nimbus/sync/handlers/eth.nim @@ -11,9 +11,10 @@ {.push raises: [].} import - std/[tables, times, hashes, sets], + std/[tables, times, hashes, sets, typetraits], chronicles, chronos, stew/endians2, + eth/common/transaction, eth/p2p, eth/p2p/peer_pool, ".."/[types, protocol], @@ -258,7 +259,7 @@ proc sendTransactions(ctx: EthWireRef, # This is used to avoid re-sending along pooledTxHashes # announcements/re-broadcasts ctx.addToKnownByPeer(txHashes, peer) - await peer.transactions(txs) + await peer.transactions(RawRlp txs.toBytes(ctx.chainId)) except TransportError: debug "Transport got closed during sendTransactions" @@ -276,16 +277,19 @@ proc fetchTransactions(ctx: EthWireRef, reqHashes: seq[Hash256], peer: Peer): Fu error "not able to get pooled transactions" return - let txs = res.get() + let txs = seq[PooledTransaction].fromBytes( + distinctBase(res.get().transactions), ctx.chainId).valueOr: + error "invalid pooled transactions" + return debug "fetchTx: received requested txs", - number = txs.transactions.len + number = txs.len # Remove from pending list regardless if tx is in result - for tx in txs.transactions: - let txHash = rlpHash(tx) + for tx in txs: + let txHash = tx.tx.compute_tx_hash(ctx.chainId) ctx.pending.excl txHash - ctx.txPool.add(txs.transactions) + ctx.txPool.add(txs) except TransportError: debug "Transport got closed during fetchTransactions" @@ -353,6 +357,7 @@ proc new*(_: type EthWireRef, txPool: TxPoolRef, peerPool: PeerPool): EthWireRef = let ctx = EthWireRef( + chainId: chain.com.chainId, db: chain.db, chain: chain, txPool: txPool, @@ -523,7 +528,7 @@ method handleAnnouncedTxs*(ctx: EthWireRef, ctx.addToKnownByPeer(txHashes, peer) for tx in txs: - if tx.versionedHashes.len > 0: + if tx.payload.blob_versioned_hashes.isSome: # EIP-4844 blobs are not persisted and cannot be broadcasted continue ctx.txPool.add PooledTransaction(tx: tx) @@ -533,7 +538,8 @@ method handleAnnouncedTxs*(ctx: EthWireRef, for i, txHash in txHashes: # Nodes must not automatically broadcast blob transactions to # their peers. per EIP-4844 spec - if ctx.txPool.inPoolAndOk(txHash) and txs[i].txType != TxEip4844: + if ctx.txPool.inPoolAndOk(txHash) and + txs[i].payload.blob_versioned_hashes.isNone: newTxHashes.add txHash validTxs.add txs[i] diff --git a/nimbus/sync/protocol/eth/eth_types.nim b/nimbus/sync/protocol/eth/eth_types.nim index ef62c2b4a..c2e3dfbc3 100644 --- a/nimbus/sync/protocol/eth/eth_types.nim +++ b/nimbus/sync/protocol/eth/eth_types.nim @@ -32,6 +32,7 @@ type forkNext*: uint64 # The RLP encoding must be variable-length EthWireBase* = ref object of RootRef + chainId*: ChainId EthState* = object totalDifficulty*: DifficultyInt diff --git a/nimbus/sync/protocol/eth66.nim b/nimbus/sync/protocol/eth66.nim index 015f4c227..673d7ae32 100644 --- a/nimbus/sync/protocol/eth66.nim +++ b/nimbus/sync/protocol/eth66.nim @@ -263,11 +263,11 @@ p2pProtocol eth66(version = ethVersion, trace trEthSendReplying & "EMPTY PooledTransactions (0x0a)", peer, sent=0, requested=txHashes.len - await response.send(txs.get) + await response.send(RawRlp(txs.get.toBytes(ctx.chainId))) # User message 0x0a: PooledTransactions. proc pooledTransactions( - peer: Peer, transactions: openArray[PooledTransaction]) + peer: Peer, transactions: RawRlp) nextId 0x0d diff --git a/nimbus/sync/protocol/eth67.nim b/nimbus/sync/protocol/eth67.nim index c3b610ab1..20571df6f 100644 --- a/nimbus/sync/protocol/eth67.nim +++ b/nimbus/sync/protocol/eth67.nim @@ -14,10 +14,11 @@ ## `eth/67 `_ import + std/typetraits, stint, chronicles, chronos, - eth/[common, p2p, p2p/private/p2p_types], + eth/[common, common/transaction, p2p, p2p/private/p2p_types], stew/byteutils, ./trace_config, ./eth/eth_types, @@ -161,12 +162,17 @@ p2pProtocol eth67(version = ethVersion, handleHandlerError(res) # User message 0x02: Transactions. - proc transactions(peer: Peer, transactions: openArray[Transaction]) = + proc transactions(peer: Peer, encodedTransactions: RawRlp) = + let + ctx = peer.networkState() + transactions = seq[Transaction].fromBytes( + distinctBase(encodedTransactions), ctx.chainId).valueOr: + raise (ref MalformedRlpError)(msg: "Invalid transaction in message") + when trEthTraceGossipOk: trace trEthRecvReceived & "Transactions (0x02)", peer, transactions=transactions.len - let ctx = peer.networkState() let res = ctx.handleAnnouncedTxs(peer, transactions) handleHandlerError(res) @@ -264,11 +270,11 @@ p2pProtocol eth67(version = ethVersion, trace trEthSendReplying & "EMPTY PooledTransactions (0x0a)", peer, sent=0, requested=txHashes.len - await response.send(txs.get) + await response.send(RawRlp(txs.get.toBytes(ctx.chainId))) # User message 0x0a: PooledTransactions. proc pooledTransactions( - peer: Peer, transactions: openArray[PooledTransaction]) + peer: Peer, transactions: RawRlp) # User message 0x0d: GetNodeData -- removed, was so 66ish # User message 0x0e: NodeData -- removed, was so 66ish diff --git a/nimbus/sync/protocol/eth68.nim b/nimbus/sync/protocol/eth68.nim index 488b55b4f..a5912e413 100644 --- a/nimbus/sync/protocol/eth68.nim +++ b/nimbus/sync/protocol/eth68.nim @@ -267,11 +267,11 @@ p2pProtocol eth68(version = ethVersion, trace trEthSendReplying & "EMPTY PooledTransactions (0x0a)", peer, sent=0, requested=txHashes.len - await response.send(txs.get) + await response.send(RawRlp(txs.get.toBytes(ctx.chainId))) # User message 0x0a: PooledTransactions. proc pooledTransactions( - peer: Peer, transactions: openArray[PooledTransaction]) + peer: Peer, transactions: RawRlp) # User message 0x0d: GetNodeData -- removed, was so 66ish # User message 0x0e: NodeData -- removed, was so 66ish diff --git a/nimbus/tracer.nim b/nimbus/tracer.nim index 31b963f4c..73039c14d 100644 --- a/nimbus/tracer.nim +++ b/nimbus/tracer.nim @@ -313,6 +313,7 @@ proc dumpDebuggingMetaData*(vmState: BaseVMState, header: BlockHeader, } var metaData = %{ + "chainId": %distinctBase(com.chainId), "blockNumber": %blockNumber.toHex, "txTraces": traceTransactions(captureCom, header, blockBody), "stateDump": dumpBlockState(captureCom, header, blockBody), diff --git a/nimbus/transaction.nim b/nimbus/transaction.nim index c57afebcc..64f080137 100644 --- a/nimbus/transaction.nim +++ b/nimbus/transaction.nim @@ -6,6 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import + std/[sequtils, typetraits], ./constants, ./errors, eth/[common, keys], ./utils/utils, common/evmforks, ./vm_gas_costs @@ -32,40 +33,25 @@ proc intrinsicGas*(tx: Transaction, fork: EVMFork): GasInt = # Compute the baseline gas cost for this transaction. This is the amount # of gas needed to send this transaction (but that is not actually used # for computation) - result = tx.payload.intrinsicGas(fork) + result = distinctBase(tx.payload.input).intrinsicGas(fork) if tx.contractCreation: result = result + gasFees[fork][GasTXCreate] if fork >= FkShanghai: # cannot use wordCount here, it will raise unlisted exception - let numWords = toWordSize(tx.payload.len) + let numWords = toWordSize(tx.payload.input.len) result = result + (gasFees[fork][GasInitcodeWord] * numWords) - if tx.txType > TxLegacy: - result = result + tx.accessList.len * ACCESS_LIST_ADDRESS_COST + if tx.payload.access_list.isSome: + template access_list: untyped = tx.payload.access_list.unsafeGet + result = result + access_list.len * ACCESS_LIST_ADDRESS_COST var numKeys = 0 - for n in tx.accessList: - inc(numKeys, n.storageKeys.len) + for n in access_list: + inc(numKeys, n.storage_keys.len) result = result + numKeys * ACCESS_LIST_STORAGE_KEY_COST proc getSignature*(tx: Transaction, output: var Signature): bool = - var bytes: array[65, byte] - bytes[0..31] = tx.R.toBytesBE() - bytes[32..63] = tx.S.toBytesBE() - - if tx.txType == TxLegacy: - var v = tx.V - if v >= EIP155_CHAIN_ID_OFFSET: - v = 28 - (v and 0x01) - elif v == 27 or v == 28: - discard - else: - return false - bytes[64] = byte(v - 27) - else: - bytes[64] = tx.V.byte - - let sig = Signature.fromRaw(bytes) + let sig = Signature.fromRaw(tx.signature.ecdsa_signature) if sig.isOk: output = sig[] return true @@ -77,13 +63,8 @@ proc toSignature*(tx: Transaction): Signature = proc getSender*(tx: Transaction, output: var EthAddress): bool = ## Find the address the transaction was sent from. - var sig: Signature - if tx.getSignature(sig): - var txHash = tx.txHashNoSignature - let pubkey = recover(sig, SkMessage(txHash.data)) - if pubkey.isOk: - output = pubkey[].toCanonicalAddress() - result = true + output = tx.signature.from_address + true proc getSender*(tx: Transaction): EthAddress = ## Raises error on failure to recover public key @@ -92,150 +73,30 @@ proc getSender*(tx: Transaction): EthAddress = proc getRecipient*(tx: Transaction, sender: EthAddress): EthAddress = if tx.contractCreation: - result = generateAddress(sender, tx.nonce) + result = generateAddress(sender, tx.payload.nonce) else: - result = tx.to.get() + result = tx.payload.to.get() -proc validateTxLegacy(tx: Transaction, fork: EVMFork) = - var - vMin = 27'i64 - vMax = 28'i64 - - if tx.V >= EIP155_CHAIN_ID_OFFSET: - let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2 - vMin = 35 + (2 * chainId) - vMax = vMin + 1 - - var isValid = tx.R >= UInt256.one - isValid = isValid and tx.S >= UInt256.one - isValid = isValid and tx.V >= vMin - isValid = isValid and tx.V <= vMax - isValid = isValid and tx.S < SECPK1_N - isValid = isValid and tx.R < SECPK1_N - - if fork >= FkHomestead: - isValid = isValid and tx.S < SECPK1_N div 2 - - if not isValid: - raise newException(ValidationError, "Invalid legacy transaction") - -proc validateTxEip2930(tx: Transaction) = - var isValid = tx.V == 0'i64 or tx.V == 1'i64 - isValid = isValid and tx.S >= UInt256.one - isValid = isValid and tx.S < SECPK1_N - isValid = isValid and tx.R < SECPK1_N - - if not isValid: - raise newException(ValidationError, "Invalid typed transaction") - -proc validateTxEip4844(tx: Transaction) = - validateTxEip2930(tx) - - var isValid = tx.payload.len <= MAX_CALLDATA_SIZE - isValid = isValid and tx.accessList.len <= MAX_ACCESS_LIST_SIZE - - for acl in tx.accessList: - isValid = isValid and - (acl.storageKeys.len <= MAX_ACCESS_LIST_STORAGE_KEYS) - - isValid = isValid and - tx.versionedHashes.len <= MAX_BLOBS_PER_BLOCK - - for bv in tx.versionedHashes: - isValid = isValid and - bv.data[0] == VERSIONED_HASH_VERSION_KZG - - if not isValid: - raise newException(ValidationError, "Invalid EIP-4844 transaction") - -proc validate*(tx: Transaction, fork: EVMFork) = +proc validate*(tx: Transaction, fork: EVMFork, chainId: ChainId) = # parameters pass validation rules - if tx.intrinsicGas(fork) > tx.gasLimit: + if tx.intrinsicGas(fork).uint64 > tx.payload.gas: raise newException(ValidationError, "Insufficient gas") - if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE: + if fork >= FkShanghai and tx.contractCreation and + tx.payload.input.len > EIP3860_MAX_INITCODE_SIZE: raise newException(ValidationError, "Initcode size exceeds max") + if tx.payload.blob_versioned_hashes.isSome: + template vhs: untyped = tx.payload.blob_versioned_hashes.unsafeGet + if vhs.len > MAX_BLOBS_PER_BLOCK: + raise newException(ValidationError, "Too many blob versioned hashes") + if not vhs.allIt(it.data[0] == VERSIONED_HASH_VERSION_KZG): + raise newException(ValidationError, "Invalid blob versioned hash") + # check signature validity - var sender: EthAddress - if not tx.getSender(sender): - raise newException(ValidationError, "Invalid signature or failed message verification") - - case tx.txType - of TxLegacy: - validateTxLegacy(tx, fork) - of TxEip4844: - validateTxEip4844(tx) - of TxEip2930, TxEip1559: - validateTxEip2930(tx) - -proc signTransaction*(tx: Transaction, privateKey: PrivateKey, chainId: ChainId, eip155: bool): Transaction = - result = tx - if eip155: - # trigger rlpEncodeEIP155 in nim-eth - result.V = chainId.int64 * 2'i64 + 35'i64 - - let - rlpTx = rlpEncode(result) - sig = sign(privateKey, rlpTx).toRaw - - case tx.txType - of TxLegacy: - if eip155: - result.V = sig[64].int64 + result.V - else: - result.V = sig[64].int64 + 27'i64 - else: - result.V = sig[64].int64 - - result.R = UInt256.fromBytesBE(sig[0..31]) - result.S = UInt256.fromBytesBE(sig[32..63]) - -# deriveChainId derives the chain id from the given v parameter -func deriveChainId*(v: int64, chainId: ChainId): ChainId = - if v == 27 or v == 28: - chainId - else: - ((v - 35) div 2).ChainId - -func validateChainId*(tx: Transaction, chainId: ChainId): bool = - if tx.txType == TxLegacy: - chainId.uint64 == deriveChainId(tx.V, chainId).uint64 - else: - chainId.uint64 == tx.chainId.uint64 - -func eip1559TxNormalization*(tx: Transaction; - baseFee: GasInt): Transaction = - ## This function adjusts a legacy transaction to EIP-1559 standard. This - ## is needed particularly when using the `validateTransaction()` utility - ## with legacy transactions. - result = tx - if tx.txType < TxEip1559: - result.maxPriorityFee = tx.gasPrice - result.maxFee = tx.gasPrice - else: - result.gasPrice = baseFee + min(result.maxPriorityFee, result.maxFee - baseFee) - -func effectiveGasTip*(tx: Transaction; baseFee: Option[UInt256]): GasInt = - var - maxPriorityFee = tx.maxPriorityFee - maxFee = tx.maxFee - baseFee = baseFee.get(0.u256).truncate(GasInt) - - if tx.txType < TxEip1559: - maxPriorityFee = tx.gasPrice - maxFee = tx.gasPrice - - min(maxPriorityFee, maxFee - baseFee) - -proc decodeTx*(bytes: openArray[byte]): Transaction = - var rlp = rlpFromBytes(bytes) - result = rlp.read(Transaction) - if rlp.hasData: - raise newException(RlpError, "rlp: input contains more than one value") - -proc decodePooledTx*(bytes: openArray[byte]): PooledTransaction = - var rlp = rlpFromBytes(bytes) - result = rlp.read(PooledTransaction) - if rlp.hasData: - raise newException(RlpError, "rlp: input contains more than one value") + let anyTx = AnyTransaction.fromOneOfBase(tx).valueOr: + raise newException(ValidationError, "Invalid combination of fields") + withTxVariant(anyTx): + if txVariant.validate_transaction(chainId).isErr: + raise newException(ValidationError, + "Invalid signature or failed message verification") diff --git a/nimbus/transaction/call_common.nim b/nimbus/transaction/call_common.nim index 46b5569c7..6361ad184 100644 --- a/nimbus/transaction/call_common.nim +++ b/nimbus/transaction/call_common.nim @@ -9,6 +9,7 @@ {.push raises: [].} import + std/typetraits, eth/common/eth_types, stint, options, stew/ptrops, chronos, ".."/[vm_types, vm_state, vm_computation, vm_state_transactions], @@ -124,7 +125,7 @@ proc initialAccessListEIP2929(call: CallParams) = # EIP2930 optional access list. for account in call.accessList: db.accessList(account.address) - for key in account.storageKeys: + for key in distinctBase(account.storageKeys): db.accessList(account.address, UInt256.fromBytesBE(key)) proc setupHost(call: CallParams): TransactionHost = diff --git a/nimbus/transaction/call_evm.nim b/nimbus/transaction/call_evm.nim index 7a6fe9cdb..87b10b410 100644 --- a/nimbus/transaction/call_evm.nim +++ b/nimbus/transaction/call_evm.nim @@ -153,40 +153,42 @@ proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState, result = CallParams( vmState: vmState, forkOverride: some(fork), - gasPrice: tx.gasPrice, - gasLimit: tx.gasLimit, + gasPrice: tx.payload.max_fee_per_gas.truncate(int64).GasInt, + gasLimit: tx.payload.gas.int64, sender: sender, - to: tx.destination, + to: tx.payload.to.get(default(EthAddress)), isCreate: tx.contractCreation, - value: tx.value, - input: tx.payload + value: tx.payload.value, + input: distinctBase(tx.payload.input) ) - if tx.txType > TxLegacy: - result.accessList = tx.accessList + if tx.payload.access_list.isSome: + result.accessList = tx.payload.access_list.unsafeGet - if tx.txType >= TxEip4844: - result.versionedHashes = tx.versionedHashes + if tx.payload.blob_versioned_hashes.isSome: + result.versionedHashes = + distinctBase(tx.payload.blob_versioned_hashes.unsafeGet) proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallParams = result = CallParams( vmState: vmState, forkOverride: some(fork), - gasPrice: tx.gasPrice, - gasLimit: tx.gasLimit, + gasPrice: tx.payload.max_fee_per_gas.truncate(int64).GasInt, + gasLimit: tx.payload.gas.int64, sender: sender, - to: tx.destination, + to: tx.payload.to.get(default(EthAddress)), isCreate: tx.contractCreation, - value: tx.value, - input: tx.payload, + value: tx.payload.value, + input: distinctBase(tx.payload.input), noIntrinsic: true, # Don't charge intrinsic gas. noRefund: true, # Don't apply gas refund/burn rule. ) - if tx.txType > TxLegacy: - result.accessList = tx.accessList + if tx.payload.access_list.isSome: + result.accessList = tx.payload.access_list.unsafeGet - if tx.txType >= TxEip4844: - result.versionedHashes = tx.versionedHashes + if tx.payload.blob_versioned_hashes.isSome: + result.versionedHashes = + distinctBase(tx.payload.blob_versioned_hashes.unsafeGet) proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): GasInt {.gcsafe, raises: [CatchableError].} = diff --git a/nimbus/utils/ec_recover.nim b/nimbus/utils/ec_recover.nim index 6d035269a..2b3c6e311 100644 --- a/nimbus/utils/ec_recover.nim +++ b/nimbus/utils/ec_recover.nim @@ -51,23 +51,6 @@ type # Private helpers # ------------------------------------------------------------------------------ -proc vrsSerialised(tx: Transaction): Result[array[65,byte],UtilsError] = - ## Parts copied from `transaction.getSignature`. - var data: array[65,byte] - data[0..31] = tx.R.toBytesBE - data[32..63] = tx.S.toBytesBE - - if tx.txType != TxLegacy: - data[64] = tx.V.byte - elif tx.V >= EIP155_CHAIN_ID_OFFSET: - data[64] = byte(1 - (tx.V and 1)) - elif tx.V == 27 or tx.V == 28: - data[64] = byte(tx.V - 27) - else: - return err((errSigPrefixError,"")) # legacy error - - ok(data) - proc encodePreSealed(header: BlockHeader): seq[byte] = ## Cut sigature off `extraData` header field. if header.extraData.len < EXTRA_SEAL: @@ -113,22 +96,6 @@ proc ecRecover*(header: BlockHeader): EcAddrResult = ## the argument header. header.extraData.recoverImpl(header.hashPreSealed) -proc ecRecover*(tx: var Transaction): EcAddrResult = - ## Extracts sender address from transaction. This function has similar - ## functionality as `transaction.getSender()`. - let txSig = tx.vrsSerialised - if txSig.isErr: - return err(txSig.error) - try: - result = txSig.value.recoverImpl(tx.txHashNoSignature) - except ValueError as ex: - return err((errTxEncError, ex.msg)) - -proc ecRecover*(tx: Transaction): EcAddrResult = - ## Variant of `ecRecover()` for call-by-value header. - var ty = tx - ty.ecRecover - # ------------------------------------------------------------------------------ # Public constructor for caching ecRecover version # ------------------------------------------------------------------------------ diff --git a/premix/debug.nim b/premix/debug.nim index e359ed10e..736963bfd 100644 --- a/premix/debug.nim +++ b/premix/debug.nim @@ -69,7 +69,8 @@ proc main() = let blockEnv = json.parseFile(paramStr(1)) - memoryDB = newCoreDbRef(LegacyDbMemory) + chainId = blockEnv["chainId"].getInt().ChainId + memoryDB = newCoreDbRef(LegacyDbMemory, chainId) blockNumber = UInt256.fromHex(blockEnv["blockNumber"].getStr()) prepareBlockEnv(blockEnv, memoryDB) diff --git a/premix/downloader.nim b/premix/downloader.nim index 515a2da7d..f7776edcd 100644 --- a/premix/downloader.nim +++ b/premix/downloader.nim @@ -49,12 +49,13 @@ proc request*( proc requestBlockBody( n: JsonNode, blockNumber: BlockNumber, + chainId: ChainId, client: Option[RpcClient] = none[RpcClient]()): BlockBody = let txs = n["transactions"] if txs.len > 0: result.transactions = newSeqOfCap[Transaction](txs.len) for tx in txs: - let txn = parseTransaction(tx) + let txn = parseTransaction(tx, chainId) validateTxSenderAndHash(tx, txn) result.transactions.add txn @@ -108,12 +109,13 @@ proc requestHeader*( proc requestBlock*( blockNumber: BlockNumber, + chainId: ChainId, flags: set[DownloadFlags] = {}, client: Option[RpcClient] = none[RpcClient]()): Block = let header = requestHeader(blockNumber, client) result.jsonData = header result.header = parseBlockHeader(header) - result.body = requestBlockBody(header, blockNumber, client) + result.body = requestBlockBody(header, blockNumber, chainId, client) if DownloadTxTrace in flags: result.traces = requestTxTraces(header, client) diff --git a/premix/dumper.nim b/premix/dumper.nim index a2e9c89f7..da1b5f6cf 100644 --- a/premix/dumper.nim +++ b/premix/dumper.nim @@ -46,8 +46,12 @@ proc dumpDebug(com: CommonRef, blockNumber: UInt256) = vmState.dumpDebuggingMetaData(header, body, false) proc main() {.used.} = - let conf = getConfiguration() - let com = CommonRef.new(newCoreDbRef(LegacyDbPersistent, conf.dataDir), false) + let + conf = getConfiguration() + params = networkParams(conf.netId) + com = CommonRef.new( + newCoreDbRef(LegacyDbPersistent, conf.dataDir, params.config.chainId), + false) if conf.head != 0.u256: dumpDebug(com, conf.head) diff --git a/premix/hunter.nim b/premix/hunter.nim index d42ec1b9b..26b259d67 100644 --- a/premix/hunter.nim +++ b/premix/hunter.nim @@ -35,10 +35,11 @@ proc parseAddress(address: string): EthAddress = proc parseU256(val: string): UInt256 = UInt256.fromHex(val) -proc prepareBlockEnv(parent: BlockHeader, thisBlock: Block): CoreDbRef = +proc prepareBlockEnv( + parent: BlockHeader, thisBlock: Block, chainId: ChainId): CoreDbRef = var - accounts = requestPostState(thisBlock) - memoryDB = newCoreDbRef LegacyDbMemory + accounts = requestPostState(thisBlock, chainId) + memoryDB = newCoreDbRef(LegacyDbMemory, chainId) accountDB = newAccountStateDB(memoryDB, parent.stateRoot, false) parentNumber = %(parent.blockNumber.prefixHex) @@ -95,13 +96,14 @@ proc putAncestorsIntoDB(vmState: HunterVMState, db: CoreDbRef) = for header in vmState.headers.values: db.addBlockNumberToHashLookup(header) -proc huntProblematicBlock(blockNumber: UInt256): ValidationResult = +proc huntProblematicBlock( + blockNumber: UInt256, chainId: ChainId): ValidationResult = let # prepare needed state from previous block parentNumber = blockNumber - 1 - thisBlock = requestBlock(blockNumber) - parentBlock = requestBlock(parentNumber) - memoryDB = prepareBlockEnv(parentBlock.header, thisBlock) + thisBlock = requestBlock(blockNumber, chainId) + parentBlock = requestBlock(parentNumber, chainId) + memoryDB = prepareBlockEnv(parentBlock.header, thisBlock, chainId) # try to execute current block com = CommonRef.new(memoryDB, false) @@ -122,7 +124,10 @@ proc huntProblematicBlock(blockNumber: UInt256): ValidationResult = result = validationResult proc main() {.used.} = - let conf = getConfiguration() + let + conf = getConfiguration() + params = networkParams(conf.netId) + chainId = params.config.chainId if conf.head == 0.u256: echo "please specify the starting block with `--head:blockNumber`" @@ -138,7 +143,7 @@ proc main() {.used.} = while true: echo blockNumber - if huntProblematicBlock(blockNumber) != ValidationResult.OK: + if huntProblematicBlock(blockNumber, chainId) != ValidationResult.OK: echo "shot down problematic block: ", blockNumber problematicBlocks.add blockNumber blockNumber = blockNumber + 1 diff --git a/premix/parser.nim b/premix/parser.nim index c58bdba00..14d9f28a6 100644 --- a/premix/parser.nim +++ b/premix/parser.nim @@ -9,9 +9,9 @@ # according to those terms. import - json, strutils, options, os, + std/[json, options, os, strutils, typetraits], eth/common, httputils, nimcrypto/utils, - stint, stew/byteutils + results, stint, stew/byteutils import ../nimbus/transaction, ../nimbus/utils/ec_recover @@ -96,15 +96,36 @@ proc fromJson*(n: JsonNode, name: string, x: var SomeInteger) = x = T(node.getInt) doAssert($x == $node.getInt, name) +func fromJson(n: JsonNode, name: string, x: var ChainId) = + n.fromJson(name, distinctBase(x)) + proc fromJson*(n: JsonNode, name: string, x: var EthTime) = x = EthTime(hexToInt(n[name].getStr(), uint64)) doAssert(x.uint64.prefixHex == toLowerAscii(n[name].getStr()), name) proc fromJson*[T](n: JsonNode, name: string, x: var Option[T]) = - if name in n: + if name in n and n[name].kind != JNull: var val: T n.fromJson(name, val) - x = some(val) + x = options.some(val) + else: + x = options.none(T) + +func fromJson*[T](n: JsonNode, name: string, x: var Opt[T]) = + if name in n and n[name].kind != JNull: + var val: T + n.fromJson(name, val) + x.ok val + else: + x.err() + +func fromJson*[E, N](n: JsonNode, name: string, x: var List[E, N]) = + var v: seq[E] + n.fromJson(name, v) + if v.len > N: + raise (ref ValueError)(msg: + "List[" & $E & ", Limit " & $N & "] cannot fit " & $v.len & " items") + x = List[E, N].init(v) proc fromJson*(n: JsonNode, name: string, x: var TxType) = let node = n[name] @@ -121,6 +142,20 @@ proc fromJson*(n: JsonNode, name: string, x: var seq[Hash256]) = hexToByteArray(v.getStr(), h.data) x.add h +func fromJson*(n: JsonNode, name: string, x: var common.AccessList) = + x.reset() + let node = n[name] + for innerNode in node: + var entry: common.AccessPair + innerNode.fromJson "address", entry.address + for storageKeyVal in innerNode["storageKeys"]: + var storageKey: common.StorageKey + hexToByteArray(storageKeyVal.getStr(), storageKey) + if not entry.storage_keys.add storageKey: + raise (ref ValueError)(msg: "StorageKeys capacity exceeded") + if not x.add entry: + raise (ref ValueError)(msg: "AccessList capacity exceeded") + proc parseBlockHeader*(n: JsonNode): BlockHeader = n.fromJson "parentHash", result.parentHash n.fromJson "sha3Uncles", result.ommersHash @@ -147,53 +182,72 @@ proc parseBlockHeader*(n: JsonNode): BlockHeader = # probably geth bug result.fee = none(UInt256) -proc parseAccessPair(n: JsonNode): AccessPair = - n.fromJson "address", result.address - let keys = n["storageKeys"] - for kn in keys: - result.storageKeys.add hexToByteArray[32](kn.getStr()) - -proc parseTransaction*(n: JsonNode): Transaction = - var tx = Transaction(txType: TxLegacy) +proc parseTransaction*(n: JsonNode, chain_id: ChainId): Transaction = + var + tx: TransactionPayload + gasPrice: Opt[UInt256] + txChainId: Opt[ChainId] + v: UInt256 + r: UInt256 + s: UInt256 n.fromJson "nonce", tx.nonce - n.fromJson "gasPrice", tx.gasPrice - n.fromJson "gas", tx.gasLimit - - if n["to"].kind != JNull: - var to: EthAddress - n.fromJson "to", to - tx.to = some(to) - + n.fromJson "gasPrice", gasPrice + n.fromJson "gas", tx.gas + n.fromJson "to", tx.to n.fromJson "value", tx.value - n.fromJson "input", tx.payload - n.fromJson "v", tx.V - n.fromJson "r", tx.R - n.fromJson "s", tx.S - - if n.hasKey("type") and n["type"].kind != JNull: - n.fromJson "type", tx.txType - - if tx.txType >= TxEip1559: - n.fromJson "maxPriorityFeePerGas", tx.maxPriorityFee - n.fromJson "maxFeePerGas", tx.maxFee - - if tx.txType >= TxEip2930: - if n.hasKey("chainId"): - let id = hexToInt(n["chainId"].getStr(), int) - tx.chainId = ChainId(id) - - let accessList = n["accessList"] - if accessList.len > 0: - for acn in accessList: - tx.accessList.add parseAccessPair(acn) - - if tx.txType >= TxEip4844: - n.fromJson "maxFeePerBlobGas", tx.maxFeePerBlobGas - - if n.hasKey("versionedHashes") and n["versionedHashes"].kind != JNull: - n.fromJson "versionedHashes", tx.versionedHashes - - tx + n.fromJson "input", tx.input + n.fromJson "v", v + n.fromJson "r", r + n.fromJson "s", s + n.fromJson "chainId", txChainId + n.fromJson "type", tx.tx_type + n.fromJson "accessList", tx.access_list + n.fromJson "maxPriorityFeePerGas", tx.max_priority_fee_per_gas + n.fromJson "maxFeePerGas", tx.max_fee_per_gas + n.fromJson "maxFeePerBlobGas", tx.max_fee_per_blob_gas + n.fromJson "versionedHashes", tx.blob_versioned_hashes + if gasPrice.get(tx.max_fee_per_gas) != tx.max_fee_per_gas: + raise (ref ValueError)(msg: "`gasPrice` and `maxFeePerGas` don't match") + if tx.tx_type.get(TxLegacy) != TxLegacy and txChainId.isNone: + raise (ref ValueError)(msg: "`chainId` is required") + if tx.tx_type == Opt.some(TxLegacy) and txChainId.isNone: + tx.tx_type.reset() + if txChainId.get(chain_id) != chain_id: + raise (ref ValueError)(msg: "Unsupported `chainId`") + if r >= SECP256K1N: + raise (ref ValueError)(msg: "Invalid `r`") + if s < UInt256.one or s >= SECP256K1N: + raise (ref ValueError)(msg: "Invalid `s`") + let anyTx = AnyTransactionPayload.fromOneOfBase(tx).valueOr: + raise (ref ValueError)(msg: "Invalid combination of fields") + withTxPayloadVariant(anyTx): + let y_parity = + when txKind == TransactionKind.Replayable: + if v == 27.u256: + false + elif v == 28.u256: + true + else: + raise (ref ValueError)(msg: "Invalid `v`") + elif txKind == TransactionKind.Legacy: + let + res = v.isEven + expected_v = + distinctBase(chain_id).u256 * 2 + (if res: 36.u256 else: 35.u256) + if v != expected_v: + raise (ref ValueError)(msg: "Invalid `v`") + res + else: + if v > UInt256.one: + raise (ref ValueError)(msg: "Invalid `v`") + v.isOdd + var signature: TransactionSignature + signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s) + signature.from_address = ecdsa_recover_from_address( + signature.ecdsa_signature, + txPayloadVariant.compute_sig_hash(chain_id)).valueOr: + raise (ref ValueError)(msg: "Cannot compute `from` address") + Transaction(payload: tx, signature: signature) proc parseWithdrawal*(n: JsonNode): Withdrawal = n.fromJson "index", result.index diff --git a/premix/persist.nim b/premix/persist.nim index f412aeb7b..4e024a9ef 100644 --- a/premix/persist.nim +++ b/premix/persist.nim @@ -52,14 +52,17 @@ proc main() {.used.} = # 52029 first block with receipts logs # 66407 failed transaction - let conf = configuration.getConfiguration() - let com = CommonRef.new( - newCoreDbRef(LegacyDbPersistent, conf.dataDir), - false, conf.netId, networkParams(conf.netId)) + let + conf = configuration.getConfiguration() + params = networkParams(conf.netId) + com = CommonRef.new( + newCoreDbRef(LegacyDbPersistent, conf.dataDir, params.config.chainId), + false, conf.netId, networkParams(conf.netId)) # move head to block number ... if conf.head != 0.u256: - var parentBlock = requestBlock(conf.head, { DownloadAndValidate }) + var parentBlock = requestBlock( + conf.head, com.chainId, { DownloadAndValidate }) discard com.db.setHead(parentBlock.header) if canonicalHeadHashKey().toOpenArray notin com.db.kvt: @@ -86,7 +89,8 @@ proc main() {.used.} = var thisBlock: Block try: - thisBlock = requestBlock(blockNumber, { DownloadAndValidate }) + thisBlock = requestBlock( + blockNumber, com.chainId, { DownloadAndValidate }) except CatchableError as e: if retryCount < 3: warn "Unable to get block data via JSON-RPC API", error = e.msg diff --git a/premix/premix.nim b/premix/premix.nim index 5e8fd07dc..240370af9 100644 --- a/premix/premix.nim +++ b/premix/premix.nim @@ -48,12 +48,14 @@ proc main() = try: let nimbus = json.parseFile(paramStr(1)) + chainId = nimbus["chainId"].getInt().ChainId blockNumber = UInt256.fromHex(nimbus["blockNumber"].getStr()) - thisBlock = requestBlock(blockNumber, {DownloadReceipts, DownloadTxTrace}) - accounts = requestPostState(thisBlock) + thisBlock = requestBlock( + blockNumber, chainId, {DownloadReceipts, DownloadTxTrace}) + accounts = requestPostState(thisBlock, chainId) geth = generateGethData(thisBlock, blockNumber, accounts) parentNumber = blockNumber - 1.u256 - parentBlock = requestBlock(parentNumber) + parentBlock = requestBlock(parentNumber, chainId) processNimbusData(nimbus) diff --git a/premix/premixcore.nim b/premix/premixcore.nim index 828224d3b..6514f7a98 100644 --- a/premix/premixcore.nim +++ b/premix/premixcore.nim @@ -86,7 +86,7 @@ proc hasInternalTx(tx: Transaction, blockNumber: UInt256, sender: EthAddress): b recipientHasCode = code.getStr.len > 2 # "0x" if tx.contractCreation: - return recipientHasCode or tx.payload.len > 0 + return recipientHasCode or tx.payload.input.len > 0 recipientHasCode @@ -143,7 +143,8 @@ proc updateAccount*(address: string, account: JsonNode, blockNumber: UInt256) = x["value"] = padding(x["value"].getStr()) account["storage"][x["key"].getStr] = x["value"] -proc requestPostState*(premix, n: JsonNode, blockNumber: UInt256) = +proc requestPostState*( + premix, n: JsonNode, blockNumber: UInt256, chainId: ChainId) = type TxKind {.pure.} = enum Regular @@ -156,7 +157,7 @@ proc requestPostState*(premix, n: JsonNode, blockNumber: UInt256) = let tracer = jsonTracer(postStateTracer) for t in txs: var txKind = TxKind.Regular - let tx = parseTransaction(t) + let tx = parseTransaction(t, chainId) let sender = tx.getSender if tx.contractCreation: txKind = TxKind.ContractCreation if hasInternalTx(tx, blockNumber, sender): @@ -171,11 +172,11 @@ proc requestPostState*(premix, n: JsonNode, blockNumber: UInt256) = t["txKind"] = %($txKind) -proc requestPostState*(thisBlock: Block): JsonNode = +proc requestPostState*(thisBlock: Block, chainId: ChainId): JsonNode = let blockNumber = thisBlock.header.blockNumber var premix = newJArray() - premix.requestPostState(thisBlock.jsonData, blockNumber) + premix.requestPostState(thisBlock.jsonData, blockNumber, chainId) premix.requestAccount(blockNumber, thisBlock.header.coinbase) for uncle in thisBlock.body.uncles: premix.requestAccount(blockNumber, uncle.coinbase) diff --git a/premix/prestate.nim b/premix/prestate.nim index 3a103533f..db83a28ea 100644 --- a/premix/prestate.nim +++ b/premix/prestate.nim @@ -9,17 +9,19 @@ # according to those terms. import + std/typetraits, json, stint, stew/byteutils, ../nimbus/db/[core_db, storage_types], eth/[rlp, common], ../nimbus/tracer proc generatePrestate*(nimbus, geth: JsonNode, blockNumber: UInt256, parent, header: BlockHeader, body: BlockBody) = let + chainId = nimbus["chainId"].getInt().ChainId state = nimbus["state"] headerHash = rlpHash(header) var - chainDB = newCoreDbRef(LegacyDbMemory) + chainDB = newCoreDbRef(LegacyDbMemory, chainId) discard chainDB.setHead(parent, true) discard chainDB.persistTransactions(blockNumber, body.transactions) @@ -34,6 +36,7 @@ proc generatePrestate*(nimbus, geth: JsonNode, blockNumber: UInt256, parent, hea chainDB.kvt.put(key, value) var metaData = %{ + "chainId": %distinctBase(chainId), "blockNumber": %blockNumber.toHex, "geth": geth } diff --git a/premix/regress.nim b/premix/regress.nim index 6e6421b4b..ec2e493d3 100644 --- a/premix/regress.nim +++ b/premix/regress.nim @@ -52,7 +52,10 @@ proc validateBlock(com: CommonRef, blockNumber: BlockNumber): BlockNumber = proc main() {.used.} = let conf = getConfiguration() - com = CommonRef.new(newCoreDbRef(LegacyDbPersistent, conf.dataDir), false) + params = networkParams(conf.netId) + com = CommonRef.new( + newCoreDbRef(LegacyDbPersistent, conf.dataDir, params.config.chainId), + false) # move head to block number ... if conf.head == 0.u256: diff --git a/stateless/witness_verification.nim b/stateless/witness_verification.nim index 7ba43c18c..d2456e73c 100644 --- a/stateless/witness_verification.nim +++ b/stateless/witness_verification.nim @@ -54,11 +54,12 @@ proc buildAccountsTableFromKeys( proc verifyWitness*( trustedStateRoot: KeccakHash, witness: BlockWitness, - flags: WitnessFlags): Result[TableRef[EthAddress, AccountData], string] = + flags: WitnessFlags, + chainId: ChainId): Result[TableRef[EthAddress, AccountData], string] = if witness.len() == 0: return err("witness is empty") - let db = newCoreDbRef(LegacyDbMemory) + let db = newCoreDbRef(LegacyDbMemory, chainId) var tb = initTreeBuilder(witness, db, flags) try: diff --git a/tests/persistBlockTestGen.nim b/tests/persistBlockTestGen.nim index ea945c907..ac3e7fee8 100644 --- a/tests/persistBlockTestGen.nim +++ b/tests/persistBlockTestGen.nim @@ -57,9 +57,12 @@ proc main() {.used.} = # nimbus --rpcapi: eth, debug --prune: archive - var conf = makeConfig() - let db = newCoreDbRef(DefaultDbPersistent, string conf.dataDir) - let com = CommonRef.new(db, false) + let + conf = makeConfig() + params = networkParams(conf.networkId) + db = newCoreDbRef( + DefaultDbPersistent, string conf.dataDir, params.config.chainId) + com = CommonRef.new(db, false) com.dumpTest(97) com.dumpTest(98) # no uncles and no tx diff --git a/tests/tracerTestGen.nim b/tests/tracerTestGen.nim index f9879c1f0..d192b168f 100644 --- a/tests/tracerTestGen.nim +++ b/tests/tracerTestGen.nim @@ -9,6 +9,7 @@ # according to those terms. import + std/typetraits, json, ../nimbus/common/common, # must be early (compilation annoyance) ../nimbus/db/core_db/persistent, @@ -32,6 +33,7 @@ proc dumpTest(com: CommonRef, blockNumber: int) = receipts = dumpReceipts(captureCom.db, header) var metaData = %{ + "chainId": %distinctBase(com.chainId), "blockNumber": %blockNumber.toHex, "txTraces": txTrace, "stateDump": stateDump, @@ -56,9 +58,12 @@ proc main() {.used.} = # nimbus --rpc-api: eth, debug --prune: archive - var conf = makeConfig() - let db = newCoreDbRef(LegacyDbPersistent, string conf.dataDir) - let com = CommonRef.new(db, false) + let + conf = makeConfig() + params = networkParams(conf.networkId) + db = newCoreDbRef( + LegacyDbPersistent, string conf.dataDir, params.config.chainId) + com = CommonRef.new(db, false) com.dumpTest(97) com.dumpTest(46147) diff --git a/vendor/nim-eth b/vendor/nim-eth index 34adff98a..24ed2afba 160000 --- a/vendor/nim-eth +++ b/vendor/nim-eth @@ -1 +1 @@ -Subproject commit 34adff98a6c5ae9d5a59bd8859a848b1cb0ae80d +Subproject commit 24ed2afba77249546d3c41485d7d37241681ad35 diff --git a/vendor/nim-ssz-serialization b/vendor/nim-ssz-serialization index 248f2bdca..338a47f56 160000 --- a/vendor/nim-ssz-serialization +++ b/vendor/nim-ssz-serialization @@ -1 +1 @@ -Subproject commit 248f2bdca2d65ff920920c72b764d0622d522596 +Subproject commit 338a47f5664868a3a9183d93fd46dbad9d183ff6 diff --git a/vendor/nim-web3 b/vendor/nim-web3 index de87f8608..77bfa128f 160000 --- a/vendor/nim-web3 +++ b/vendor/nim-web3 @@ -1 +1 @@ -Subproject commit de87f860874be944cdc3dfd08765c687fff736c4 +Subproject commit 77bfa128f316c1959b92b2c4e7041c861e3ef2e6 diff --git a/vendor/nimbus-eth2 b/vendor/nimbus-eth2 index fc9bc1da3..9202e336e 160000 --- a/vendor/nimbus-eth2 +++ b/vendor/nimbus-eth2 @@ -1 +1 @@ -Subproject commit fc9bc1da3ae7dde04f4591eba302f9e8b20c3924 +Subproject commit 9202e336e4427ee16dd7ca9da4a0f93379b08b22