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
# Merge related
firstPoSBlockNumber : Option[uint64]
firstPoSBlockNumber* : Option[uint64]
ttdReached* : bool
transitionPayloadTimestamp: Option[int]
chainTotalDifficulty : UInt256

View File

@ -10,11 +10,13 @@
import
std/strutils,
./engine_spec
chronicles,
./engine_spec,
../../../../nimbus/common/hardforks
type
ForkIDSpec* = ref object of EngineSpec
produceBlocksBeforePeering: int
produceBlocksBeforePeering*: int
method withMainFork(cs: ForkIDSpec, fork: EngineFork): BaseSpec =
var res = cs.clone()
@ -22,15 +24,25 @@ method withMainFork(cs: ForkIDSpec, fork: EngineFork): BaseSpec =
return res
method getName(cs: ForkIDSpec): string =
name = "Fork ID: Genesis at %d, %s at %d", cs.GetGenesistimestamp(), cs.mainFork, cs.ForkTime)
if cs.previousForkTime != 0 (
name += ", %s at %d", cs.mainFork.PreviousFork(), cs.previousForkTime)
)
if cs.produceBlocksBeforePeering > 0 (
name += ", Produce %d blocks before peering", cs.produceBlocksBeforePeering)
)
var name = "Fork ID: Genesis at $1, $2 at $3" % [$cs.getGenesistimestamp(), $cs.mainFork, $cs.forkTime]
if cs.previousForkTime != 0:
name.add ", $1 at $2" % [$cs.mainFork.pred, $cs.previousForkTime]
if cs.produceBlocksBeforePeering > 0:
name.add ", Produce $1 blocks before peering" % [$cs.produceBlocksBeforePeering]
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 =
# Wait until TTD is reached by this client
@ -38,41 +50,8 @@ method execute(cs: ForkIDSpec, env: TestEnv): bool =
testCond ok
# 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
engine = t.Engine
conn, err = devp2p.PeerEngineClient(engine, t.CLMock)
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)
)
)
let engine = env.addEngine()
return true

View File

@ -240,6 +240,10 @@ method execute(cs: InvalidMissingAncestorReOrgSyncTest, env: TestEnv): bool =
# Append the common ancestor
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)
# CommonAncestor◄─▲── P1 ◄─ P2 ◄─ P3 ◄─ ... ◄─ Pn
# │

View File

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

View File

