# 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/[options, strutils, typetraits], stew/byteutils, ./blobs, ../types, ../tx_sender, ../../../../nimbus/constants, ../../../../nimbus/utils/utils, ../../../../nimbus/common as nimbus_common, ../../../../nimbus/beacon/web3_eth_conv, ../../../../nimbus/beacon/payload_conv, web3/execution_types type EngineAPIVersionResolver* = ref object of RootRef com: CommonRef method setEngineAPIVersionResolver*(cust: EngineAPIVersionResolver, v: CommonRef) {.base, gcsafe.} = cust.com = v method forkchoiceUpdatedVersion*(cust: EngineAPIVersionResolver, headTimestamp: uint64, payloadAttributesTimestamp: Opt[uint64] = Opt.none(uint64)): Version {.base, gcsafe.} = let ts = if payloadAttributesTimestamp.isNone: headTimestamp.EthTime else: payloadAttributesTimestamp.get().EthTime if cust.com.isCancunOrLater(ts): Version.V3 elif cust.com.isShanghaiOrLater(ts): Version.V2 else: Version.V1 method newPayloadVersion*(cust: EngineAPIVersionResolver, timestamp: uint64): Version {.base, gcsafe.} = let ts = timestamp.EthTime if cust.com.isCancunOrLater(ts): Version.V3 elif cust.com.isShanghaiOrLater(ts): Version.V2 else: Version.V1 method getPayloadVersion*(cust: EngineAPIVersionResolver, timestamp: uint64): Version {.base, gcsafe.} = let ts = timestamp.EthTime if cust.com.isCancunOrLater(ts): Version.V3 elif cust.com.isShanghaiOrLater(ts): Version.V2 else: Version.V1 type GetPayloadCustomizer* = ref object of EngineAPIVersionResolver method getPayloadID*(cust: GetPayloadCustomizer, basePayloadID: PayloadID): PayloadID {.base, gcsafe.} = doAssert(false, "getPayloadID unimplemented") method getExpectedError*(cust: GetPayloadCustomizer): int {.base, gcsafe.} = doAssert(false, "getExpectedError unimplemented") type BaseGetPayloadCustomizer* = ref object of GetPayloadCustomizer customPayloadID*: Opt[PayloadID] expectedError* : int method getPayloadID(cust: BaseGetPayloadCustomizer, basePayloadID: PayloadID): PayloadID = if cust.customPayloadID.isSome: return cust.customPayloadID.get return basePayloadID method getExpectedError(cust: BaseGetPayloadCustomizer): int = cust.expectedError type UpgradeGetPayloadVersion* = ref object of BaseGetPayloadCustomizer method getPayloadVersion(cust: UpgradeGetPayloadVersion, timestamp: uint64): Version = let version = procCall getPayloadVersion(cust.GetPayloadCustomizer, timestamp) doAssert(version != Version.high, "cannot upgrade version " & $Version.high) version.succ type DowngradeGetPayloadVersion* = ref object of BaseGetPayloadCustomizer method getPayloadVersion(cust: DowngradeGetPayloadVersion, timestamp: uint64): Version = let version = procCall getPayloadVersion(cust.GetPayloadCustomizer, timestamp) doAssert(version != Version.V1, "cannot downgrade version 1") version.pred type PayloadAttributesCustomizer* = ref object of BaseGetPayloadCustomizer method getPayloadAttributes*(cust: PayloadAttributesCustomizer, basePayloadAttributes: PayloadAttributes): PayloadAttributes {.base, gcsafe.} = doAssert(false, "getPayloadAttributes unimplemented") type BasePayloadAttributesCustomizer* = ref object of PayloadAttributesCustomizer timestamp* : Opt[uint64] prevRandao* : Opt[common.Hash256] suggestedFeeRecipient* : Opt[common.EthAddress] withdrawals* : Opt[seq[Withdrawal]] removeWithdrawals* : bool beaconRoot* : Opt[common.Hash256] removeBeaconRoot* : bool method getPayloadAttributes(cust: BasePayloadAttributesCustomizer, basePayloadAttributes: PayloadAttributes): PayloadAttributes = var customPayloadAttributes = PayloadAttributes( timestamp: basePayloadAttributes.timestamp, prevRandao: basePayloadAttributes.prevRandao, suggestedFeeRecipient: basePayloadAttributes.suggestedFeeRecipient, withdrawals: basePayloadAttributes.withdrawals, parentBeaconBlockRoot: basePayloadAttributes.parentBeaconBlockRoot, ) if cust.timestamp.isSome: customPayloadAttributes.timestamp = w3Qty cust.timestamp.get if cust.prevRandao.isSome: customPayloadAttributes.prevRandao = w3Hash cust.prevRandao.get if cust.suggestedFeeRecipient.isSome: customPayloadAttributes.suggestedFeeRecipient = w3Addr cust.suggestedFeeRecipient.get if cust.removeWithdrawals: customPayloadAttributes.withdrawals = Opt.none(seq[WithdrawalV1]) elif cust.withdrawals.isSome: customPayloadAttributes.withdrawals = w3Withdrawals cust.withdrawals if cust.removeBeaconRoot: customPayloadAttributes.parentBeaconBlockRoot = Opt.none(Web3Hash) elif cust.beaconRoot.isSome: customPayloadAttributes.parentBeaconBlockRoot = w3Hash cust.beaconRoot return customPayloadAttributes type ForkchoiceUpdatedCustomizer* = ref object of BasePayloadAttributesCustomizer method getForkchoiceState*(cust: ForkchoiceUpdatedCustomizer, baseForkchoiceUpdate: ForkchoiceStateV1): ForkchoiceStateV1 {.base, gcsafe.} = doAssert(false, "getForkchoiceState unimplemented") method getExpectInvalidStatus*(cust: ForkchoiceUpdatedCustomizer): bool {.base, gcsafe.} = doAssert(false, "getExpectInvalidStatus unimplemented") # Customizer that makes no modifications to the forkchoice directive call. # Used as base to other customizers. type BaseForkchoiceUpdatedCustomizer* = ref object of ForkchoiceUpdatedCustomizer expectInvalidStatus*: bool method getPayloadAttributes(cust: BaseForkchoiceUpdatedCustomizer, basePayloadAttributes: PayloadAttributes): PayloadAttributes = var customPayloadAttributes = procCall getPayloadAttributes(cust.BasePayloadAttributesCustomizer, basePayloadAttributes) return customPayloadAttributes method getForkchoiceState(cust: BaseForkchoiceUpdatedCustomizer, baseForkchoiceUpdate: ForkchoiceStateV1): ForkchoiceStateV1 = return baseForkchoiceUpdate method getExpectInvalidStatus(cust: BaseForkchoiceUpdatedCustomizer): bool = return cust.expectInvalidStatus # Customizer that upgrades the version of the forkchoice directive call to the next version. type UpgradeForkchoiceUpdatedVersion* = ref object of BaseForkchoiceUpdatedCustomizer method forkchoiceUpdatedVersion(cust: UpgradeForkchoiceUpdatedVersion, headTimestamp: uint64, payloadAttributesTimestamp: Opt[uint64] = Opt.none(uint64)): Version = let version = procCall forkchoiceUpdatedVersion(EngineAPIVersionResolver(cust), headTimestamp, payloadAttributesTimestamp) doAssert(version != Version.high, "cannot upgrade version " & $Version.high) version.succ # Customizer that downgrades the version of the forkchoice directive call to the previous version. type DowngradeForkchoiceUpdatedVersion* = ref object of BaseForkchoiceUpdatedCustomizer method forkchoiceUpdatedVersion(cust: DowngradeForkchoiceUpdatedVersion, headTimestamp: uint64, payloadAttributesTimestamp: Opt[uint64] = Opt.none(uint64)): Version = let version = procCall forkchoiceUpdatedVersion(EngineAPIVersionResolver(cust), headTimestamp, payloadAttributesTimestamp) doAssert(version != Version.V1, "cannot downgrade version 1") version.pred type TimestampDeltaPayloadAttributesCustomizer* = ref object of BaseForkchoiceUpdatedCustomizer timestampDelta*: int method getPayloadAttributes(cust: TimestampDeltaPayloadAttributesCustomizer, basePayloadAttributes: PayloadAttributes): PayloadAttributes = var customPayloadAttributes = procCall getPayloadAttributes(cust.BasePayloadAttributesCustomizer, basePayloadAttributes) customPayloadAttributes.timestamp = w3Qty(customPayloadAttributes.timestamp, cust.timestampDelta) return customPayloadAttributes type VersionedHashesCustomizer* = ref object of RootRef blobs*: Opt[seq[BlobID]] hashVersions*: seq[byte] method getVersionedHashes*(cust: VersionedHashesCustomizer, baseVersionedHashes: openArray[common.Hash256]): Opt[seq[common.Hash256]] {.base, gcsafe.} = if cust.blobs.isNone: return Opt.none(seq[common.Hash256]) let blobs = cust.blobs.get var v = newSeq[common.Hash256](blobs.len) var version: byte for i, blobID in blobs: if cust.hashVersions.len > i: version = cust.hashVersions[i] v[i] = blobID.getVersionedHash(version) Opt.some(v) method description*(cust: VersionedHashesCustomizer): string {.base, gcsafe.} = result = "VersionedHashes: " if cust.blobs.isSome: for x in cust.blobs.get: result.add x.toHex if cust.hashVersions.len > 0: result.add " with versions " result.add cust.hashVersions.toHex type IncreaseVersionVersionedHashes* = ref object of VersionedHashesCustomizer method getVersionedHashes(cust: IncreaseVersionVersionedHashes, baseVersionedHashes: openArray[common.Hash256]): Opt[seq[common.Hash256]] = doAssert(baseVersionedHashes.len > 0, "no versioned hashes available for modification") var v = newSeq[common.Hash256](baseVersionedHashes.len) for i, h in baseVersionedHashes: v[i] = h v[i].data[0] = v[i].data[0] + 1 Opt.some(v) type CorruptVersionedHashes* = ref object of VersionedHashesCustomizer method getVersionedHashes(cust: CorruptVersionedHashes, baseVersionedHashes: openArray[common.Hash256]): Opt[seq[common.Hash256]] = doAssert(baseVersionedHashes.len > 0, "no versioned hashes available for modification") var v = newSeq[common.Hash256](baseVersionedHashes.len) for i, h in baseVersionedHashes: v[i] = h v[i].data[h.data.len-1] = v[i].data[h.data.len-1] + 1 Opt.some(v) type RemoveVersionedHash* = ref object of VersionedHashesCustomizer method getVersionedHashes(cust: RemoveVersionedHash, baseVersionedHashes: openArray[common.Hash256]): Opt[seq[common.Hash256]] = doAssert(baseVersionedHashes.len > 0, "no versioned hashes available for modification") var v = newSeq[common.Hash256](baseVersionedHashes.len - 1) for i, h in baseVersionedHashes: if i < baseVersionedHashes.len-1: v[i] = h v[i].data[h.data.len-1] = v[i].data[h.data.len-1] + 1 Opt.some(v) type ExtraVersionedHash* = ref object of VersionedHashesCustomizer method getVersionedHashes(cust: ExtraVersionedHash, baseVersionedHashes: openArray[common.Hash256]): Opt[seq[common.Hash256]] = var v = newSeq[common.Hash256](baseVersionedHashes.len + 1) for i, h in baseVersionedHashes: v[i] = h var extraHash = common.Hash256.randomBytes() extraHash.data[0] = VERSIONED_HASH_VERSION_KZG v[^1] = extraHash Opt.some(v) type PayloadCustomizer* = ref object of EngineAPIVersionResolver method customizePayload*(cust: PayloadCustomizer, data: ExecutableData): ExecutableData {.base, gcsafe.} = doAssert(false, "customizePayload unimplemented") method getTimestamp(cust: PayloadCustomizer, basePayload: ExecutionPayload): uint64 {.base, gcsafe.} = doAssert(false, "getTimestamp unimplemented") type NewPayloadCustomizer* = ref object of PayloadCustomizer expectedError* : int expectInvalidStatus*: bool method getExpectedError*(cust: NewPayloadCustomizer): int {.base, gcsafe.} = cust.expectedError method getExpectInvalidStatus*(cust: NewPayloadCustomizer): bool {.base, gcsafe.}= cust.expectInvalidStatus type CustomPayloadData* = object parentHash* : Opt[common.Hash256] feeRecipient* : Opt[common.EthAddress] stateRoot* : Opt[common.Hash256] receiptsRoot* : Opt[common.Hash256] logsBloom* : Opt[BloomFilter] prevRandao* : Opt[common.Hash256] number* : Opt[uint64] gasLimit* : Opt[GasInt] gasUsed* : Opt[GasInt] timestamp* : Opt[uint64] extraData* : Opt[common.Blob] baseFeePerGas* : Opt[UInt256] blockHash* : Opt[common.Hash256] transactions* : Opt[seq[Transaction]] withdrawals* : Opt[seq[Withdrawal]] removeWithdrawals* : bool blobGasUsed* : Opt[uint64] removeBlobGasUsed* : bool excessBlobGas* : Opt[uint64] removeExcessBlobGas* : bool parentBeaconRoot* : Opt[common.Hash256] removeParentBeaconRoot* : bool versionedHashesCustomizer*: VersionedHashesCustomizer func getTimestamp*(cust: CustomPayloadData, basePayload: ExecutionPayload): uint64 = if cust.timestamp.isSome: return cust.timestamp.get return basePayload.timestamp.uint64 # Construct a customized payload by taking an existing payload as base and mixing it CustomPayloadData # blockHash is calculated automatically. proc customizePayload*(cust: CustomPayloadData, data: ExecutableData): ExecutableData {.gcsafe.} = var customHeader = blockHeader(data.basePayload, beaconRoot = data.beaconRoot) if cust.transactions.isSome: customHeader.txRoot = calcTxRoot(cust.transactions.get) # Overwrite custom information if cust.parentHash.isSome: customHeader.parentHash = cust.parentHash.get if cust.feeRecipient.isSome: customHeader.coinbase = cust.feeRecipient.get if cust.stateRoot.isSome: customHeader.stateRoot = cust.stateRoot.get if cust.receiptsRoot.isSome: customHeader.receiptsRoot = cust.receiptsRoot.get if cust.logsBloom.isSome: customHeader.logsBloom = cust.logsBloom.get if cust.prevRandao.isSome: customHeader.mixHash = cust.prevRandao.get if cust.number.isSome: customHeader.number = cust.number.get if cust.gasLimit.isSome: customHeader.gasLimit = cust.gasLimit.get if cust.gasUsed.isSome: customHeader.gasUsed = cust.gasUsed.get if cust.timestamp.isSome: customHeader.timestamp = cust.timestamp.get.EthTime if cust.extraData.isSome: customHeader.extraData = cust.extraData.get if cust.baseFeePerGas.isSome: customHeader.baseFeePerGas = cust.baseFeePerGas if cust.removeWithdrawals: customHeader.withdrawalsRoot = Opt.none(common.Hash256) elif cust.withdrawals.isSome: let h = calcWithdrawalsRoot(cust.withdrawals.get) customHeader.withdrawalsRoot = Opt.some(h) if cust.removeBlobGasUsed: customHeader.blobGasUsed = Opt.none(uint64) elif cust.blobGasUsed.isSome: customHeader.blobGasUsed = cust.blobGasUsed if cust.removeExcessBlobGas: customHeader.excessBlobGas = Opt.none(uint64) elif cust.excessBlobGas.isSome: customHeader.excessBlobGas = cust.excessBlobGas if cust.removeParentBeaconRoot: customHeader.parentBeaconBlockRoot = Opt.none(common.Hash256) elif cust.parentBeaconRoot.isSome: customHeader.parentBeaconBlockRoot = cust.parentBeaconRoot var blk = EthBlock( header: customHeader, transactions: if cust.transactions.isSome: cust.transactions.get else: ethTxs data.basePayload.transactions ) if cust.removeWithdrawals: blk.withdrawals = Opt.none(seq[Withdrawal]) elif cust.withdrawals.isSome: blk.withdrawals = cust.withdrawals elif data.basePayload.withdrawals.isSome: blk.withdrawals = ethWithdrawals data.basePayload.withdrawals result = ExecutableData( basePayload : executionPayload(blk), beaconRoot : blk.header.parentBeaconBlockRoot, attr : data.attr, versionedHashes: data.versionedHashes, ) if cust.versionedHashesCustomizer.isNil.not: doAssert(data.versionedHashes.isSome) result.versionedHashes = cust.versionedHashesCustomizer.getVersionedHashes(data.versionedHashes.get) # Base new payload directive call cust. # Used as base to other customizers. type BaseNewPayloadVersionCustomizer* = ref object of NewPayloadCustomizer payloadCustomizer* : CustomPayloadData method customizePayload(cust: BaseNewPayloadVersionCustomizer, data: ExecutableData): ExecutableData = cust.payloadCustomizer.customizePayload(data) # Customizer that upgrades the version of the payload to the next version. type UpgradeNewPayloadVersion* = ref object of BaseNewPayloadVersionCustomizer method newPayloadVersion(cust: UpgradeNewPayloadVersion, timestamp: uint64): Version = let version = procCall newPayloadVersion(EngineAPIVersionResolver(cust), timestamp) doAssert(version != Version.high, "cannot upgrade version " & $Version.high) version.succ # Customizer that downgrades the version of the payload to the previous version. type DowngradeNewPayloadVersion* = ref object of BaseNewPayloadVersionCustomizer method newPayloadVersion(cust: DowngradeNewPayloadVersion, timestamp: uint64): Version = let version = procCall newPayloadVersion(EngineAPIVersionResolver(cust), timestamp) doAssert(version != Version.V1, "cannot downgrade version 1") version.pred proc customizePayloadTransactions*(data: ExecutableData, customTransactions: openArray[Transaction]): ExecutableData = let cpd = CustomPayloadData( transactions: Opt.some(@customTransactions), ) customizePayload(cpd, data) proc `$`*(cust: CustomPayloadData): string = var fieldList = newSeq[string]() if cust.parentHash.isSome: fieldList.add "parentHash=" & cust.parentHash.get.short if cust.feeRecipient.isSome: fieldList.add "Coinbase=" & $cust.feeRecipient.get if cust.stateRoot.isSome: fieldList.add "stateRoot=" & cust.stateRoot.get.short if cust.receiptsRoot.isSome: fieldList.add "receiptsRoot=" & cust.receiptsRoot.get.short if cust.logsBloom.isSome: fieldList.add "logsBloom=" & cust.logsBloom.get.toHex if cust.prevRandao.isSome: fieldList.add "prevRandao=" & cust.prevRandao.get.short if cust.number.isSome: fieldList.add "Number=" & $cust.number.get if cust.gasLimit.isSome: fieldList.add "gasLimit=" & $cust.gasLimit.get if cust.gasUsed.isSome: fieldList.add "gasUsed=" & $cust.gasUsed.get if cust.timestamp.isSome: fieldList.add "timestamp=" & $cust.timestamp.get if cust.extraData.isSome: fieldList.add "extraData=" & cust.extraData.get.toHex if cust.baseFeePerGas.isSome: fieldList.add "baseFeePerGas=" & $cust.baseFeePerGas.get if cust.transactions.isSome: fieldList.add "transactions=" & $cust.transactions.get.len if cust.withdrawals.isSome: fieldList.add "withdrawals=" & $cust.withdrawals.get.len fieldList.join(", ") type InvalidPayloadBlockField* = enum InvalidParentHash InvalidStateRoot InvalidReceiptsRoot InvalidNumber InvalidGasLimit InvalidGasUsed InvalidTimestamp InvalidPrevRandao RemoveTransaction InvalidTransactionSignature InvalidTransactionNonce InvalidTransactionGas InvalidTransactionGasPrice InvalidTransactionValue InvalidTransactionGasTipPrice InvalidTransactionChainID InvalidParentBeaconBlockRoot InvalidExcessBlobGas InvalidBlobGasUsed InvalidBlobCountGasUsed InvalidVersionedHashesVersion InvalidVersionedHashes IncompleteVersionedHashes ExtraVersionedHashes InvalidWithdrawals func scramble(data: Web3Hash): Opt[common.Hash256] = var h = ethHash data h.data[^1] = byte(255 - h.data[^1]) Opt.some(h) func scramble(data: common.Hash256): Opt[common.Hash256] = var h = data h.data[0] = byte(255 - h.data[0]) Opt.some(h) # This function generates an invalid payload by taking a base payload and modifying the specified field such that it ends up being invalid. # One small consideration is that the payload needs to contain transactions and specially transactions using the PREVRANDAO opcode for all the fields to be compatible with this function. proc generateInvalidPayload*(sender: TxSender, data: ExecutableData, payloadField: InvalidPayloadBlockField): ExecutableData = var customPayloadMod: CustomPayloadData let basePayload = data.basePayload case payloadField of InvalidParentHash: customPayloadMod = CustomPayloadData( parentHash: scramble(basePayload.parentHash), ) of InvalidStateRoot: customPayloadMod = CustomPayloadData( stateRoot: scramble(basePayload.stateRoot), ) of InvalidReceiptsRoot: customPayloadMod = CustomPayloadData( receiptsRoot: scramble(basePayload.receiptsRoot), ) of InvalidNumber: let modNumber = basePayload.blockNumber.uint64 - 1 customPayloadMod = CustomPayloadData( number: Opt.some(modNumber), ) of InvalidGasLimit: let modGasLimit = basePayload.gasLimit.GasInt * 2 customPayloadMod = CustomPayloadData( gasLimit: Opt.some(modGasLimit), ) of InvalidGasUsed: let modGasUsed = basePayload.gasUsed.GasInt - 1 customPayloadMod = CustomPayloadData( gasUsed: Opt.some(modGasUsed), ) of InvalidTimestamp: let modTimestamp = basePayload.timestamp.uint64 - 1 customPayloadMod = CustomPayloadData( timestamp: Opt.some(modTimestamp), ) of InvalidPrevRandao: # This option potentially requires a transaction that uses the PREVRANDAO opcode. # Otherwise the payload will still be valid. let randomHash = common.Hash256.randomBytes() customPayloadMod = CustomPayloadData( prevRandao: Opt.some(randomHash), ) of InvalidParentBeaconBlockRoot: doAssert(data.beaconRoot.isSome, "no parent beacon block root available for modification") customPayloadMod = CustomPayloadData( parentBeaconRoot: scramble(data.beaconRoot.get), ) of InvalidBlobGasUsed: doAssert(basePayload.blobGasUsed.isSome, "no blob gas used available for modification") let modBlobGasUsed = basePayload.blobGasUsed.get.uint64 + 1 customPayloadMod = CustomPayloadData( blobGasUsed: Opt.some(modBlobGasUsed), ) of InvalidBlobCountGasUsed: doAssert(basePayload.blobGasUsed.isSome, "no blob gas used available for modification") let modBlobGasUsed = basePayload.blobGasUsed.get.uint64 + GAS_PER_BLOB customPayloadMod = CustomPayloadData( blobGasUsed: Opt.some(modBlobGasUsed), ) of InvalidExcessBlobGas: doAssert(basePayload.excessBlobGas.isSome, "no excess blob gas available for modification") let modExcessBlobGas = basePayload.excessBlobGas.get.uint64 + 1 customPayloadMod = CustomPayloadData( excessBlobGas: Opt.some(modExcessBlobGas), ) of InvalidVersionedHashesVersion: doAssert(data.versionedHashes.isSome, "no versioned hashes available for modification") customPayloadMod = CustomPayloadData( versionedHashesCustomizer: IncreaseVersionVersionedHashes(), ) of InvalidVersionedHashes: doAssert(data.versionedHashes.isSome, "no versioned hashes available for modification") customPayloadMod = CustomPayloadData( versionedHashesCustomizer: CorruptVersionedHashes(), ) of IncompleteVersionedHashes: doAssert(data.versionedHashes.isSome, "no versioned hashes available for modification") customPayloadMod = CustomPayloadData( versionedHashesCustomizer: RemoveVersionedHash(), ) of ExtraVersionedHashes: doAssert(data.versionedHashes.isSome, "no versioned hashes available for modification") customPayloadMod = CustomPayloadData( versionedHashesCustomizer: ExtraVersionedHash(), ) of InvalidWithdrawals: # These options are not supported yet. # TODO: Implement doAssert(false, "invalid payload field not supported yet: " & $payloadField) of RemoveTransaction: let emptyTxs = newSeq[Transaction]() customPayloadMod = CustomPayloadData( transactions: Opt.some(emptyTxs), ) of InvalidTransactionSignature, InvalidTransactionNonce, InvalidTransactionGas, InvalidTransactionGasPrice, InvalidTransactionGasTipPrice, InvalidTransactionValue, InvalidTransactionChainID: doAssert(basePayload.transactions.len > 0, "no transactions available for modification") let baseTx = rlp.decode(distinctBase basePayload.transactions[0], Transaction) var custTx: CustomTransactionData case payloadField of InvalidTransactionSignature: var sig = CustSig(R: baseTx.R - 1.u256) custTx.signature = Opt.some(sig) of InvalidTransactionNonce: custTx.nonce = Opt.some(baseTx.nonce - 1) of InvalidTransactionGas: custTx.gas = Opt.some(0.GasInt) of InvalidTransactionGasPrice: custTx.gasPriceOrGasFeeCap = Opt.some(0.GasInt) of InvalidTransactionGasTipPrice: custTx.gasTipCap = Opt.some(gasTipPrice.GasInt * 2.GasInt) of InvalidTransactionValue: # Vault account initially has 0x123450000000000000000, so this value should overflow custTx.value = Opt.some(UInt256.fromHex("0x123450000000000000001")) of InvalidTransactionChainID: custTx.chainId = Opt.some(ChainId(baseTx.chainId.uint64 + 1)) else: discard let acc = sender.getNextAccount() let modifiedTx = sender.customizeTransaction(acc, baseTx, custTx) customPayloadMod = CustomPayloadData( transactions: Opt.some(@[modifiedTx]), ) customPayloadMod.customizePayload(data) # Generates an alternative withdrawals list that contains the same # amounts and accounts, but the order in the list is different, so # stateRoot of the resulting payload should be the same. when false: proc randomizeWithdrawalsOrder(src: openArray[Withdrawal]): seq[Withdrawal] = result = @src result.shuffle