diff --git a/nimbus/common/common.nim b/nimbus/common/common.nim index e7d03f972..4f89b7cbf 100644 --- a/nimbus/common/common.nim +++ b/nimbus/common/common.nim @@ -349,6 +349,9 @@ proc isBlockAfterTtd*(com: CommonRef, header: BlockHeader): bool func isShanghaiOrLater*(com: CommonRef, t: EthTime): bool = com.config.shanghaiTime.isSome and t >= com.config.shanghaiTime.get +func isCancunOrLater*(com: CommonRef, t: EthTime): bool = + com.config.cancunTime.isSome and t >= com.config.cancunTime.get + proc consensus*(com: CommonRef, header: BlockHeader): ConsensusType {.gcsafe, raises: [CatchableError].} = if com.isBlockAfterTtd(header): diff --git a/nimbus/constants.nim b/nimbus/constants.nim index e341101b0..f0b2c9f51 100644 --- a/nimbus/constants.nim +++ b/nimbus/constants.nim @@ -71,4 +71,19 @@ 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_VERSIONED_HASHES_LIST_SIZE* = 1 shl 24 # 2^24 + MAX_TX_WRAP_COMMITMENTS* = 1 shl 12 # 2^12 + LIMIT_BLOBS_PER_TX* = 1 shl 12 # 2^12 + BLOB_COMMITMENT_VERSION_KZG* = 0x01.byte + FIELD_ELEMENTS_PER_BLOB* = 4096 + DATA_GAS_PER_BLOB* = (1 shl 17).uint64 # 2^17 + TARGET_DATA_GAS_PER_BLOCK* = (1 shl 18).uint64 # 2^18 + MIN_DATA_GASPRICE* = 1'u64 + DATA_GASPRICE_UPDATE_FRACTION* = 2225652'u64 + MAX_DATA_GAS_PER_BLOCK* = (1 shl 19).uint64 # 2^19 + # End diff --git a/nimbus/core/eip4844.nim b/nimbus/core/eip4844.nim index e926054a2..ac3a9d892 100644 --- a/nimbus/core/eip4844.nim +++ b/nimbus/core/eip4844.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2022 Status Research & Development GmbH +# Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -9,15 +9,216 @@ # according to those terms. import + std/[os, strutils], + kzg4844/kzg_ex as kzg, stew/results, + stint, + ../constants, ../common/common {.push raises: [].} +type + Bytes32 = array[32, byte] + Bytes64 = array[64, byte] + Bytes48 = array[48, byte] + +const + BLS_MODULUS_STR = "52435875175126190479447740508185965837690552500527637822603658699938581184513" + BLS_MODULUS = parse(BLS_MODULUS_STR, UInt256, 10) + PrecompileInputLength = 192 + +proc pointEvaluationResult(): Bytes64 {.compileTime.} = + result[0..<32] = FIELD_ELEMENTS_PER_BLOB.u256.toBytesBE[0..^1] + result[32..^1] = BLS_MODULUS.toBytesBE[0..^1] + +const + PointEvaluationResult* = pointEvaluationResult() + POINT_EVALUATION_PRECOMPILE_GAS* = 50000.GasInt + + +# kzgToVersionedHash implements kzg_to_versioned_hash from EIP-4844 +proc kzgToVersionedHash(kzg: kzg.KZGCommitment): VersionedHash = + result = keccakHash(kzg) + result.data[0] = BLOB_COMMITMENT_VERSION_KZG + +# pointEvaluation implements point_evaluation_precompile from EIP-4844 +# return value and gas consumption is handled by pointEvaluation in +# precompiles.nim +proc pointEvaluation*(input: openArray[byte]): Result[void, string] = + # Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof. + # Also verify that the provided commitment matches the provided versioned_hash. + # The data is encoded as follows: versioned_hash | z | y | commitment | proof | + + if input.len < PrecompileInputLength: + return err("invalid input length") + + var + versionedHash: Bytes32 + z: Bytes32 + y: Bytes32 + commitment: Bytes48 + kzgProof: Bytes48 + + versionedHash[0..<32] = input[0..<32] + z[0..<32] = input[32..<64] + y[0..<32] = input[64..<96] + commitment[0..<48] = input[96..<144] + kzgProof[0..<48] = input[144..<192] + + # Verify KZG proof + let res = kzg.verifyKzgProof(commitment, z, y, kzgProof) + if res.isErr: + return err(res.error) + + # The actual verify result + if not res.get(): + return err("Failed to verify KZG proof") + + ok() + +# calcExcessDataGas implements calc_excess_data_gas from EIP-4844 +proc calcExcessDataGas*(parent: BlockHeader): uint64 = + let + excessDataGas = parent.excessDataGas.get(0'u64) + dataGasUsed = parent.dataGasUsed.get(0'u64) + + if excessDataGas + dataGasUsed < TARGET_DATA_GAS_PER_BLOCK: + 0'u64 + else: + excessDataGas + dataGasUsed - TARGET_DATA_GAS_PER_BLOCK + +# fakeExponential approximates factor * e ** (num / denom) using a taylor expansion +# as described in the EIP-4844 spec. +func fakeExponential*(factor, numerator, denominator: uint64): uint64 = + var + i = 1'u64 + output = 0'u64 + numeratorAccum = factor * denominator + + while numeratorAccum > 0'u64: + output += numeratorAccum + numeratorAccum = (numeratorAccum * numerator) div (denominator * i) + i = i + 1'u64 + + output div denominator + +proc getTotalDataGas*(tx: Transaction): uint64 = + DATA_GAS_PER_BLOB * tx.versionedHashes.len.uint64 + +proc getTotalDataGas*(versionedHashesLen: int): uint64 = + DATA_GAS_PER_BLOB * versionedHasheslen.uint64 + +# getDataGasPrice implements get_data_gas_price from EIP-4844 +func getDataGasprice*(parentExcessDataGas: uint64): uint64 = + fakeExponential( + MIN_DATA_GASPRICE, + parentExcessDataGas, + DATA_GASPRICE_UPDATE_FRACTION + ) + +proc calcDataFee*(tx: Transaction, + parentExcessDataGas: Option[uint64]): uint64 = + tx.getTotalDataGas * + getDataGasprice(parentExcessDataGas.get(0'u64)) + +proc calcDataFee*(versionedHashesLen: int, + parentExcessDataGas: Option[uint64]): uint64 = + getTotalDataGas(versionedHashesLen) * + getDataGasprice(parentExcessDataGas.get(0'u64)) + +func dataGasUsed(txs: openArray[Transaction]): uint64 = + for tx in txs: + result += tx.getTotalDataGas + # https://eips.ethereum.org/EIPS/eip-4844 func validateEip4844Header*( - com: CommonRef, header: BlockHeader -): Result[void, string] = - if header.excessDataGas.isSome: - return err("EIP-4844 not yet implemented") + com: CommonRef, header, parentHeader: BlockHeader, + txs: openArray[Transaction]): Result[void, string] {.raises: [].} = + + if not com.forkGTE(Cancun): + if header.dataGasUsed.isSome: + return err("unexpected EIP-4844 dataGasUsed in block header") + + if header.excessDataGas.isSome: + return err("unexpected EIP-4844 excessDataGas in block header") + + return ok() + + if header.dataGasUsed.isNone: + return err("expect EIP-4844 dataGasUsed in block header") + + if header.excessDataGas.isNone: + return err("expect EIP-4844 excessDataGas in block header") + + let + headerDataGasUsed = header.dataGasUsed.get() + dataGasUsed = dataGasUsed(txs) + headerExcessDataGas = header.excessDataGas.get + excessDataGas = calcExcessDataGas(parentHeader) + + if dataGasUsed <= MAX_DATA_GAS_PER_BLOCK: + return err("dataGasUsed should greater than MAX_DATA_GAS_PER_BLOCK: " & $dataGasUsed) + + if headerDataGasUsed != dataGasUsed: + return err("calculated dataGas not equal header.dataGasUsed") + + if headerExcessDataGas != excessDataGas: + return err("calculated excessDataGas not equal header.excessDataGas") + return ok() + +proc validateBlobTransactionWrapper*(tx: Transaction): + Result[void, string] {.raises: [].} = + if not tx.networkPayload.isNil: + return err("tx wrapper is none") + + # note: assert blobs are not malformatted + let goodFormatted = tx.versionedHashes.len == + tx.networkPayload.commitments.len and + tx.versionedHashes.len == + tx.networkPayload.blobs.len and + tx.versionedHashes.len == + tx.networkPayload.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(): + 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.versionedHashes.len: + # this additional check also done in tx validation + if tx.versionedHashes[i].data[0] != BLOB_COMMITMENT_VERSION_KZG: + return err("wrong kzg version in versioned hash at index " & $i) + + if tx.versionedHashes[i] != kzgToVersionedHash(tx.networkPayload.commitments[i]): + return err("tx versioned hash not match commitments at index " & $i) + + ok() + +proc loadKzgTrustedSetup*(): Result[void, string] = + const + vendorDir = currentSourcePath.parentDir.replace('\\', '/') & "/../../vendor" + trustedSetupDir = vendorDir & "/nim-kzg4844/kzg4844/csources/src" + + const const_preset = "mainnet" + const trustedSetup = + when const_preset == "mainnet": + staticRead trustedSetupDir & "/trusted_setup.txt" + elif const_preset == "minimal": + staticRead trustedSetupDir & "/trusted_setup_4.txt" + else: + "" + if const_preset == "mainnet" or const_preset == "minimal": + Kzg.loadTrustedSetupFromString(trustedSetup) + else: + ok() diff --git a/nimbus/core/executor/process_transaction.nim b/nimbus/core/executor/process_transaction.nim index 79d09002d..24b94b33e 100644 --- a/nimbus/core/executor/process_transaction.nim +++ b/nimbus/core/executor/process_transaction.nim @@ -78,6 +78,7 @@ proc asyncProcessTransactionImpl( baseFee = baseFee256.truncate(GasInt) tx = eip1559TxNormalization(tx, baseFee, fork) priorityFee = min(tx.maxPriorityFee, tx.maxFee - baseFee) + excessDataGas = vmState.parent.excessDataGas.get(0'u64) # Return failure unless explicitely set `ok()` var res: Result[GasInt, string] = err("") @@ -99,7 +100,7 @@ proc asyncProcessTransactionImpl( # before leaving is crucial for some unit tests that us a direct/deep call # of the `processTransaction()` function. So there is no `return err()` # statement, here. - let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, fork) + let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, excessDataGas, fork) if txRes.isOk: # EIP-1153 diff --git a/nimbus/core/sealer.nim b/nimbus/core/sealer.nim index 3d01f42fa..cc06c3a0b 100644 --- a/nimbus/core/sealer.nim +++ b/nimbus/core/sealer.nim @@ -144,7 +144,7 @@ template unsafeQuantityToInt64(q: web3types.Quantity): int64 = int64 q proc toTypedTransaction(tx: Transaction): TypedTransaction = - web3types.TypedTransaction(rlp.encode(tx)) + web3types.TypedTransaction(rlp.encode(tx.removeNetworkPayload)) func toWithdrawal(x: WithdrawalV1): Withdrawal = result.index = x.index.uint64 diff --git a/nimbus/core/tx_pool/tx_chain.nim b/nimbus/core/tx_pool/tx_chain.nim index 6a34e81e7..2771e23c0 100644 --- a/nimbus/core/tx_pool/tx_chain.nim +++ b/nimbus/core/tx_pool/tx_chain.nim @@ -52,6 +52,10 @@ type profit: UInt256 ## Net reward (w/o PoW specific block rewards) txRoot: Hash256 ## `rootHash` after packing stateRoot: Hash256 ## `stateRoot` after packing + dataGasUsed: + Option[uint64] ## EIP-4844 block dataGasUsed + excessDataGas: + Option[uint64] ## EIP-4844 block excessDataGas TxChainRef* = ref object ##\ ## State cache of the transaction environment for creating a new\ @@ -139,6 +143,8 @@ proc resetTxEnv(dh: TxChainRef; parent: BlockHeader; fee: Option[UInt256]) dh.txEnv.txRoot = EMPTY_ROOT_HASH dh.txEnv.stateRoot = dh.txEnv.vmState.parent.stateRoot + dh.txEnv.dataGasUsed = none(uint64) + dh.txEnv.excessDataGas = none(uint64) proc update(dh: TxChainRef; parent: BlockHeader) {.gcsafe,raises: [CatchableError].} = @@ -216,7 +222,9 @@ proc getHeader*(dh: TxChainRef): BlockHeader # extraData: Blob # signing data # mixDigest: Hash256 # mining hash for given difficulty # nonce: BlockNonce # mining free vaiable - fee: dh.txEnv.vmState.fee) + fee: dh.txEnv.vmState.fee, + dataGasUsed: dh.txEnv.dataGasUsed, + excessDataGas: dh.txEnv.excessDataGas) if dh.com.forkGTE(Shanghai): result.withdrawalsRoot = some(calcWithdrawalsRoot(dh.withdrawals)) @@ -369,6 +377,14 @@ proc `txRoot=`*(dh: TxChainRef; val: Hash256) = proc `withdrawals=`*(dh: TxChainRef, val: sink seq[Withdrawal]) = dh.withdrawals = system.move(val) +proc `excessDataGas=`*(dh: TxChainRef; val: Option[uint64]) = + ## Setter + dh.txEnv.excessDataGas = val + +proc `dataGasUsed=`*(dh: TxChainRef; val: Option[uint64]) = + ## Setter + dh.txEnv.dataGasUsed = val + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/nimbus/core/tx_pool/tx_info.nim b/nimbus/core/tx_pool/tx_info.nim index f7f0163f8..79349d7de 100644 --- a/nimbus/core/tx_pool/tx_info.nim +++ b/nimbus/core/tx_pool/tx_info.nim @@ -101,6 +101,10 @@ type ## Running basic validator failed on current transaction "Tx rejected by basic validator" + txInfoErrInvalidBlob = ##\ + ## Invalid EIP-4844 kzg validation on blob wrapper + "Invalid EIP-4844 blob validation" + # ------ Signature problems ------------------------------------------------ txInfoErrInvalidSender = ##\ diff --git a/nimbus/core/tx_pool/tx_tasks/tx_add.nim b/nimbus/core/tx_pool/tx_tasks/tx_add.nim index 852d0cb76..33c3526f5 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_add.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_add.nim @@ -23,7 +23,8 @@ import ./tx_recover, chronicles, eth/[common, keys], - stew/[keyed_queue, sorted_set] + stew/[keyed_queue, sorted_set], + ../../eip4844 {.push raises: [].} @@ -179,6 +180,14 @@ proc addTxs*(xp: TxPoolRef; for tx in txs.items: var reason: TxInfo + + if tx.txType == TxEip4844: + let res = tx.validateBlobTransactionWrapper() + if res.isErr: + # move item to waste basket + reason = txInfoErrInvalidBlob + xp.txDB.reject(tx, reason, txItemPending, res.error) + invalidTxMeter(1) # Create tx item wrapper, preferably recovered from waste basket let rcTx = xp.recoverItem(tx, txItemPending, info) diff --git a/nimbus/core/tx_pool/tx_tasks/tx_classify.nim b/nimbus/core/tx_pool/tx_tasks/tx_classify.nim index 51ba6568a..bc4fc6c0a 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_classify.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_classify.nim @@ -233,8 +233,9 @@ proc classifyValidatePacked*(xp: TxPoolRef; else: xp.chain.limits.trgLimit tx = item.tx.eip1559TxNormalization(xp.chain.baseFee.GasInt, fork) + excessDataGas = vmState.parent.excessDataGas.get(0'u64) - roDB.validateTransaction(tx, item.sender, gasLimit, baseFee, fork).isOk + roDB.validateTransaction(tx, item.sender, gasLimit, baseFee, excessDataGas, 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_packer.nim b/nimbus/core/tx_pool/tx_tasks/tx_packer.nim index 2dd16493a..030ac4f1b 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_packer.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_packer.nim @@ -18,7 +18,7 @@ import std/[sets, tables], ../../../db/accounts_cache, ../../../common/common, - "../.."/[dao, executor, validate], + "../.."/[dao, executor, validate, eip4844], ../../../transaction/call_evm, ../../../transaction, ../../../vm_state, @@ -43,6 +43,7 @@ type tr: HexaryTrie cleanState: bool balance: UInt256 + dataGasUsed: uint64 const receiptsExtensionSize = ##\ @@ -137,8 +138,12 @@ proc runTxCommit(pst: TxPackerStateRef; item: TxItemRef; gasBurned: GasInt) vmState.cumulativeGasUsed += gasBurned vmState.receipts[inx] = vmState.makeReceipt(item.tx.txType) + # EIP-4844, count dataGasUsed + if item.tx.txType >= TxEip4844: + pst.dataGasUsed += item.tx.getTotalDataGas + # Update txRoot - pst.tr.put(rlp.encode(inx), rlp.encode(item.tx)) + pst.tr.put(rlp.encode(inx), rlp.encode(item.tx.removeNetworkPayload)) # Add the item to the `packed` bucket. This implicitely increases the # receipts index `inx` at the next visit of this function. @@ -244,6 +249,11 @@ proc vmExecCommit(pst: TxPackerStateRef) xp.chain.txRoot = pst.tr.rootHash xp.chain.stateRoot = vmState.stateDB.rootHash + if vmState.com.forkGTE(Cancun): + # EIP-4844 + let excessDataGas = calcExcessDataGas(vmState.parent) + xp.chain.excessDataGas = some(excessDataGas) + proc balanceDelta: UInt256 = let postBalance = vmState.readOnlyStateDB.getBalance(xp.chain.feeRecipient) if pst.balance < postBalance: diff --git a/nimbus/core/validate.nim b/nimbus/core/validate.nim index cfc60acf6..b7492ad65 100644 --- a/nimbus/core/validate.nim +++ b/nimbus/core/validate.nim @@ -12,7 +12,8 @@ import std/[sequtils, sets, times, strutils], ../common/common, ../db/accounts_cache, - ".."/[errors, transaction, vm_state, vm_types], + ".."/[transaction, common/common], + ".."/[errors], "."/[dao, eip4844, gaslimit, withdrawals], ./pow/[difficulty, header], ./pow, @@ -72,7 +73,7 @@ proc validateSeal(pow: PowRef; header: BlockHeader): Result[void,string] = ok() proc validateHeader(com: CommonRef; header, parentHeader: BlockHeader; - numTransactions: int; checkSealOK: bool; + txs: openArray[Transaction]; checkSealOK: bool; pow: PowRef): Result[void,string] = template inDAOExtraRange(blockNumber: BlockNumber): bool = @@ -87,8 +88,8 @@ proc validateHeader(com: CommonRef; header, parentHeader: BlockHeader; if header.extraData.len > 32: return err("BlockHeader.extraData larger than 32 bytes") - if header.gasUsed == 0 and 0 < numTransactions: - return err("zero gasUsed but tranactions present"); + if header.gasUsed == 0 and 0 < txs.len: + return err("zero gasUsed but transactions present"); if header.gasUsed < 0 or header.gasUsed > header.gasLimit: return err("gasUsed should be non negative and smaller or equal gasLimit") @@ -125,7 +126,7 @@ proc validateHeader(com: CommonRef; header, parentHeader: BlockHeader; return pow.validateSeal(header) ? com.validateWithdrawals(header) - ? com.validateEip4844Header(header) + ? com.validateEip4844Header(header, parentHeader, txs) ok() @@ -147,7 +148,7 @@ func validateUncle(currBlock, uncle, uncleParent: BlockHeader): proc validateUncles(com: CommonRef; header: BlockHeader; - uncles: seq[BlockHeader]; checkSealOK: bool; + uncles: openArray[BlockHeader]; checkSealOK: bool; pow: PowRef): Result[void,string] = let hasUncles = uncles.len > 0 let shouldHaveUncles = header.ommersHash != EMPTY_UNCLE_HASH @@ -235,13 +236,23 @@ proc validateUncles(com: CommonRef; header: BlockHeader; # Public function, extracted from executor # ------------------------------------------------------------------------------ +func gasCost(tx: Transaction): UInt256 = + if tx.txType >= TxEip4844: + tx.gasLimit.u256 * tx.maxFee.u256 + tx.getTotalDataGas.u256 * tx.maxFeePerDataGas.u256 + elif tx.txType >= TxEip1559: + tx.gasLimit.u256 * tx.maxFee.u256 + else: + tx.gasLimit.u256 * tx.gasPrice.u256 + proc validateTransaction*( roDB: ReadOnlyStateDB; ## Parent accounts environment for transaction tx: Transaction; ## tx to validate sender: EthAddress; ## tx.getSender or tx.ecRecover maxLimit: GasInt; ## gasLimit from block header baseFee: UInt256; ## baseFee from block header + excessDataGas: uint64; ## excessDataGas from parent block header fork: EVMFork): Result[void, string] = + let balance = roDB.getBalance(sender) nonce = roDB.getNonce(sender) @@ -252,6 +263,9 @@ proc validateTransaction*( 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: return err("invalid tx: initcode size exceeds maximum") @@ -319,24 +333,49 @@ proc validateTransaction*( if codeHash != EMPTY_SHA3: return err("invalid tx: sender is not an EOA. sender=$1, codeHash=$2" % [ sender.toHex, codeHash.data.toHex]) + + + if fork >= FkCancun: + if tx.payload.len > MAX_CALLDATA_SIZE: + return err("invalid tx: payload len exceeds MAX_CALLDATA_SIZE. len=" & + $tx.payload.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) + + 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]) + + if tx.txType == TxEip4844: + if tx.to.isNone: + return err("invalid tx: destination must be not empty") + + if tx.versionedHashes.len == 0: + return err("invalid tx: there must be at least one blob") + + if tx.versionedHashes.len > MAX_VERSIONED_HASHES_LIST_SIZE: + return err("invalid tx: access list len exceeds MAX_VERSIONED_HASHES_LIST_SIZE. len=" & + $tx.versionedHashes.len) + + for i, bv in tx.versionedHashes: + if bv.data[0] != BLOB_COMMITMENT_VERSION_KZG: + return err("invalid tx: one of blobVersionedHash has invalid version. " & + "get=$1, expect=$2" % [$bv.data[0].int, $BLOB_COMMITMENT_VERSION_KZG.int]) + + # ensure that the user was willing to at least pay the current data gasprice + let dataGasPrice = getDataGasPrice(excessDataGas) + if tx.maxFeePerDataGas.uint64 < dataGasPrice: + return err("invalid tx: maxFeePerDataGas smaller than dataGasPrice. " & + "maxFeePerDataGas=$1, dataGasPrice=$2" % [$tx.maxFeePerDataGas, $dataGasPrice]) + except CatchableError as ex: return err(ex.msg) ok() -proc validateTransaction*( - vmState: BaseVMState; ## Parent accounts environment for transaction - tx: Transaction; ## tx to validate - sender: EthAddress; ## tx.getSender or tx.ecRecover - header: BlockHeader; ## Header for the block containing the current tx - fork: EVMFork): Result[void, string] = - ## Variant of `validateTransaction()` - let - roDB = vmState.readOnlyStateDB - gasLimit = header.gasLimit - baseFee = header.baseFee - roDB.validateTransaction(tx, sender, gasLimit, baseFee, fork) - # ------------------------------------------------------------------------------ # Public functions, extracted from test_blockchain_json # ------------------------------------------------------------------------------ @@ -344,8 +383,8 @@ proc validateTransaction*( proc validateHeaderAndKinship*( com: CommonRef; header: BlockHeader; - uncles: seq[BlockHeader]; - numTransactions: int; + uncles: openArray[BlockHeader]; + txs: openArray[Transaction]; checkSealOK: bool; pow: PowRef): Result[void, string] = if header.isGenesis: @@ -360,7 +399,7 @@ proc validateHeaderAndKinship*( return err("Failed to load block header from DB") result = com.validateHeader( - header, parent, numTransactions, checkSealOK, pow) + header, parent, txs, checkSealOK, pow) if result.isErr: return @@ -384,7 +423,7 @@ proc validateHeaderAndKinship*( pow: PowRef): Result[void, string] = com.validateHeaderAndKinship( - header, body.uncles, body.transactions.len, checkSealOK, pow) + header, body.uncles, body.transactions, checkSealOK, pow) proc validateHeaderAndKinship*( com: CommonRef; @@ -392,7 +431,7 @@ proc validateHeaderAndKinship*( checkSealOK: bool; pow: PowRef): Result[void,string] = com.validateHeaderAndKinship( - ethBlock.header, ethBlock.uncles, ethBlock.txs.len, + ethBlock.header, ethBlock.uncles, ethBlock.txs, checkSealOK, pow) # ------------------------------------------------------------------------------ diff --git a/nimbus/db/db_chain.nim b/nimbus/db/db_chain.nim index 8ab5c10f9..fe67ee03e 100644 --- a/nimbus/db/db_chain.nim +++ b/nimbus/db/db_chain.nim @@ -186,8 +186,8 @@ proc persistTransactions*(db: ChainDBRef, blockNumber: var trie = initHexaryTrie(db.db) for idx, tx in transactions: let - encodedTx = rlp.encode(tx) - txHash = keccakHash(encodedTx) + encodedTx = rlp.encode(tx.removeNetworkPayload) + txHash = rlpHash(tx) # beware EIP-4844 txKey: TransactionKey = (blockNumber, idx) trie.put(rlp.encode(idx), encodedTx) db.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(txKey)) @@ -219,7 +219,8 @@ iterator getBlockTransactionHashes*(db: ChainDBRef, blockHeader: BlockHeader): H ## Returns an iterable of the transaction hashes from th block specified ## by the given block header. for encodedTx in db.getBlockTransactionData(blockHeader.txRoot): - yield keccakHash(encodedTx) + let tx = rlp.decode(encodedTx, Transaction) + yield rlpHash(tx) # beware EIP-4844 proc getTransactionCount*(chain: ChainDBRef, txRoot: Hash256): int = var trie = initHexaryTrie(chain.db, txRoot) diff --git a/nimbus/evm/computation.nim b/nimbus/evm/computation.nim index da3d56d6e..46ffd7abe 100644 --- a/nimbus/evm/computation.nim +++ b/nimbus/evm/computation.nim @@ -115,8 +115,22 @@ template getGasPrice*(c: Computation): GasInt = else: c.vmState.txGasPrice +template getVersionedHash*(c: Computation, index: int): VersionedHash = + when evmc_enabled: + # TODO: implement + Hash256() + else: + c.vmState.txVersionedHashes[index] + +template getVersionedHashesLen*(c: Computation): int = + when evmc_enabled: + # TODO: implement + 0 + else: + c.vmState.txVersionedHashes.len + proc getBlockHash*(c: Computation, number: UInt256): Hash256 - {.gcsafe, raises: [CatchableError].} = + {.gcsafe, raises: [CatchableError].} = when evmc_enabled: let blockNumber = c.host.getTxContext().block_number.u256 diff --git a/nimbus/evm/interpreter/gas_costs.nim b/nimbus/evm/interpreter/gas_costs.nim index 5a86d0cb3..3aec92443 100644 --- a/nimbus/evm/interpreter/gas_costs.nim +++ b/nimbus/evm/interpreter/gas_costs.nim @@ -611,6 +611,7 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) = ChainIdOp: fixed GasBase, SelfBalance: fixed GasLow, BaseFee: fixed GasBase, + BlobHash: fixed GasVeryLow, # 50s: Stack, Memory, Storage and Flow Operations Pop: fixed GasBase, diff --git a/nimbus/evm/interpreter/op_codes.nim b/nimbus/evm/interpreter/op_codes.nim index e12fb8dcc..0a22e9e93 100644 --- a/nimbus/evm/interpreter/op_codes.nim +++ b/nimbus/evm/interpreter/op_codes.nim @@ -104,8 +104,9 @@ type ChainIdOp = 0x46, ## Get current chain’s EIP-155 unique identifier. SelfBalance = 0x47, ## Get current contract's balance. BaseFee = 0x48, ## Get block’s base fee. EIP-3198 + BlobHash = 0x49, ## Get transaction's versionedHash. EIP-4844 - Nop0x49, Nop0x4A, Nop0x4B, Nop0x4C, Nop0x4D, + Nop0x4A, Nop0x4B, Nop0x4C, Nop0x4D, Nop0x4E, Nop0x4F, ## .. # 50s: Stack, Memory, Storage and Flow Operations diff --git a/nimbus/evm/interpreter/op_handlers/oph_blockdata.nim b/nimbus/evm/interpreter/op_handlers/oph_blockdata.nim index b0d0f750f..72c5b3304 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_blockdata.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_blockdata.nim @@ -17,6 +17,7 @@ import ../../computation, ../../stack, ../../async/operations, + ../utils/utils_numeric, ../op_codes, ./oph_defs @@ -80,6 +81,18 @@ const k.cpt.stack.push: k.cpt.getBaseFee + blobHashOp: Vm2OpFn = proc (k: var Vm2Ctx) = + ## 0x49, Get current transaction's EIP-4844 versioned hash. + let index = k.cpt.stack.popInt().safeInt + let len = k.cpt.getVersionedHashesLen + + if index < len: + k.cpt.stack.push: + k.cpt.getVersionedHash(index) + else: + k.cpt.stack.push: + 0 + # ------------------------------------------------------------------------------ # Public, op exec table entries # ------------------------------------------------------------------------------ @@ -157,6 +170,14 @@ const info: "Get current block's EIP-1559 base fee", exec: (prep: vm2OpIgnore, run: baseFeeOp, + post: vm2OpIgnore)), + + (opCode: BlobHash, ## 0x49, EIP-4844 Transaction versioned hash + forks: Vm2OpCancunAndLater, + name: "blobHash", + info: "Get current transaction's EIP-4844 versioned hash", + exec: (prep: vm2OpIgnore, + run: blobHashOp, post: vm2OpIgnore))] # ------------------------------------------------------------------------------ diff --git a/nimbus/evm/interpreter/op_handlers/oph_defs.nim b/nimbus/evm/interpreter/op_handlers/oph_defs.nim index c3da024ce..789ec8127 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_defs.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_defs.nim @@ -86,8 +86,7 @@ const Vm2OpShanghaiAndLater* = Vm2OpParisAndLater - {FkParis} - # TODO: fix this after EIP-1153 accepted in a fork - Vm2Op1153AndLater* = + Vm2OpCancunAndLater* = Vm2OpShanghaiAndLater - {FkShanghai} # ------------------------------------------------------------------------------ diff --git a/nimbus/evm/interpreter/op_handlers/oph_memory.nim b/nimbus/evm/interpreter/op_handlers/oph_memory.nim index 613a76d85..3a3ef9c41 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_memory.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_memory.nim @@ -512,7 +512,7 @@ const post: vm2OpIgnore)), (opCode: Tload, ## 0xb3, Load word from transient storage. - forks: Vm2Op1153AndLater, + forks: Vm2OpCancunAndLater, name: "tLoad", info: "Load word from transient storage", exec: (prep: vm2OpIgnore, @@ -520,7 +520,7 @@ const post: vm2OpIgnore)), (opCode: Tstore, ## 0xb4, Save word to transient storage. - forks: Vm2Op1153AndLater, + forks: Vm2OpCancunAndLater, name: "tStore", info: "Save word to transient storage", exec: (prep: vm2OpIgnore, diff --git a/nimbus/evm/precompiles.nim b/nimbus/evm/precompiles.nim index b36cce53e..6555e25f8 100644 --- a/nimbus/evm/precompiles.nim +++ b/nimbus/evm/precompiles.nim @@ -9,28 +9,31 @@ # according to those terms. import - std/[macros], + std/[math, macros], + stew/results, "."/[types, blake2b_f, blscurve], ./interpreter/[gas_meter, gas_costs, utils/utils_numeric], ../errors, eth/[common, keys], chronicles, nimcrypto/[ripemd, sha2, utils], bncurve/[fields, groups], ../common/evmforks, + ../core/eip4844, ./modexp + type PrecompileAddresses* = enum # Frontier to Spurious Dragron - paEcRecover = 1 - paSha256 - paRipeMd160 - paIdentity + paEcRecover = 0x01, + paSha256 = 0x02, + paRipeMd160 = 0x03, + paIdentity = 0x04, # Byzantium and Constantinople - paModExp - paEcAdd - paEcMul - paPairing + paModExp = 0x05, + paEcAdd = 0x06, + paEcMul = 0x07, + paPairing = 0x08, # Istanbul - paBlake2bf + paBlake2bf = 0x09, # Berlin # EIP-2537: disabled # reason: not included in berlin @@ -43,12 +46,38 @@ type # paBlsPairing # paBlsMapG1 # paBlsMapG2 + # Cancun + paNoop0x0A, paNoop0x0B, paNoop0x0C, + paNoop0x0D, paNoop0x0E, paNoop0x0F, + paNoop0x10, paNoop0x11, paNoop0x12, + paNoop0x13, -iterator activePrecompiles*(): EthAddress = + paPointEvaluation = 0x14 + +proc getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses = + if fork < FkByzantium: paIdentity + elif fork < FkIstanbul: paPairing + # EIP 2537: disabled + # reason: not included in berlin + # elif fork < FkBerlin: paBlake2bf + elif fork < FkCancun: paBlake2bf + else: PrecompileAddresses.high + +proc validPrecompileAddr(addrByte, maxPrecompileAddr: byte): bool = + (addrByte in PrecompileAddresses.low.byte .. maxPrecompileAddr) and + (addrByte notin paNoop0x0A.byte .. paNoop0x13.byte) + +proc validPrecompileAddr(addrByte: byte, fork: EVMFork): bool = + let maxPrecompileAddr = getMaxPrecompileAddr(fork) + validPrecompileAddr(addrByte, maxPrecompileAddr.byte) + +iterator activePrecompiles*(fork: EVMFork): EthAddress = var res: EthAddress - for c in PrecompileAddresses.low..PrecompileAddresses.high: - res[^1] = c.byte - yield res + let maxPrecompileAddr = getMaxPrecompileAddr(fork) + for c in PrecompileAddresses.low..maxPrecompileAddr: + if validPrecompileAddr(c.byte, maxPrecompileAddr.byte): + res[^1] = c.byte + yield res proc getSignature(computation: Computation): (array[32, byte], Signature) = # input is Hash, V, R, S @@ -643,21 +672,30 @@ proc blsMapG2*(c: Computation) = if not encodePoint(p, c.output): raise newException(ValidationError, "blsMapG2 encodePoint error") -proc getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses = - if fork < FkByzantium: paIdentity - elif fork < FkIstanbul: paPairing - # EIP 2537: disabled - # reason: not included in berlin - # elif fork < FkBerlin: paBlake2bf - else: PrecompileAddresses.high +proc pointEvaluation*(c: Computation) = + # Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof. + # Also verify that the provided commitment matches the provided versioned_hash. + # The data is encoded as follows: versioned_hash | z | y | commitment | proof | + + template input: untyped = + c.msg.data + + c.gasMeter.consumeGas(POINT_EVALUATION_PRECOMPILE_GAS, + reason = "EIP-4844 Point Evaluation Precompile") + + let res = pointEvaluation(input) + if res.isErr: + raise newException(ValidationError, res.error) + + # return a constant + c.output = @PointEvaluationResult proc execPrecompiles*(computation: Computation, fork: EVMFork): bool {.inline.} = for i in 0..18: if computation.msg.codeAddress[i] != 0: return let lb = computation.msg.codeAddress[19] - let maxPrecompileAddr = getMaxPrecompileAddr(fork) - if lb in PrecompileAddresses.low.byte .. maxPrecompileAddr.byte: + if validPrecompileAddr(lb, fork): result = true let precompile = PrecompileAddresses(lb) #trace "Call precompile", precompile = precompile, codeAddr = computation.msg.codeAddress @@ -672,6 +710,8 @@ proc execPrecompiles*(computation: Computation, fork: EVMFork): bool {.inline.} of paEcMul: bn256ecMul(computation, fork) of paPairing: bn256ecPairing(computation, fork) of paBlake2bf: blake2bf(computation) + of paPointEvaluation: pointEvaluation(computation) + else: discard # EIP 2537: disabled # reason: not included in berlin # of paBlsG1Add: blsG1Add(computation) diff --git a/nimbus/evm/state_transactions.nim b/nimbus/evm/state_transactions.nim index 577249ddb..aa2a8305d 100644 --- a/nimbus/evm/state_transactions.nim +++ b/nimbus/evm/state_transactions.nim @@ -23,7 +23,11 @@ import {.push raises: [].} -proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt, forkOverride=none(EVMFork)) = +proc setupTxContext*(vmState: BaseVMState, + origin: EthAddress, + gasPrice: GasInt, + versionedHashes: openArray[VersionedHash], + forkOverride=none(EVMFork)) = ## this proc will be called each time a new transaction ## is going to be executed vmState.txOrigin = origin @@ -34,6 +38,7 @@ proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt, else: vmState.determineFork vmState.gasCosts = vmState.fork.forkToSchedule + vmState.txVersionedHashes = @versionedHashes # FIXME-awkwardFactoring: the factoring out of the pre and # post parts feels awkward to me, but for now I'd really like diff --git a/nimbus/evm/types.nim b/nimbus/evm/types.nim index 0dd65b5ef..38927175b 100644 --- a/nimbus/evm/types.nim +++ b/nimbus/evm/types.nim @@ -52,6 +52,7 @@ type cumulativeGasUsed*: GasInt txOrigin* : EthAddress txGasPrice* : GasInt + txVersionedHashes*: VersionedHashes gasCosts* : GasCosts fork* : EVMFork minerAddress* : EthAddress diff --git a/nimbus/graphql/ethapi.nim b/nimbus/graphql/ethapi.nim index 6fe5e0f6e..b52a55654 100644 --- a/nimbus/graphql/ethapi.nim +++ b/nimbus/graphql/ethapi.nim @@ -569,8 +569,7 @@ const logProcs = { proc txHash(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let tx = TxNode(parent) - encodedTx = rlp.encode(tx.tx) - txHash = keccakHash(encodedTx) + txHash = rlpHash(tx.tx) # beware EIP-4844 resp(txHash) proc txNonce(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = @@ -1249,8 +1248,8 @@ proc sendRawTransaction(ud: RootRef, params: Args, parent: Node): RespResult {.a let ctx = GraphqlContextRef(ud) try: let data = hexToSeqByte(params[0].val.stringVal) - let _ = decodeTx(data) # we want to know if it is a valid tx blob - let txHash = keccakHash(data) + let tx = decodeTx(data) # we want to know if it is a valid tx blob + let txHash = rlpHash(tx) # beware EIP-4844 resp(txHash) except CatchableError as em: return err("failed to process raw transaction: " & em.msg) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 3085c02fc..a7cf3cd5e 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -258,10 +258,9 @@ proc setupEthRpc*( tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1) eip155 = com.isEIP155(com.syncCurrent) signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155) - rlpTx = rlp.encode(signedTx) txPool.add(signedTx) - result = keccakHash(rlpTx).ethHashStr + result = rlpHash(signedTx).ethHashStr server.rpc("eth_sendRawTransaction") do(data: HexDataStr) -> EthHashStr: ## Creates new message call transaction or a contract creation for signed transactions. @@ -274,7 +273,7 @@ proc setupEthRpc*( signedTx = decodeTx(txBytes) txPool.add(signedTx) - result = keccakHash(txBytes).ethHashStr + result = rlpHash(signedTx).ethHashStr server.rpc("eth_call") do(call: EthCall, quantityTag: string) -> HexDataStr: ## Executes a new message call immediately without creating a transaction on the block chain. diff --git a/nimbus/sync/handlers/eth.nim b/nimbus/sync/handlers/eth.nim index 63130b45e..9217c0b31 100644 --- a/nimbus/sync/handlers/eth.nim +++ b/nimbus/sync/handlers/eth.nim @@ -483,7 +483,9 @@ method handleAnnouncedTxs*(ctx: EthWireRef, peer: Peer, txs: openArray[Transacti var newTxHashes = newSeqOfCap[Hash256](txHashes.len) var validTxs = newSeqOfCap[Transaction](txHashes.len) for i, txHash in txHashes: - if ctx.inPoolAndOk(txHash): + # Nodes must not automatically broadcast blob transactions to + # their peers. per EIP-4844 spec + if ctx.inPoolAndOk(txHash) and txs[i].txType != TxEip4844: newTxHashes.add txHash validTxs.add txs[i] diff --git a/nimbus/sync/legacy.nim b/nimbus/sync/legacy.nim index 3b7d7936c..382786b04 100644 --- a/nimbus/sync/legacy.nim +++ b/nimbus/sync/legacy.nim @@ -180,6 +180,7 @@ proc validateDifficulty(ctx: LegacySyncRef, return false proc validateHeader(ctx: LegacySyncRef, header: BlockHeader, + txs: openArray[Transaction], height = none(BlockNumber)): bool {.raises: [CatchableError].} = if header.parentHash == GENESIS_PARENT_HASH: @@ -242,7 +243,7 @@ proc validateHeader(ctx: LegacySyncRef, header: BlockHeader, msg=res.error return false - res = com.validateEip4844Header(header) + res = com.validateEip4844Header(header, parentHeader, txs) if res.isErr: trace "validate eip4844 error", msg=res.error @@ -1052,7 +1053,7 @@ proc handleNewBlock(ctx: LegacySyncRef, number=blk.header.blockNumber return - if not ctx.validateHeader(blk.header): + if not ctx.validateHeader(blk.header, blk.txs): error "invalid header from peer", peer, hash=short(blk.header.blockHash) return diff --git a/nimbus/sync/protocol/les_protocol.nim b/nimbus/sync/protocol/les_protocol.nim index abe48a1a0..d95f9506b 100644 --- a/nimbus/sync/protocol/les_protocol.nim +++ b/nimbus/sync/protocol/les_protocol.nim @@ -458,10 +458,7 @@ p2pProtocol les(version = lesVersion, var results: seq[TransactionStatusMsg] for t in transactions: - let hash = t.rlpHash # TODO: this is not optimal, we can compute - # the hash from the request bytes. - # The RLP module can offer a helper Hashed[T] - # to make this easy. + let hash = t.rlpHash var s = ctx.getTransactionStatus(hash) if s.status == TransactionStatus.Unknown: ctx.addTransactions([t]) diff --git a/nimbus/transaction.nim b/nimbus/transaction.nim index 7c4bb004a..92e3e7026 100644 --- a/nimbus/transaction.nim +++ b/nimbus/transaction.nim @@ -117,7 +117,7 @@ proc validateTxLegacy(tx: Transaction, fork: EVMFork) = isValid = isValid and tx.S < SECPK1_N div 2 if not isValid: - raise newException(ValidationError, "Invalid transaction") + raise newException(ValidationError, "Invalid legacy transaction") proc validateTxEip2930(tx: Transaction) = var isValid = tx.V in {0'i64, 1'i64} @@ -126,7 +126,27 @@ proc validateTxEip2930(tx: Transaction) = isValid = isValid and tx.R < SECPK1_N if not isValid: - raise newException(ValidationError, "Invalid transaction") + 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_VERSIONED_HASHES_LIST_SIZE + + for bv in tx.versionedHashes: + isValid = isValid and + bv.data[0] == BLOB_COMMITMENT_VERSION_KZG + + if not isValid: + raise newException(ValidationError, "Invalid EIP-4844 transaction") proc validate*(tx: Transaction, fork: EVMFork) = # parameters pass validation rules @@ -144,7 +164,9 @@ proc validate*(tx: Transaction, fork: EVMFork) = case tx.txType of TxLegacy: validateTxLegacy(tx, fork) - else: + of TxEip4844: + validateTxEip4844(tx) + of TxEip2930, TxEip1559: validateTxEip2930(tx) proc signTransaction*(tx: Transaction, privateKey: PrivateKey, chainId: ChainId, eip155: bool): Transaction = diff --git a/nimbus/transaction/call_common.nim b/nimbus/transaction/call_common.nim index 433bd1c29..d8bd02a76 100644 --- a/nimbus/transaction/call_common.nim +++ b/nimbus/transaction/call_common.nim @@ -16,6 +16,7 @@ import ".."/[db/accounts_cache], ../evm/async/operations, ../common/evmforks, + ../core/eip4844, ./host_types when defined(evmc_enabled): @@ -36,6 +37,7 @@ type value*: HostValue # Value sent from sender to recipient. input*: seq[byte] # Input data. accessList*: AccessList # EIP-2930 (Berlin) tx access list. + versionedHashes*: VersionedHashes # EIP-4844 (Cancun) blob versioned hashes noIntrinsic*: bool # Don't charge intrinsic gas. noAccessList*: bool # Don't initialise EIP-2929 access list. noGasCharge*: bool # Don't charge sender account for gas. @@ -67,10 +69,11 @@ proc hostToComputationMessage*(msg: EvmcMessage): Message = flags: if msg.isStatic: emvcStatic else: emvcNoFlags ) -func intrinsicGas*(call: CallParams, fork: EVMFork): GasInt {.inline.} = +func intrinsicGas*(call: CallParams, vmState: BaseVMState): GasInt {.inline.} = # 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). + let fork = vmState.fork var gas = gasFees[fork][GasTransaction] # EIP-2 (Homestead) extra intrinsic gas for contract creations. @@ -90,6 +93,12 @@ func intrinsicGas*(call: CallParams, fork: EVMFork): GasInt {.inline.} = for account in call.accessList: gas += ACCESS_LIST_ADDRESS_COST gas += account.storageKeys.len * ACCESS_LIST_STORAGE_KEY_COST + + # EIP-4844 + if fork >= FkCancun: + gas += calcDataFee(call.versionedHashes.len, + vmState.parent.excessDataGas).GasInt + return gas proc initialAccessListEIP2929(call: CallParams) = @@ -109,8 +118,8 @@ proc initialAccessListEIP2929(call: CallParams) = if vmState.fork >= FkShanghai: db.accessList(vmState.coinbase) - # TODO: Check this only adds the correct subset of precompiles. - for c in activePrecompiles(): + # Adds the correct subset of precompiles. + for c in activePrecompiles(vmState.fork): db.accessList(c) # EIP2930 optional access list. @@ -124,12 +133,13 @@ proc setupHost(call: CallParams): TransactionHost = vmState.setupTxContext( origin = call.origin.get(call.sender), gasPrice = call.gasPrice, + versionedHashes = call.versionedHashes, forkOverride = call.forkOverride ) var intrinsicGas: GasInt = 0 if not call.noIntrinsic: - intrinsicGas = intrinsicGas(call, vmState.fork) + intrinsicGas = intrinsicGas(call, vmState) let host = TransactionHost( vmState: vmState, @@ -286,7 +296,7 @@ proc asyncRunComputation*(call: CallParams): Future[CallResult] {.async.} = # This has to come before the newComputation call inside setupHost. if not call.isCreate: await ifNecessaryGetCodeForAccounts(call.vmState, @[call.to.toEvmc.fromEvmc]) - + let host = setupHost(call) prepareToRunComputation(host, call) diff --git a/nimbus/transaction/call_evm.nim b/nimbus/transaction/call_evm.nim index 0f34bc995..2d4025126 100644 --- a/nimbus/transaction/call_evm.nim +++ b/nimbus/transaction/call_evm.nim @@ -29,6 +29,7 @@ type value* : Option[UInt256] data* : seq[byte] accessList* : AccessList + versionedHashes*: VersionedHashes proc toCallParams(vmState: BaseVMState, cd: RpcCallData, globalGasCap: GasInt, baseFee: Option[UInt256], @@ -73,7 +74,8 @@ proc toCallParams(vmState: BaseVMState, cd: RpcCallData, gasPrice: gasPrice, value: cd.value.get(0.u256), input: cd.data, - accessList: cd.accessList + accessList: cd.accessList, + versionedHashes:cd.versionedHashes ) proc rpcCallEvm*(call: RpcCallData, header: BlockHeader, com: CommonRef): CallResult @@ -152,7 +154,7 @@ proc rpcEstimateGas*(cd: RpcCallData, header: BlockHeader, com: CommonRef, gasCa hi = gasCap cap = hi - let intrinsicGas = intrinsicGas(params, fork) + let intrinsicGas = intrinsicGas(params, vmState) # Create a helper to check if a gas allowance results in an executable transaction proc executable(gasLimit: GasInt): bool @@ -201,6 +203,9 @@ proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState, if tx.txType > TxLegacy: shallowCopy(result.accessList, tx.accessList) + if tx.txType >= TxEip4844: + result.versionedHashes = tx.versionedHashes + proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallParams = result = CallParams( vmState: vmState, @@ -219,6 +224,9 @@ proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState if tx.txType > TxLegacy: shallowCopy(result.accessList, tx.accessList) + if tx.txType >= TxEip4844: + result.versionedHashes = tx.versionedHashes + proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): GasInt {.gcsafe, raises: [CatchableError].} = let call = callParamsForTx(tx, sender, vmState, fork) diff --git a/nimbus/utils/ec_recover.nim b/nimbus/utils/ec_recover.nim index 6c93889b7..12abe2d88 100644 --- a/nimbus/utils/ec_recover.nim +++ b/nimbus/utils/ec_recover.nim @@ -119,7 +119,10 @@ proc ecRecover*(tx: var Transaction): EcAddrResult = let txSig = tx.vrsSerialised if txSig.isErr: return err(txSig.error) - txSig.value.recoverImpl(tx.txHashNoSignature) + 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. diff --git a/nimbus/utils/utils_defs.nim b/nimbus/utils/utils_defs.nim index bbc72467e..2f77b9605 100644 --- a/nimbus/utils/utils_defs.nim +++ b/nimbus/utils/utils_defs.nim @@ -42,6 +42,10 @@ type ## database lookup failed "not found" + errTxEncError = ##\ + ## TRansaction encoding error + "tx enc error" + UtilsError* = ##\ ## Error message, tinned component + explanatory text (if any) (UtilsErrorType,string) diff --git a/tests/all_tests.nim b/tests/all_tests.nim index c53d14b38..a4aec5332 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -48,4 +48,5 @@ cliBuilder: ./test_configuration, ./test_keyed_queue_rlp, ./test_txpool, - ./test_merge + ./test_merge, + ./test_eip4844 diff --git a/tests/macro_assembler.nim b/tests/macro_assembler.nim index 593064cc9..2aeef94ff 100644 --- a/tests/macro_assembler.nim +++ b/tests/macro_assembler.nim @@ -377,13 +377,14 @@ proc verifyAsmResult(vmState: BaseVMState, boa: Assembler, asmResult: CallResult proc createSignedTx(payload: Blob, chainId: ChainId): Transaction = let privateKey = PrivateKey.fromHex("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d")[] let unsignedTx = Transaction( - txType: TxLegacy, + txType: TxEIP4844, nonce: 0, gasPrice: 1.GasInt, gasLimit: 500_000_000.GasInt, to: codeAddress.some, value: 500.u256, - payload: payload + payload: payload, + versionedHashes: @[EMPTY_UNCLE_HASH, EMPTY_SHA3] ) signTransaction(unsignedTx, privateKey, chainId, false) diff --git a/tests/test_blockchain_json.nim b/tests/test_blockchain_json.nim index 8fbf4457c..cf6707291 100644 --- a/tests/test_blockchain_json.nim +++ b/tests/test_blockchain_json.nim @@ -412,6 +412,7 @@ proc blockchainJsonMain*(debugMode = false) = testFixture(n, testStatusIMPL, debugMode = true, config.trace) when isMainModule: + import std/times var message: string let start = getTime() diff --git a/tests/test_eip4844.nim b/tests/test_eip4844.nim new file mode 100644 index 000000000..4b312f9da --- /dev/null +++ b/tests/test_eip4844.nim @@ -0,0 +1,164 @@ +# Nimbus +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or +# http://www.apache.org/licenses/LICENSE-2.0) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or +# http://opensource.org/licenses/MIT) +# at your option. This file may not be copied, modified, or distributed except +# according to those terms. + +import + stew/byteutils, + unittest2, + eth/[common, keys], + ../nimbus/transaction + +const + recipient = hexToByteArray[20]("095e7baea6a6c7c4c2dfeb977efac326af552d87") + zeroG1 = hexToByteArray[48]("0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + source = hexToByteArray[20]("0x0000000000000000000000000000000000000001") + storageKey= default(StorageKey) + accesses = @[AccessPair(address: source, storageKeys: @[storageKey])] + blob = default(NetworkBlob) + abcdef = hexToSeqByte("abcdef") + hexKey = "af1a9be9f1a54421cac82943820a0fe0f601bb5f4f6d0bccc81c613f0ce6ae22" + senderTop = hexToByteArray[20]("73cf19657412508833f618a15e8251306b3e6ee5") + +proc tx0(i: int): Transaction = + Transaction( + txType: TxLegacy, + nonce: i.AccountNonce, + to: recipient.some, + gasLimit: 1.GasInt, + gasPrice: 2.GasInt, + payload: abcdef) + +proc tx1(i: int): Transaction = + Transaction( + # Legacy tx contract creation. + txType: TxLegacy, + nonce: i.AccountNonce, + gasLimit: 1.GasInt, + gasPrice: 2.GasInt, + payload: abcdef) + +proc tx2(i: int): Transaction = + Transaction( + # Tx with non-zero access list. + txType: TxEip2930, + chainId: 1.ChainId, + nonce: i.AccountNonce, + to: recipient.some, + gasLimit: 123457.GasInt, + gasPrice: 10.GasInt, + accessList: accesses, + payload: abcdef) + +proc tx3(i: int): Transaction = + Transaction( + # Tx with empty access list. + txType: TxEip2930, + chainId: 1.ChainId, + nonce: i.AccountNonce, + to: recipient.some, + gasLimit: 123457.GasInt, + gasPrice: 10.GasInt, + payload: abcdef) + +proc tx4(i: int): Transaction = + Transaction( + # Contract creation with access list. + txType: TxEip2930, + chainId: 1.ChainId, + nonce: i.AccountNonce, + gasLimit: 123457.GasInt, + gasPrice: 10.GasInt, + accessList: accesses) + +proc tx5(i: int): Transaction = + Transaction( + txType: TxEip1559, + chainId: 1.ChainId, + nonce: i.AccountNonce, + gasLimit: 123457.GasInt, + maxPriorityFee: 42.GasInt, + maxFee: 10.GasInt, + accessList: accesses) + +proc tx6(i: int): Transaction = + const + digest = "010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014".toDigest + + Transaction( + txType: TxEip4844, + chainId: 1.ChainId, + nonce: i.AccountNonce, + gasLimit: 123457.GasInt, + maxPriorityFee: 42.GasInt, + maxFee: 10.GasInt, + accessList: accesses, + versionedHashes: @[digest], + networkPayload: NetworkPayload( + commitments: @[zeroG1], + blobs: @[blob], + proofs: @[zeroG1], + ) + ) + +proc tx7(i: int): Transaction = + const + digest = "01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e".toDigest + + Transaction( + txType: TxEip4844, + chainID: 1.ChainId, + nonce: i.AccountNonce, + gasLimit: 123457.GasInt, + maxPriorityFee: 42.GasInt, + maxFee: 10.GasInt, + accessList: accesses, + versionedHashes: @[digest], + maxFeePerDataGas: 10000000.GasInt, + ) + +proc tx8(i: int): Transaction = + const + digest = "01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e".toDigest + + Transaction( + txType: TxEip4844, + chainID: 1.ChainId, + nonce: i.AccountNonce, + to: some(recipient), + gasLimit: 123457.GasInt, + maxPriorityFee: 42.GasInt, + maxFee: 10.GasInt, + accessList: accesses, + versionedHashes: @[digest], + maxFeePerDataGas: 10000000.GasInt, + ) + +proc privKey(keyHex: string): PrivateKey = + let kRes = PrivateKey.fromHex(keyHex) + if kRes.isErr: + echo kRes.error + quit(QuitFailure) + + kRes.get() + +proc eip4844Main*() = + var signerKey = privKey(hexKey) + + suite "EIP4844 sign transaction": + let txs = @[tx0(3), tx1(3), tx2(3), tx3(3), tx4(3), + tx5(3), tx6(3), tx7(3), tx8(3)] + + test "sign transaction": + for tx in txs: + let signedTx = signTransaction(tx, signerKey, 1.ChainId, true) + let sender = signedTx.getSender() + check sender == senderTop + +when isMainModule: + eip4844Main() diff --git a/tests/test_generalstate_json.nim b/tests/test_generalstate_json.nim index 2543a24f1..477ea9f9d 100644 --- a/tests/test_generalstate_json.nim +++ b/tests/test_generalstate_json.nim @@ -190,6 +190,7 @@ proc generalStateJsonMain*(debugMode = false) = testFixture(n, testStatusIMPL, config.trace, true) when isMainModule: + import std/times var message: string let start = getTime() diff --git a/tests/test_op_env.nim b/tests/test_op_env.nim index ddab4edd5..b09a72ebf 100644 --- a/tests/test_op_env.nim +++ b/tests/test_op_env.nim @@ -374,5 +374,79 @@ proc opEnvMain*() = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" fork: Merge + # from here onward, versionedHashes is taken from + # tx.versionedHashes = @[EMPTY_UNCLE_HASH, EMPTY_SHA3] + # preset in macro_assembler: createSignedTx + when not defined(evmc_enabled): + assembler: + title: "EIP-4844: BlobHash 1" + code: + PUSH1 "0x01" + BlobHash + STOP + stack: + "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + fork: Cancun + + assembler: + title: "EIP-4844: BlobHash 0" + code: + PUSH1 "0x00" + BlobHash + STOP + stack: + "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + fork: Cancun + + assembler: + title: "EIP-4844: BlobHash 2" + code: + PUSH1 "0x02" + BlobHash + STOP + stack: + "0x0000000000000000000000000000000000000000000000000000000000000000" + fork: Cancun + + assembler: + title: "EIP-4844: BlobHash 32 Bit high" + code: + PUSH4 "0xffffffff" + BlobHash + STOP + stack: + "0x0000000000000000000000000000000000000000000000000000000000000000" + fork: Cancun + + assembler: + title: "EIP-4844: BlobHash 64 Bit high" + code: + PUSH8 "0xffffffffffffffff" + BlobHash + STOP + stack: + "0x0000000000000000000000000000000000000000000000000000000000000000" + fork: Cancun + + assembler: + title: "EIP-4844: BlobHash 128 Bit high" + code: + PUSH16 "0xffffffffffffffffffffffffffffffff" + BlobHash + STOP + stack: + "0x0000000000000000000000000000000000000000000000000000000000000000" + fork: Cancun + + assembler: + title: "EIP-4844: BlobHash 256 Bit high" + code: + PUSH32 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + BlobHash + STOP + stack: + "0x0000000000000000000000000000000000000000000000000000000000000000" + fork: Cancun + when isMainModule: opEnvMain() diff --git a/tools/t8n/helpers.nim b/tools/t8n/helpers.nim index e8c936908..0a8369c97 100644 --- a/tools/t8n/helpers.nim +++ b/tools/t8n/helpers.nim @@ -61,13 +61,16 @@ template fromJson(T: type GasInt, n: JsonNode, field: string): GasInt = template fromJson(T: type ChainId, n: JsonNode, field: string): ChainId = parseHexOrInt[uint64](n[field].getStr()).ChainId -proc fromJson(T: type Hash256, n: JsonNode, field: string): Hash256 = - var num = n[field].getStr() +proc fromJson(T: type Hash256, n: JsonNode): Hash256 = + var num = n.getStr() num.removePrefix("0x") if num.len < 64: num = repeat('0', 64 - num.len) & num Hash256(data: hexToByteArray(num, 32)) +proc fromJson(T: type Hash256, n: JsonNode, field: string): Hash256 = + fromJson(T, n[field]) + template fromJson(T: type EthTime, n: JsonNode, field: string): EthTime = fromUnix(parseHexOrInt[int64](n[field].getStr())) @@ -99,6 +102,11 @@ proc fromJson(T: type Withdrawal, n: JsonNode): Withdrawal = amount: fromJson(uint64, n, "amount") ) +proc fromJson(T: type VersionedHashes, n: JsonNode, field: string): VersionedHashes = + let list = n[field] + for x in list: + result.add Hash256.fromJson(x) + template `gas=`(tx: var Transaction, x: GasInt) = tx.gasLimit = x @@ -120,6 +128,9 @@ template `maxPriorityFeePerGas=`(tx: var Transaction, x: GasInt) = template `maxFeePerGas=`(tx: var Transaction, x: GasInt) = tx.maxFee = x +template `blobVersionedHashes=`(tx: var Transaction, x: VersionedHashes) = + tx.versionedHashes = x + template required(o: untyped, T: type, oField: untyped) = const fName = astToStr(oField) if not n.hasKey(fName): @@ -168,6 +179,8 @@ proc parseEnv*(ctx: var TransContext, n: JsonNode) = optional(ctx.env, UInt256, parentBaseFee) optional(ctx.env, GasInt, parentGasUsed) optional(ctx.env, GasInt, parentGasLimit) + optional(ctx.env, uint64, parentDataGasUsed) + optional(ctx.env, uint64, parentExcessDataGas) if n.hasKey("blockHashes"): let w = n["blockHashes"] @@ -216,6 +229,13 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction = required(tx, GasInt, maxPriorityFeePerGas) required(tx, GasInt, maxFeePerGas) omitZero(tx, AccessList, accessList) + of TxEip4844: + required(tx, ChainId, chainId) + required(tx, GasInt, maxPriorityFeePerGas) + required(tx, GasInt, maxFeePerGas) + omitZero(tx, AccessList, accessList) + required(tx, GasInt, maxFeePerDataGas) + required(tx, VersionedHashes, blobVersionedHashes) var eip155 = true if n.hasKey("protected"): @@ -231,7 +251,7 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction = proc parseTxLegacy(item: var Rlp): Result[Transaction, string] = try: var tx: Transaction - item.readTxLegacy(tx) + item.decodeTxLegacy(tx) return ok(tx) except RlpError as x: return err(x.msg) @@ -240,7 +260,7 @@ proc parseTxTyped(item: var Rlp): Result[Transaction, string] = try: var tx: Transaction var rr = rlpFromBytes(item.read(Blob)) - rr.readTxTyped(tx) + rr.decodeTxTyped(tx) return ok(tx) except RlpError as x: return err(x.msg) @@ -420,3 +440,7 @@ proc `@@`*(x: ExecutionResult): JsonNode = result["currentBaseFee"] = @@(x.currentBaseFee) if x.withdrawalsRoot.isSome: result["withdrawalsRoot"] = @@(x.withdrawalsRoot) + if x.dataGasUsed.isSome: + result["dataGasUsed"] = @@(x.dataGasUsed) + if x.excessDataGas.isSome: + result["excessDataGas"] = @@(x.excessDataGas) diff --git a/tools/t8n/t8n_test.nim b/tools/t8n/t8n_test.nim index 82269f960..02e1f53df 100644 --- a/tools/t8n/t8n_test.nim +++ b/tools/t8n/t8n_test.nim @@ -466,6 +466,15 @@ const output: T8nOutput(alloc: true, result: true), expOut: "exp.json", ), + TestSpec( + name : "Cancun optional fields", + base : "testdata/00-517", + input : t8nInput( + "alloc.json", "txs.json", "env.json", "Cancun", "", + ), + output: T8nOutput(alloc: true, result: true), + expOut: "exp.json", + ), ] proc main() = diff --git a/tools/t8n/testdata/00-517/alloc.json b/tools/t8n/testdata/00-517/alloc.json new file mode 100644 index 000000000..d67655a8a --- /dev/null +++ b/tools/t8n/testdata/00-517/alloc.json @@ -0,0 +1,8 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0", + "code": "0x", + "nonce": "0xac", + "storage": {} + } +} diff --git a/tools/t8n/testdata/00-517/env.json b/tools/t8n/testdata/00-517/env.json new file mode 100644 index 000000000..b1f22a39b --- /dev/null +++ b/tools/t8n/testdata/00-517/env.json @@ -0,0 +1,19 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": null, + "currentRandom": "0xdeadc0de", + "currentGasLimit": "0x750a163df65e8a", + "currentBaseFee": "0x500", + "currentNumber": "1", + "currentTimestamp": "1000", + "withdrawals": [ + { + "index": "0x42", + "validatorIndex": "0x42", + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "amount": "0x2a" + } + ], + "parentDataGasUsed": "0x100", + "parentExcessDataGas": "0x100" +} diff --git a/tools/t8n/testdata/00-517/exp.json b/tools/t8n/testdata/00-517/exp.json new file mode 100644 index 000000000..311609cb4 --- /dev/null +++ b/tools/t8n/testdata/00-517/exp.json @@ -0,0 +1,22 @@ +{ + "alloc": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x9c7652400", + "nonce": "0xac" + } + }, + "result": { + "stateRoot": "0x6e061c2f6513af27d267a0e3b07cb9a10f1ba3a0f65ab648d3a17c36e15021d2", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": null, + "gasUsed": "0x0", + "currentBaseFee": "0x500", + "withdrawalsRoot": "0x4921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5", + "dataGasUsed": "0x0", + "excessDataGas": "0x0" + } +} diff --git a/tools/t8n/testdata/00-517/txs.json b/tools/t8n/testdata/00-517/txs.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tools/t8n/testdata/00-517/txs.json @@ -0,0 +1 @@ +[] diff --git a/tools/t8n/transition.nim b/tools/t8n/transition.nim index 1c4ae9d4a..e6caa3085 100644 --- a/tools/t8n/transition.nim +++ b/tools/t8n/transition.nim @@ -20,7 +20,8 @@ import ../../nimbus/utils/utils, ../../nimbus/core/pow/difficulty, ../../nimbus/core/dao, - ../../nimbus/core/executor/[process_transaction, executor_helpers] + ../../nimbus/core/executor/[process_transaction, executor_helpers], + ../../nimbus/core/eip4844 import stew/byteutils const @@ -203,6 +204,7 @@ proc exec(ctx: var TransContext, vmState.receipts = newSeqOfCap[Receipt](txList.len) vmState.cumulativeGasUsed = 0 + var dataGasUsed = 0'u64 for txIndex, txRes in txList: if txRes.isErr: rejected.add RejectedTx( @@ -239,6 +241,7 @@ proc exec(ctx: var TransContext, rec, tx, sender, txIndex, gasUsed ) includedTx.add tx + dataGasUsed += tx.getTotalDataGas # Add mining reward? (-1 means rewards are disabled) if stateReward.isSome and stateReward.get >= 0: @@ -286,6 +289,10 @@ proc exec(ctx: var TransContext, withdrawalsRoot: header.withdrawalsRoot ) + if fork >= FkCancun: + result.result.dataGasUsed = some dataGasUsed + result.result.excessDataGas = some calcExcessDataGas(vmState.parent) + template wrapException(body: untyped) = when wrapExceptionEnabled: try: @@ -403,7 +410,9 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) = timestamp: ctx.env.parentTimestamp, difficulty: ctx.env.parentDifficulty.get(0.u256), ommersHash: uncleHash, - blockNumber: ctx.env.currentNumber - 1.toBlockNumber + blockNumber: ctx.env.currentNumber - 1.toBlockNumber, + dataGasUsed: ctx.env.parentDataGasUsed, + excessDataGas: ctx.env.parentExcessDataGas ) # Sanity check, to not `panic` in state_transition @@ -419,6 +428,17 @@ proc transitionAction*(ctx: var TransContext, conf: T8NConf) = if com.isShanghaiOrLater(ctx.env.currentTimestamp) and ctx.env.withdrawals.isNone: raise newError(ErrorConfig, "Shanghai config but missing 'withdrawals' in env section") + if com.isCancunOrLater(ctx.env.currentTimestamp): + if ctx.env.parentDataGasUsed.isNone: + raise newError(ErrorConfig, "Cancun config but missing 'parentDataGasUsed' in env section") + + if ctx.env.parentExcessDataGas.isNone: + raise newError(ErrorConfig, "Cancun config but missing 'parentExcessDataGas' in env section") + + let res = loadKzgTrustedSetup() + if res.isErr: + raise newError(ErrorConfig, res.error) + if com.forkGTE(MergeFork): if ctx.env.currentRandom.isNone: raise newError(ErrorConfig, "post-merge requires currentRandom to be defined in env") diff --git a/tools/t8n/txpriv.nim b/tools/t8n/txpriv.nim index 88a3e5997..b3ad515b4 100644 --- a/tools/t8n/txpriv.nim +++ b/tools/t8n/txpriv.nim @@ -8,99 +8,12 @@ # at your option. This file may not be copied, modified, or distributed except # according to those terms. -import - eth/common +include eth/common/eth_types_rlp -from stew/objects - import checkedEnumAssign +# reexport private procs -# these procs are duplicates of nim-eth/eth_types_rlp.nim -# both `readTxLegacy` and `readTxTyped` are exported here +template decodeTxLegacy*(rlp: var Rlp, tx: var Transaction) = + readTxLegacy(rlp, tx) -template read[T](rlp: var Rlp, val: var T)= - val = rlp.read(type val) - -proc read[T](rlp: var Rlp, val: var Option[T])= - if rlp.blobLen != 0: - val = some(rlp.read(T)) - else: - rlp.skipElem - -proc readTxLegacy*(rlp: var Rlp, tx: var Transaction)= - tx.txType = TxLegacy - rlp.tryEnterList() - rlp.read(tx.nonce) - rlp.read(tx.gasPrice) - rlp.read(tx.gasLimit) - rlp.read(tx.to) - rlp.read(tx.value) - rlp.read(tx.payload) - rlp.read(tx.V) - rlp.read(tx.R) - rlp.read(tx.S) - -proc readTxEip2930(rlp: var Rlp, tx: var Transaction)= - tx.txType = TxEip2930 - rlp.tryEnterList() - tx.chainId = rlp.read(uint64).ChainId - rlp.read(tx.nonce) - rlp.read(tx.gasPrice) - rlp.read(tx.gasLimit) - rlp.read(tx.to) - rlp.read(tx.value) - rlp.read(tx.payload) - rlp.read(tx.accessList) - rlp.read(tx.V) - rlp.read(tx.R) - rlp.read(tx.S) - -proc readTxEip1559(rlp: var Rlp, tx: var Transaction)= - tx.txType = TxEip1559 - rlp.tryEnterList() - tx.chainId = rlp.read(uint64).ChainId - rlp.read(tx.nonce) - rlp.read(tx.maxPriorityFee) - rlp.read(tx.maxFee) - rlp.read(tx.gasLimit) - rlp.read(tx.to) - rlp.read(tx.value) - rlp.read(tx.payload) - rlp.read(tx.accessList) - rlp.read(tx.V) - rlp.read(tx.R) - rlp.read(tx.S) - -proc readTxTyped*(rlp: var Rlp, tx: var Transaction) {.inline.} = - # EIP-2718: We MUST decode the first byte as a byte, not `rlp.read(int)`. - # If decoded with `rlp.read(int)`, bad transaction data (from the network) - # or even just incorrectly framed data for other reasons fails with - # any of these misleading error messages: - # - "Message too large to fit in memory" - # - "Number encoded with a leading zero" - # - "Read past the end of the RLP stream" - # - "Small number encoded in a non-canonical way" - # - "Attempt to read an Int value past the RLP end" - # - "The RLP contains a larger than expected Int value" - if not rlp.isSingleByte: - if not rlp.hasData: - raise newException(MalformedRlpError, - "Transaction expected but source RLP is empty") - raise newException(MalformedRlpError, - "TypedTransaction type byte is out of range, must be 0x00 to 0x7f") - let txType = rlp.getByteValue - rlp.position += 1 - - var txVal: TxType - if checkedEnumAssign(txVal, txType): - case txVal: - of TxEip2930: - rlp.readTxEip2930(tx) - return - of TxEip1559: - rlp.readTxEip1559(tx) - return - else: - discard - - raise newException(UnsupportedRlpError, - "TypedTransaction type must be 1 or 2 in this version, got " & $txType) +template decodeTxTyped*(rlp: var Rlp, tx: var Transaction) = + readTxTyped(rlp, tx) diff --git a/tools/t8n/types.nim b/tools/t8n/types.nim index 05418007f..f8feb55c5 100644 --- a/tools/t8n/types.nim +++ b/tools/t8n/types.nim @@ -44,6 +44,8 @@ type parentGasUsed*: Option[GasInt] parentGasLimit*: Option[GasInt] withdrawals*: Option[seq[Withdrawal]] + parentDataGasUsed*: Option[uint64] + parentExcessDataGas*: Option[uint64] TxsType* = enum TxsNone @@ -92,6 +94,8 @@ type gasUsed*: GasInt currentBaseFee*: Option[UInt256] withdrawalsRoot*: Option[Hash256] + dataGasUsed*: Option[uint64] + excessDataGas*: Option[uint64] const ErrorEVM* = 2.T8NExitCode diff --git a/vendor/nim-eth b/vendor/nim-eth index 6dacb2ca5..6b8a7b009 160000 --- a/vendor/nim-eth +++ b/vendor/nim-eth @@ -1 +1 @@ -Subproject commit 6dacb2ca5ce4e5528924f56c87620f38e1d0c0d4 +Subproject commit 6b8a7b009eb94bfdac1410f243865984bdd7c4f2