@ -9,7 +9,8 @@
# according to those terms.
import
std/strutils,
std/[strutils, typetraits],
chronicles,
./engine_spec
type
@ -36,6 +37,16 @@ method withMainFork(cs: UniquePayloadIDTest, fork: EngineFork): BaseSpec =
method getName(cs: UniquePayloadIDTest): string =
"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
# when the attributes change
method execute(cs: UniquePayloadIDTest, env: TestEnv): bool =
@ -43,56 +54,63 @@ method execute(cs: UniquePayloadIDTest, env: TestEnv): bool =
let ok = waitFor env.clMock.waitForTTD()
testCond ok
env.clMock.produceSingleBlock(BlockProcessCallbacks(
let pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
onPayloadAttributesGenerated: proc(): bool =
payloadAttributes = env.clMock.latestPayloadAttributes
case cs.fieldModification (
var attr = env.clMock.latestPayloadAttributes
case cs.fieldModification
of PayloadAttributesIncreasetimestamp:
payloadAttributes.timestamp += 1
attr.timestamp = w3Qty(attr.timestamp, 1)
of PayloadAttributesRandom:
payloadAttributes.Random[0] = payloadAttributes.Random[0] + 1
attr.prevRandao = attr.prevRandao.plusOne
of PayloadAttributesSuggestedFeerecipient:
payloadAttributes.SuggestedFeeRecipient[0] = payloadAttributes.SuggestedFeeRecipient[0] + 1
attr.suggestedFeeRecipient = attr.suggestedFeeRecipient.plusOne
of PayloadAttributesAddWithdrawal:
newWithdrawal = &types.Withdrawal()
payloadAttributes.Withdrawals = append(payloadAttributes.Withdrawals, newWithdrawal)
let newWithdrawal = WithdrawalV1()
var wd = attr.withdrawals.get
wd.add newWithdrawal
attr.withdrawals = some(wd)
of PayloadAttributesRemoveWithdrawal:
payloadAttributes.Withdrawals = payloadAttributes.Withdrawals[1:]
var wd = attr.withdrawals.get
wd.delete(0)
attr.withdrawals = some(wd)
of PayloadAttributesModifyWithdrawalAmount,
PayloadAttributesModifyWithdrawalIndex,
PayloadAttributesModifyWithdrawalValidator,
PayloadAttributesModifyWithdrawalAddress:
if len(payloadAttributes.Withdrawals) == 0 (
fatal "Cannot modify withdrawal when there are no withdrawals")
)
modifiedWithdrawal = *payloadAttributes.Withdrawals[0]
case cs.fieldModification (
testCond attr.withdrawals.isSome:
fatal "Cannot modify withdrawal when there are no withdrawals"
var wds = attr.withdrawals.get
testCond wds.len > 0:
fatal "Cannot modify withdrawal when there are no withdrawals"
var wd = wds[0]
case cs.fieldModification
of PayloadAttributesModifyWithdrawalAmount:
modifiedWithdrawal.Amount += 1
wd.amount = w3Qty(wd.amount, 1)
of PayloadAttributesModifyWithdrawalIndex:
modifiedWithdrawal.Index += 1
wd.index = w3Qty(wd.index, 1)
of PayloadAttributesModifyWithdrawalValidator:
modifiedWithdrawal.Validator += 1
wd.validatorIndex = w3Qty(wd.validatorIndex, 1)
of PayloadAttributesModifyWithdrawalAddress:
modifiedWithdrawal.Address[0] = modifiedWithdrawal.Address[0] + 1
)
payloadAttributes.Withdrawals = append(types.Withdrawals(&modifiedWithdrawal), payloadAttributes.Withdrawals[1:]...)
wd.address = wd.address.plusOne
else:
fatal "Unknown field change", field=cs.fieldModification
return false
wds[0] = wd
attr.withdrawals = some(wds)
of PayloadAttributesParentBeaconRoot:
if payloadAttributes.BeaconRoot == nil (
fatal "Cannot modify parent beacon root when there is no parent beacon root")
)
newBeaconRoot = *payloadAttributes.BeaconRoot
newBeaconRoot[0] = newBeaconRoot[0] + 1
payloadAttributes.BeaconRoot = &newBeaconRoot
default:
fatal "Unknown field change: %s", cs.fieldModification)
)
testCond attr.parentBeaconBlockRoot.isSome:
fatal "Cannot modify parent beacon root when there is no parent beacon root"
let newBeaconRoot = attr.parentBeaconBlockRoot.get.plusOne
attr.parentBeaconBlockRoot = some(newBeaconRoot)
# 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 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()
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,
eth/common,
chronicles,
./engine_spec
./engine_spec,
../helper
type
PrevRandaoTransactionTest* = ref object of EngineSpec
@ -24,13 +25,6 @@ type
currentTxIndex: int
txs: seq[Transaction]
proc checkPrevRandaoValue(client: RpcClient, expectedPrevRandao: common.Hash256, blockNumber: uint64): bool =
let storageKey = blockNumber.u256
let r = client.storageAt(prevRandaoContractAddr, storageKey)
let expected = UInt256.fromBytesBE(expectedPrevRandao.data)
r.expectStorageEqual(expected)
return true
method withMainFork(cs: PrevRandaoTransactionTest, fork: EngineFork): BaseSpec =
var res = cs.clone()
res.mainFork = fork

View File

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

View File

@ -10,6 +10,8 @@
import
std/strutils,
eth/common,
chronicles,
./engine_spec
type
@ -24,13 +26,16 @@ type
checkType*: BlockStatusRPCcheckType
# TODO: Syncing bool
Shadow = ref object
txHash: common.Hash256
method withMainFork(cs: BlockStatus, fork: EngineFork): BaseSpec =
var res = cs.clone()
res.mainFork = fork
return res
method getName(cs: BlockStatus): string =
"RPC" & $b.checkType
"RPC " & $cs.checkType
# Test to verify Block information available at the Eth RPC after NewPayload/ForkchoiceUpdated
method execute(cs: BlockStatus, env: TestEnv): bool =
@ -38,69 +43,69 @@ method execute(cs: BlockStatus, env: TestEnv): bool =
let ok = waitFor env.clMock.waitForTTD()
testCond ok
case b.checkType
of SafeOnSafeblockHash, FinalizedOnFinalizedblockHash:
var number *big.Int
if b.checkType == SafeOnSafeblockHash:
if cs.checkType in [SafeOnSafeblockHash, FinalizedOnFinalizedblockHash]:
var number = Finalized
if cs.checkType == SafeOnSafeblockHash:
number = Safe
else:
number = Finalized
p = env.engine.client.headerByNumber(number)
let p = env.engine.client.namedHeader(number)
p.expectError()
)
# Produce blocks before starting the test
env.clMock.produceBlocks(5, BlockProcessCallbacks())
testCond env.clMock.produceBlocks(5, BlockProcessCallbacks())
var tx typ.Transaction
callbacks = BlockProcessCallbacks(
var shadow = Shadow()
var callbacks = BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool =
let tc = BaseTx(
recipient: &ZeroAddr,
amount: 1.u256,
txType: cs.txType,
gasLimit: 75000,
),
let ok = env.sendNextTx(tc)
testCond ok:
fatal "Error trying to send transaction: %v", err)
recipient: some(ZeroAddr),
amount: 1.u256,
txType: cs.txType,
gasLimit: 75000,
)
),
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:
callbacks.onGetPayload = proc(): bool
r = env.engine.client.latestHeader()
r.expectHash(env.clMock.latestForkchoice.headblockHash)
callbacks.onGetPayload = proc(): bool =
let r = env.engine.client.namedHeader(Head)
r.expectHash(ethHash env.clMock.latestForkchoice.headblockHash)
s = env.engine.client.TestBlockNumber()
s.ExpectNumber(env.clMock.latestHeadNumber.Uint64())
let s = env.engine.client.blockNumber()
s.expectNumber(env.clMock.latestHeadNumber.uint64)
p = env.engine.client.latestHeader()
p.expectHash(env.clMock.latestForkchoice.headblockHash)
let p = env.engine.client.namedHeader(Head)
p.expectHash(ethHash env.clMock.latestForkchoice.headblockHash)
# 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()
return true
of LatestOnHeadblockHash:
callbacks.onForkchoiceBroadcast = proc(): bool
r = env.engine.client.latestHeader()
r.expectHash(env.clMock.latestForkchoice.headblockHash)
s = env.engine.client.txReceipt(tx.Hash())
s.ExpectTransactionHash(tx.Hash())
callbacks.onForkchoiceBroadcast = proc(): bool =
let r = env.engine.client.namedHeader(Head)
r.expectHash(ethHash env.clMock.latestForkchoice.headblockHash)
let s = env.engine.client.txReceipt(shadow.txHash)
s.expectTransactionHash(shadow.txHash)
return true
of SafeOnSafeblockHash:
callbacks.onSafeBlockChange = proc(): bool
r = env.engine.client.headerByNumber(Safe)
r.expectHash(env.clMock.latestForkchoice.safeblockHash)
callbacks.onSafeBlockChange = proc(): bool =
let r = env.engine.client.namedHeader(Safe)
r.expectHash(ethHash env.clMock.latestForkchoice.safeblockHash)
return true
of FinalizedOnFinalizedblockHash:
callbacks.onFinalizedBlockChange = proc(): bool
r = env.engine.client.headerByNumber(Finalized)
r.expectHash(env.clMock.latestForkchoice.finalizedblockHash)
callbacks.onFinalizedBlockChange = proc(): bool =
let r = env.engine.client.namedHeader(Finalized)
r.expectHash(ethHash env.clMock.latestForkchoice.finalizedblockHash)
return true
# 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
import
std/strutils,
chronicles,
../cancun/customizer,
./engine_spec
type
@ -25,7 +27,10 @@ method withMainFork(cs: EngineNewPayloadVersionTest, fork: EngineFork): BaseSpec
# when the timestamp payload attribute does not match the upgraded/downgraded version.
type
ForkchoiceUpdatedOnPayloadRequestTest* = ref object of EngineSpec
ForkchoiceUpdatedCustomizer
name*: string
about*: string
forkchoiceUpdatedCustomizer*: ForkchoiceUpdatedCustomizer
payloadAttributesCustomizer*: PayloadAttributesCustomizer
method withMainFork(cs: ForkchoiceUpdatedOnPayloadRequestTest, fork: EngineFork): BaseSpec =
var res = cs.clone()
@ -33,43 +38,35 @@ method withMainFork(cs: ForkchoiceUpdatedOnPayloadRequestTest, fork: EngineFork)
return res
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 =
# Wait until TTD is reached by this client
let ok = waitFor env.clMockWaitForTTD()
let ok = waitFor env.clMock.waitForTTD()
testCond ok
env.clMock.produceSingleBlock(clmock.BlockProcessCallbacks(
let pbRes = env.clMock.produceSingleBlock(clmock.BlockProcessCallbacks(
onPayloadAttributesGenerated: proc(): bool =
var (
payloadAttributes = &env.clMockLatestPayloadAttributes
expectedStatus test.PayloadStatus = 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
)
var
attr = env.clMock.latestPayloadAttributes
expectedStatus = PayloadExecutionStatus.valid
r = env.engine.client.forkchoiceUpdated(env.clMockLatestForkchoice, payloadAttributes, env.clMockLatestHeader.Time)
r.ExpectationDescription = cs.Expectation
if expectedError != nil (
r.expectErrorCode(*expectedError)
attr = cs.payloadAttributesCustomizer.getPayloadAttributes(attr)
let expectedError = cs.forkchoiceUpdatedCustomizer.getExpectedError()
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:
r.expectNoError()
r.expectPayloadStatus(expectedStatus)
)
),
return true
))
)
testCond pbRes
return true

