More Engine API tests

This commit is contained in:
jangko 2023-11-03 09:24:51 +07:00
parent 25bc8e4b22
commit 19f313d891
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
14 changed files with 866 additions and 868 deletions

View File

@ -72,7 +72,7 @@ type
latestForkchoice* : ForkchoiceStateV1 latestForkchoice* : ForkchoiceStateV1
# Merge related # Merge related
firstPoSBlockNumber : Option[uint64] firstPoSBlockNumber* : Option[uint64]
ttdReached* : bool ttdReached* : bool
transitionPayloadTimestamp: Option[int] transitionPayloadTimestamp: Option[int]
chainTotalDifficulty : UInt256 chainTotalDifficulty : UInt256

View File

@ -10,11 +10,13 @@
import import
std/strutils, std/strutils,
./engine_spec chronicles,
./engine_spec,
../../../../nimbus/common/hardforks
type type
ForkIDSpec* = ref object of EngineSpec ForkIDSpec* = ref object of EngineSpec
produceBlocksBeforePeering: int produceBlocksBeforePeering*: int
method withMainFork(cs: ForkIDSpec, fork: EngineFork): BaseSpec = method withMainFork(cs: ForkIDSpec, fork: EngineFork): BaseSpec =
var res = cs.clone() var res = cs.clone()
@ -22,15 +24,25 @@ method withMainFork(cs: ForkIDSpec, fork: EngineFork): BaseSpec =
return res return res
method getName(cs: ForkIDSpec): string = method getName(cs: ForkIDSpec): string =
name = "Fork ID: Genesis at %d, %s at %d", cs.GetGenesistimestamp(), cs.mainFork, cs.ForkTime) var name = "Fork ID: Genesis at $1, $2 at $3" % [$cs.getGenesistimestamp(), $cs.mainFork, $cs.forkTime]
if cs.previousForkTime != 0 ( if cs.previousForkTime != 0:
name += ", %s at %d", cs.mainFork.PreviousFork(), cs.previousForkTime) name.add ", $1 at $2" % [$cs.mainFork.pred, $cs.previousForkTime]
)
if cs.produceBlocksBeforePeering > 0 ( if cs.produceBlocksBeforePeering > 0:
name += ", Produce %d blocks before peering", cs.produceBlocksBeforePeering) name.add ", Produce $1 blocks before peering" % [$cs.produceBlocksBeforePeering]
)
return name return name
)
method getForkConfig*(cs: ForkIDSpec): ChainConfig =
let forkConfig = procCall getForkConfig(BaseSpec(cs))
if forkConfig.isNil:
return nil
# Merge fork happen at block 0
let mainFork = cs.getMainFork()
if mainFork == ForkParis:
forkConfig.mergeForkBlock = some(0.u256)
return forkConfig
method execute(cs: ForkIDSpec, env: TestEnv): bool = method execute(cs: ForkIDSpec, env: TestEnv): bool =
# Wait until TTD is reached by this client # Wait until TTD is reached by this client
@ -38,41 +50,8 @@ method execute(cs: ForkIDSpec, env: TestEnv): bool =
testCond ok testCond ok
# Produce blocks before starting the test if required # Produce blocks before starting the test if required
env.clMock.produceBlocks(cs.produceBlocksBeforePeering, BlockProcessCallbacks()) testCond env.clMock.produceBlocks(cs.produceBlocksBeforePeering, BlockProcessCallbacks())
# Get client index's enode # Get client index's enode
engine = t.Engine let engine = env.addEngine()
conn, err = devp2p.PeerEngineClient(engine, t.CLMock) return true
if err != nil (
fatal "Error peering engine client: %v", err)
)
defer conn.Close()
info "Connected to client, remote public key: %s", conn.RemoteKey())
# Sleep
await sleepAsync(1 * time.Second)
# Timeout value for all requests
timeout = 20 * time.Second
# Send a ping request to verify that we are not immediately disconnected
pingReq = &devp2p.Ping()
if size, err = conn.Write(pingReq); err != nil (
fatal "Could not write to connection: %v", err)
else:
info "Wrote %d bytes to conn", size)
)
# Finally wait for the pong response
msg, err = conn.WaitForResponse(timeout, 0)
if err != nil (
fatal "Error waiting for response: %v", err)
)
switch msg = msg.(type) (
case *devp2p.Pong:
info "Received pong response: %v", msg)
default:
fatal "Unexpected message type: %v", err)
)
)

View File

@ -240,6 +240,10 @@ method execute(cs: InvalidMissingAncestorReOrgSyncTest, env: TestEnv): bool =
# Append the common ancestor # Append the common ancestor
shadow.payloads.add env.clMock.latestExecutableData shadow.payloads.add env.clMock.latestExecutableData
if not cs.reOrgFromCanonical:
# Add back the original client before side chain production
env.cLMock.addEngine(env.engine)
# Produce blocks but at the same time create an side chain which contains an invalid payload at some point (INV_P) # Produce blocks but at the same time create an side chain which contains an invalid payload at some point (INV_P)
# CommonAncestor◄─▲── P1 ◄─ P2 ◄─ P3 ◄─ ... ◄─ Pn # CommonAncestor◄─▲── P1 ◄─ P2 ◄─ P3 ◄─ ... ◄─ Pn
# │ # │

View File

