More Engine API tests
This commit is contained in:
parent
25bc8e4b22
commit
19f313d891
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
|
@ -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
|
||||||
# │
|
# │
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: ×tamp,
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]#
|
]#
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue