# 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/strutils, chronicles, ./step_desc, ./helpers, ./customizer, ./blobs, ../engine_client, ../test_env, ../types, ../../../../nimbus/core/eip4844, ../../../../nimbus/common/common type NewPayloads* = ref object of TestStep # Payload Count payloadCount*: int # Number of blob transactions that are expected to be included in the payload expectedIncludedBlobCount*: int # Blob IDs expected to be found in the payload expectedBlobs*: seq[BlobID] # Delay between FcU and GetPayload calls getPayloadDelay*: int # GetPayload modifier when requesting the new Payload getPayloadCustomizer*: GetPayloadCustomizer # ForkchoiceUpdate modifier when requesting the new Payload fcUOnPayloadRequest*: ForkchoiceUpdatedCustomizer # Extra modifications on NewPayload to potentially generate an invalid payload newPayloadCustomizer*: NewPayloadCustomizer # ForkchoiceUpdate modifier when setting the new payload as head fcUOnHeadSet*: ForkchoiceUpdatedCustomizer # Expected responses on the NewPayload call expectationDescription*: string func getPayloadCount(step: NewPayloads): int = var payloadCount = step.payloadCount if payloadCount == 0: payloadCount = 1 return payloadCount proc verifyPayload(step: NewPayloads, com: CommonRef, client: RpcClient, blobTxsInPayload: openArray[Transaction], shouldOverrideBuilder: Opt[bool], payload: ExecutionPayload, previousPayload = Opt.none(ExecutionPayload)): bool = var parentExcessBlobGas = 0'u64 parentBlobGasUsed = 0'u64 if previousPayload.isSome: let prevPayload = previousPayload.get if prevPayload.excessBlobGas.isSome: parentExcessBlobGas = prevPayload.excessBlobGas.get.uint64 if prevPayload.blobGasUsed.isSome: parentBlobGasUsed = prevPayload.blobGasUsed.get.uint64 let parent = Header( excessBlobGas: Opt.some(parentExcessBlobGas), blobGasUsed: Opt.some(parentBlobGasUsed) ) expectedExcessBlobGas = calcExcessBlobGas(parent) if com.isCancunOrLater(payload.timestamp.EthTime): if payload.excessBlobGas.isNone: error "payload contains nil excessDataGas" return false if payload.blobGasUsed.isNone: error "payload contains nil dataGasUsed" return false if payload.excessBlobGas.get.uint64 != expectedExcessBlobGas: error "payload contains incorrect excessDataGas", want=expectedExcessBlobGas, have=payload.excessBlobGas.get.uint64 return false if shouldOverrideBuilder.isNone: error "shouldOverrideBuilder was not included in the getPayload response" return false var totalBlobCount = 0 expectedBlobGasPrice = getBlobBaseFee(expectedExcessBlobGas) for tx in blobTxsInPayload: let blobCount = tx.versionedHashes.len totalBlobCount += blobCount # Retrieve receipt from client let r = client.txReceipt(tx.rlpHash) let expectedBlobGasUsed = blobCount.uint64 * GAS_PER_BLOB r.expectBlobGasUsed(expectedBlobGasUsed) r.expectBlobGasPrice(expectedBlobGasPrice) if totalBlobCount != step.expectedIncludedBlobCount: error "expected blobs in transactions", expect=step.expectedIncludedBlobCount, got=totalBlobCount return false if not verifyBeaconRootStorage(client, payload): return false else: if payload.excessBlobGas.isSome: error "payload contains non-nil excessDataGas pre-fork" return false if payload.blobGasUsed.isSome: error "payload contains non-nil dataGasUsed pre-fork" return false return true proc verifyBlobBundle(step: NewPayloads, blobDataInPayload: openArray[BlobWrapData], payload: ExecutionPayload, blobBundle: BlobsBundleV1): bool = if blobBundle.blobs.len != blobBundle.commitments.len or blobBundle.blobs.len != blobBundle.proofs.len: error "unexpected length in blob bundle", blobs=len(blobBundle.blobs), proofs=len(blobBundle.proofs), kzgs=len(blobBundle.commitments) return false if len(blobBundle.blobs) != step.expectedIncludedBlobCount: error "expected blobs", expect=step.expectedIncludedBlobCount, get=len(blobBundle.blobs) return false # Verify that the calculated amount of blobs in the payload matches the # amount of blobs in the bundle if len(blobDataInPayload) != len(blobBundle.blobs): error "expected blobs in the bundle", expect=len(blobDataInPayload), get=len(blobBundle.blobs) return false for i, blobData in blobDataInPayload: let bundleCommitment = blobBundle.commitments[i].data let bundleBlob = blobBundle.blobs[i].data let bundleProof = blobBundle.proofs[i].data if bundleCommitment != blobData.commitment.bytes: error "KZG mismatch at index of the bundle", index=i return false if bundleBlob != blobData.blob.bytes: error "blob mismatch at index of the bundle", index=i return false if bundleProof != blobData.proof.bytes: error "proof mismatch at index of the bundle", index=i return false if len(step.expectedBlobs) != 0: # Verify that the blobs in the payload match the expected blobs for expectedBlob in step.expectedBlobs: var found = false for blobData in blobDataInPayload: if expectedBlob.verifyBlob(blobData.blob): found = true break if not found: error "could not find expected blob", expectedBlob return false return true type Shadow = ref object p: int payloadCount: int prevPayload: ExecutionPayload method execute*(step: NewPayloads, ctx: CancunTestContext): bool = # Create a new payload # Produce the payload let env = ctx.env var originalGetPayloadDelay = env.clMock.payloadProductionClientDelay if step.getPayloadDelay != 0: env.clMock.payloadProductionClientDelay = step.getPayloadDelay var shadow = Shadow( payloadCount: step.getPayloadCount(), prevPayload: env.clMock.latestPayloadBuilt ) for p in 0..