@ -10,6 +10,9 @@
import import
std/strutils, std/strutils,
eth/common,
chronicles,
../cancun/customizer,
./engine_spec ./engine_spec
type type
@ -37,45 +40,49 @@ method execute(cs: ReExecutePayloadTest, env: TestEnv): bool =
let pbRes = env.clMock.produceBlocks(payloadReExecCount, BlockProcessCallbacks( let pbRes = env.clMock.produceBlocks(payloadReExecCount, BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
# Send at least one transaction per payload # Send at least one transaction per payload
_, err = env.sendNextTx( let tc = BaseTx(
env.clMock.nextBlockProducer, txType: cs.txType,
BaseTx( gasLimit: 75000,
txType: cs.txType,
gasLimit: 75000,
),
) )
if err != nil ( let ok = env.sendNextTx(env.clMock.nextBlockProducer, tc)
fatal "Error trying to send transaction: %v", t.TestName, err) testCond ok:
) fatal "Error trying to send transaction"
), return true
,
onGetPayload: proc(): bool = onGetPayload: proc(): bool =
# Check that the transaction was included # Check that the transaction was included
if len(env.clMock.latestPayloadBuilt.transactions) == 0 ( testCond len(env.clMock.latestPayloadBuilt.transactions) != 0:
fatal "Client failed to include the expected transaction in payload built", t.TestName) fatal "Client failed to include the expected transaction in payload built"
) return true
),
)) ))
testCond pbRes
# Re-execute the payloads # Re-execute the payloads
r = env.engine.client.blockNumber() let r = env.engine.client.blockNumber()
r.expectNoError() r.expectNoError()
lastBlock = r.blockNumber let lastBlock = r.get
info "Started re-executing payloads at block: %v", t.TestName, lastBlock) info "Started re-executing payloads at block", number=lastBlock
for i = lastBlock - uint64(payloadReExecCount) + 1; i <= lastBlock; i++ ( let start = lastBlock - uint64(payloadReExecCount) + 1
payload, found = env.clMock.executedPayloadHistory[i]
if !found (
fatal "(test issue) Payload with index %d does not exist", i)
)
r = env.engine.client.newPayload(payload) for i in start..lastBlock:
doAssert env.clMock.executedPayloadHistory.hasKey(i)
let payload = env.clMock.executedPayloadHistory[i]
let r = env.engine.client.newPayload(payload)
r.expectStatus(PayloadExecutionStatus.valid) r.expectStatus(PayloadExecutionStatus.valid)
r.expectLatestValidHash(payload.blockHash) r.expectLatestValidHash(payload.blockHash)
)
) return true
type type
InOrderPayloadExecutionTest* = ref object of EngineSpec InOrderPayloadExecutionTest* = ref object of EngineSpec
Shadow = ref object
recipient: EthAddress
amountPerTx: UInt256
txPerPayload: int
payloadCount: int
txsIncluded: int
method withMainFork(cs: InOrderPayloadExecutionTest, fork: EngineFork): BaseSpec = method withMainFork(cs: InOrderPayloadExecutionTest, fork: EngineFork): BaseSpec =
var res = cs.clone() var res = cs.clone()
@ -92,93 +99,93 @@ method execute(cs: InOrderPayloadExecutionTest, env: TestEnv): bool =
testCond ok testCond ok
# Send a single block to allow sending newer transaction types on the payloads # Send a single block to allow sending newer transaction types on the payloads
env.clMock.produceSingleBlock(BlockProcessCallbacks()) testCond env.clMock.produceSingleBlock(BlockProcessCallbacks())
# First prepare payloads on a first client, which will also contain multiple transactions # First prepare payloads on a first client, which will also contain multiple transactions
# We will be also verifying that the transactions are correctly interpreted in the canonical chain, # We will be also verifying that the transactions are correctly interpreted in the canonical chain,
# prepare a random account to receive funds. # prepare a random account to receive funds.
recipient = EthAddress.randomBytes() var shadow = Shadow(
amountPerTx = big.NewInt(1000) recipient: EthAddress.randomBytes(),
txPerPayload = 20 amountPerTx: 1000.u256,
payloadCount = 10 txPerPayload: 20,
txsIncluded = 0 payloadCount: 10,
txsIncluded: 0,
)
env.clMock.produceBlocks(payloadCount, BlockProcessCallbacks( let pbRes = env.clMock.produceBlocks(shadow.payloadCount, BlockProcessCallbacks(
# We send the transactions after we got the Payload ID, before the CLMocker gets the prepared Payload # We send the transactions after we got the Payload ID, before the CLMocker gets the prepared Payload
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
_, err = env.sendNextTxs( let tc = BaseTx(
env.clMock.nextBlockProducer, recipient: some(shadow.recipient),
BaseTx( amount: shadow.amountPerTx,
recipient: &recipient, txType: cs.txType,
amount: amountPerTx, gasLimit: 75000,
txType: cs.txType,
gasLimit: 75000,
),
uint64(txPerPayload),
) )
if err != nil ( let ok = env.sendNextTxs(env.clMock.nextBlockProducer, tc, shadow.txPerPayload)
fatal "Error trying to send transaction: %v", t.TestName, err) testCond ok:
) fatal "Error trying to send transaction"
), return true
,
onGetPayload: proc(): bool = onGetPayload: proc(): bool =
if len(env.clMock.latestPayloadBuilt.Transactions) < (txPerPayload / 2) ( if len(env.clMock.latestPayloadBuilt.transactions) < (shadow.txPerPayload div 2):
fatal "Client failed to include all the expected transactions in payload built: %d < %d", t.TestName, len(env.clMock.latestPayloadBuilt.Transactions), (txPerPayload / 2)) fatal "Client failed to include all the expected transactions in payload built"
)
txsIncluded += len(env.clMock.latestPayloadBuilt.Transactions) shadow.txsIncluded += len(env.clMock.latestPayloadBuilt.transactions)
), return true
)) ))
expectedBalance = amountPerTx.Mul(amountPerTx, big.NewInt(int64(txsIncluded))) testCond pbRes
let expectedBalance = shadow.amountPerTx * shadow.txsIncluded.u256
# Check balance on this first client # Check balance on this first client
r = env.engine.client.balanceAt(recipient, nil) let r = env.engine.client.balanceAt(shadow.recipient)
r.expectBalanceEqual(expectedBalance) r.expectBalanceEqual(expectedBalance)
# Start a second client to send newPayload consecutively without fcU # Start a second client to send newPayload consecutively without fcU
let sec = env.addEngine(false, false) let sec = env.addEngine(false, false)
# Send the forkchoiceUpdated with the latestExecutedPayload hash, we should get SYNCING back # Send the forkchoiceUpdated with the latestExecutedPayload hash, we should get SYNCING back
fcU = ForkchoiceStateV1( let fcU = ForkchoiceStateV1(
headblockHash: env.clMock.latestExecutedPayload.blockHash, headblockHash: env.clMock.latestExecutedPayload.blockHash,
safeblockHash: env.clMock.latestExecutedPayload.blockHash, safeblockHash: env.clMock.latestExecutedPayload.blockHash,
finalizedblockHash: env.clMock.latestExecutedPayload.blockHash, finalizedblockHash: env.clMock.latestExecutedPayload.blockHash,
) )
s = sec.client.forkchoiceUpdated(fcU, nil, env.clMock.latestExecutedPayload.timestamp) var version = sec.version(env.clMock.latestExecutedPayload.timestamp)
var s = sec.client.forkchoiceUpdated(version, fcU)
s.expectPayloadStatus(PayloadExecutionStatus.syncing) s.expectPayloadStatus(PayloadExecutionStatus.syncing)
s.expectLatestValidHash(nil) s.expectLatestValidHash()
s.ExpectNoValidationError() s.expectNoValidationError()
# Send all the payloads in the increasing order # Send all the payloads in the increasing order
for k = env.clMock.firstPoSBlockNumber.Uint64(); k <= env.clMock.latestExecutedPayload.blockNumber; k++ ( let start = env.clMock.firstPoSBlockNumber.get
payload = env.clMock.executedPayloadHistory[k] for k in start..env.clMock.latestExecutedPayload.blockNumber.uint64:
let payload = env.clMock.executedPayloadHistory[k]
s = sec.client.newPayload(payload) let s = sec.client.newPayload(payload)
s.expectStatus(PayloadExecutionStatus.valid) s.expectStatus(PayloadExecutionStatus.valid)
s.expectLatestValidHash(payload.blockHash) s.expectLatestValidHash(payload.blockHash)
) version = sec.version(env.clMock.latestExecutedPayload.timestamp)
s = sec.client.forkchoiceUpdated(version, fcU)
s = sec.client.forkchoiceUpdated(fcU, nil, env.clMock.latestExecutedPayload.timestamp)
s.expectPayloadStatus(PayloadExecutionStatus.valid) s.expectPayloadStatus(PayloadExecutionStatus.valid)
s.expectLatestValidHash(fcU.headblockHash) s.expectLatestValidHash(fcU.headblockHash)
s.ExpectNoValidationError() s.expectNoValidationError()
# At this point we should have our funded account balance equal to the expected value. # At this point we should have our funded account balance equal to the expected value.
q = sec.client.balanceAt(recipient, nil) let q = sec.client.balanceAt(shadow.recipient)
q.expectBalanceEqual(expectedBalance) q.expectBalanceEqual(expectedBalance)
# Add the client to the CLMocker # Add the client to the CLMocker
env.clMock.addEngine(secondaryClient) env.clMock.addEngine(sec)
# Produce a single block on top of the canonical chain, all clients must accept this # Produce a single block on top of the canonical chain, all clients must accept this
env.clMock.produceSingleBlock(BlockProcessCallbacks()) testCond env.clMock.produceSingleBlock(BlockProcessCallbacks())
# Head must point to the latest produced payload # Head must point to the latest produced payload
p = sec.client.TestHeaderByNumber(nil) let p = sec.client.latestHeader()
p.expectHash(env.clMock.latestExecutedPayload.blockHash) p.expectHash(ethHash env.clMock.latestExecutedPayload.blockHash)
) return true
type type
MultiplePayloadsExtendingCanonicalChainTest* = ref object of EngineSpec MultiplePayloadsExtendingCanonicalChainTest* = ref object of EngineSpec
@ -194,12 +201,10 @@ method withMainFork(cs: MultiplePayloadsExtendingCanonicalChainTest, fork: Engin
return res return res
method getName(cs: MultiplePayloadsExtendingCanonicalChainTest): string = method getName(cs: MultiplePayloadsExtendingCanonicalChainTest): string =
name = "Multiple New Payloads Extending Canonical Chain" var name = "Multiple New Payloads Extending Canonical Chain"
if s.SetHeadToFirstPayloadReceived ( if cs.setHeadToFirstPayloadReceived:
name += " (FcU to first payload received)" name.add " (FcU to first payload received)"
) name
return name
)
# Consecutive Payload Execution: Secondary client should be able to set the forkchoiceUpdated to payloads received consecutively # Consecutive Payload Execution: Secondary client should be able to set the forkchoiceUpdated to payloads received consecutively
method execute(cs: MultiplePayloadsExtendingCanonicalChainTest, env: TestEnv): bool = method execute(cs: MultiplePayloadsExtendingCanonicalChainTest, env: TestEnv): bool =
@ -208,57 +213,48 @@ method execute(cs: MultiplePayloadsExtendingCanonicalChainTest, env: TestEnv): b
testCond ok testCond ok
# Produce blocks before starting the test # Produce blocks before starting the test
env.clMock.produceBlocks(5, BlockProcessCallbacks()) testCond env.clMock.produceBlocks(5, BlockProcessCallbacks())
callbacks = BlockProcessCallbacks( var callbacks = BlockProcessCallbacks(
# We send the transactions after we got the Payload ID, before the CLMocker gets the prepared Payload # We send the transactions after we got the Payload ID, before the CLMocker gets the prepared Payload
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
let recipient = EthAddress.randomBytes() let recipient = EthAddress.randomBytes()
_, err = env.sendNextTx( let tc = BaseTx(
env.clMock.nextBlockProducer, recipient: some(recipient),
BaseTx( txType: cs.txType,
recipient: &recipient, gasLimit: 75000,
txType: cs.txType,
gasLimit: 75000,
),
) )
if err != nil ( let ok = env.sendNextTx(env.clMock.nextBlockProducer, tc)
fatal "Error trying to send transaction: %v", t.TestName, err) testCond ok:
) fatal "Error trying to send transaction"
), return true
) )
reExecFunc = proc(): bool = let reExecFunc = proc(): bool =
payloadCount = 80 var payloadCount = 80
if cs.payloadCount > 0 ( if cs.payloadCount > 0:
payloadCount = cs.payloadCount payloadCount = cs.payloadCount
)
basePayload = env.clMock.latestPayloadBuilt let basePayload = env.clMock.latestExecutableData
# Check that the transaction was included # Check that the transaction was included
if len(basePayload.Transactions) == 0 ( testCond len(basePayload.basePayload.transactions)> 0:
fatal "Client failed to include the expected transaction in payload built", t.TestName) fatal "Client failed to include the expected transaction in payload built"
)
# Fabricate and send multiple new payloads by changing the PrevRandao field # Fabricate and send multiple new payloads by changing the PrevRandao field
for i = 0; i < payloadCount; i++ ( for i in 0..<payloadCount:
newPrevRandao = common.Hash256.randomBytes() let newPrevRandao = common.Hash256.randomBytes()
customizer = CustomPayloadData( let customizer = CustomPayloadData(
prevRandao: &newPrevRandao, prevRandao: some(newPrevRandao),
) )
newPayload, err = customizePayload(basePayload) let newPayload = customizer.customizePayload(basePayload)
if err != nil ( let version = env.engine.version(newPayload.timestamp)
fatal "Unable to customize payload %v: %v", t.TestName, i, err) let r = env.engine.client.newPayload(version, newPayload)
)
r = env.engine.client.newPayload(newPayload)
r.expectStatus(PayloadExecutionStatus.valid) r.expectStatus(PayloadExecutionStatus.valid)
r.expectLatestValidHash(newPayload.blockHash) r.expectLatestValidHash(newPayload.blockHash)
) return true
)
if cs.SetHeadToFirstPayloadReceived ( if cs.setHeadToFirstPayloadReceived:
# We are going to set the head of the chain to the first payload executed by the client # We are going to set the head of the chain to the first payload executed by the client
# Therefore our re-execution function must be executed after the payload was broadcast # Therefore our re-execution function must be executed after the payload was broadcast
callbacks.onNewPayloadBroadcast = reExecFunc callbacks.onNewPayloadBroadcast = reExecFunc
@ -266,15 +262,18 @@ method execute(cs: MultiplePayloadsExtendingCanonicalChainTest, env: TestEnv): b
# Otherwise, we execute the payloads after we get the canonical one so it's # Otherwise, we execute the payloads after we get the canonical one so it's
# executed last # executed last
callbacks.onGetPayload = reExecFunc callbacks.onGetPayload = reExecFunc
)
env.clMock.produceSingleBlock(callbacks) testCond env.clMock.produceSingleBlock(callbacks)
# At the end the CLMocker continues to try to execute fcU with the original payload, which should not fail # At the end the CLMocker continues to try to execute fcU with the original payload, which should not fail
) return true
type type
NewPayloadOnSyncingClientTest* = ref object of EngineSpec NewPayloadOnSyncingClientTest* = ref object of EngineSpec
Shadow2 = ref object
recipient: EthAddress
previousPayload: ExecutionPayload
method withMainFork(cs: NewPayloadOnSyncingClientTest, fork: EngineFork): BaseSpec = method withMainFork(cs: NewPayloadOnSyncingClientTest, fork: EngineFork): BaseSpec =
var res = cs.clone() var res = cs.clone()
res.mainFork = fork res.mainFork = fork
@ -285,98 +284,94 @@ method getName(cs: NewPayloadOnSyncingClientTest): string =
# Send a valid payload on a client that is currently SYNCING # Send a valid payload on a client that is currently SYNCING
method execute(cs: NewPayloadOnSyncingClientTest, env: TestEnv): bool = method execute(cs: NewPayloadOnSyncingClientTest, env: TestEnv): bool =
var var shadow = Shadow2(
# Set a random transaction recipient # Set a random transaction recipient
let recipient = EthAddress.randomBytes() recipient: EthAddress.randomBytes(),
previousPayload ExecutableData )
sec = env.addEngine()
var sec = env.addEngine()
# Wait until TTD is reached by all clients # Wait until TTD is reached by all clients
let ok = waitFor env.clMock.waitForTTD() let ok = waitFor env.clMock.waitForTTD()
testCond ok testCond ok
# Produce blocks before starting the test # Produce blocks before starting the test
env.clMock.produceBlocks(5, BlockProcessCallbacks()) testCond env.clMock.produceBlocks(5, BlockProcessCallbacks())
# Disconnect the first engine client from the CL Mocker and produce a block # Disconnect the first engine client from the CL Mocker and produce a block
env.clMock.removeEngine(env.engine) env.clMock.removeEngine(env.engine)
env.clMock.produceSingleBlock(BlockProcessCallbacks( var pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
# Send at least one transaction per payload # Send at least one transaction per payload
_, err = env.sendNextTx( let tc = BaseTx(
env.clMock.nextBlockProducer, recipient: some(shadow.recipient),
BaseTx( txType: cs.txType,
recipient: &recipient, gasLimit: 75000,
txType: cs.txType,
gasLimit: 75000,
),
) )
if err != nil ( let ok = env.sendNextTx(env.clMock.nextBlockProducer, tc)
fatal "Error trying to send transaction: %v", t.TestName, err) testCond ok:
) fatal "Error trying to send transaction"
), return true
,
onGetPayload: proc(): bool = onGetPayload: proc(): bool =
# Check that the transaction was included # Check that the transaction was included
if len(env.clMock.latestPayloadBuilt.Transactions) == 0 ( testCond len(env.clMock.latestPayloadBuilt.transactions) > 0:
fatal "Client failed to include the expected transaction in payload built", t.TestName) fatal "Client failed to include the expected transaction in payload built"
) return true
),
)) ))
testCond true
previousPayload = env.clMock.latestPayloadBuilt shadow.previousPayload = env.clMock.latestPayloadBuilt
# Send the fcU to set it to syncing mode # Send the fcU to set it to syncing mode
r = env.engine.client.forkchoiceUpdated(env.clMock.latestForkchoice, nil, env.clMock.latestHeader.Time) let version = env.engine.version(env.clMock.latestHeader.timestamp)
let r = env.engine.client.forkchoiceUpdated(version, env.clMock.latestForkchoice)
r.expectPayloadStatus(PayloadExecutionStatus.syncing) r.expectPayloadStatus(PayloadExecutionStatus.syncing)
env.clMock.produceSingleBlock(BlockProcessCallbacks( pbREs = env.clMock.produceSingleBlock(BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
# Send at least one transaction per payload # Send at least one transaction per payload
_, err = env.sendNextTx( let tc = BaseTx(
env.clMock.nextBlockProducer, recipient: some(shadow.recipient),
BaseTx( txType: cs.txType,
recipient: &recipient, gasLimit: 75000,
txType: cs.txType,
gasLimit: 75000,
),
) )
if err != nil ( let ok = env.sendNextTx(env.clMock.nextBlockProducer, tc)
fatal "Error trying to send transaction: %v", t.TestName, err) testCond ok:
) fatal "Error trying to send transaction"
), return true
,
# Run test after the new payload has been obtained # Run test after the new payload has been obtained
onGetPayload: proc(): bool = onGetPayload: proc(): bool =
# Send the new payload from the second client to the first, it won't be able to validate it # 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) let r = env.engine.client.newPayload(env.clMock.latestPayloadBuilt)
r.expectStatusEither(PayloadExecutionStatus.accepted, PayloadExecutionStatus.syncing) r.expectStatusEither([PayloadExecutionStatus.accepted, PayloadExecutionStatus.syncing])
r.expectLatestValidHash(nil) r.expectLatestValidHash()
# Send the forkchoiceUpdated with a reference to the valid payload on the SYNCING client. # Send the forkchoiceUpdated with a reference to the valid payload on the SYNCING client.
var ( var
random = common.Hash256() random = w3Hash()
suggestedFeeRecipient = common.Address() suggestedFeeRecipient = w3Address()
let customizer = BasePayloadAttributesCustomizer(
prevRandao: some(ethHash random),
suggestedFeerecipient: some(ethAddr suggestedFeeRecipient),
) )
payloadAttributesCustomizer = &BasePayloadAttributesCustomizer(
Random: &random, let newAttr = customizer.getPayloadAttributes(env.clMock.latestPayloadAttributes)
SuggestedFeerecipient: &suggestedFeeRecipient, var fcu = ForkchoiceStateV1(
)
newPayloadAttributes, err = payloadAttributesCustomizer.GetPayloadAttributes(env.clMock.latestPayloadAttributes)
if err != nil (
fatal "Unable to customize payload attributes: %v", t.TestName, err)
)
s = env.engine.client.forkchoiceUpdated(ForkchoiceStateV1(
headblockHash: env.clMock.latestPayloadBuilt.blockHash, headblockHash: env.clMock.latestPayloadBuilt.blockHash,
safeblockHash: env.clMock.latestPayloadBuilt.blockHash, safeblockHash: env.clMock.latestPayloadBuilt.blockHash,
finalizedblockHash: env.clMock.latestPayloadBuilt.blockHash, finalizedblockHash: env.clMock.latestPayloadBuilt.blockHash,
), newPayloadAttributes, env.clMock.latestPayloadBuilt.timestamp) )
var version = env.engine.version(env.clMock.latestPayloadBuilt.timestamp)
var s = env.engine.client.forkchoiceUpdated(version, fcu, some(newAttr))
s.expectPayloadStatus(PayloadExecutionStatus.syncing) s.expectPayloadStatus(PayloadExecutionStatus.syncing)
# Send the previous payload to be able to continue # Send the previous payload to be able to continue
p = env.engine.client.newPayload(previousPayload) var p = env.engine.client.newPayload(shadow.previousPayload)
p.expectStatus(PayloadExecutionStatus.valid) p.expectStatus(PayloadExecutionStatus.valid)
p.expectLatestValidHash(previousPayload.blockHash) p.expectLatestValidHash(shadow.previousPayload.blockHash)
# Send the new payload again # Send the new payload again
@ -384,14 +379,16 @@ method execute(cs: NewPayloadOnSyncingClientTest, env: TestEnv): bool =
p.expectStatus(PayloadExecutionStatus.valid) p.expectStatus(PayloadExecutionStatus.valid)
p.expectLatestValidHash(env.clMock.latestPayloadBuilt.blockHash) p.expectLatestValidHash(env.clMock.latestPayloadBuilt.blockHash)
s = env.engine.client.forkchoiceUpdated(ForkchoiceStateV1( fcu = ForkchoiceStateV1(
headblockHash: env.clMock.latestPayloadBuilt.blockHash, headblockHash: env.clMock.latestPayloadBuilt.blockHash,
safeblockHash: env.clMock.latestPayloadBuilt.blockHash, safeblockHash: env.clMock.latestPayloadBuilt.blockHash,
finalizedblockHash: env.clMock.latestPayloadBuilt.blockHash, finalizedblockHash: env.clMock.latestPayloadBuilt.blockHash,
), nil, env.clMock.latestPayloadBuilt.timestamp) )
version = env.engine.version(env.clMock.latestPayloadBuilt.timestamp)
s = env.engine.client.forkchoiceUpdated(version, fcu)
s.expectPayloadStatus(PayloadExecutionStatus.valid) s.expectPayloadStatus(PayloadExecutionStatus.valid)
return true return true
)) ))
testCond pbRes testCond pbRes
@ -415,59 +412,57 @@ method execute(cs: NewPayloadWithMissingFcUTest, env: TestEnv): bool =
testCond ok testCond ok
# Get last genesis block hash # Get last genesis block hash
genesisHash = env.engine.client.latestHeader().Header.Hash() let res = env.engine.client.latestHeader()
let genesisHash = res.get.blockHash
# Produce blocks on the main client, these payloads will be replayed on the secondary client. # Produce blocks on the main client, these payloads will be replayed on the secondary client.
env.clMock.produceBlocks(5, BlockProcessCallbacks( let pbRes = env.clMock.produceBlocks(5, BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
var recipient common.Address let recipient = common.EthAddress.randomBytes()
randomBytes(recipient[:]) let tc = BaseTx(
recipient: some(recipient),
txType: cs.txType,
gasLimit: 75000,
)
# Send at least one transaction per payload # Send at least one transaction per payload
_, err = env.sendNextTx( let ok = env.sendNextTx(env.clMock.nextBlockProducer, tc)
env.clMock.nextBlockProducer, testCond ok:
BaseTx( fatal "Error trying to send transaction"
recipient: &recipient, return true
txType: cs.txType, ,
gasLimit: 75000,
),
)
if err != nil (
fatal "Error trying to send transaction: %v", t.TestName, err)
)
),
onGetPayload: proc(): bool = onGetPayload: proc(): bool =
# Check that the transaction was included # Check that the transaction was included
if len(env.clMock.latestPayloadBuilt.Transactions) == 0 ( testCond len(env.clMock.latestPayloadBuilt.transactions) > 0:
fatal "Client failed to include the expected transaction in payload built", t.TestName) fatal "Client failed to include the expected transaction in payload built"
) return true
),
)) ))
testCond pbRes
var sec = env.addEngine() var sec = env.addEngine()
let start = env.clMock.firstPoSBlockNumber.get
# Send each payload in the correct order but skip the ForkchoiceUpdated for each # Send each payload in the correct order but skip the ForkchoiceUpdated for each
for i = env.clMock.firstPoSBlockNumber.Uint64(); i <= env.clMock.latestHeadNumber.Uint64(); i++ ( for i in start..env.clMock.latestHeadNumber.uint64:
payload = env.clMock.executedPayloadHistory[i] let payload = env.clMock.executedPayloadHistory[i]
p = sec.newPayload(payload) let p = sec.client.newPayload(payload)
p.expectStatus(PayloadExecutionStatus.valid) p.expectStatus(PayloadExecutionStatus.valid)
p.expectLatestValidHash(payload.blockHash) p.expectLatestValidHash(payload.blockHash)
)
# Verify that at this point, the client's head still points to the last non-PoS block # Verify that at this point, the client's head still points to the last non-PoS block
r = sec.latestHeader() let r = sec.client.latestHeader()
r.expectHash(genesisHash) r.expectHash(genesisHash)
# Verify that the head correctly changes after the last ForkchoiceUpdated # Verify that the head correctly changes after the last ForkchoiceUpdated
fcU = ForkchoiceStateV1( let fcU = ForkchoiceStateV1(
headblockHash: env.clMock.executedPayloadHistory[env.clMock.latestHeadNumber.Uint64()].blockHash, headblockHash: env.clMock.executedPayloadHistory[env.clMock.latestHeadNumber.uint64].blockHash,
safeblockHash: env.clMock.executedPayloadHistory[env.clMock.latestHeadNumber.Uint64()-1].blockHash, safeblockHash: env.clMock.executedPayloadHistory[env.clMock.latestHeadNumber.uint64-1].blockHash,
finalizedblockHash: env.clMock.executedPayloadHistory[env.clMock.latestHeadNumber.Uint64()-2].blockHash, finalizedblockHash: env.clMock.executedPayloadHistory[env.clMock.latestHeadNumber.uint64-2].blockHash,
) )
p = sec.forkchoiceUpdated(fcU, nil, env.clMock.latestHeader.Time) let version = sec.version(env.clMock.latestHeader.timestamp)
let p = sec.client.forkchoiceUpdated(version, fcU)
p.expectPayloadStatus(PayloadExecutionStatus.valid) p.expectPayloadStatus(PayloadExecutionStatus.valid)
p.expectLatestValidHash(fcU.headblockHash) p.expectLatestValidHash(fcU.headblockHash)
# Now the head should've changed to the latest PoS block # Now the head should've changed to the latest PoS block
s = sec.latestHeader() let s = sec.client.latestHeader()
s.expectHash(fcU.headblockHash) s.expectHash(ethHash fcU.headblockHash)
) return true

View File

@ -9,7 +9,8 @@
# according to those terms. # according to those terms.
import import
std/strutils, std/[strutils, typetraits],
chronicles,
./engine_spec ./engine_spec
type type
@ -36,6 +37,16 @@ method withMainFork(cs: UniquePayloadIDTest, fork: EngineFork): BaseSpec =
method getName(cs: UniquePayloadIDTest): string = method getName(cs: UniquePayloadIDTest): string =
"Unique Payload ID - " & $cs.fieldModification "Unique Payload ID - " & $cs.fieldModification
func plusOne(x: FixedBytes[32]): FixedBytes[32] =
var z = x.bytes
z[0] = z[0] + 1.byte
FixedBytes[32](z)
func plusOne(x: Address): Address =
var z = distinctBase x
z[0] = z[0] + 1.byte
Address(z)
# Check that the payload id returned on a forkchoiceUpdated call is different # Check that the payload id returned on a forkchoiceUpdated call is different
# when the attributes change # when the attributes change
method execute(cs: UniquePayloadIDTest, env: TestEnv): bool = method execute(cs: UniquePayloadIDTest, env: TestEnv): bool =
@ -43,56 +54,63 @@ method execute(cs: UniquePayloadIDTest, env: TestEnv): bool =
let ok = waitFor env.clMock.waitForTTD() let ok = waitFor env.clMock.waitForTTD()
testCond ok testCond ok
env.clMock.produceSingleBlock(BlockProcessCallbacks( let pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
onPayloadAttributesGenerated: proc(): bool = onPayloadAttributesGenerated: proc(): bool =
payloadAttributes = env.clMock.latestPayloadAttributes var attr = env.clMock.latestPayloadAttributes
case cs.fieldModification ( case cs.fieldModification
of PayloadAttributesIncreasetimestamp: of PayloadAttributesIncreasetimestamp:
payloadAttributes.timestamp += 1 attr.timestamp = w3Qty(attr.timestamp, 1)
of PayloadAttributesRandom: of PayloadAttributesRandom:
payloadAttributes.Random[0] = payloadAttributes.Random[0] + 1 attr.prevRandao = attr.prevRandao.plusOne
of PayloadAttributesSuggestedFeerecipient: of PayloadAttributesSuggestedFeerecipient:
payloadAttributes.SuggestedFeeRecipient[0] = payloadAttributes.SuggestedFeeRecipient[0] + 1 attr.suggestedFeeRecipient = attr.suggestedFeeRecipient.plusOne
of PayloadAttributesAddWithdrawal: of PayloadAttributesAddWithdrawal:
newWithdrawal = &types.Withdrawal() let newWithdrawal = WithdrawalV1()
payloadAttributes.Withdrawals = append(payloadAttributes.Withdrawals, newWithdrawal) var wd = attr.withdrawals.get
wd.add newWithdrawal
attr.withdrawals = some(wd)
of PayloadAttributesRemoveWithdrawal: of PayloadAttributesRemoveWithdrawal:
payloadAttributes.Withdrawals = payloadAttributes.Withdrawals[1:] var wd = attr.withdrawals.get
wd.delete(0)
attr.withdrawals = some(wd)
of PayloadAttributesModifyWithdrawalAmount, of PayloadAttributesModifyWithdrawalAmount,
PayloadAttributesModifyWithdrawalIndex, PayloadAttributesModifyWithdrawalIndex,
PayloadAttributesModifyWithdrawalValidator, PayloadAttributesModifyWithdrawalValidator,
PayloadAttributesModifyWithdrawalAddress: PayloadAttributesModifyWithdrawalAddress:
if len(payloadAttributes.Withdrawals) == 0 ( testCond attr.withdrawals.isSome:
fatal "Cannot modify withdrawal when there are no withdrawals") fatal "Cannot modify withdrawal when there are no withdrawals"
) var wds = attr.withdrawals.get
modifiedWithdrawal = *payloadAttributes.Withdrawals[0] testCond wds.len > 0:
case cs.fieldModification ( fatal "Cannot modify withdrawal when there are no withdrawals"
var wd = wds[0]
case cs.fieldModification
of PayloadAttributesModifyWithdrawalAmount: of PayloadAttributesModifyWithdrawalAmount:
modifiedWithdrawal.Amount += 1 wd.amount = w3Qty(wd.amount, 1)
of PayloadAttributesModifyWithdrawalIndex: of PayloadAttributesModifyWithdrawalIndex:
modifiedWithdrawal.Index += 1 wd.index = w3Qty(wd.index, 1)
of PayloadAttributesModifyWithdrawalValidator: of PayloadAttributesModifyWithdrawalValidator:
modifiedWithdrawal.Validator += 1 wd.validatorIndex = w3Qty(wd.validatorIndex, 1)
of PayloadAttributesModifyWithdrawalAddress: of PayloadAttributesModifyWithdrawalAddress:
modifiedWithdrawal.Address[0] = modifiedWithdrawal.Address[0] + 1 wd.address = wd.address.plusOne
) else:
payloadAttributes.Withdrawals = append(types.Withdrawals(&modifiedWithdrawal), payloadAttributes.Withdrawals[1:]...) fatal "Unknown field change", field=cs.fieldModification
return false
wds[0] = wd
attr.withdrawals = some(wds)
of PayloadAttributesParentBeaconRoot: of PayloadAttributesParentBeaconRoot:
if payloadAttributes.BeaconRoot == nil ( testCond attr.parentBeaconBlockRoot.isSome:
fatal "Cannot modify parent beacon root when there is no parent beacon root") fatal "Cannot modify parent beacon root when there is no parent beacon root"
) let newBeaconRoot = attr.parentBeaconBlockRoot.get.plusOne
newBeaconRoot = *payloadAttributes.BeaconRoot attr.parentBeaconBlockRoot = some(newBeaconRoot)
newBeaconRoot[0] = newBeaconRoot[0] + 1
payloadAttributes.BeaconRoot = &newBeaconRoot
default:
fatal "Unknown field change: %s", cs.fieldModification)
)
# Request the payload with the modified attributes and add the payload ID to the list of known IDs # Request the payload with the modified attributes and add the payload ID to the list of known IDs
let version = env.engine.version(env.clMock.latestHeader.timestamp) let version = env.engine.version(env.clMock.latestHeader.timestamp)
let r = env.engine.client.forkchoiceUpdated(version, env.clMock.latestForkchoice, some(payloadAttributes)) let r = env.engine.client.forkchoiceUpdated(version, env.clMock.latestForkchoice, some(attr))
r.expectNoError() r.expectNoError()
env.clMock.addPayloadID(env.engine, r.get.payloadID.get) testCond env.clMock.addPayloadID(env.engine, r.get.payloadID.get)
), return true
)) ))
) testCond pbRes
return true

View File

@ -12,7 +12,8 @@ import
std/strutils, std/strutils,
eth/common, eth/common,
chronicles, chronicles,
./engine_spec ./engine_spec,
../helper
type type
PrevRandaoTransactionTest* = ref object of EngineSpec PrevRandaoTransactionTest* = ref object of EngineSpec
@ -24,13 +25,6 @@ type
currentTxIndex: int currentTxIndex: int
txs: seq[Transaction] 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 = method withMainFork(cs: PrevRandaoTransactionTest, fork: EngineFork): BaseSpec =
var res = cs.clone() var res = cs.clone()
res.mainFork = fork res.mainFork = fork

View File

@ -10,9 +10,11 @@
import import
std/strutils, std/strutils,
eth/common,
chronicles, chronicles,
./engine_spec, ./engine_spec,
../cancun/customizer ../cancun/customizer,
../helper
type type
SidechainReOrgTest* = ref object of EngineSpec SidechainReOrgTest* = ref object of EngineSpec
@ -37,79 +39,74 @@ method execute(cs: SidechainReOrgTest, env: TestEnv): bool =
# Produce two payloads, send fcU with first payload, check transaction outcome, then reorg, check transaction outcome again # Produce two payloads, send fcU with first payload, check transaction outcome, then reorg, check transaction outcome again
# This single transaction will change its outcome based on the payload # This single transaction will change its outcome based on the payload
tx, err = t.sendNextTx( let tc = BaseTx(
t.TestContext, recipient: some(prevRandaoContractAddr),
t.Engine, txType: cs.txType,
BaseTx( gasLimit: 75000,
recipient: &globals.PrevRandaoContractAddr,
amount: big0,
payload: nil,
txType: cs.txType,
gasLimit: 75000,
),
) )
if err != nil ( let ok2 = env.sendNextTx(env.engine, tc)
fatal "Error trying to send transaction: %v", t.TestName, err) testCond ok2:
) fatal "Error trying to send transaction"
info "sent tx %v", t.TestName, tx.Hash())
env.clMock.produceSingleBlock(BlockProcessCallbacks( info "sent tx"
let pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
onNewPayloadBroadcast: proc(): bool = onNewPayloadBroadcast: proc(): bool =
# At this point the CLMocker has a payload that will result in a specific outcome, # At this point the CLMocker has a payload that will result in a specific outcome,
# we can produce an alternative payload, send it, fcU to it, and verify the changes # we can produce an alternative payload, send it, fcU to it, and verify the changes
alternativePrevRandao = common.Hash() let alternativePrevRandao = common.Hash256.randomBytes()
rand.Read(alternativePrevRandao[:]) let timestamp = w3Qty(env.clMock.latestPayloadBuilt.timestamp, 1)
timestamp = env.clMock.latestPayloadBuilt.timestamp + 1 let customizer = BasePayloadAttributesCustomizer(
payloadAttributes, err = (BasePayloadAttributesCustomizer( timestamp: some(timestamp.uint64),
Timestamp: &timestamp, prevRandao: some(alternativePrevRandao),
Random: &alternativePrevRandao,
)).getPayloadAttributes(env.clMock.LatestPayloadAttributes)
if err != nil (
fatal "Unable to customize payload attributes: %v", t.TestName, err)
) )
r = env.engine.client.forkchoiceUpdated( let attr = customizer.getPayloadAttributes(env.clMock.latestPayloadAttributes)
&env.clMock.latestForkchoice,
payloadAttributes, var version = env.engine.version(env.clMock.latestPayloadBuilt.timestamp)
env.clMock.latestPayloadBuilt.timestamp, let r = env.engine.client.forkchoiceUpdated(version, env.clMock.latestForkchoice, some(attr))
)
r.expectNoError() r.expectNoError()
await sleepAsync(env.clMock.PayloadProductionClientDelay) let period = chronos.seconds(env.clMock.payloadProductionClientDelay)
waitFor sleepAsync(period)
g = env.engine.client.getPayload(r.Response.PayloadID, payloadAttributes) version = env.engine.version(attr.timestamp)
let g = env.engine.client.getPayload(r.get.payloadID.get, version)
g.expectNoError() g.expectNoError()
alternativePayload = g.Payload
if len(alternativePayload.Transactions) == 0 (
fatal "alternative payload does not contain the prevRandao opcode tx", t.TestName)
)
s = env.engine.client.newPayload(alternativePayload) let alternativePayload = g.get.executionPayload
testCond len(alternativePayload.transactions) > 0:
fatal "alternative payload does not contain the prevRandao opcode tx"
let s = env.engine.client.newPayload(alternativePayload)
s.expectStatus(PayloadExecutionStatus.valid) s.expectStatus(PayloadExecutionStatus.valid)
s.expectLatestValidHash(alternativePayload.blockHash) s.expectLatestValidHash(alternativePayload.blockHash)
# We sent the alternative payload, fcU to it # We sent the alternative payload, fcU to it
p = env.engine.client.forkchoiceUpdated(api.ForkchoiceStateV1( let fcu = ForkchoiceStateV1(
headBlockHash: alternativePayload.blockHash, headBlockHash: alternativePayload.blockHash,
safeBlockHash: env.clMock.latestForkchoice.safeBlockHash, safeBlockHash: env.clMock.latestForkchoice.safeBlockHash,
finalizedBlockHash: env.clMock.latestForkchoice.finalizedBlockHash, finalizedBlockHash: env.clMock.latestForkchoice.finalizedBlockHash,
), nil, alternativePayload.timestamp) )
version = env.engine.version(alternativePayload.timestamp)
let p = env.engine.client.forkchoiceUpdated(version, fcu)
p.expectPayloadStatus(PayloadExecutionStatus.valid) p.expectPayloadStatus(PayloadExecutionStatus.valid)
# PrevRandao should be the alternative prevRandao we sent # PrevRandao should be the alternative prevRandao we sent
checkPrevRandaoValue(t, alternativePrevRandao, alternativePayload.blockNumber) testCond checkPrevRandaoValue(env.engine.client, alternativePrevRandao, alternativePayload.blockNumber.uint64)
), return true
)) ))
testCond pbRes
# The reorg actually happens after the CLMocker continues, # The reorg actually happens after the CLMocker continues,
# verify here that the reorg was successful # verify here that the reorg was successful
latestBlockNum = env.clMock.LatestHeadNumber.Uint64() let latestBlockNum = env.clMock.latestHeadNumber.uint64
checkPrevRandaoValue(t, env.clMock.PrevRandaoHistory[latestBlockNum], latestBlockNum) testCond checkPrevRandaoValue(env.engine.client, env.clMock.prevRandaoHistory[latestBlockNum], latestBlockNum)
return true
)
# Test performing a re-org that involves removing or modifying a transaction # Test performing a re-org that involves removing or modifying a transaction
type type
TransactionReOrgScenario = enum TransactionReOrgScenario = enum
TransactionNoScenario
TransactionReOrgScenarioReOrgOut = "Re-Org Out" TransactionReOrgScenarioReOrgOut = "Re-Org Out"
TransactionReOrgScenarioReOrgBackIn = "Re-Org Back In" TransactionReOrgScenarioReOrgBackIn = "Re-Org Back In"
TransactionReOrgScenarioReOrgDifferentBlock = "Re-Org to Different Block" TransactionReOrgScenarioReOrgDifferentBlock = "Re-Org to Different Block"
@ -117,8 +114,8 @@ type
type type
TransactionReOrgTest* = ref object of EngineSpec TransactionReOrgTest* = ref object of EngineSpec
TransactionCount int transactionCount*: int
Scenario TransactionReOrgScenario scenario*: TransactionReOrgScenario
method withMainFork(cs: TransactionReOrgTest, fork: EngineFork): BaseSpec = method withMainFork(cs: TransactionReOrgTest, fork: EngineFork): BaseSpec =
var res = cs.clone() var res = cs.clone()
@ -126,10 +123,9 @@ method withMainFork(cs: TransactionReOrgTest, fork: EngineFork): BaseSpec =
return res return res
method getName(cs: TransactionReOrgTest): string = method getName(cs: TransactionReOrgTest): string =
name = "Transaction Re-Org" var name = "Transaction Re-Org"
if s.Scenario != "" ( if cs.scenario != TransactionNoScenario:
name = fmt.Sprintf("%s, %s", name, s.Scenario) name.add ", " & $cs.scenario
)
return name return name
# Test transaction status after a forkchoiceUpdated re-orgs to an alternative hash where a transaction is not present # Test transaction status after a forkchoiceUpdated re-orgs to an alternative hash where a transaction is not present
@ -140,11 +136,11 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
# Produce blocks before starting the test (So we don't try to reorg back to the genesis block) # Produce blocks before starting the test (So we don't try to reorg back to the genesis block)
testCond env.clMock.produceBlocks(5, BlockProcessCallbacks()) testCond env.clMock.produceBlocks(5, BlockProcessCallbacks())
#[
# Create transactions that modify the state in order to check after the reorg. # Create transactions that modify the state in order to check after the reorg.
var ( var (
err error err error
txCount = spec.TransactionCount txCount = cs.transactionCount
sstoreContractAddr = common.HexToAddress("0000000000000000000000000000000000000317") sstoreContractAddr = common.HexToAddress("0000000000000000000000000000000000000317")
) )
@ -158,7 +154,6 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
data = common.LeftPadBytes([]byte(byte(i)), 32) data = common.LeftPadBytes([]byte(byte(i)), 32)
t.Logf("transactionReorg, i=%v, data=%v\n", i, data) t.Logf("transactionReorg, i=%v, data=%v\n", i, data)
return t.sendNextTx( return t.sendNextTx(
t.TestContext,
t.Engine, t.Engine,
BaseTx( BaseTx(
recipient: &sstoreContractAddr, recipient: &sstoreContractAddr,
@ -173,7 +168,7 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
) )
var ( var (
altPayload ExecutableData shadow.payload ExecutableData
nextTx Transaction nextTx Transaction
tx Transaction tx Transaction
) )
@ -184,7 +179,7 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
env.clMock.produceSingleBlock(BlockProcessCallbacks( env.clMock.produceSingleBlock(BlockProcessCallbacks(
OnPayloadAttributesGenerated: proc(): bool = OnPayloadAttributesGenerated: proc(): bool =
# At this point we have not broadcast the transaction. # At this point we have not broadcast the transaction.
if spec.Scenario == TransactionReOrgScenarioReOrgOut ( if cs.scenario == TransactionReOrgScenarioReOrgOut (
# Any payload we get should not contain any # Any payload we get should not contain any
payloadAttributes = env.clMock.LatestPayloadAttributes payloadAttributes = env.clMock.LatestPayloadAttributes
rand.Read(payloadAttributes.Random[:]) rand.Read(payloadAttributes.Random[:])
@ -195,25 +190,25 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
) )
g = env.engine.client.getPayload(r.Response.PayloadID, payloadAttributes) g = env.engine.client.getPayload(r.Response.PayloadID, payloadAttributes)
g.expectNoError() g.expectNoError()
altPayload = &g.Payload shadow.payload = &g.Payload
if len(altPayload.Transactions) != 0 ( if len(shadow.payload.transactions) != 0 (
fatal "Empty payload contains transactions: %v", t.TestName, altPayload) fatal "Empty payload contains transactions: %v", t.TestName, shadow.payload)
) )
) )
if spec.Scenario != TransactionReOrgScenarioReOrgBackIn ( if cs.scenario != TransactionReOrgScenarioReOrgBackIn (
# At this point we can broadcast the transaction and it will be included in the next payload # At this point we can broadcast the transaction and it will be included in the next payload
# Data is the key where a `1` will be stored # Data is the key where a `1` will be stored
tx, err = sendTransaction(i) tx, err = sendTransaction(i)
if err != nil ( testCond ok:
fatal "Error trying to send transaction: %v", t.TestName, err) fatal "Error trying to send transaction: %v", t.TestName, err)
) )
# Get the receipt # Get the receipt
ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout) ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout)
defer cancel() defer cancel()
receipt, _ = t.Eth.TransactionReceipt(ctx, tx.Hash()) receipt, _ = t.Eth.TransactionReceipt(ctx, shadow.txHash)
if receipt != nil ( if receipt != nil (
fatal "Receipt obtained before tx included in block: %v", t.TestName, receipt) fatal "Receipt obtained before tx included in block: %v", t.TestName, receipt)
) )
@ -222,36 +217,36 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
), ),
onGetpayload: proc(): bool = onGetpayload: proc(): bool =
# Check that indeed the payload contains the transaction # Check that indeed the payload contains the transaction
if tx != nil ( if shadow.tx.isSome:
if !TransactionInPayload(env.clMock.latestPayloadBuilt, tx) ( if !TransactionInPayload(env.clMock.latestPayloadBuilt, tx) (
fatal "Payload built does not contain the transaction: %v", t.TestName, env.clMock.latestPayloadBuilt) fatal "Payload built does not contain the transaction: %v", t.TestName, env.clMock.latestPayloadBuilt)
) )
) )
if spec.Scenario == TransactionReOrgScenarioReOrgDifferentBlock || spec.Scenario == TransactionReOrgScenarioNewPayloadOnRevert ( if cs.scenario == TransactionReOrgScenarioReOrgDifferentBlock || cs.scenario == TransactionReOrgScenarioNewPayloadOnRevert (
# Create side payload with different hash # Create side payload with different hash
var err error var err error
customizer = &CustomPayloadData( customizer = &CustomPayloadData(
extraData: &([]byte(0x01)), extraData: &([]byte(0x01)),
) )
altPayload, err = customizer.customizePayload(env.clMock.latestPayloadBuilt) shadow.payload, err = customizer.customizePayload(env.clMock.latestPayloadBuilt)
if err != nil ( testCond ok:
fatal "Error creating reorg payload %v", err) fatal "Error creating reorg payload %v", err)
) )
if altPayload.parentHash != env.clMock.latestPayloadBuilt.parentHash ( if shadow.payload.parentHash != env.clMock.latestPayloadBuilt.parentHash (
fatal "Incorrect parent hash for payloads: %v != %v", t.TestName, altPayload.parentHash, env.clMock.latestPayloadBuilt.parentHash) fatal "Incorrect parent hash for payloads: %v != %v", t.TestName, shadow.payload.parentHash, env.clMock.latestPayloadBuilt.parentHash)
) )
if altPayload.blockHash == env.clMock.latestPayloadBuilt.blockHash ( if shadow.payload.blockHash == env.clMock.latestPayloadBuilt.blockHash (
fatal "Incorrect hash for payloads: %v == %v", t.TestName, altPayload.blockHash, env.clMock.latestPayloadBuilt.blockHash) fatal "Incorrect hash for payloads: %v == %v", t.TestName, shadow.payload.blockHash, env.clMock.latestPayloadBuilt.blockHash)
) )
elif spec.Scenario == TransactionReOrgScenarioReOrgBackIn ( elif cs.scenario == TransactionReOrgScenarioReOrgBackIn (
# At this point we broadcast the transaction and request a new payload from the client that must # At this point we broadcast the transaction and request a new payload from the client that must
# contain the transaction. # contain the transaction.
# Since we are re-orging out and back in on the next block, the verification of this transaction # Since we are re-orging out and back in on the next block, the verification of this transaction
# being included happens on the next block # being included happens on the next block
nextTx, err = sendTransaction(i) shadow.nextTx, err = sendTransaction(i)
if err != nil ( testCond ok:
fatal "Error trying to send transaction: %v", t.TestName, err) fatal "Error trying to send transaction: %v", t.TestName, err)
) )
@ -275,7 +270,7 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
g = env.engine.client.getPayload(f.Response.PayloadID, env.clMock.LatestPayloadAttributes) g = env.engine.client.getPayload(f.Response.PayloadID, env.clMock.LatestPayloadAttributes)
g.expectNoError() g.expectNoError()
if !TransactionInPayload(g.Payload, nextTx) ( if !TransactionInPayload(g.Payload, shadow.nextTx) (
fatal "Payload built does not contain the transaction: %v", t.TestName, g.Payload) fatal "Payload built does not contain the transaction: %v", t.TestName, g.Payload)
) )
@ -291,59 +286,59 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
) )
), ),
onNewPayloadBroadcast: proc(): bool = onNewPayloadBroadcast: proc(): bool =
if tx != nil ( if shadow.tx.isSome:
# Get the receipt # Get the receipt
ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout) ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout)
defer cancel() defer cancel()
receipt, _ = t.Eth.TransactionReceipt(ctx, tx.Hash()) receipt, _ = t.Eth.TransactionReceipt(ctx, shadow.txHash)
if receipt != nil ( if receipt != nil (
fatal "Receipt obtained before tx included in block (NewPayload): %v", t.TestName, receipt) fatal "Receipt obtained before tx included in block (NewPayload): %v", t.TestName, receipt)
) )
) )
), ),
onForkchoiceBroadcast: proc(): bool = onForkchoiceBroadcast: proc(): bool =
if spec.Scenario != TransactionReOrgScenarioReOrgBackIn ( if cs.scenario != TransactionReOrgScenarioReOrgBackIn (
# Transaction is now in the head of the canonical chain, re-org and verify it's removed # Transaction is now in the head of the canonical chain, re-org and verify it's removed
# Get the receipt # Get the receipt
txt = env.engine.client.txReceipt(tx.Hash()) txt = env.engine.client.txReceipt(shadow.txHash)
txt.expectBlockHash(env.clMock.latestForkchoice.headBlockHash) txt.expectBlockHash(env.clMock.latestForkchoice.headBlockHash)
if altPayload.parentHash != env.clMock.latestPayloadBuilt.parentHash ( if shadow.payload.parentHash != env.clMock.latestPayloadBuilt.parentHash (
fatal "Incorrect parent hash for payloads: %v != %v", t.TestName, altPayload.parentHash, env.clMock.latestPayloadBuilt.parentHash) fatal "Incorrect parent hash for payloads: %v != %v", t.TestName, shadow.payload.parentHash, env.clMock.latestPayloadBuilt.parentHash)
) )
if altPayload.blockHash == env.clMock.latestPayloadBuilt.blockHash ( if shadow.payload.blockHash == env.clMock.latestPayloadBuilt.blockHash (
fatal "Incorrect hash for payloads: %v == %v", t.TestName, altPayload.blockHash, env.clMock.latestPayloadBuilt.blockHash) fatal "Incorrect hash for payloads: %v == %v", t.TestName, shadow.payload.blockHash, env.clMock.latestPayloadBuilt.blockHash)
) )
if altPayload == nil ( if shadow.payload == nil (
fatal "No payload to re-org to", t.TestName) fatal "No payload to re-org to", t.TestName)
) )
r = env.engine.client.newPayload(altPayload) r = env.engine.client.newPayload(shadow.payload)
r.expectStatus(PayloadExecutionStatus.valid) r.expectStatus(PayloadExecutionStatus.valid)
r.expectLatestValidHash(altPayload.blockHash) r.expectLatestValidHash(shadow.payload.blockHash)
s = env.engine.client.forkchoiceUpdated(api.ForkchoiceStateV1( s = env.engine.client.forkchoiceUpdated(api.ForkchoiceStateV1(
headBlockHash: altPayload.blockHash, headBlockHash: shadow.payload.blockHash,
safeBlockHash: env.clMock.latestForkchoice.safeBlockHash, safeBlockHash: env.clMock.latestForkchoice.safeBlockHash,
finalizedBlockHash: env.clMock.latestForkchoice.finalizedBlockHash, finalizedBlockHash: env.clMock.latestForkchoice.finalizedBlockHash,
), nil, altPayload.timestamp) ), nil, shadow.payload.timestamp)
s.expectPayloadStatus(PayloadExecutionStatus.valid) s.expectPayloadStatus(PayloadExecutionStatus.valid)
p = env.engine.client.headerByNumber(Head) p = env.engine.client.headerByNumber(Head)
p.expectHash(altPayload.blockHash) p.expectHash(shadow.payload.blockHash)
txt = env.engine.client.txReceipt(tx.Hash()) txt = env.engine.client.txReceipt(shadow.txHash)
if spec.Scenario == TransactionReOrgScenarioReOrgOut ( if cs.scenario == TransactionReOrgScenarioReOrgOut (
if txt.Receipt != nil ( if txt.Receipt != nil (
receiptJson, _ = json.MarshalIndent(txt.Receipt, "", " ") receiptJson, _ = json.MarshalIndent(txt.Receipt, "", " ")
fatal "Receipt was obtained when the tx had been re-org'd out: %s", t.TestName, receiptJson) fatal "Receipt was obtained when the tx had been re-org'd out: %s", t.TestName, receiptJson)
) )
elif spec.Scenario == TransactionReOrgScenarioReOrgDifferentBlock || spec.Scenario == TransactionReOrgScenarioNewPayloadOnRevert ( elif cs.scenario == TransactionReOrgScenarioReOrgDifferentBlock || cs.scenario == TransactionReOrgScenarioNewPayloadOnRevert (
txt.expectBlockHash(altPayload.blockHash) txt.expectBlockHash(shadow.payload.blockHash)
) )
# Re-org back # Re-org back
if spec.Scenario == TransactionReOrgScenarioNewPayloadOnRevert ( if cs.scenario == TransactionReOrgScenarioNewPayloadOnRevert (
r = env.engine.client.newPayload(env.clMock.latestPayloadBuilt) r = env.engine.client.newPayload(env.clMock.latestPayloadBuilt)
r.expectStatus(PayloadExecutionStatus.valid) r.expectStatus(PayloadExecutionStatus.valid)
r.expectLatestValidHash(env.clMock.latestPayloadBuilt.blockHash) r.expectLatestValidHash(env.clMock.latestPayloadBuilt.blockHash)
@ -351,21 +346,21 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
env.clMock.BroadcastForkchoiceUpdated(env.clMock.latestForkchoice, nil, 1) env.clMock.BroadcastForkchoiceUpdated(env.clMock.latestForkchoice, nil, 1)
) )
if tx != nil ( if shadow.tx.isSome:
# Now it should be back with main payload # Now it should be back with main payload
txt = env.engine.client.txReceipt(tx.Hash()) txt = env.engine.client.txReceipt(shadow.txHash)
txt.expectBlockHash(env.clMock.latestForkchoice.headBlockHash) txt.expectBlockHash(env.clMock.latestForkchoice.headBlockHash)
if spec.Scenario != TransactionReOrgScenarioReOrgBackIn ( if cs.scenario != TransactionReOrgScenarioReOrgBackIn (
tx = nil tx = nil
) )
) )
if spec.Scenario == TransactionReOrgScenarioReOrgBackIn && i > 0 ( if cs.scenario == TransactionReOrgScenarioReOrgBackIn && i > 0 (
# Reasoning: Most of the clients do not re-add blob transactions to the pool # Reasoning: Most of the clients do not re-add blob transactions to the pool
# after a re-org, so we need to wait until the next tx is sent to actually # after a re-org, so we need to wait until the next tx is sent to actually
# verify. # verify.
tx = nextTx tx = shadow.nextTx
) )
), ),
@ -373,7 +368,7 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
) )
if tx != nil ( if shadow.tx.isSome:
# Produce one last block and verify that the block contains the transaction # Produce one last block and verify that the block contains the transaction
env.clMock.produceSingleBlock(BlockProcessCallbacks( env.clMock.produceSingleBlock(BlockProcessCallbacks(
onForkchoiceBroadcast: proc(): bool = onForkchoiceBroadcast: proc(): bool =
@ -383,7 +378,7 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
# Get the receipt # Get the receipt
ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout) ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout)
defer cancel() defer cancel()
receipt, _ = t.Eth.TransactionReceipt(ctx, tx.Hash()) receipt, _ = t.Eth.TransactionReceipt(ctx, shadow.txHash)
if receipt == nil ( if receipt == nil (
fatal "Receipt not obtained after tx included in block: %v", t.TestName, receipt) fatal "Receipt not obtained after tx included in block: %v", t.TestName, receipt)
) )
@ -393,17 +388,18 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
) )
) )
]#
# Test that performing a re-org back into a previous block of the canonical chain does not produce errors and the chain # Test that performing a re-org back into a previous block of the canonical chain does not produce errors and the chain
# is still capable of progressing. # is still capable of progressing.
type type
ReOrgBackToCanonicalTest* = ref object of EngineSpec ReOrgBackToCanonicalTest* = ref object of EngineSpec
# Depth of the re-org to back in the canonical chain # Depth of the re-org to back in the canonical chain
ReOrgDepth uint64 reOrgDepth*: int
# Number of transactions to send on each payload # Number of transactions to send on each payload
TransactionPerPayload uint64 transactionPerPayload*: int
# Whether to execute a sidechain payload on the re-org # Whether to execute a sidechain payload on the re-org
ExecuteSidePayloadOnReOrg bool executeSidePayloadOnReOrg*: bool
method withMainFork(cs: ReOrgBackToCanonicalTest, fork: EngineFork): BaseSpec = method withMainFork(cs: ReOrgBackToCanonicalTest, fork: EngineFork): BaseSpec =
var res = cs.clone() var res = cs.clone()
@ -411,19 +407,16 @@ method withMainFork(cs: ReOrgBackToCanonicalTest, fork: EngineFork): BaseSpec =
return res return res
method getName(cs: ReOrgBackToCanonicalTest): string = method getName(cs: ReOrgBackToCanonicalTest): string =
name = fmt.Sprintf("Re-Org Back into Canonical Chain, Depth=%d", s.ReOrgDepth) var name = "Re-Org Back into Canonical Chain, Depth=" & $cs.reOrgDepth
if s.ExecuteSidePayloadOnReOrg ( if cs.executeSidePayloadOnReOrg:
name += ", Execute Side Payload on Re-Org" name.add ", Execute Side Payload on Re-Org"
)
return name return name
proc getDepth(cs: ReOrgBackToCanonicalTest): int = proc getDepth(cs: ReOrgBackToCanonicalTest): int =
if s.ReOrgDepth == 0 ( if cs.reOrgDepth == 0:
return 3 return 3
) return cs.reOrgDepth
return s.ReOrgDepth
)
# Test that performing a re-org back into a previous block of the canonical chain does not produce errors and the chain # Test that performing a re-org back into a previous block of the canonical chain does not produce errors and the chain
# is still capable of progressing. # is still capable of progressing.
@ -433,25 +426,25 @@ method execute(cs: ReOrgBackToCanonicalTest, env: TestEnv): bool =
testCond ok testCond ok
# Check the CLMock configured safe and finalized # Check the CLMock configured safe and finalized
if env.clMock.slotsToSafe.Cmp(new(big.Int).SetUint64(spec.ReOrgDepth)) <= 0 ( testCond env.clMock.slotsToSafe > cs.reOrgDepth:
fatal "[TEST ISSUE] CLMock configured slots to safe less than re-org depth: %v <= %v", t.TestName, env.clMock.slotsToSafe, spec.ReOrgDepth) fatal "[TEST ISSUE] CLMock configured slots to safe less than re-org depth"
)
if env.clMock.slotsToFinalized.Cmp(new(big.Int).SetUint64(spec.ReOrgDepth)) <= 0 ( testCond env.clMock.slotsToFinalized > cs.reOrgDepth:
fatal "[TEST ISSUE] CLMock configured slots to finalized less than re-org depth: %v <= %v", t.TestName, env.clMock.slotsToFinalized, spec.ReOrgDepth) fatal "[TEST ISSUE] CLMock configured slots to finalized less than re-org depth"
)
# Produce blocks before starting the test (So we don't try to reorg back to the genesis block) # Produce blocks before starting the test (So we don't try to reorg back to the genesis block)
testCond env.clMock.produceBlocks(5, BlockProcessCallbacks()) testCond env.clMock.produceBlocks(5, BlockProcessCallbacks())
#[
# We are going to reorg back to a previous hash several times # We are going to reorg back to a previous hash several times
previousHash = env.clMock.latestForkchoice.headBlockHash previousHash = env.clMock.latestForkchoice.headBlockHash
previousTimestamp = env.clMock.latestPayloadBuilt.timestamp previousTimestamp = env.clMock.latestPayloadBuilt.timestamp
if spec.ExecuteSidePayloadOnReOrg ( if cs.executeSidePayloadOnReOrg (
var ( var (
sidePayload ExecutableData shadow.payload ExecutableData
sidePayloadParentForkchoice api.ForkchoiceStateV1 shadow.parentForkchoice api.ForkchoiceStateV1
sidePayloadParentTimestamp uint64 shadow.parentTimestamp uint64
) )
env.clMock.produceSingleBlock(BlockProcessCallbacks( env.clMock.produceSingleBlock(BlockProcessCallbacks(
OnPayloadAttributesGenerated: proc(): bool = OnPayloadAttributesGenerated: proc(): bool =
@ -464,13 +457,13 @@ method execute(cs: ReOrgBackToCanonicalTest, env: TestEnv): bool =
) )
g = env.engine.client.getPayload(r.Response.PayloadID, payloadAttributes) g = env.engine.client.getPayload(r.Response.PayloadID, payloadAttributes)
g.expectNoError() g.expectNoError()
sidePayload = &g.Payload shadow.payload = &g.Payload
sidePayloadParentForkchoice = env.clMock.latestForkchoice shadow.parentForkchoice = env.clMock.latestForkchoice
sidePayloadParentTimestamp = env.clMock.latestHeader.Time shadow.parentTimestamp = env.clMock.latestHeader.Time
), ),
)) ))
# Continue producing blocks until we reach the depth of the re-org # Continue producing blocks until we reach the depth of the re-org
testCond env.clMock.produceBlocks(int(spec.GetDepth()-1), BlockProcessCallbacks( testCond env.clMock.produceBlocks(int(cs.GetDepth()-1), BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
# Send a transaction on each payload of the canonical chain # Send a transaction on each payload of the canonical chain
var err error var err error
@ -485,9 +478,9 @@ method execute(cs: ReOrgBackToCanonicalTest, env: TestEnv): bool =
gasLimit: 75000, gasLimit: 75000,
ForkConfig: t.ForkConfig, ForkConfig: t.ForkConfig,
), ),
spec.TransactionPerPayload, cs.transactionPerPayload,
) )
if err != nil ( testCond ok:
fatal "Error trying to send transactions: %v", t.TestName, err) fatal "Error trying to send transactions: %v", t.TestName, err)
) )
), ),
@ -498,19 +491,19 @@ method execute(cs: ReOrgBackToCanonicalTest, env: TestEnv): bool =
onGetpayload: proc(): bool = onGetpayload: proc(): bool =
# We are about to execute the new payload of the canonical chain, re-org back to # We are about to execute the new payload of the canonical chain, re-org back to
# the side payload # the side payload
f = env.engine.client.forkchoiceUpdated(sidePayloadParentForkchoice, nil, sidePayloadParentTimestamp) f = env.engine.client.forkchoiceUpdated(shadow.parentForkchoice, nil, shadow.parentTimestamp)
f.expectPayloadStatus(PayloadExecutionStatus.valid) f.expectPayloadStatus(PayloadExecutionStatus.valid)
f.expectLatestValidHash(sidePayloadParentForkchoice.headBlockHash) f.expectLatestValidHash(shadow.parentForkchoice.headBlockHash)
# Execute the side payload # Execute the side payload
n = env.engine.client.newPayload(sidePayload) n = env.engine.client.newPayload(shadow.payload)
n.expectStatus(PayloadExecutionStatus.valid) n.expectStatus(PayloadExecutionStatus.valid)
n.expectLatestValidHash(sidePayload.blockHash) n.expectLatestValidHash(shadow.payload.blockHash)
# At this point the next canonical payload will be executed by the CL mock, so we can # At this point the next canonical payload will be executed by the CL mock, so we can
# continue producing blocks # continue producing blocks
), ),
)) ))
else: else:
testCond env.clMock.produceBlocks(int(spec.GetDepth()), BlockProcessCallbacks( testCond env.clMock.produceBlocks(int(cs.GetDepth()), BlockProcessCallbacks(
onForkchoiceBroadcast: proc(): bool = onForkchoiceBroadcast: proc(): bool =
# Send a fcU with the headBlockHash pointing back to the previous block # Send a fcU with the headBlockHash pointing back to the previous block
forkchoiceUpdatedBack = api.ForkchoiceStateV1( forkchoiceUpdatedBack = api.ForkchoiceStateV1(
@ -533,21 +526,22 @@ method execute(cs: ReOrgBackToCanonicalTest, env: TestEnv): bool =
# Verify that the client is pointing to the latest payload sent # Verify that the client is pointing to the latest payload sent
r = env.engine.client.headerByNumber(Head) r = env.engine.client.headerByNumber(Head)
r.expectHash(env.clMock.latestPayloadBuilt.blockHash) r.expectHash(env.clMock.latestPayloadBuilt.blockHash)
) )
]#
type type
ReOrgBackFromSyncingTest* = ref object of EngineSpec ReOrgBackFromSyncingTest* = ref object of EngineSpec
Shadow = ref object
payloads: seq[ExecutableData]
method withMainFork(cs: ReOrgBackFromSyncingTest, fork: EngineFork): BaseSpec = method withMainFork(cs: ReOrgBackFromSyncingTest, fork: EngineFork): BaseSpec =
var res = cs.clone() var res = cs.clone()
res.mainFork = fork res.mainFork = fork
return res return res
method getName(cs: ReOrgBackFromSyncingTest): string = method getName(cs: ReOrgBackFromSyncingTest): string =
name = "Re-Org Back to Canonical Chain From Syncing Chain" "Re-Org Back to Canonical Chain From Syncing Chain"
return name
)
# Test that performs a re-org back to the canonical chain after re-org to syncing/unavailable chain. # Test that performs a re-org back to the canonical chain after re-org to syncing/unavailable chain.
method execute(cs: ReOrgBackFromSyncingTest, env: TestEnv): bool = method execute(cs: ReOrgBackFromSyncingTest, env: TestEnv): bool =
@ -556,72 +550,71 @@ method execute(cs: ReOrgBackFromSyncingTest, env: TestEnv): bool =
testCond ok testCond ok
# Produce an alternative chain # Produce an alternative chain
sidechainPayloads = make([]ExecutableData, 0) var shadow = Shadow()
testCond env.clMock.produceBlocks(10, BlockProcessCallbacks( var pbRes = env.clMock.produceBlocks(10, BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
# Send a transaction on each payload of the canonical chain # Send a transaction on each payload of the canonical chain
var err error let tc = BaseTx(
_, err = t.sendNextTx( recipient: some(ZeroAddr),
t.TestContext, amount: 1.u256,
t.Engine, txType: cs.txType,
BaseTx( gasLimit: 75000,
recipient: &ZeroAddr,
amount: big1,
payload: nil,
txType: cs.txType,
gasLimit: 75000,
),
) )
if err != nil ( let ok = env.sendNextTx(env.engine, tc)
fatal "Error trying to send transactions: %v", t.TestName, err) testCond ok:
) fatal "Error trying to send transactions"
), return true
,
onGetpayload: proc(): bool = onGetpayload: proc(): bool =
# Check that at least one transaction made it into the payload # Check that at least one transaction made it into the payload
if len(env.clMock.latestPayloadBuilt.Transactions) == 0 ( testCond len(env.clMock.latestPayloadBuilt.transactions) > 0:
fatal "No transactions in payload: %v", t.TestName, env.clMock.latestPayloadBuilt) fatal "No transactions in payload"
)
# Generate an alternative payload by simply adding extraData to the block # Generate an alternative payload by simply adding extraData to the block
altParentHash = env.clMock.latestPayloadBuilt.parentHash var altParentHash = env.clMock.latestPayloadBuilt.parentHash
if len(sidechainPayloads) > 0 ( if len(shadow.payloads) > 0:
altParentHash = sidechainPayloads[len(sidechainPayloads)-1].blockHash altParentHash = shadow.payloads[^1].blockHash
let customizer = CustomPayloadData(
parentHash: some(ethHash altParentHash),
extraData: some(@[0x01.byte]),
) )
customizer = &CustomPayloadData(
parentHash: &altParentHash, let payload = customizer.customizePayload(env.clMock.latestExecutableData)
extraData: &([]byte(0x01)), shadow.payloads.add payload
) return true
altPayload, err = customizer.customizePayload(env.clMock.latestPayloadBuilt)
if err != nil (
fatal "Unable to customize payload: %v", t.TestName, err)
)
sidechainPayloads = append(sidechainPayloads, altPayload)
),
)) ))
env.clMock.produceSingleBlock(BlockProcessCallbacks( testCond pbRes
pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
onGetpayload: proc(): bool = onGetpayload: proc(): bool =
# Re-org to the unavailable sidechain in the middle of block production # Re-org to the unavailable sidechain in the middle of block production
# to be able to re-org back to the canonical chain # to be able to re-org back to the canonical chain
r = env.engine.client.newPayload(sidechainPayloads[len(sidechainPayloads)-1]) var version = env.engine.version(shadow.payloads[^1].timestamp)
r.expectStatusEither(PayloadExecutionStatus.syncing, PayloadExecutionStatus.accepted) let r = env.engine.client.newPayload(version, shadow.payloads[^1])
r.expectLatestValidHash(nil) r.expectStatusEither([PayloadExecutionStatus.syncing, PayloadExecutionStatus.accepted])
r.expectLatestValidHash()
# We are going to send one of the alternative payloads and fcU to it # We are going to send one of the alternative payloads and fcU to it
forkchoiceUpdatedBack = api.ForkchoiceStateV1( let fcu = ForkchoiceStateV1(
headBlockHash: sidechainPayloads[len(sidechainPayloads)-1].blockHash, headBlockHash: shadow.payloads[^1].blockHash,
safeBlockHash: env.clMock.latestForkchoice.safeBlockHash, safeBlockHash: env.clMock.latestForkchoice.safeBlockHash,
finalizedBlockHash: env.clMock.latestForkchoice.finalizedBlockHash, finalizedBlockHash: env.clMock.latestForkchoice.finalizedBlockHash,
) )
# It is only expected that the client does not produce an error and the CL Mocker is able to progress after the re-org # It is only expected that the client does not produce an error and the CL Mocker is able to progress after the re-org
s = env.engine.client.forkchoiceUpdated(forkchoiceUpdatedBack, nil, sidechainPayloads[len(sidechainPayloads)-1].timestamp) version = env.engine.version(shadow.payloads[^1].timestamp)
s.expectLatestValidHash(nil) let s = env.engine.client.forkchoiceUpdated(version, fcu)
s.expectLatestValidHash()
s.expectPayloadStatus(PayloadExecutionStatus.syncing) s.expectPayloadStatus(PayloadExecutionStatus.syncing)
# After this, the CLMocker will continue and try to re-org to canonical chain once again # After this, the CLMocker will continue and try to re-org to canonical chain once again
# CLMocker will fail the test if this is not possible, so nothing left to do. # CLMocker will fail the test if this is not possible, so nothing left to do.
), return true
)) ))
) testCond pbRes
return true
type type
ReOrgPrevValidatedPayloadOnSideChainTest* = ref object of EngineSpec ReOrgPrevValidatedPayloadOnSideChainTest* = ref object of EngineSpec
@ -632,9 +625,15 @@ method withMainFork(cs: ReOrgPrevValidatedPayloadOnSideChainTest, fork: EngineFo
return res return res
method getName(cs: ReOrgPrevValidatedPayloadOnSideChainTest): string = method getName(cs: ReOrgPrevValidatedPayloadOnSideChainTest): string =
name = "Re-org to Previously Validated Sidechain Payload" "Re-org to Previously Validated Sidechain Payload"
return name
) func toSeq(x: string): seq[byte] =
for z in x:
result.add z.byte
func ethAddress(a, b: int): EthAddress =
result[0] = a.byte
result[1] = b.byte
# Test that performs a re-org to a previously validated payload on a side chain. # Test that performs a re-org to a previously validated payload on a side chain.
method execute(cs: ReOrgPrevValidatedPayloadOnSideChainTest, env: TestEnv): bool = method execute(cs: ReOrgPrevValidatedPayloadOnSideChainTest, env: TestEnv): bool =
@ -645,99 +644,90 @@ method execute(cs: ReOrgPrevValidatedPayloadOnSideChainTest, env: TestEnv): bool
# Produce blocks before starting the test # Produce blocks before starting the test
testCond env.clMock.produceBlocks(5, BlockProcessCallbacks()) testCond env.clMock.produceBlocks(5, BlockProcessCallbacks())
var ( var shadow = Shadow()
sidechainPayloads = make([]ExecutableData, 0)
sidechainPayloadCount = 5
)
# Produce a canonical chain while at the same time generate a side chain to which we will re-org. # Produce a canonical chain while at the same time generate a side chain to which we will re-org.
testCond env.clMock.produceBlocks(sidechainPayloadCount, BlockProcessCallbacks( var pbRes = env.clMock.produceBlocks(5, BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
# Send a transaction on each payload of the canonical chain # Send a transaction on each payload of the canonical chain
var err error let tc = BaseTx(
_, err = t.sendNextTx( recipient: some(ZeroAddr),
t.TestContext, amount: 1.u256,
t.Engine, txType: cs.txType,
BaseTx( gasLimit: 75000,
recipient: &ZeroAddr,
amount: big1,
payload: nil,
txType: cs.txType,
gasLimit: 75000,
ForkConfig: t.ForkConfig,
),
) )
if err != nil ( let ok = env.sendNextTx(env.engine, tc)
fatal "Error trying to send transactions: %v", t.TestName, err) testCond ok:
) fatal "Error trying to send transactions"
), return true
,
onGetpayload: proc(): bool = onGetpayload: proc(): bool =
# Check that at least one transaction made it into the payload # Check that at least one transaction made it into the payload
if len(env.clMock.latestPayloadBuilt.Transactions) == 0 ( testCond len(env.clMock.latestPayloadBuilt.transactions) > 0:
fatal "No transactions in payload: %v", t.TestName, env.clMock.latestPayloadBuilt) fatal "No transactions in payload"
)
# The side chain will consist simply of the same payloads with extra data appended
extraData = []byte("side")
customData = CustomPayloadData(
extraData: &extraData,
)
if len(sidechainPayloads) > 0 (
customData.parentHash = &sidechainPayloads[len(sidechainPayloads)-1].blockHash
)
altPayload, err = customData.customizePayload(env.clMock.latestPayloadBuilt)
if err != nil (
fatal "Unable to customize payload: %v", t.TestName, err)
)
sidechainPayloads = append(sidechainPayloads, altPayload)
r = env.engine.client.newPayload(altPayload) # The side chain will consist simply of the same payloads with extra data appended
var customData = CustomPayloadData(
extraData: some(toSeq("side")),
)
if len(shadow.payloads) > 0:
customData.parentHash = some(ethHash shadow.payloads[^1].blockHash)
let payload = customData.customizePayload(env.clMock.latestExecutableData)
shadow.payloads.add payload
let version = env.engine.version(payload.timestamp)
let r = env.engine.client.newPayload(version, payload)
r.expectStatus(PayloadExecutionStatus.valid) r.expectStatus(PayloadExecutionStatus.valid)
r.expectLatestValidHash(altPayload.blockHash) r.expectLatestValidHash(payload.blockHash)
), return true
)) ))
testCond pbRes
# Attempt to re-org to one of the sidechain payloads, but not the leaf, # Attempt to re-org to one of the sidechain payloads, but not the leaf,
# and also build a new payload from this sidechain. # and also build a new payload from this sidechain.
env.clMock.produceSingleBlock(BlockProcessCallbacks( pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
onGetpayload: proc(): bool = onGetpayload: proc(): bool =
var ( var
prevRandao = common.Hash() prevRandao = common.Hash256.randomBytes()
suggestedFeeRecipient = common.Address(0x12, 0x34) suggestedFeeRecipient = ethAddress(0x12, 0x34)
)
rand.Read(prevRandao[:]) let payloadAttributesCustomizer = BasePayloadAttributesCustomizer(
payloadAttributesCustomizer = &BasePayloadAttributesCustomizer( prevRandao: some(prevRandao),
Random: &prevRandao, suggestedFeerecipient: some(suggestedFeeRecipient),
SuggestedFeerecipient: &suggestedFeeRecipient,
) )
reOrgPayload = sidechainPayloads[len(sidechainPayloads)-2] let reOrgPayload = shadow.payloads[^2]
reOrgPayloadAttributes = sidechainPayloads[len(sidechainPayloads)-1].PayloadAttributes let reOrgPayloadAttributes = shadow.payloads[^1].attr
let newPayloadAttributes = payloadAttributesCustomizer.getPayloadAttributes(reOrgPayloadAttributes)
newPayloadAttributes, err = payloadAttributesCustomizer.getPayloadAttributes(reOrgPayloadAttributes) let fcu = ForkchoiceStateV1(
if err != nil (
fatal "Unable to customize payload attributes: %v", t.TestName, err)
)
r = env.engine.client.forkchoiceUpdated(api.ForkchoiceStateV1(
headBlockHash: reOrgPayload.blockHash, headBlockHash: reOrgPayload.blockHash,
safeBlockHash: env.clMock.latestForkchoice.safeBlockHash, safeBlockHash: env.clMock.latestForkchoice.safeBlockHash,
finalizedBlockHash: env.clMock.latestForkchoice.finalizedBlockHash, finalizedBlockHash: env.clMock.latestForkchoice.finalizedBlockHash,
), newPayloadAttributes, reOrgPayload.timestamp) )
var version = env.engine.version(reOrgPayload.timestamp)
let r = env.engine.client.forkchoiceUpdated(version, fcu, some(newPayloadAttributes))
r.expectPayloadStatus(PayloadExecutionStatus.valid) r.expectPayloadStatus(PayloadExecutionStatus.valid)
r.expectLatestValidHash(reOrgPayload.blockHash) r.expectLatestValidHash(reOrgPayload.blockHash)
p = env.engine.client.getPayload(r.Response.PayloadID, newPayloadAttributes) version = env.engine.version(newPayloadAttributes.timestamp)
let p = env.engine.client.getPayload(r.get.payloadID.get, version)
p.expectPayloadParentHash(reOrgPayload.blockHash) p.expectPayloadParentHash(reOrgPayload.blockHash)
s = env.engine.client.newPayload(p.Payload) let payload = p.get.executionPayload
let s = env.engine.client.newPayload(payload)
s.expectStatus(PayloadExecutionStatus.valid) s.expectStatus(PayloadExecutionStatus.valid)
s.expectLatestValidHash(p.Payload.blockHash) s.expectLatestValidHash(payload.blockHash)
# After this, the CLMocker will continue and try to re-org to canonical chain once again # After this, the CLMocker will continue and try to re-org to canonical chain once again
# CLMocker will fail the test if this is not possible, so nothing left to do. # CLMocker will fail the test if this is not possible, so nothing left to do.
), return true
)) ))
) testCond pbRes
return true
type type
SafeReOrgToSideChainTest* = ref object of EngineSpec SafeReOrgToSideChainTest* = ref object of EngineSpec
@ -748,9 +738,7 @@ method withMainFork(cs: SafeReOrgToSideChainTest, fork: EngineFork): BaseSpec =
return res return res
method getName(cs: SafeReOrgToSideChainTest): string = method getName(cs: SafeReOrgToSideChainTest): string =
name = "Safe Re-Org to Side Chain" "Safe Re-Org to Side Chain"
return name
)
# Test that performs a re-org of the safe block to a side chain. # Test that performs a re-org of the safe block to a side chain.
method execute(cs: SafeReOrgToSideChainTest, env: TestEnv): bool = method execute(cs: SafeReOrgToSideChainTest, env: TestEnv): bool =
@ -759,70 +747,74 @@ method execute(cs: SafeReOrgToSideChainTest, env: TestEnv): bool =
testCond ok testCond ok
# Produce an alternative chain # Produce an alternative chain
sidechainPayloads = make([]ExecutableData, 0) var shadow = Shadow()
if s.slotsToSafe.Uint64() != 1 ( testCond cs.slotsToSafe == 1:
fatal "[TEST ISSUE] CLMock configured slots to safe not equal to 1: %v", t.TestName, s.slotsToSafe) fatal "[TEST ISSUE] CLMock configured slots to safe not equal to 1"
)
if s.slotsToFinalized.Uint64() != 2 ( testCond cs.slotsToFinalized == 2:
fatal "[TEST ISSUE] CLMock configured slots to finalized not equal to 2: %v", t.TestName, s.slotsToFinalized) fatal "[TEST ISSUE] CLMock configured slots to finalized not equal to 2"
)
# Produce three payloads `P1`, `P2`, `P3`, along with the side chain payloads `P2'`, `P3'` # Produce three payloads `P1`, `P2`, `P3`, along with the side chain payloads `P2'`, `P3'`
# First payload is finalized so no alternative payload # First payload is finalized so no alternative payload
env.clMock.produceSingleBlock(BlockProcessCallbacks()) testCond env.clMock.produceSingleBlock(BlockProcessCallbacks())
testCond env.clMock.produceBlocks(2, BlockProcessCallbacks( testCond env.clMock.produceBlocks(2, BlockProcessCallbacks(
onGetpayload: proc(): bool = onGetpayload: proc(): bool =
# Generate an alternative payload by simply adding extraData to the block # Generate an alternative payload by simply adding extraData to the block
altParentHash = env.clMock.latestPayloadBuilt.parentHash var altParentHash = env.clMock.latestPayloadBuilt.parentHash
if len(sidechainPayloads) > 0 ( if len(shadow.payloads) > 0:
altParentHash = sidechainPayloads[len(sidechainPayloads)-1].blockHash altParentHash = shadow.payloads[^1].blockHash
let customizer = CustomPayloadData(
parentHash: some(ethHash altParentHash),
extraData: some(@[0x01.byte]),
) )
customizer = &CustomPayloadData(
parentHash: &altParentHash, let payload = customizer.customizePayload(env.clMock.latestExecutableData)
extraData: &([]byte(0x01)), shadow.payloads.add payload
) return true
altPayload, err = customizer.customizePayload(env.clMock.latestPayloadBuilt)
if err != nil (
fatal "Unable to customize payload: %v", t.TestName, err)
)
sidechainPayloads = append(sidechainPayloads, altPayload)
),
)) ))
# Verify current state of labels # Verify current state of labels
head = env.engine.client.headerByNumber(Head) let head = env.engine.client.namedHeader(Head)
head.expectHash(env.clMock.latestPayloadBuilt.blockHash) head.expectHash(ethHash env.clMock.latestPayloadBuilt.blockHash)
safe = env.engine.client.headerByNumber(Safe) let safe = env.engine.client.namedHeader(Safe)
safe.expectHash(env.clMock.executedPayloadHistory[2].blockHash) safe.expectHash(ethHash env.clMock.executedPayloadHistory[2].blockHash)
finalized = env.engine.client.headerByNumber(Finalized) let finalized = env.engine.client.namedHeader(Finalized)
finalized.expectHash(env.clMock.executedPayloadHistory[1].blockHash) finalized.expectHash(ethHash env.clMock.executedPayloadHistory[1].blockHash)
# Re-org the safe/head blocks to point to the alternative side chain # Re-org the safe/head blocks to point to the alternative side chain
env.clMock.produceSingleBlock(BlockProcessCallbacks( let pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
onGetpayload: proc(): bool = onGetpayload: proc(): bool =
for _, p = range sidechainPayloads ( for p in shadow.payloads:
r = env.engine.client.newPayload(p) let version = env.engine.version(p.timestamp)
r.expectStatusEither(PayloadExecutionStatus.valid, PayloadExecutionStatus.accepted) let r = env.engine.client.newPayload(version, p)
) r.expectStatusEither([PayloadExecutionStatus.valid, PayloadExecutionStatus.accepted])
r = env.engine.client.forkchoiceUpdated(api.ForkchoiceStateV1(
headBlockHash: sidechainPayloads[1].blockHash, let fcu = ForkchoiceStateV1(
safeBlockHash: sidechainPayloads[0].blockHash, headBlockHash: shadow.payloads[1].blockHash,
safeBlockHash: shadow.payloads[0].blockHash,
finalizedBlockHash: env.clMock.executedPayloadHistory[1].blockHash, finalizedBlockHash: env.clMock.executedPayloadHistory[1].blockHash,
), nil, sidechainPayloads[1].timestamp) )
let version = env.engine.version(shadow.payloads[1].timestamp)
let r = env.engine.client.forkchoiceUpdated(version, fcu)
r.expectPayloadStatus(PayloadExecutionStatus.valid) r.expectPayloadStatus(PayloadExecutionStatus.valid)
head = env.engine.client.headerByNumber(Head) let head = env.engine.client.namedHeader(Head)
head.expectHash(sidechainPayloads[1].blockHash) head.expectHash(ethHash shadow.payloads[1].blockHash)
safe = env.engine.client.headerByNumber(Safe) let safe = env.engine.client.namedHeader(Safe)
safe.expectHash(sidechainPayloads[0].blockHash) safe.expectHash(ethHash shadow.payloads[0].blockHash)
finalized = env.engine.client.headerByNumber(Finalized) let finalized = env.engine.client.namedHeader(Finalized)
finalized.expectHash(env.clMock.executedPayloadHistory[1].blockHash) finalized.expectHash(ethHash env.clMock.executedPayloadHistory[1].blockHash)
), return true
)) ))
)
testCond pbRes
return true

View File

@ -10,6 +10,8 @@
import import
std/strutils, std/strutils,
eth/common,
chronicles,
./engine_spec ./engine_spec
type type
@ -24,13 +26,16 @@ type
checkType*: BlockStatusRPCcheckType checkType*: BlockStatusRPCcheckType
# TODO: Syncing bool # TODO: Syncing bool
Shadow = ref object
txHash: common.Hash256
method withMainFork(cs: BlockStatus, fork: EngineFork): BaseSpec = method withMainFork(cs: BlockStatus, fork: EngineFork): BaseSpec =
var res = cs.clone() var res = cs.clone()
res.mainFork = fork res.mainFork = fork
return res return res
method getName(cs: BlockStatus): string = method getName(cs: BlockStatus): string =
"RPC" & $b.checkType "RPC " & $cs.checkType
# Test to verify Block information available at the Eth RPC after NewPayload/ForkchoiceUpdated # Test to verify Block information available at the Eth RPC after NewPayload/ForkchoiceUpdated
method execute(cs: BlockStatus, env: TestEnv): bool = method execute(cs: BlockStatus, env: TestEnv): bool =
@ -38,69 +43,69 @@ method execute(cs: BlockStatus, env: TestEnv): bool =
let ok = waitFor env.clMock.waitForTTD() let ok = waitFor env.clMock.waitForTTD()
testCond ok testCond ok
case b.checkType if cs.checkType in [SafeOnSafeblockHash, FinalizedOnFinalizedblockHash]:
of SafeOnSafeblockHash, FinalizedOnFinalizedblockHash: var number = Finalized
var number *big.Int if cs.checkType == SafeOnSafeblockHash:
if b.checkType == SafeOnSafeblockHash:
number = Safe number = Safe
else:
number = Finalized
p = env.engine.client.headerByNumber(number) let p = env.engine.client.namedHeader(number)
p.expectError() p.expectError()
)
# Produce blocks before starting the test # Produce blocks before starting the test
env.clMock.produceBlocks(5, BlockProcessCallbacks()) testCond env.clMock.produceBlocks(5, BlockProcessCallbacks())
var tx typ.Transaction var shadow = Shadow()
callbacks = BlockProcessCallbacks( var callbacks = BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool = onPayloadProducerSelected: proc(): bool =
let tc = BaseTx( let tc = BaseTx(
recipient: &ZeroAddr, recipient: some(ZeroAddr),
amount: 1.u256, amount: 1.u256,
txType: cs.txType, txType: cs.txType,
gasLimit: 75000, gasLimit: 75000,
),
let ok = env.sendNextTx(tc)
testCond ok:
fatal "Error trying to send transaction: %v", err)
) )
),
let tx = env.makeNextTx(tc)
shadow.txHash = tx.rlpHash
let ok = env.sendTx(tx)
testCond ok:
fatal "Error trying to send transaction"
return true
) )
case b.checkType ( case cs.checkType
of LatestOnNewPayload: of LatestOnNewPayload:
callbacks.onGetPayload = proc(): bool callbacks.onGetPayload = proc(): bool =
r = env.engine.client.latestHeader() let r = env.engine.client.namedHeader(Head)
r.expectHash(env.clMock.latestForkchoice.headblockHash) r.expectHash(ethHash env.clMock.latestForkchoice.headblockHash)
s = env.engine.client.TestBlockNumber() let s = env.engine.client.blockNumber()
s.ExpectNumber(env.clMock.latestHeadNumber.Uint64()) s.expectNumber(env.clMock.latestHeadNumber.uint64)
p = env.engine.client.latestHeader() let p = env.engine.client.namedHeader(Head)
p.expectHash(env.clMock.latestForkchoice.headblockHash) p.expectHash(ethHash env.clMock.latestForkchoice.headblockHash)
# Check that the receipt for the transaction we just sent is still not available # Check that the receipt for the transaction we just sent is still not available
q = env.engine.client.txReceipt(tx.Hash()) let q = env.engine.client.txReceipt(shadow.txHash)
q.expectError() q.expectError()
return true
of LatestOnHeadblockHash: of LatestOnHeadblockHash:
callbacks.onForkchoiceBroadcast = proc(): bool callbacks.onForkchoiceBroadcast = proc(): bool =
r = env.engine.client.latestHeader() let r = env.engine.client.namedHeader(Head)
r.expectHash(env.clMock.latestForkchoice.headblockHash) r.expectHash(ethHash env.clMock.latestForkchoice.headblockHash)
let s = env.engine.client.txReceipt(shadow.txHash)
s = env.engine.client.txReceipt(tx.Hash()) s.expectTransactionHash(shadow.txHash)
s.ExpectTransactionHash(tx.Hash()) return true
of SafeOnSafeblockHash: of SafeOnSafeblockHash:
callbacks.onSafeBlockChange = proc(): bool callbacks.onSafeBlockChange = proc(): bool =
r = env.engine.client.headerByNumber(Safe) let r = env.engine.client.namedHeader(Safe)
r.expectHash(env.clMock.latestForkchoice.safeblockHash) r.expectHash(ethHash env.clMock.latestForkchoice.safeblockHash)
return true
of FinalizedOnFinalizedblockHash: of FinalizedOnFinalizedblockHash:
callbacks.onFinalizedBlockChange = proc(): bool callbacks.onFinalizedBlockChange = proc(): bool =
r = env.engine.client.headerByNumber(Finalized) let r = env.engine.client.namedHeader(Finalized)
r.expectHash(env.clMock.latestForkchoice.finalizedblockHash) r.expectHash(ethHash env.clMock.latestForkchoice.finalizedblockHash)
return true
# Perform the test # Perform the test
env.clMock.produceSingleBlock(callbacks) testCond env.clMock.produceSingleBlock(callbacks)
return true

View File

@ -11,6 +11,8 @@
# Test versioning of the Engine API methods # Test versioning of the Engine API methods
import import
std/strutils, std/strutils,
chronicles,
../cancun/customizer,
./engine_spec ./engine_spec
type type
@ -25,7 +27,10 @@ method withMainFork(cs: EngineNewPayloadVersionTest, fork: EngineFork): BaseSpec
# when the timestamp payload attribute does not match the upgraded/downgraded version. # when the timestamp payload attribute does not match the upgraded/downgraded version.
type type
ForkchoiceUpdatedOnPayloadRequestTest* = ref object of EngineSpec ForkchoiceUpdatedOnPayloadRequestTest* = ref object of EngineSpec
ForkchoiceUpdatedCustomizer name*: string
about*: string
forkchoiceUpdatedCustomizer*: ForkchoiceUpdatedCustomizer
payloadAttributesCustomizer*: PayloadAttributesCustomizer
method withMainFork(cs: ForkchoiceUpdatedOnPayloadRequestTest, fork: EngineFork): BaseSpec = method withMainFork(cs: ForkchoiceUpdatedOnPayloadRequestTest, fork: EngineFork): BaseSpec =
var res = cs.clone() var res = cs.clone()
@ -33,43 +38,35 @@ method withMainFork(cs: ForkchoiceUpdatedOnPayloadRequestTest, fork: EngineFork)
return res return res
method getName(cs: ForkchoiceUpdatedOnPayloadRequestTest): string = method getName(cs: ForkchoiceUpdatedOnPayloadRequestTest): string =
return "ForkchoiceUpdated Version on Payload Request: " + cs.BaseSpec.GetName() "ForkchoiceUpdated Version on Payload Request: " & cs.name
method execute(cs: ForkchoiceUpdatedOnPayloadRequestTest, env: TestEnv): bool = method execute(cs: ForkchoiceUpdatedOnPayloadRequestTest, env: TestEnv): bool =
# Wait until TTD is reached by this client # Wait until TTD is reached by this client
let ok = waitFor env.clMockWaitForTTD() let ok = waitFor env.clMock.waitForTTD()
testCond ok testCond ok
env.clMock.produceSingleBlock(clmock.BlockProcessCallbacks( let pbRes = env.clMock.produceSingleBlock(clmock.BlockProcessCallbacks(
onPayloadAttributesGenerated: proc(): bool = onPayloadAttributesGenerated: proc(): bool =
var ( var
payloadAttributes = &env.clMockLatestPayloadAttributes attr = env.clMock.latestPayloadAttributes
expectedStatus test.PayloadStatus = PayloadExecutionStatus.valid expectedStatus = PayloadExecutionStatus.valid
expectedError *int
err error
)
cs.SetEngineAPIVersionResolver(t.ForkConfig)
testEngine = t.TestEngine.WithEngineAPIVersionResolver(cs.ForkchoiceUpdatedCustomizer)
payloadAttributes, err = cs.GetPayloadAttributes(payloadAttributes)
if err != nil (
t.Fatalf("FAIL: Error getting custom payload attributes: %v", err)
)
expectedError, err = cs.GetExpectedError()
if err != nil (
t.Fatalf("FAIL: Error getting custom expected error: %v", err)
)
if cs.GetExpectInvalidStatus() (
expectedStatus = PayloadExecutionStatus.invalid
)
r = env.engine.client.forkchoiceUpdated(env.clMockLatestForkchoice, payloadAttributes, env.clMockLatestHeader.Time) attr = cs.payloadAttributesCustomizer.getPayloadAttributes(attr)
r.ExpectationDescription = cs.Expectation
if expectedError != nil ( let expectedError = cs.forkchoiceUpdatedCustomizer.getExpectedError()
r.expectErrorCode(*expectedError) if cs.forkchoiceUpdatedCustomizer.getExpectInvalidStatus():
expectedStatus = PayloadExecutionStatus.invalid
cs.forkchoiceUpdatedCustomizer.setEngineAPIVersionResolver(env.engine.com)
let version = cs.forkchoiceUpdatedCustomizer.forkchoiceUpdatedVersion(env.clMock.latestHeader.timestamp.uint64)
let r = env.engine.client.forkchoiceUpdated(version, env.clMock.latestForkchoice, some(attr))
#r.ExpectationDescription = cs.Expectation
if expectedError != 0:
r.expectErrorCode(expectedError)
else: else:
r.expectNoError() r.expectNoError()
r.expectPayloadStatus(expectedStatus) r.expectPayloadStatus(expectedStatus)
) return true
),
)) ))
) testCond pbRes
return true

