# Nimbus # Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or # http://opensource.org/licenses/MIT) # at your option. This file may not be copied, modified, or distributed except # according to those terms. import std/[tables, math, strutils], chronos, chronicles, stew/byteutils, eth/common, ./types, ./base_spec, ./test_env, ./clmock, ./cancun/step_desc, ./cancun/helpers, ./cancun/blobs, ./cancun/customizer, ./engine_tests, ./engine/engine_spec, ../../../nimbus/constants, ../../../nimbus/common/chain_config import ./cancun/step_newpayloads, ./cancun/step_sendblobtx, ./cancun/step_launch_client, ./cancun/step_sendmodpayload, ./cancun/step_devp2p_pooledtx, ./engine/suggested_fee_recipient, ./engine/payload_attributes, ./engine/invalid_payload, ./engine/prev_randao, ./engine/payload_id # Precalculate the first data gas cost increase const DATA_GAS_COST_INCREMENT_EXCEED_BLOBS = getMinExcessBlobsForBlobGasPrice(2).int TARGET_BLOBS_PER_BLOCK = int(TARGET_BLOB_GAS_PER_BLOCK div GAS_PER_BLOB) proc getGenesis(param: NetworkParams) = # Add bytecode pre deploy to the EIP-4788 address. param.genesis.alloc[BEACON_ROOTS_ADDRESS] = GenesisAccount( balance: 0.u256, nonce: 1, code: hexToSeqByte("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500"), ) # Execution specification reference: # https:#github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md proc specExecute(ws: BaseSpec): bool = let cs = CancunSpec(ws) conf = envConfig(ws.getForkConfig()) getGenesis(conf.networkParams) let env = TestEnv.new(conf) env.engine.setRealTTD() env.setupCLMock() ws.configureCLMock(env.clMock) testCond waitFor env.clMock.waitForTTD() let blobTestCtx = CancunTestContext( env: env, txPool: TestBlobTxPool(), ) if cs.getPayloadDelay != 0: env.clMock.payloadProductionClientDelay = cs.getPayloadDelay result = true for stepId, step in cs.testSequence: echo "INFO: Executing step ", stepId+1, ": ", step.description() if not step.execute(blobTestCtx): fatal "FAIL: Error executing", step=stepId+1 result = false break env.close() # List of all blob tests let cancunTestListA* = [ TestDesc( name: "Blob Transactions On Block 1, Shanghai Genesis", about: """ Tests the Cancun fork since Block 1. Verifications performed: - Correct implementation of Engine API changes for Cancun: - engine_newPayloadV3, engine_forkchoiceUpdatedV3, engine_getPayloadV3 - Correct implementation of EIP-4844: - Blob transaction ordering and inclusion - Blob transaction blob gas cost checks - Verify Blob bundle on built payload - Eth RPC changes for Cancun: - Blob fields in eth_getBlockByNumber - Beacon root in eth_getBlockByNumber - Blob fields in transaction receipts from eth_getTransactionReceipt """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 1, testSequence: @[ # We are starting at Shanghai genesis so send a couple payloads to reach the fork NewPayloads().TestStep, # First, we send a couple of blob transactions on genesis, # with enough data gas cost to make sure they are included in the first block. SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), # We create the first payload, and verify that the blob transactions # are included in the payload. # We also verify that the blob transactions are included in the blobs bundle. NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), # Try to increase the data gas cost of the blob transactions # by maxing out the number of blobs for the next payloads. SendBlobTransactions( transactionCount: DATA_GAS_COST_INCREMENT_EXCEED_BLOBS div (MAX_BLOBS_PER_BLOCK-TARGET_BLOBS_PER_BLOCK) + 1, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), # Next payloads will have max data blobs each NewPayloads( payloadCount: DATA_GAS_COST_INCREMENT_EXCEED_BLOBS div (MAX_BLOBS_PER_BLOCK - TARGET_BLOBS_PER_BLOCK), expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, ), # But there will be an empty payload, since the data gas cost increased # and the last blob transaction was not included. NewPayloads( expectedIncludedBlobCount: 0, ), # But it will be included in the next payload NewPayloads( expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, ), ] ) ), TestDesc( name: "Blob Transactions On Block 1, Cancun Genesis", about: """ Tests the Cancun fork since genesis. Verifications performed: * See Blob Transactions On Block 1, Shanghai Genesis """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads(), # Create a single empty payload to push the client through the fork. # First, we send a couple of blob transactions on genesis, # with enough data gas cost to make sure they are included in the first block. SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), # We create the first payload, and verify that the blob transactions # are included in the payload. # We also verify that the blob transactions are included in the blobs bundle. NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), # Try to increase the data gas cost of the blob transactions # by maxing out the number of blobs for the next payloads. SendBlobTransactions( transactionCount: DATA_GAS_COST_INCREMENT_EXCEED_BLOBS div (MAX_BLOBS_PER_BLOCK-TARGET_BLOBS_PER_BLOCK) + 1, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), # Next payloads will have max data blobs each NewPayloads( payloadCount: DATA_GAS_COST_INCREMENT_EXCEED_BLOBS div (MAX_BLOBS_PER_BLOCK - TARGET_BLOBS_PER_BLOCK), expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, ), # But there will be an empty payload, since the data gas cost increased # and the last blob transaction was not included. NewPayloads( expectedIncludedBlobCount: 0, ), # But it will be included in the next payload NewPayloads( expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, ), ] ), ), TestDesc( name: "Blob Transaction Ordering, Single Account", about: """ Send N blob transactions with MAX_BLOBS_PER_BLOCK-1 blobs each, using account A. Using same account, and an increased nonce from the previously sent transactions, send N blob transactions with 1 blob each. Verify that the payloads are created with the correct ordering: - The first payloads must include the first N blob transactions - The last payloads must include the last single-blob transactions All transactions have sufficient data gas price to be included any of the payloads. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ # First send the MAX_BLOBS_PER_BLOCK-1 blob transactions. SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK - 1, blobTransactionMaxBlobGasCost: u256(100), ), # Then send the single-blob transactions SendBlobTransactions( transactionCount: MAX_BLOBS_PER_BLOCK + 1, blobsPerTransaction: 1, blobTransactionMaxBlobGasCost: u256(100), ), # First four payloads have MAX_BLOBS_PER_BLOCK-1 blobs each NewPayloads( payloadCount: 4, expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK - 1, ), # The rest of the payloads have full blobs NewPayloads( payloadCount: 2, expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, ), ] ), ), TestDesc( name: "Blob Transaction Ordering, Single Account 2", about: """ Send N blob transactions with MAX_BLOBS_PER_BLOCK-1 blobs each, using account A. Using same account, and an increased nonce from the previously sent transactions, send a single 2-blob transaction, and send N blob transactions with 1 blob each. Verify that the payloads are created with the correct ordering: - The first payloads must include the first N blob transactions - The last payloads must include the rest of the transactions All transactions have sufficient data gas price to be included any of the payloads. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ # First send the MAX_BLOBS_PER_BLOCK-1 blob transactions. SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK - 1, blobTransactionMaxBlobGasCost: u256(100), ), # Then send the dual-blob transaction SendBlobTransactions( transactionCount: 1, blobsPerTransaction: 2, blobTransactionMaxBlobGasCost: u256(100), ), # Then send the single-blob transactions SendBlobTransactions( transactionCount: MAX_BLOBS_PER_BLOCK - 2, blobsPerTransaction: 1, blobTransactionMaxBlobGasCost: u256(100), ), # First five payloads have MAX_BLOBS_PER_BLOCK-1 blobs each NewPayloads( payloadCount: 5, expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK - 1, ), # The rest of the payloads have full blobs NewPayloads( payloadCount: 1, expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, ), ] ), ), TestDesc( name: "Blob Transaction Ordering, Multiple Accounts", about: """ Send N blob transactions with MAX_BLOBS_PER_BLOCK-1 blobs each, using account A. Send N blob transactions with 1 blob each from account B. Verify that the payloads are created with the correct ordering: - All payloads must have full blobs. All transactions have sufficient data gas price to be included any of the payloads. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ # First send the MAX_BLOBS_PER_BLOCK-1 blob transactions from # account A. SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK - 1, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 0, ), # Then send the single-blob transactions from account B SendBlobTransactions( transactionCount: 5, blobsPerTransaction: 1, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 1, ), # All payloads have full blobs NewPayloads( payloadCount: 5, expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, ), ] ), ), TestDesc( name: "Blob Transaction Ordering, Multiple Clients", about: """ Send N blob transactions with MAX_BLOBS_PER_BLOCK-1 blobs each, using account A, to client A. Send N blob transactions with 1 blob each from account B, to client B. Verify that the payloads are created with the correct ordering: - All payloads must have full blobs. All transactions have sufficient data gas price to be included any of the payloads. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ # Start a secondary client to also receive blob transactions LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), # Skip adding the second client to the CL Mock to guarantee # that all payloads are produced by client A. # This is done to not have client B prioritizing single-blob # transactions to fill one single payload. skipAddingToCLMock: true, ), # Create a block without any blobs to get past genesis NewPayloads( payloadCount: 1, expectedIncludedBlobCount: 0, ), # First send the MAX_BLOBS_PER_BLOCK-1 blob transactions from # account A, to client A. SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK - 1, blobTransactionMaxBlobGasCost: u256(120), accountIndex: 0, clientIndex: 0, ), # Then send the single-blob transactions from account B, to client # B. SendBlobTransactions( transactionCount: 5, blobsPerTransaction: 1, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 1, clientIndex: 1, ), # All payloads have full blobs NewPayloads( payloadCount: 5, expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, # Wait a bit more on before requesting the built payload from the client getPayloadDelay: 2, ), ] ), ), TestDesc( name: "Replace Blob Transactions", about: """ Test sending multiple blob transactions with the same nonce, but higher gas tip so the transaction is replaced. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ # Send multiple blob transactions with the same nonce. SendBlobTransactions( # Blob ID 0 transactionCount: 1, blobTransactionMaxBlobGasCost: u256(1), blobTransactionGasFeeCap: GasInt(10 ^ 9), blobTransactionGasTipCap: GasInt(10 ^ 9), ), SendBlobTransactions( # Blob ID 1 transactionCount: 1, blobTransactionMaxBlobGasCost: u256(10 ^ 2), blobTransactionGasFeeCap: GasInt(10 ^ 10), blobTransactionGasTipCap: GasInt(10 ^ 10), replaceTransactions: true, ), SendBlobTransactions( # Blob ID 2 transactionCount: 1, blobTransactionMaxBlobGasCost: u256(10 ^ 3), blobTransactionGasFeeCap: GasInt(10 ^ 11), blobTransactionGasTipCap: GasInt(10 ^ 11), replaceTransactions: true, ), SendBlobTransactions( # Blob ID 3 transactionCount: 1, blobTransactionMaxBlobGasCost: u256(10 ^ 4), blobTransactionGasFeeCap: GasInt(10 ^ 12), blobTransactionGasTipCap: GasInt(10 ^ 12), replaceTransactions: true, ), # We create the first payload, which must contain the blob tx # with the higher tip. NewPayloads( expectedIncludedBlobCount: 1, expectedblobs: @[BlobID(3)], ), ] ), ), # ForkchoiceUpdatedV3 before cancun TestDesc( name: "ForkchoiceUpdatedV3 Set Head to Shanghai Payload, Nil Payload Attributes", about: """ Test sending ForkchoiceUpdatedV3 to set the head of the chain to a Shanghai payload: - Send NewPayloadV2 with Shanghai payload on block 1 - Use ForkchoiceUpdatedV3 to set the head to the payload, with nil payload attributes Verify that client returns no error. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( fcUOnHeadSet: UpgradeForkchoiceUpdatedVersion(), expectationDescription: """ ForkchoiceUpdatedV3 before Cancun returns no error without payload attributes """, ).TestStep, ] ), ), TestDesc( name: "ForkchoiceUpdatedV3 To Request Shanghai Payload, Nil Beacon Root", about: """ Test sending ForkchoiceUpdatedV3 to request a Shanghai payload: - Payload Attributes uses Shanghai timestamp - Payload Attributes' Beacon Root is nil Verify that client returns INVALID_PAYLOAD_ATTRIBUTES. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( fcUOnPayloadRequest: UpgradeForkchoiceUpdatedVersion( expectedError: engineApiInvalidPayloadAttributes, ), expectationDescription: """ ForkchoiceUpdatedV3 before Cancun with any nil field must return INVALID_PAYLOAD_ATTRIBUTES (code $1) """ % [$engineApiInvalidPayloadAttributes], ).TestStep, ] ), ), TestDesc( name: "ForkchoiceUpdatedV3 To Request Shanghai Payload, Zero Beacon Root", about: """ Test sending ForkchoiceUpdatedV3 to request a Shanghai payload: - Payload Attributes uses Shanghai timestamp - Payload Attributes' Beacon Root zero Verify that client returns UNSUPPORTED_FORK_ERROR. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( fcUOnPayloadRequest: UpgradeForkchoiceUpdatedVersion( beaconRoot: Opt.some(default(Hash32)), expectedError: engineApiUnsupportedFork, ), expectationDescription: """ ForkchoiceUpdatedV3 before Cancun with beacon root must return UNSUPPORTED_FORK_ERROR (code $1) """ % [$engineApiUnsupportedFork], ).TestStep, ] ), ), # ForkchoiceUpdatedV2 before cancun with beacon root TestDesc( name: "ForkchoiceUpdatedV2 To Request Shanghai Payload, Zero Beacon Root", about: """ Test sending ForkchoiceUpdatedV2 to request a Cancun payload: - Payload Attributes uses Shanghai timestamp - Payload Attributes' Beacon Root zero Verify that client returns INVALID_PAYLOAD_ATTRIBUTES. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( fcUOnPayloadRequest: BaseForkchoiceUpdatedCustomizer( beaconRoot: Opt.some(default(Hash32)), expectedError: engineApiInvalidPayloadAttributes, ), expectationDescription: """ ForkchoiceUpdatedV2 before Cancun with beacon root field must return INVALID_PAYLOAD_ATTRIBUTES (code $1) """ % [$engineApiInvalidPayloadAttributes], ).TestStep, ] ), ), # ForkchoiceUpdatedV2 after cancun TestDesc( name: "ForkchoiceUpdatedV2 To Request Cancun Payload, Zero Beacon Root", about: """ Test sending ForkchoiceUpdatedV2 to request a Cancun payload: - Payload Attributes uses Cancun timestamp - Payload Attributes' Beacon Root zero Verify that client returns INVALID_PAYLOAD_ATTRIBUTES. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 1, testSequence: @[ NewPayloads( fcUOnPayloadRequest: DowngradeForkchoiceUpdatedVersion( beaconRoot: Opt.some(default(Hash32)), expectedError: engineApiInvalidPayloadAttributes, ), expectationDescription: """ ForkchoiceUpdatedV2 after Cancun with beacon root field must return INVALID_PAYLOAD_ATTRIBUTES (code $1) """ % [$engineApiInvalidPayloadAttributes], ).TestStep, ] ), ), TestDesc( name: "ForkchoiceUpdatedV2 To Request Cancun Payload, Nil Beacon Root", about: """ Test sending ForkchoiceUpdatedV2 to request a Cancun payload: - Payload Attributes uses Cancun timestamp - Payload Attributes' Beacon Root nil (not provided) Verify that client returns UNSUPPORTED_FORK_ERROR. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 1, testSequence: @[ NewPayloads( fcUOnPayloadRequest: DowngradeForkchoiceUpdatedVersion( removeBeaconRoot: true, expectedError: engineApiUnsupportedFork, ), expectationDescription: """ ForkchoiceUpdatedV2 after Cancun must return UNSUPPORTED_FORK_ERROR (code $1) """ % [$engineApiUnsupportedFork], ).TestStep, ] ), ), # ForkchoiceUpdatedV3 with modified BeaconRoot Attribute TestDesc( name: "ForkchoiceUpdatedV3 Modifies Payload ID on Different Beacon Root", about: """ Test requesting a Cancun Payload using ForkchoiceUpdatedV3 twice with the beacon root payload attribute as the only change between requests and verify that the payload ID is different. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ SendBlobTransactions( transactionCount: 1, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), ), NewPayloads( expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, fcUOnPayloadRequest: BaseForkchoiceUpdatedCustomizer( beaconRoot: Opt.some(default(Hash32)), ), ), SendBlobTransactions( transactionCount: 1, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), ), NewPayloads( expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, fcUOnPayloadRequest: BaseForkchoiceUpdatedCustomizer( beaconRoot: Opt.some(toHash(1.u256)), ), ), ] ), ), # GetPayloadV3 Before Cancun, Negative Tests TestDesc( name: "GetPayloadV3 To Request Shanghai Payload", about: """ Test requesting a Shanghai PayloadID using GetPayloadV3. Verify that client returns UNSUPPORTED_FORK_ERROR. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( getPayloadCustomizer: UpgradeGetPayloadVersion( expectedError: engineApiUnsupportedFork, ), expectationDescription: """ GetPayloadV3 To Request Shanghai Payload must return UNSUPPORTED_FORK_ERROR (code $1) """ % [$engineApiUnsupportedFork], ).TestStep, ] ), ), # GetPayloadV2 After Cancun, Negative Tests TestDesc( name: "GetPayloadV2 To Request Cancun Payload", about: """ Test requesting a Cancun PayloadID using GetPayloadV2. Verify that client returns UNSUPPORTED_FORK_ERROR. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 1, testSequence: @[ NewPayloads( getPayloadCustomizer: DowngradeGetPayloadVersion( expectedError: engineApiUnsupportedFork, ), expectationDescription: """ GetPayloadV2 To Request Cancun Payload must return UNSUPPORTED_FORK_ERROR (code $1) """ % [$engineApiUnsupportedFork], ).TestStep, ] ), ), # NewPayloadV3 Before Cancun, Negative Tests TestDesc( name: "NewPayloadV3 Before Cancun, Nil Data Fields, Nil Versioned Hashes, Nil Beacon Root", about: """ Test sending NewPayloadV3 Before Cancun with: - nil ExcessBlobGas - nil BlobGasUsed - nil Versioned Hashes Array - nil Beacon Root Verify that client returns INVALID_PARAMS_ERROR """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( newPayloadCustomizer: UpgradeNewPayloadVersion( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer() ), expectedError: engineApiInvalidParams, ), expectationDescription: """ NewPayloadV3 before Cancun with any nil field must return INVALID_PARAMS_ERROR (code $1) """ % [$engineApiInvalidParams], ).TestStep, ] ), ), TestDesc( name: "NewPayloadV3 Before Cancun, Nil ExcessBlobGas, 0x00 BlobGasUsed, Nil Versioned Hashes, Nil Beacon Root", about: """ Test sending NewPayloadV3 Before Cancun with: - nil ExcessBlobGas - 0x00 BlobGasUsed - nil Versioned Hashes Array - nil Beacon Root """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( newPayloadCustomizer: UpgradeNewPayloadVersion( payloadCustomizer: CustomPayloadData( blobGasUsed: Opt.some(0'u64), ), expectedError: engineApiInvalidParams, ), expectationDescription: """ NewPayloadV3 before Cancun with any nil field must return INVALID_PARAMS_ERROR (code $1) """ % [$engineApiInvalidParams], ).TestStep, ] ), ), TestDesc( name: "NewPayloadV3 Before Cancun, Nil Data Fields, Empty Array Versioned Hashes, Nil Beacon Root", about: """ Test sending NewPayloadV3 Before Cancun with: - nil ExcessBlobGas - nil BlobGasUsed - Empty Versioned Hashes Array - nil Beacon Root """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( newPayloadCustomizer: UpgradeNewPayloadVersion( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(newSeq[BlobID]()), ), ), expectedError: engineApiInvalidParams, ), expectationDescription: """ NewPayloadV3 before Cancun with any nil field must return INVALID_PARAMS_ERROR (code $1) """ % [$engineApiInvalidParams], ).TestStep, ] ), ), TestDesc( name: "NewPayloadV3 Before Cancun, 0x00 Data Fields, Empty Array Versioned Hashes, Zero Beacon Root", about: """ Test sending NewPayloadV3 Before Cancun with: - 0x00 ExcessBlobGas - 0x00 BlobGasUsed - Empty Versioned Hashes Array - Zero Beacon Root """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( newPayloadCustomizer: UpgradeNewPayloadVersion( payloadCustomizer: CustomPayloadData( excessBlobGas: Opt.some(0'u64), blobGasUsed: Opt.some(0'u64), parentBeaconRoot: Opt.some(default(Hash32)), versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(newSeq[BlobID]()), ), ), expectedError: engineApiUnsupportedFork, ), expectationDescription: """ NewPayloadV3 before Cancun with no nil fields must return UNSUPPORTED_FORK_ERROR (code $1) """ % [$engineApiUnsupportedFork], ).TestStep, ] ), ), TestDesc( name: "NewPayloadV3 After Cancun, 0x00 Blob Fields, Empty Array Versioned Hashes, Nil Beacon Root", about: """ Test sending NewPayloadV3 After Cancun with: - 0x00 ExcessBlobGas - nil BlobGasUsed - Empty Versioned Hashes Array """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 1, testSequence: @[ NewPayloads( newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( removeParentBeaconRoot: true, ), expectedError: engineApiInvalidParams, ), expectationDescription: """ NewPayloadV3 after Cancun with nil parentBeaconBlockRoot must return INVALID_PARAMS_ERROR (code $1) """ % [$engineApiInvalidParams], ).TestStep, ] ), ), # Fork time tests TestDesc( name: "ForkchoiceUpdatedV2 then ForkchoiceUpdatedV3 Valid Payload Building Requests", about: """ Test requesting a Shanghai ForkchoiceUpdatedV2 payload followed by a Cancun ForkchoiceUpdatedV3 request. Verify that client correctly returns the Cancun payload. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, # We request two blocks from the client, first on shanghai and then on cancun, both with # the same parent. # Client must respond correctly to later request. forkHeight: 1, blockTimestampIncrement: 2, testSequence: @[ # First, we send a couple of blob transactions on genesis, # with enough data gas cost to make sure they are included in the first block. SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, # This customizer only simulates requesting a Shanghai payload 1 second before cancun. # CL Mock will still request the Cancun payload afterwards fcUOnPayloadRequest: TimestampDeltaPayloadAttributesCustomizer( removeBeaconRoot: true, timestampDelta: -1, ), expectationDescription: """ ForkchoiceUpdatedV3 must construct transaction with blob payloads even if a ForkchoiceUpdatedV2 was previously requested """, ), ] ), ), # Test versioned hashes in Engine API NewPayloadV3 TestDesc( name: "NewPayloadV3 Versioned Hashes, Missing Hash", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is missing one of the hashes. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK-1)), ), ), expectInvalidStatus: true, ), expectationDescription: """ NewPayloadV3 with incorrect list of versioned hashes must return INVALID status """, ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Extra Hash", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is has an extra hash for a blob that is not in the payload. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, # TODO: It could be worth it to also test this with a blob that is in the # mempool but was not included in the payload. testSequence: @[ SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK+1)), ), ), expectInvalidStatus: true, ), expectationDescription: """ NewPayloadV3 with incorrect list of versioned hashes must return INVALID status """, ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Out of Order", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is out of order. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobListByIndex(BlobID(TARGET_BLOBS_PER_BLOCK-1), 0)), ), ), expectInvalidStatus: true, ), expectationDescription: """ NewPayloadV3 with incorrect list of versioned hashes must return INVALID status """, ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Repeated Hash", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array has a blob that is repeated in the array. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK, BlobID(TARGET_BLOBS_PER_BLOCK-1))), ), ), expectInvalidStatus: true, ), expectationDescription: """ NewPayloadV3 with incorrect list of versioned hashes must return INVALID status """, ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Incorrect Hash", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array has a blob hash that does not belong to any blob contained in the payload. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK-1, BlobID(TARGET_BLOBS_PER_BLOCK))), ), ), expectInvalidStatus: true, ), expectationDescription: """ NewPayloadV3 with incorrect hash in list of versioned hashes must return INVALID status """, ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Incorrect Version", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array has a single blob that has an incorrect version. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK)), hashVersions: @[VERSIONED_HASH_VERSION_KZG.byte, (VERSIONED_HASH_VERSION_KZG + 1).byte], ), ), expectInvalidStatus: true, ), expectationDescription: """ NewPayloadV3 with incorrect version in list of versioned hashes must return INVALID status """, ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Nil Hashes", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is nil, even though the fork has already happened. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.none(seq[BlobID]), ), ), expectedError: engineApiInvalidParams, ), expectationDescription: """ NewPayloadV3 after Cancun with nil VersionedHashes must return INVALID_PARAMS_ERROR (code -32602) """, ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Empty Hashes", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is empty, even though there are blobs in the payload. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(newSeq[BlobID]()), ), ), expectInvalidStatus: true, ), expectationDescription: """ NewPayloadV3 with incorrect list of versioned hashes must return INVALID status """, ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Non-Empty Hashes", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is contains hashes, even though there are no blobs in the payload. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads( expectedblobs: @[], newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(@[BlobID(0)]), ), ), expectInvalidStatus: true, ), expectationDescription: """ NewPayloadV3 with incorrect list of versioned hashes must return INVALID status """, ).TestStep, ] ), ), # Test versioned hashes in Engine API NewPayloadV3 on syncing clients TestDesc( name: "NewPayloadV3 Versioned Hashes, Missing Hash (Syncing)", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is missing one of the hashes. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads(), # Send new payload so the parent is unknown to the secondary client SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), skipAddingToCLMock: true, skipConnectingToBootnode: true, # So the client is in a perpetual syncing state ), SendModifiedLatestPayload( clientID: 1, newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK-1)), ), ), expectInvalidStatus: true, ), ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Extra Hash (Syncing)", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is has an extra hash for a blob that is not in the payload. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, # TODO: It could be worth it to also test this with a blob that is in the # mempool but was not included in the payload. testSequence: @[ NewPayloads(), # Send new payload so the parent is unknown to the secondary client SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), skipAddingToCLMock: true, skipConnectingToBootnode: true, # So the client is in a perpetual syncing state ), SendModifiedLatestPayload( clientID: 1, newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK+1)), ), ), expectInvalidStatus: true, ), ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Out of Order (Syncing)", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is out of order. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads(), # Send new payload so the parent is unknown to the secondary client SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), skipAddingToCLMock: true, skipConnectingToBootnode: true, # So the client is in a perpetual syncing state ), SendModifiedLatestPayload( clientID: 1, newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobListByIndex(BlobID(TARGET_BLOBS_PER_BLOCK-1), 0)), ), ), expectInvalidStatus: true, ), ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Repeated Hash (Syncing)", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array has a blob that is repeated in the array. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads(), # Send new payload so the parent is unknown to the secondary client SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), skipAddingToCLMock: true, skipConnectingToBootnode: true, # So the client is in a perpetual syncing state ), SendModifiedLatestPayload( clientID: 1, newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK, BlobID(TARGET_BLOBS_PER_BLOCK-1))), ), ), expectInvalidStatus: true, ), ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Incorrect Hash (Syncing)", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array has a blob that is repeated in the array. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads(), # Send new payload so the parent is unknown to the secondary client SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), skipAddingToCLMock: true, skipConnectingToBootnode: true, # So the client is in a perpetual syncing state ), SendModifiedLatestPayload( clientID: 1, newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK-1, BlobID(TARGET_BLOBS_PER_BLOCK))), ), ), expectInvalidStatus: true, ), ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Incorrect Version (Syncing)", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array has a single blob that has an incorrect version. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads(), # Send new payload so the parent is unknown to the secondary client SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), skipAddingToCLMock: true, skipConnectingToBootnode: true, # So the client is in a perpetual syncing state ), SendModifiedLatestPayload( clientID: 1, newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(getBlobList(0, TARGET_BLOBS_PER_BLOCK)), hashVersions: @[VERSIONED_HASH_VERSION_KZG.byte, (VERSIONED_HASH_VERSION_KZG + 1).byte], ), ), expectInvalidStatus: true, ), ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Nil Hashes (Syncing)", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is nil, even though the fork has already happened. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads(), # Send new payload so the parent is unknown to the secondary client SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), skipAddingToCLMock: true, skipConnectingToBootnode: true, # So the client is in a perpetual syncing state ), SendModifiedLatestPayload( clientID: 1, newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.none(seq[BlobID]), ), ), expectedError: engineApiInvalidParams, ), ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Empty Hashes (Syncing)", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is empty, even though there are blobs in the payload. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads(), # Send new payload so the parent is unknown to the secondary client SendBlobTransactions( transactionCount: TARGET_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(1), ), NewPayloads( expectedIncludedBlobCount: TARGET_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, TARGET_BLOBS_PER_BLOCK), ), LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), skipAddingToCLMock: true, skipConnectingToBootnode: true, # So the client is in a perpetual syncing state ), SendModifiedLatestPayload( clientID: 1, newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(newSeq[BlobID]()), ), ), expectInvalidStatus: true, ), ), ] ), ), TestDesc( name: "NewPayloadV3 Versioned Hashes, Non-Empty Hashes (Syncing)", about: """ Tests VersionedHashes in Engine API NewPayloadV3 where the array is contains hashes, even though there are no blobs in the payload. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads(), # Send new payload so the parent is unknown to the secondary client NewPayloads( expectedblobs: @[], ), LaunchClients( #engineStarter: hive_rpc.HiveRPCEngineStarter{), skipAddingToCLMock: true, skipConnectingToBootnode: true, # So the client is in a perpetual syncing state ), SendModifiedLatestPayload( clientID: 1, newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( versionedHashesCustomizer: VersionedHashesCustomizer( blobs: Opt.some(@[BlobID(0)]), ), ), expectInvalidStatus: true, ), ), ] ), ), # BlobGasUsed, ExcessBlobGas Negative Tests # Most cases are contained in https:#github.com/ethereum/execution-spec-tests/tree/main/tests/cancun/eip4844_blobs # and can be executed using """pyspec""" simulator. TestDesc( name: "Incorrect blobGasUsed: Non-Zero on Zero Blobs", about: """ Send a payload with zero blobs, but non-zero BlobGasUsed. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads( newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( blobGasUsed: Opt.some(1'u64), ), expectInvalidStatus: true, ), ).TestStep, ] ), ), TestDesc( name: "Incorrect blobGasUsed: GAS_PER_BLOB on Zero Blobs", about: """ Send a payload with zero blobs, but non-zero BlobGasUsed. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ NewPayloads( newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( blobGasUsed: Opt.some(GAS_PER_BLOB.uint64), ), expectInvalidStatus: true, ), ).TestStep, ] ), ), # DevP2P tests TestDesc( name: "Request Blob Pooled Transactions", about: """ Requests blob pooled transactions and verify correct encoding. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ # Get past the genesis NewPayloads( payloadCount: 1, ), # Send multiple transactions with multiple blobs each SendBlobTransactions( transactionCount: 1, blobTransactionMaxBlobGasCost: u256(1), ), DevP2PRequestPooledTransactionHash( clientIndex: 0, transactionIndexes: @[0], waitForNewPooledTransaction: true, ), ] ), ), # Need special rlp encoder #[TestDescXXX( name: "NewPayloadV3 Before Cancun, 0x00 ExcessBlobGas, Nil BlobGasUsed, Nil Versioned Hashes, Nil Beacon Root", about: """ Test sending NewPayloadV3 Before Cancun with: - 0x00 ExcessBlobGas - nil BlobGasUsed - nil Versioned Hashes Array - nil Beacon Root """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( newPayloadCustomizer: UpgradeNewPayloadVersion( payloadCustomizer: CustomPayloadData( excessBlobGas: some(0'u64), ), expectedError: engineApiInvalidParams, ), expectationDescription: """ NewPayloadV3 before Cancun with any nil field must return INVALID_PARAMS_ERROR (code $1) """ % [$engineApiInvalidParams], ).TestStep, ] ), ), TestDescXXX( name: "NewPayloadV3 Before Cancun, Nil Data Fields, Nil Versioned Hashes, Zero Beacon Root", about: """ Test sending NewPayloadV3 Before Cancun with: - nil ExcessBlobGas - nil BlobGasUsed - nil Versioned Hashes Array - Zero Beacon Root """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 2, testSequence: @[ NewPayloads( newPayloadCustomizer: UpgradeNewPayloadVersion( payloadCustomizer: CustomPayloadData( parentBeaconRoot: some(default(Hash32)), ), expectedError: engineApiInvalidParams, ), expectationDescription: """ NewPayloadV3 before Cancun with any nil field must return INVALID_PARAMS_ERROR (code $1) """ % [$engineApiInvalidParams], ).TestStep, ] ), ), # NewPayloadV3 After Cancun, Negative Tests TestDescXXX( name: "NewPayloadV3 After Cancun, Nil ExcessBlobGas, 0x00 BlobGasUsed, Empty Array Versioned Hashes, Zero Beacon Root", about: """ Test sending NewPayloadV3 After Cancun with: - nil ExcessBlobGas - 0x00 BlobGasUsed - Empty Versioned Hashes Array - Zero Beacon Root """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 1, testSequence: @[ NewPayloads( newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( removeExcessBlobGas: true, ), expectedError: engineApiInvalidParams, ), expectationDescription: """ NewPayloadV3 after Cancun with nil ExcessBlobGas must return INVALID_PARAMS_ERROR (code $1) """ % [$engineApiInvalidParams], ).TestStep, ] ), ), TestDescXXX( name: "NewPayloadV3 After Cancun, 0x00 ExcessBlobGas, Nil BlobGasUsed, Empty Array Versioned Hashes", about: """ Test sending NewPayloadV3 After Cancun with: - 0x00 ExcessBlobGas - nil BlobGasUsed - Empty Versioned Hashes Array """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, forkHeight: 1, testSequence: @[ NewPayloads( newPayloadCustomizer: BaseNewPayloadVersionCustomizer( payloadCustomizer: CustomPayloadData( removeblobGasUsed: true, ), expectedError: engineApiInvalidParams, ), expectationDescription: """ NewPayloadV3 after Cancun with nil BlobGasUsed must return INVALID_PARAMS_ERROR (code $1) """ % [$engineApiInvalidParams], ).TestStep, ] ), ), TestDesc( name: "Parallel Blob Transactions", about: """ Test sending multiple blob transactions in parallel from different accounts. Verify that a payload is created with the maximum number of blobs. """, run: specExecute, spec: CancunSpec( mainFork: ForkCancun, testSequence: @[ # Send multiple blob transactions with the same nonce. ParallelSteps{ Steps: []TestStep{ SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 0, ), SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 1, ), SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 2, ), SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 3, ), SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 4, ), SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 5, ), SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 6, ), SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 7, ), SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 8, ), SendBlobTransactions( transactionCount: 5, blobsPerTransaction: MAX_BLOBS_PER_BLOCK, blobTransactionMaxBlobGasCost: u256(100), accountIndex: 9, ), ), ), # We create the first payload, which is guaranteed to have the first MAX_BLOBS_PER_BLOCK blobs. NewPayloads( expectedIncludedBlobCount: MAX_BLOBS_PER_BLOCK, expectedblobs: getBlobList(0, MAX_BLOBS_PER_BLOCK), ), ] ), ),]# ] proc makeCancunTest(): seq[EngineSpec] = # Append all engine api tests with Cancun as main fork #[for x in engineTestList: let t = EngineSpec(x.spec) result.add t.withMainFork(ForkCancun).EngineSpec]# # Payload Attributes for syncing in [false, true]: result.add InvalidPayloadAttributesTest( description: "Missing BeaconRoot", mainFork : ForkCancun, syncing : syncing, customizer : BasePayloadAttributesCustomizer( removeBeaconRoot: true, ) ) const payloadIdTests = [ PayloadAttributesParentBeaconRoot, # TODO: Remove when withdrawals suite is refactored PayloadAttributesAddWithdrawal, PayloadAttributesModifyWithdrawalAmount, PayloadAttributesModifyWithdrawalIndex, PayloadAttributesModifyWithdrawalValidator, PayloadAttributesModifyWithdrawalAddress, PayloadAttributesRemoveWithdrawal, ] # Unique Payload ID Tests for t in payloadIdTests: result.add UniquePayloadIDTest( mainFork: ForkCancun, fieldModification: t, ) # Invalid Payload Tests const invalidFields = [ InvalidParentBeaconBlockRoot, InvalidBlobGasUsed, InvalidBlobCountGasUsed, InvalidExcessBlobGas, InvalidVersionedHashes, InvalidVersionedHashesVersion, IncompleteVersionedHashes, ExtraVersionedHashes, ] invalidDetectedOnSyncs = [ InvalidBlobGasUsed, InvalidBlobCountGasUsed, InvalidVersionedHashes, InvalidVersionedHashesVersion, IncompleteVersionedHashes, ExtraVersionedHashes ] nilLatestValidHashes = [ InvalidVersionedHashes, InvalidVersionedHashesVersion, IncompleteVersionedHashes, ExtraVersionedHashes ] for invalidField in invalidFields: for syncing in [false, true]: # Invalidity of payload can be detected even when syncing because the # blob gas only depends on the transactions contained. let invalidDetectedOnSync = invalidField in invalidDetectedOnSyncs nilLatestValidHash = invalidField in nilLatestValidHashes result.add InvalidPayloadTestCase( mainFork : ForkCancun, txType : Opt.some(TxEip4844), invalidField : invalidField, syncing : syncing, invalidDetectedOnSync: invalidDetectedOnSync, nilLatestValidHash : nilLatestValidHash, ) # Invalid Transaction ChainID Tests result.add InvalidTxChainIDTest( mainFork: ForkCancun, txType : Opt.some(TxEip4844), ) result.add PayloadBuildAfterInvalidPayloadTest( mainFork: ForkCancun, txType : Opt.some(TxEip4844), invalidField: InvalidParentBeaconBlockRoot, ) # Suggested Fee Recipient Tests (New Transaction Type) result.add SuggestedFeeRecipientTest( mainFork: ForkCancun, txType : Opt.some(TxEip4844), transactionCount: 1, # Only one blob tx gets through due to blob gas limit ) # Prev Randao Tests (New Transaction Type) result.add PrevRandaoTransactionTest( mainFork: ForkCancun, txType : Opt.some(TxEip4844), ) proc getGenesisProc(cs: BaseSpec, param: NetworkParams) = getGenesis(param) proc filCancunTests(): seq[TestDesc] = result.add cancunTestListA let list = makeCancunTest() for x in list: var z = x z.getGenesisFn = getGenesisProc result.add TestDesc( name: x.getName(), run: executeEngineSpec, spec: z, ) let cancunTestList* = filCancunTests()