View File

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

View File

@ -20,18 +20,18 @@ import
import
./engine/suggested_fee_recipient,
./engine/payload_attributes,
#./engine/payload_execution,
./engine/payload_execution,
./engine/invalid_ancestor,
./engine/invalid_payload,
./engine/prev_randao,
#./engine/payload_id,
./engine/payload_id,
./engine/forkchoice,
#./engine/versioning,
./engine/versioning,
./engine/bad_hash,
#./engine/fork_id,
#./engine/reorg,
./engine/misc
#./engine/rpc
./engine/fork_id,
./engine/reorg,
./engine/misc,
./engine/rpc
proc getGenesis(cs: EngineSpec, param: NetworkParams) =
# Set the terminal total difficulty
@ -63,17 +63,7 @@ proc specExecute(ws: BaseSpec): bool =
env.close()
# Execution specification reference:
# 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)
)
]#
# https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md
# Register all test combinations for Paris
proc makeEngineTest*(): seq[EngineSpec] =
@ -200,8 +190,87 @@ proc makeEngineTest*(): seq[EngineSpec] =
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
#[let blockStatusRPCCheckType = [
let blockStatusRPCCheckType = [
LatestOnNewPayload,
LatestOnHeadBlockHash,
SafeOnSafeBlockHash,
@ -211,6 +280,74 @@ proc makeEngineTest*(): seq[EngineSpec] =
for field in blockStatusRPCCheckType:
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
invalidReorgList = [
InvalidStateRoot,
@ -263,186 +400,8 @@ proc makeEngineTest*(): seq[EngineSpec] =
reOrgFromCanonical: reOrgFromCanonical,
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
eth/[common, rlp],
chronicles,
../../../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 =
for txBytes in payload.transactions:
let currTx = rlp.decode(common.Blob txBytes, Transaction)
if rlpHash(currTx) == txHash:
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 =
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 =
env.sender.sendTx(eng.client, tc, nonce)

View File

@ -62,6 +62,10 @@ const
DefaultSleep* = 1
prevRandaoContractAddr* = hexToByteArray[20]("0000000000000000000000000000000000000316")
GenesisTimestamp* = 0x1234
Head* = "latest"
Pending* = "pending"
Finalized* = "finalized"
Safe* = "safe"
func toAddress*(x: UInt256): EthAddress =
var
@ -172,8 +176,12 @@ template expectLatestValidHash*(res: untyped) =
testCond res.isOk:
error "Unexpected error", msg=res.error
let s = res.get
testCond s.latestValidHash.isNone:
error "Expect latest valid hash isNone"
when s is ForkchoiceUpdatedResponse:
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) =
testCond res.isErr:
@ -196,6 +204,17 @@ template expectStatusEither*(res: untyped, cond: openArray[PayloadExecutionStatu
testCond s.payloadStatus.status in cond:
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) =
testCond res.isOk:
error "Unexpected FCU Error", msg=res.error
@ -258,6 +277,26 @@ template expectBlobGasPrice*(res: untyped, expected: UInt256) =
testCond rec.blobGasPrice.get == expected:
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 =
x.basePayload.timestamp