View File

@ -24,10 +24,10 @@ import
./cancun_tests ./cancun_tests
proc combineTests(): seq[TestDesc] = proc combineTests(): seq[TestDesc] =
#result.add wdTestList result.add wdTestList
#result.add ecTestList result.add ecTestList
#result.add authTestList result.add authTestList
#result.add engineTestList result.add engineTestList
result.add cancunTestList result.add cancunTestList
let let

View File

@ -20,18 +20,18 @@ import
import import
./engine/suggested_fee_recipient, ./engine/suggested_fee_recipient,
./engine/payload_attributes, ./engine/payload_attributes,
#./engine/payload_execution, ./engine/payload_execution,
./engine/invalid_ancestor, ./engine/invalid_ancestor,
./engine/invalid_payload, ./engine/invalid_payload,
./engine/prev_randao, ./engine/prev_randao,
#./engine/payload_id, ./engine/payload_id,
./engine/forkchoice, ./engine/forkchoice,
#./engine/versioning, ./engine/versioning,
./engine/bad_hash, ./engine/bad_hash,
#./engine/fork_id, ./engine/fork_id,
#./engine/reorg, ./engine/reorg,
./engine/misc ./engine/misc,
#./engine/rpc ./engine/rpc
proc getGenesis(cs: EngineSpec, param: NetworkParams) = proc getGenesis(cs: EngineSpec, param: NetworkParams) =
# Set the terminal total difficulty # Set the terminal total difficulty
@ -63,17 +63,7 @@ proc specExecute(ws: BaseSpec): bool =
env.close() env.close()
# Execution specification reference: # Execution specification reference:
# https:#github.com/ethereum/execution-apis/blob/main/src/engine/specification.md # https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md
#[var (
big0 = new(big.Int)
big1 = u256(1)
Head *big.Int # Nil
Pending = u256(-2)
Finalized = u256(-3)
Safe = u256(-4)
)
]#
# Register all test combinations for Paris # Register all test combinations for Paris
proc makeEngineTest*(): seq[EngineSpec] = proc makeEngineTest*(): seq[EngineSpec] =
@ -200,8 +190,87 @@ proc makeEngineTest*(): seq[EngineSpec] =
transactionCount: 20, transactionCount: 20,
) )
# Payload Execution Tests
result.add ReExecutePayloadTest()
result.add InOrderPayloadExecutionTest()
result.add MultiplePayloadsExtendingCanonicalChainTest(
setHeadToFirstPayloadReceived: true,
)
result.add MultiplePayloadsExtendingCanonicalChainTest(
setHeadToFirstPayloadReceived: false,
)
result.add NewPayloadOnSyncingClientTest()
result.add NewPayloadWithMissingFcUTest()
const invalidPayloadBlockField = [
InvalidTransactionSignature,
InvalidTransactionNonce,
InvalidTransactionGasPrice,
InvalidTransactionGasTipPrice,
InvalidTransactionGas,
InvalidTransactionValue,
InvalidTransactionChainID,
]
# Invalid Transaction Payload Tests
for invalidField in invalidPayloadBlockField:
let invalidDetectedOnSync = invalidField == InvalidTransactionChainID
for syncing in [false, true]:
if invalidField != InvalidTransactionGasTipPrice:
for testTxType in [TxLegacy, TxEip1559]:
result.add InvalidPayloadTestCase(
txType: some(testTxType),
invalidField: invalidField,
syncing: syncing,
invalidDetectedOnSync: invalidDetectedOnSync,
)
else:
result.add InvalidPayloadTestCase(
txType: some(TxEip1559),
invalidField: invalidField,
syncing: syncing,
invalidDetectedOnSync: invalidDetectedOnSync,
)
const payloadAttributesFieldChange = [
PayloadAttributesIncreaseTimestamp,
PayloadAttributesRandom,
PayloadAttributesSuggestedFeeRecipient,
]
# Payload ID Tests
for payloadAttributeFieldChange in payloadAttributesFieldChange:
result.add UniquePayloadIDTest(
fieldModification: payloadAttributeFieldChange,
)
# Endpoint Versions Tests
# Early upgrade of ForkchoiceUpdated when requesting a payload
result.add ForkchoiceUpdatedOnPayloadRequestTest(
name: "Early upgrade",
about: """
Early upgrade of ForkchoiceUpdated when requesting a payload.
The test sets the fork height to 1, and the block timestamp increments to 2
seconds each block.
CL Mock prepares the payload attributes for the first block, which should contain
the attributes of the next fork.
The test then reduces the timestamp by 1, but still uses the next forkchoice updated
version, which should result in UNSUPPORTED_FORK_ERROR error.
""",
forkHeight: 1,
blockTimestampIncrement: 2,
forkchoiceUpdatedCustomizer: UpgradeForkchoiceUpdatedVersion(
expectedError: engineApiUnsupportedFork,
),
payloadAttributescustomizer: TimestampDeltaPayloadAttributesCustomizer(
timestampDelta: -1,
),
)
# Register RPC tests # Register RPC tests
#[let blockStatusRPCCheckType = [ let blockStatusRPCCheckType = [
LatestOnNewPayload, LatestOnNewPayload,
LatestOnHeadBlockHash, LatestOnHeadBlockHash,
SafeOnSafeBlockHash, SafeOnSafeBlockHash,
@ -211,6 +280,74 @@ proc makeEngineTest*(): seq[EngineSpec] =
for field in blockStatusRPCCheckType: for field in blockStatusRPCCheckType:
result.add BlockStatus(checkType: field) result.add BlockStatus(checkType: field)
# Fork ID Tests
for genesisTimestamp in 0..1:
for forkTime in 0..2:
for prevForkTime in 0..forkTime:
for currentBlock in 0..1:
result.add ForkIDSpec(
mainFork: ForkParis,
genesistimestamp: genesisTimestamp,
forkTime: forkTime.uint64,
previousForkTime: prevForkTime.uint64,
produceBlocksBeforePeering: currentBlock,
)
# Re-org using the Engine API tests
# Sidechain re-org tests
result.add SidechainReOrgTest()
result.add ReOrgBackFromSyncingTest(
slotsToSafe: 32,
slotsToFinalized: 64,
)
result.add ReOrgPrevValidatedPayloadOnSideChainTest(
slotsToSafe: 32,
slotsToFinalized: 64,
)
result.add SafeReOrgToSideChainTest(
slotsToSafe: 1,
slotsToFinalized: 2,
)
# Re-org a transaction out of a block, or into a new block
result.add TransactionReOrgTest(
scenario: TransactionReOrgScenarioReOrgOut,
)
result.add TransactionReOrgTest(
scenario: TransactionReOrgScenarioReOrgDifferentBlock,
)
result.add TransactionReOrgTest(
scenario: TransactionReOrgScenarioNewPayloadOnRevert,
)
result.add TransactionReOrgTest(
scenario: TransactionReOrgScenarioReOrgBackIn,
)
# Re-Org back into the canonical chain tests
result.add ReOrgBackToCanonicalTest(
slotsToSafe: 10,
slotsToFinalized: 20,
timeoutSeconds: 60,
transactionPerPayload: 1,
reOrgDepth: 5,
)
result.add ReOrgBackToCanonicalTest(
slotsToSafe: 32,
slotsToFinalized: 64,
timeoutSeconds: 120,
transactionPerPayload: 50,
reOrgDepth: 10,
executeSidePayloadOnReOrg: true,
)
#[
const const
invalidReorgList = [ invalidReorgList = [
InvalidStateRoot, InvalidStateRoot,
@ -263,186 +400,8 @@ proc makeEngineTest*(): seq[EngineSpec] =
reOrgFromCanonical: reOrgFromCanonical, reOrgFromCanonical: reOrgFromCanonical,
invalidIndex: invalidIndex, invalidIndex: invalidIndex,
) )
]#
#[
# Payload ID Tests
for _, payloadAttributeFieldChange := range []PayloadAttributesFieldChange(
PayloadAttributesIncreaseTimestamp,
PayloadAttributesRandom,
PayloadAttributesSuggestedFeeRecipient,
) (
result.add UniquePayloadIDTest(
FieldModification: payloadAttributeFieldChange,
))
)
# Endpoint Versions Tests
# Early upgrade of ForkchoiceUpdated when requesting a payload
result.add
ForkchoiceUpdatedOnPayloadRequestTest(
BaseSpec: test.BaseSpec(
Name: "Early upgrade",
About: `
Early upgrade of ForkchoiceUpdated when requesting a payload.
The test sets the fork height to 1, and the block timestamp increments to 2
seconds each block.
CL Mock prepares the payload attributes for the first block, which should contain
the attributes of the next fork.
The test then reduces the timestamp by 1, but still uses the next forkchoice updated
version, which should result in UNSUPPORTED_FORK_ERROR error.
`,
forkHeight: 1,
BlockTimestampIncrement: 2,
),
ForkchoiceUpdatedcustomizer: UpgradeForkchoiceUpdatedVersion(
ForkchoiceUpdatedcustomizer: BaseForkchoiceUpdatedCustomizer(
PayloadAttributescustomizer: TimestampDeltaPayloadAttributesCustomizer(
PayloadAttributescustomizer: BasePayloadAttributesCustomizer(),
TimestampDelta: -1,
),
ExpectedError: globals.UNSUPPORTED_FORK_ERROR,
),
),
),
)
# Payload Execution Tests
result.add
ReExecutePayloadTest(),
InOrderPayloadExecutionTest(),
MultiplePayloadsExtendingCanonicalChainTest(
SetHeadToFirstPayloadReceived: true,
),
MultiplePayloadsExtendingCanonicalChainTest(
SetHeadToFirstPayloadReceived: false,
),
NewPayloadOnSyncingClientTest(),
NewPayloadWithMissingFcUTest(),
)
# Invalid Transaction Payload Tests
for _, invalidField := range []InvalidPayloadBlockField(
InvalidTransactionSignature,
InvalidTransactionNonce,
InvalidTransactionGasPrice,
InvalidTransactionGasTipPrice,
InvalidTransactionGas,
InvalidTransactionValue,
InvalidTransactionChainID,
) (
invalidDetectedOnSync := invalidField == InvalidTransactionChainID
for _, syncing in [false, true) (
if invalidField != InvalidTransactionGasTipPrice (
for _, testTxType := range []TestTransactionType(TxLegacy, TxEip1559) (
result.add InvalidPayloadTestCase(
BaseSpec: test.BaseSpec(
txType: some( testTxType,
),
InvalidField: invalidField,
Syncing: syncing,
InvalidDetectedOnSync: invalidDetectedOnSync,
))
)
) else (
result.add InvalidPayloadTestCase(
BaseSpec: test.BaseSpec(
txType: some( TxEip1559,
),
InvalidField: invalidField,
Syncing: syncing,
InvalidDetectedOnSync: invalidDetectedOnSync,
))
)
)
)
# Re-org using the Engine API tests
# Sidechain re-org tests
result.add
SidechainReOrgTest(),
ReOrgBackFromSyncingTest(
BaseSpec: test.BaseSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
),
),
ReOrgPrevValidatedPayloadOnSideChainTest(
BaseSpec: test.BaseSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
),
),
SafeReOrgToSideChainTest(
BaseSpec: test.BaseSpec(
slotsToSafe: u256(1),
slotsToFinalized: u256(2),
),
),
)
// Re-org a transaction out of a block, or into a new block
result.add
TransactionReOrgTest{
Scenario: TransactionReOrgScenarioReOrgOut,
},
TransactionReOrgTest{
Scenario: TransactionReOrgScenarioReOrgDifferentBlock,
},
TransactionReOrgTest{
Scenario: TransactionReOrgScenarioNewPayloadOnRevert,
},
TransactionReOrgTest{
Scenario: TransactionReOrgScenarioReOrgBackIn,
},
)
# Re-Org back into the canonical chain tests
result.add
ReOrgBackToCanonicalTest(
BaseSpec: test.BaseSpec(
slotsToSafe: u256(10),
slotsToFinalized: u256(20),
TimeoutSeconds: 60,
),
TransactionPerPayload: 1,
ReOrgDepth: 5,
),
ReOrgBackToCanonicalTest(
BaseSpec: test.BaseSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
TimeoutSeconds: 120,
),
TransactionPerPayload: 50,
ReOrgDepth: 10,
ExecuteSidePayloadOnReOrg: true,
),
)
# Fork ID Tests
for genesisTimestamp := uint64(0); genesisTimestamp <= 1; genesisTimestamp++ (
for forkTime := uint64(0); forkTime <= 2; forkTime++ (
for prevForkTime := uint64(0); prevForkTime <= forkTime; prevForkTime++ (
for currentBlock := 0; currentBlock <= 1; currentBlock++ (
result.add
ForkIDSpec(
BaseSpec: test.BaseSpec(
MainFork: config.Paris,
Genesistimestamp: pUint64(genesisTimestamp),
ForkTime: forkTime,
PreviousForkTime: prevForkTime,
),
ProduceBlocksBeforePeering: currentBlock,
),
)
)
)
)
)
]# ]#

View File

@ -10,11 +10,21 @@
import import
eth/[common, rlp], eth/[common, rlp],
chronicles,
../../../nimbus/beacon/execution_types, ../../../nimbus/beacon/execution_types,
../../../nimbus/beacon/web3_eth_conv ../../../nimbus/beacon/web3_eth_conv,
./engine_client,
./types
proc txInPayload*(payload: ExecutionPayload, txHash: common.Hash256): bool = proc txInPayload*(payload: ExecutionPayload, txHash: common.Hash256): bool =
for txBytes in payload.transactions: for txBytes in payload.transactions:
let currTx = rlp.decode(common.Blob txBytes, Transaction) let currTx = rlp.decode(common.Blob txBytes, Transaction)
if rlpHash(currTx) == txHash: if rlpHash(currTx) == txHash:
return true return true
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

View File

@ -133,6 +133,12 @@ proc makeNextTx*(env: TestEnv, tc: BaseTx): Transaction =
proc sendNextTx*(env: TestEnv, eng: EngineEnv, tc: BaseTx): bool = proc sendNextTx*(env: TestEnv, eng: EngineEnv, tc: BaseTx): bool =
env.sender.sendNextTx(eng.client, tc) env.sender.sendNextTx(eng.client, tc)
proc sendNextTxs*(env: TestEnv, eng: EngineEnv, tc: BaseTx, num: int): bool =
for i in 0..<num:
if not env.sender.sendNextTx(eng.client, tc):
return false
return true
proc sendTx*(env: TestEnv, eng: EngineEnv, tc: BaseTx, nonce: AccountNonce): bool = proc sendTx*(env: TestEnv, eng: EngineEnv, tc: BaseTx, nonce: AccountNonce): bool =
env.sender.sendTx(eng.client, tc, nonce) env.sender.sendTx(eng.client, tc, nonce)

View File

@ -62,6 +62,10 @@ const
DefaultSleep* = 1 DefaultSleep* = 1
prevRandaoContractAddr* = hexToByteArray[20]("0000000000000000000000000000000000000316") prevRandaoContractAddr* = hexToByteArray[20]("0000000000000000000000000000000000000316")
GenesisTimestamp* = 0x1234 GenesisTimestamp* = 0x1234
Head* = "latest"
Pending* = "pending"
Finalized* = "finalized"
Safe* = "safe"
func toAddress*(x: UInt256): EthAddress = func toAddress*(x: UInt256): EthAddress =
var var
@ -172,8 +176,12 @@ template expectLatestValidHash*(res: untyped) =
testCond res.isOk: testCond res.isOk:
error "Unexpected error", msg=res.error error "Unexpected error", msg=res.error
let s = res.get let s = res.get
testCond s.latestValidHash.isNone: when s is ForkchoiceUpdatedResponse:
error "Expect latest valid hash isNone" testCond s.payloadStatus.latestValidHash.isNone:
error "Expect latest valid hash isNone"
else:
testCond s.latestValidHash.isNone:
error "Expect latest valid hash isNone"
template expectErrorCode*(res: untyped, errCode: int, expectedDesc: string) = template expectErrorCode*(res: untyped, errCode: int, expectedDesc: string) =
testCond res.isErr: testCond res.isErr:
@ -196,6 +204,17 @@ template expectStatusEither*(res: untyped, cond: openArray[PayloadExecutionStatu
testCond s.payloadStatus.status in cond: testCond s.payloadStatus.status in cond:
error "Unexpected expectStatusEither status", expect=cond, get=s.payloadStatus.status error "Unexpected expectStatusEither status", expect=cond, get=s.payloadStatus.status
template expectNoValidationError*(res: untyped) =
testCond res.isOk:
error "Unexpected expectNoValidationError error", msg=res.error
let s = res.get()
when s is PayloadStatusV1:
testCond s.validationError.isNone:
error "Unexpected validation error isSome"
else:
testCond s.payloadStatus.validationError.isNone:
error "Unexpected validation error isSome"
template expectPayloadStatus*(res: untyped, cond: PayloadExecutionStatus) = template expectPayloadStatus*(res: untyped, cond: PayloadExecutionStatus) =
testCond res.isOk: testCond res.isOk:
error "Unexpected FCU Error", msg=res.error error "Unexpected FCU Error", msg=res.error
@ -258,6 +277,26 @@ template expectBlobGasPrice*(res: untyped, expected: UInt256) =
testCond rec.blobGasPrice.get == expected: testCond rec.blobGasPrice.get == expected:
error "expectBlobGasPrice", expect=expected, get=rec.blobGasPrice.get error "expectBlobGasPrice", expect=expected, get=rec.blobGasPrice.get
template expectNumber*(res: untyped, expected: uint64) =
testCond res.isOk:
error "expectNumber", msg=res.error
testCond res.get == expected:
error "expectNumber", expect=expected, get=res.get
template expectTransactionHash*(res: untyped, expected: common.Hash256) =
testCond res.isOk:
error "expectTransactionHash", msg=res.error
let rec = res.get
testCond rec.txHash == expected:
error "expectTransactionHash", expect=expected.short, get=rec.txHash.short
template expectPayloadParentHash*(res: untyped, expected: Web3Hash) =
testCond res.isOk:
error "expectPayloadParentHash", msg=res.error
let rec = res.get
testCond rec.executionPayload.parentHash == expected:
error "expectPayloadParentHash", expect=expected.short, get=rec.executionPayload.parentHash.short
func timestamp*(x: ExecutableData): auto = func timestamp*(x: ExecutableData): auto =
x.basePayload.timestamp x.basePayload.timestamp