diff --git a/hive_integration/nodocker/engine/engine/forkchoice.nim b/hive_integration/nodocker/engine/engine/forkchoice.nim index f17db877c..748e2c8af 100644 --- a/hive_integration/nodocker/engine/engine/forkchoice.nim +++ b/hive_integration/nodocker/engine/engine/forkchoice.nim @@ -10,17 +10,23 @@ import std/strutils, - ./engine_spec + chronicles, + ./engine_spec, + ../cancun/customizer type - ForkchoiceStateField = enum + ForkchoiceStateField* = enum HeadblockHash = "Head" SafeblockHash = "Safe" FinalizedblockHash = "Finalized" type InconsistentForkchoiceTest* = ref object of EngineSpec - field*: ForkchoiceStateField + field*: ForkchoiceStateField + + Shadow = ref object + canon: seq[ExecutableData] + alt: seq[ExecutableData] method withMainFork(cs: InconsistentForkchoiceTest, fork: EngineFork): BaseSpec = var res = cs.clone() @@ -28,64 +34,67 @@ method withMainFork(cs: InconsistentForkchoiceTest, fork: EngineFork): BaseSpec return res method getName(cs: InconsistentForkchoiceTest): string = - return "Inconsistent %s in ForkchoiceState", cs.Field) -) + return "Inconsistent $1 in ForkchoiceState" % [$cs.field] # Send an inconsistent ForkchoiceState with a known payload that belongs to a side chain as head, safe or finalized. method execute(cs: InconsistentForkchoiceTest, env: TestEnv): bool = - # Wait until TTD is reached by this client - let ok = waitFor env.clMock.waitForTTD() + # Wait until TTD is reached by this client + let ok = waitFor env.clMock.waitForTTD() testCond ok - shadow.canon = make([]*ExecutableData, 0) - shadow.alt = make([]*ExecutableData, 0) - # Produce blocks before starting the test - env.clMock.produceBlocks(3, BlockProcessCallbacks( - onGetPayload: proc(): bool = - # Generate and send an alternative side chain - customData = CustomPayloadData() - customData.ExtraData = &([]byte(0x01)) - if len(shadow.alt) > 0 ( - customData.parentHash = &shadow.alt[len(shadow.alt)-1].blockHash - ) - alternativePayload, err = customData.CustomizePayload(env.clMock.latestPayloadBuilt) - if err != nil ( - fatal "Unable to construct alternative payload: %v", t.TestName, err) - ) - shadow.alt = append(shadow.alt, alternativePayload) - latestCanonicalPayload = env.clMock.latestPayloadBuilt - shadow.canon = append(shadow.canon, &latestCanonicalPayload) + var shadow = Shadow() - # Send the alternative payload - r = env.engine.client.newPayload(alternativePayload) - r.expectStatusEither(PayloadExecutionStatus.valid, test.Accepted) - ), - )) - # Send the invalid ForkchoiceStates - inconsistentFcU = ForkchoiceStateV1( - headblockHash: shadow.canon[len(shadow.alt)-1].blockHash, - safeblockHash: shadow.canon[len(shadow.alt)-2].blockHash, - finalizedblockHash: shadow.canon[len(shadow.alt)-3].blockHash, - ) - switch cs.Field ( - case HeadblockHash: - inconsistentFcU.headblockHash = shadow.alt[len(shadow.alt)-1].blockHash - case SafeblockHash: - inconsistentFcU.safeblockHash = shadow.alt[len(shadow.canon)-2].blockHash - case FinalizedblockHash: - inconsistentFcU.finalizedblockHash = shadow.alt[len(shadow.canon)-3].blockHash - ) - r = env.engine.client.forkchoiceUpdated(inconsistentFcU, nil, env.clMock.latestPayloadBuilt.timestamp) - r.expectError() + # Produce blocks before starting the test + let pbRes = env.clMock.produceBlocks(3, BlockProcessCallbacks( + onGetPayload: proc(): bool = + # Generate and send an alternative side chain + var customData = CustomPayloadData( + extraData: some(@[0x01.byte]) + ) - # Return to the canonical chain - r = env.engine.client.forkchoiceUpdated(env.clMock.latestForkchoice, nil, env.clMock.latestPayloadBuilt.timestamp) - r.expectPayloadStatus(PayloadExecutionStatus.valid) -) + if shadow.alt.len > 0: + customData.parentHash = some(ethHash shadow.alt[^1].blockHash) + + let altPayload = customData.customizePayload(env.clMock.latestExecutableData) + shadow.alt.add altPayload + shadow.canon.add env.clMock.latestExecutableData + + # Send the alternative payload + let version = env.engine.version(altPayload.timestamp) + let r = env.engine.client.newPayload(version, altPayload) + r.expectStatusEither([PayloadExecutionStatus.valid, PayloadExecutionStatus.accepted]) + return true + )) + + testCond pbRes + + # Send the invalid ForkchoiceStates + var inconsistentFcU = ForkchoiceStateV1( + headblockHash: shadow.canon[len(shadow.alt)-1].blockHash, + safeblockHash: shadow.canon[len(shadow.alt)-2].blockHash, + finalizedblockHash: shadow.canon[len(shadow.alt)-3].blockHash, + ) + + case cs.field + of HeadblockHash: + inconsistentFcU.headblockHash = shadow.alt[len(shadow.alt)-1].blockHash + of SafeblockHash: + inconsistentFcU.safeblockHash = shadow.alt[len(shadow.canon)-2].blockHash + of FinalizedblockHash: + inconsistentFcU.finalizedblockHash = shadow.alt[len(shadow.canon)-3].blockHash + + let version = env.engine.version(env.clMock.latestPayloadBuilt.timestamp) + var r = env.engine.client.forkchoiceUpdated(version, inconsistentFcU) + r.expectError() + + # Return to the canonical chain + r = env.engine.client.forkchoiceUpdated(version, env.clMock.latestForkchoice) + r.expectPayloadStatus(PayloadExecutionStatus.valid) + return true type ForkchoiceUpdatedUnknownblockHashTest* = ref object of EngineSpec - field: ForkchoiceStateField + field*: ForkchoiceStateField method withMainFork(cs: ForkchoiceUpdatedUnknownblockHashTest, fork: EngineFork): BaseSpec = var res = cs.clone() @@ -93,77 +102,75 @@ method withMainFork(cs: ForkchoiceUpdatedUnknownblockHashTest, fork: EngineFork) return res method getName(cs: ForkchoiceUpdatedUnknownblockHashTest): string = - return "Unknown %sblockHash", cs.Field) -) + return "Unknown $1blockHash" % [$cs.field] # Send an inconsistent ForkchoiceState with a known payload that belongs to a side chain as head, safe or finalized. method execute(cs: ForkchoiceUpdatedUnknownblockHashTest, env: TestEnv): bool = - # Wait until TTD is reached by this client - let ok = waitFor env.clMock.waitForTTD() + # Wait until TTD is reached by this client + let ok = waitFor env.clMock.waitForTTD() testCond ok - # Produce blocks before starting the test - env.clMock.produceBlocks(5, BlockProcessCallbacks()) + # Produce blocks before starting the test + testCond env.clMock.produceBlocks(5, BlockProcessCallbacks()) - # Generate a random block hash - randomblockHash = common.Hash256() - randomBytes(randomblockHash[:]) + # Generate a random block hash + let randomblockHash = Web3Hash.randomBytes() - if cs.Field == HeadblockHash ( + if cs.field == HeadblockHash: + let fcu = ForkchoiceStateV1( + headblockHash: randomblockHash, + safeblockHash: env.clMock.latestForkchoice.safeblockHash, + finalizedblockHash: env.clMock.latestForkchoice.finalizedblockHash, + ) - forkchoiceStateUnknownHeadHash = ForkchoiceStateV1( - headblockHash: randomblockHash, - safeblockHash: env.clMock.latestForkchoice.safeblockHash, - finalizedblockHash: env.clMock.latestForkchoice.finalizedblockHash, - ) + info "forkchoiceStateUnknownHeadHash", + head=fcu.headblockHash.short, + safe=fcu.safeblockHash.short, + final=fcu.finalizedblockHash.short - t.Logf("INFO (%v) forkchoiceStateUnknownHeadHash: %v\n", t.TestName, forkchoiceStateUnknownHeadHash) + # Execution specification:: + # - (payloadStatus: (status: SYNCING, latestValidHash: null, validationError: null), payloadId: null) + # if forkchoiceState.headblockHash references an unknown payload or a payload that can't be validated + # because requisite data for the validation is missing + let version = env.engine.version(env.clMock.latestExecutedPayload.timestamp) + var r = env.engine.client.forkchoiceUpdated(version, fcu) + r.expectPayloadStatus(PayloadExecutionStatus.syncing) - # Execution specification:: - # - (payloadStatus: (status: SYNCING, latestValidHash: null, validationError: null), payloadId: null) - # if forkchoiceState.headblockHash references an unknown payload or a payload that can't be validated - # because requisite data for the validation is missing - r = env.engine.client.forkchoiceUpdated(forkchoiceStateUnknownHeadHash, nil, env.clMock.latestExecutedPayload.timestamp) - r.expectPayloadStatus(PayloadExecutionStatus.syncing) + var payloadAttributes = env.clMock.latestPayloadAttributes + payloadAttributes.timestamp = w3Qty(payloadAttributes.timestamp, 1) - payloadAttributes = env.clMock.latestPayloadAttributes - payloadAttributes.timestamp += 1 + # Test again using PayloadAttributes, should also return SYNCING and no PayloadID + r = env.engine.client.forkchoiceUpdated(version, fcu, some(payloadAttributes)) + r.expectPayloadStatus(PayloadExecutionStatus.syncing) + r.expectPayloadID(none(PayloadID)) + else: + let pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks( + # Run test after a new payload has been broadcast + onNewPayloadBroadcast: proc(): bool = + var fcu = ForkchoiceStateV1( + headblockHash: env.clMock.latestExecutedPayload.blockHash, + safeblockHash: env.clMock.latestForkchoice.safeblockHash, + finalizedblockHash: env.clMock.latestForkchoice.finalizedblockHash, + ) - # Test again using PayloadAttributes, should also return SYNCING and no PayloadID - r = env.engine.client.forkchoiceUpdated(forkchoiceStateUnknownHeadHash, - &payloadAttributes, env.clMock.latestExecutedPayload.timestamp) - r.expectPayloadStatus(PayloadExecutionStatus.syncing) - r.ExpectPayloadID(nil) - else: - env.clMock.produceSingleBlock(BlockProcessCallbacks( - # Run test after a new payload has been broadcast - onNewPayloadBroadcast: proc(): bool = + if cs.field == SafeblockHash: + fcu.safeblockHash = randomblockHash + elif cs.field == FinalizedblockHash: + fcu.finalizedblockHash = randomblockHash - forkchoiceStateRandomHash = ForkchoiceStateV1( - headblockHash: env.clMock.latestExecutedPayload.blockHash, - safeblockHash: env.clMock.latestForkchoice.safeblockHash, - finalizedblockHash: env.clMock.latestForkchoice.finalizedblockHash, - ) + let version = env.engine.version(env.clMock.latestExecutedPayload.timestamp) + var r = env.engine.client.forkchoiceUpdated(version, fcu) + r.expectError() - if cs.Field == SafeblockHash ( - forkchoiceStateRandomHash.safeblockHash = randomblockHash - elif cs.Field == FinalizedblockHash ( - forkchoiceStateRandomHash.finalizedblockHash = randomblockHash - ) + var payloadAttributes = env.clMock.latestPayloadAttributes + payloadAttributes.prevRandao = w3Hash() + payloadAttributes.suggestedFeeRecipient = w3Address() - r = env.engine.client.forkchoiceUpdated(forkchoiceStateRandomHash, nil, env.clMock.latestExecutedPayload.timestamp) - r.expectError() + # Test again using PayloadAttributes, should also return INVALID and no PayloadID + r = env.engine.client.forkchoiceUpdated(version, fcu, some(payloadAttributes)) + r.expectError() + return true + )) + testCond pbRes - payloadAttributes = env.clMock.latestPayloadAttributes - payloadAttributes.Random = common.Hash256() - payloadAttributes.SuggestedFeeRecipient = common.Address() - - # Test again using PayloadAttributes, should also return INVALID and no PayloadID - r = env.engine.client.forkchoiceUpdated(forkchoiceStateRandomHash, - &payloadAttributes, env.clMock.latestExecutedPayload.timestamp) - r.expectError() - - ), - )) - ) -) + return true diff --git a/hive_integration/nodocker/engine/engine/payload_execution.nim b/hive_integration/nodocker/engine/engine/payload_execution.nim index 5b3b56bbe..462a8ce32 100644 --- a/hive_integration/nodocker/engine/engine/payload_execution.nim +++ b/hive_integration/nodocker/engine/engine/payload_execution.nim @@ -350,7 +350,7 @@ method execute(cs: NewPayloadOnSyncingClientTest, env: TestEnv): bool = onGetPayload: proc(): bool = # Send the new payload from the second client to the first, it won't be able to validate it r = env.engine.client.newPayload(env.clMock.latestPayloadBuilt) - r.expectStatusEither(test.Accepted, PayloadExecutionStatus.syncing) + r.expectStatusEither(PayloadExecutionStatus.accepted, PayloadExecutionStatus.syncing) r.expectLatestValidHash(nil) # Send the forkchoiceUpdated with a reference to the valid payload on the SYNCING client. diff --git a/hive_integration/nodocker/engine/engine/prev_randao.nim b/hive_integration/nodocker/engine/engine/prev_randao.nim index f6d74786d..b4cc854f0 100644 --- a/hive_integration/nodocker/engine/engine/prev_randao.nim +++ b/hive_integration/nodocker/engine/engine/prev_randao.nim @@ -10,11 +10,26 @@ import std/strutils, + eth/common, + chronicles, ./engine_spec type PrevRandaoTransactionTest* = ref object of EngineSpec - blockCount int + blockCount*: int + + Shadow = ref object + startBlockNumber: uint64 + blockCount: int + currentTxIndex: int + txs: seq[Transaction] + +proc checkPrevRandaoValue(client: RpcClient, expectedPrevRandao: common.Hash256, blockNumber: uint64): bool = + let storageKey = blockNumber.u256 + let r = client.storageAt(prevRandaoContractAddr, storageKey) + let expected = UInt256.fromBytesBE(expectedPrevRandao.data) + r.expectStorageEqual(expected) + return true method withMainFork(cs: PrevRandaoTransactionTest, fork: EngineFork): BaseSpec = var res = cs.clone() @@ -22,66 +37,56 @@ method withMainFork(cs: PrevRandaoTransactionTest, fork: EngineFork): BaseSpec = return res method getName(cs: PrevRandaoTransactionTest): string = - return "PrevRandao Opcode Transactions Test (%s)", cs.txType) -) + "PrevRandao Opcode Transactions Test ($1)" % [$cs.txType] method execute(cs: PrevRandaoTransactionTest, env: TestEnv): bool = let ok = waitFor env.clMock.waitForTTD() testCond ok # Create a single block to not having to build on top of genesis - env.clMock.produceSingleBlock(BlockProcessCallbacks()) + testCond env.clMock.produceSingleBlock(BlockProcessCallbacks()) - startBlockNumber = env.clMock.latestHeader.blockNumber.Uint64() + 1 + var shadow = Shadow( + startBlockNumber: env.clMock.latestHeader.blockNumber.truncate(uint64) + 1, + # Send transactions in PoS, the value of the storage in these blocks must match the prevRandao value + blockCount: 10, + currentTxIndex: 0, + ) - # Send transactions in PoS, the value of the storage in these blocks must match the prevRandao value - var ( - blockCount = 10 - currentTxIndex = 0 - txs = make([]typ.Transaction, 0) - ) - if cs.blockCount > 0 ( - blockCount = cs.blockCount - ) - env.clMock.produceBlocks(blockCount, BlockProcessCallbacks( + if cs.blockCount > 0: + shadow.blockCount = cs.blockCount + + let pbRes = env.clMock.produceBlocks(shadow.blockCount, BlockProcessCallbacks( onPayloadProducerSelected: proc(): bool = - tx, err = env.sendNextTx( - t.TestContext, - t.Engine, - &BaseTx( - recipient: prevRandaoContractAddr, - amount: big0, - payload: nil, - txType: cs.txType, - gasLimit: 75000, - ), + let tc = BaseTx( + recipient: some(prevRandaoContractAddr), + amount: 0.u256, + txType: cs.txType, + gasLimit: 75000, ) - if err != nil ( - fatal "Error trying to send transaction: %v", t.TestName, err) - ) - txs = append(txs, tx) - currentTxIndex++ - ), + let tx = env.makeNextTx(tc) + let ok = env.sendTx(tx) + testCond ok: + fatal "Error trying to send transaction" + + shadow.txs.add(tx) + inc shadow.currentTxIndex + return true + , onForkchoiceBroadcast: proc(): bool = # Check the transaction tracing, which is client specific - expectedPrevRandao = env.clMock.prevRandaoHistory[env.clMock.latestHeader.blockNumber.Uint64()+1] - ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout) - defer cancel() - if err = DebugPrevRandaoTransaction(ctx, t.Client.RPC(), t.Client.Type, txs[currentTxIndex-1], - &expectedPrevRandao); err != nil ( - fatal "Error during transaction tracing: %v", t.TestName, err) - ) - ), + let expectedPrevRandao = env.clMock.prevRandaoHistory[env.clMock.latestHeader.blockNumber.truncate(uint64)+1] + let res = debugPrevRandaoTransaction(env.engine.client, shadow.txs[shadow.currentTxIndex-1], expectedPrevRandao) + testCond res.isOk: + fatal "Error during transaction tracing", msg=res.error + + return true )) + testCond pbRes - for i = uint64(startBlockNumber); i <= env.clMock.latestExecutedPayload.blockNumber; i++ ( - checkPrevRandaoValue(t, env.clMock.prevRandaoHistory[i], i) - ) -) + for i in shadow.startBlockNumber..env.clMock.latestExecutedPayload.blockNumber.uint64: + if not checkPrevRandaoValue(env.engine.client, env.clMock.prevRandaoHistory[i], i): + fatal "wrong prev randao", index=i + return false -func checkPrevRandaoValue(t *test.Env, expectedPrevRandao common.Hash, blockNumber uint64) ( - storageKey = common.Hash256() - storageKey[31] = byte(blockNumber) - r = env.engine.client.TestStorageAt(globals.PrevRandaoContractAddr, storageKey, nil) - r.ExpectStorageEqual(expectedPrevRandao) -) + return true diff --git a/hive_integration/nodocker/engine/engine/reorg.nim b/hive_integration/nodocker/engine/engine/reorg.nim index 79878cdd6..0626e117e 100644 --- a/hive_integration/nodocker/engine/engine/reorg.nim +++ b/hive_integration/nodocker/engine/engine/reorg.nim @@ -603,7 +603,7 @@ method execute(cs: ReOrgBackFromSyncingTest, env: TestEnv): bool = # Re-org to the unavailable sidechain in the middle of block production # to be able to re-org back to the canonical chain r = env.engine.client.newPayload(sidechainPayloads[len(sidechainPayloads)-1]) - r.expectStatusEither(PayloadExecutionStatus.syncing, test.Accepted) + r.expectStatusEither(PayloadExecutionStatus.syncing, PayloadExecutionStatus.accepted) r.expectLatestValidHash(nil) # We are going to send one of the alternative payloads and fcU to it forkchoiceUpdatedBack = api.ForkchoiceStateV1( @@ -805,7 +805,7 @@ method execute(cs: SafeReOrgToSideChainTest, env: TestEnv): bool = onGetpayload: proc(): bool = for _, p = range sidechainPayloads ( r = env.engine.client.newPayload(p) - r.expectStatusEither(PayloadExecutionStatus.valid, test.Accepted) + r.expectStatusEither(PayloadExecutionStatus.valid, PayloadExecutionStatus.accepted) ) r = env.engine.client.forkchoiceUpdated(api.ForkchoiceStateV1( headBlockHash: sidechainPayloads[1].blockHash, diff --git a/hive_integration/nodocker/engine/engine/rpc.nim b/hive_integration/nodocker/engine/engine/rpc.nim index bec9250f0..e9f6f441a 100644 --- a/hive_integration/nodocker/engine/engine/rpc.nim +++ b/hive_integration/nodocker/engine/engine/rpc.nim @@ -13,7 +13,7 @@ import ./engine_spec type - BlockStatusRPCcheckType = enum + BlockStatusRPCcheckType* = enum LatestOnNewPayload = "Latest Block on NewPayload" LatestOnHeadblockHash = "Latest Block on HeadblockHash Update" SafeOnSafeblockHash = "Safe Block on SafeblockHash Update" @@ -21,7 +21,7 @@ type type BlockStatus* = ref object of EngineSpec - checkType: BlockStatusRPCcheckType + checkType*: BlockStatusRPCcheckType # TODO: Syncing bool method withMainFork(cs: BlockStatus, fork: EngineFork): BaseSpec = @@ -46,7 +46,7 @@ method execute(cs: BlockStatus, env: TestEnv): bool = else: number = Finalized - p = env.engine.client.TestHeaderByNumber(number) + p = env.engine.client.headerByNumber(number) p.expectError() ) @@ -59,22 +59,19 @@ method execute(cs: BlockStatus, env: TestEnv): bool = let tc = BaseTx( recipient: &ZeroAddr, amount: 1.u256, - payload: nil, txType: cs.txType, gasLimit: 75000, - ForkConfig: t.ForkConfig, ), - tx, err = env.sendNextTx( - ) - if err != nil ( + let ok = env.sendNextTx(tc) + testCond ok: fatal "Error trying to send transaction: %v", err) ) ), ) - switch b.checkType ( - case LatestOnNewPayload: + case b.checkType ( + of LatestOnNewPayload: callbacks.onGetPayload = proc(): bool r = env.engine.client.latestHeader() r.expectHash(env.clMock.latestForkchoice.headblockHash) @@ -88,27 +85,22 @@ method execute(cs: BlockStatus, env: TestEnv): bool = # Check that the receipt for the transaction we just sent is still not available q = env.engine.client.txReceipt(tx.Hash()) q.expectError() - ) - case LatestOnHeadblockHash: + of LatestOnHeadblockHash: callbacks.onForkchoiceBroadcast = proc(): bool r = env.engine.client.latestHeader() r.expectHash(env.clMock.latestForkchoice.headblockHash) s = env.engine.client.txReceipt(tx.Hash()) s.ExpectTransactionHash(tx.Hash()) - ) - case SafeOnSafeblockHash: + of SafeOnSafeblockHash: callbacks.onSafeBlockChange = proc(): bool - r = env.engine.client.TestHeaderByNumber(Safe) + r = env.engine.client.headerByNumber(Safe) r.expectHash(env.clMock.latestForkchoice.safeblockHash) - ) - case FinalizedOnFinalizedblockHash: + of FinalizedOnFinalizedblockHash: callbacks.onFinalizedBlockChange = proc(): bool - r = env.engine.client.TestHeaderByNumber(Finalized) + r = env.engine.client.headerByNumber(Finalized) r.expectHash(env.clMock.latestForkchoice.finalizedblockHash) - ) - ) # Perform the test env.clMock.produceSingleBlock(callbacks) -) + diff --git a/hive_integration/nodocker/engine/engine/suggested_fee_recipient.nim b/hive_integration/nodocker/engine/engine/suggested_fee_recipient.nim index 6d2f54be9..7c3105e3c 100644 --- a/hive_integration/nodocker/engine/engine/suggested_fee_recipient.nim +++ b/hive_integration/nodocker/engine/engine/suggested_fee_recipient.nim @@ -10,11 +10,13 @@ import std/strutils, - ./engine_spec + chronicles, + ./engine_spec, + ../../../../nimbus/transaction type SuggestedFeeRecipientTest* = ref object of EngineSpec - transactionCount: int + transactionCount*: int method withMainFork(cs: SuggestedFeeRecipientTest, fork: EngineFork): BaseSpec = var res = cs.clone() @@ -38,54 +40,55 @@ method execute(cs: SuggestedFeeRecipientTest, env: TestEnv): bool = txRecipient = EthAddress.randomBytes() # Send multiple transactions - for i = 0; i < cs.transactionCount; i++ ( - _, err = env.sendNextTx( - t.TestContext, - t.Engine, - &BaseTx( - recipient: &txRecipient, - amount: big0, - payload: nil, - txType: cs.txType, - gasLimit: 75000, - ), + for i in 0.. 0: