Engine API: Fix latestValidHash value when invalid timestamp detected
This commit is contained in:
parent
19f313d891
commit
7de6199ba3
|
@ -438,14 +438,18 @@ proc broadcastNextNewPayload(cl: CLMocker): bool =
|
||||||
cl.executedPayloadHistory[number] = cl.latestPayloadBuilt
|
cl.executedPayloadHistory[number] = cl.latestPayloadBuilt
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc broadcastForkchoiceUpdated(cl: CLMocker, eng: EngineEnv,
|
proc broadcastForkchoiceUpdated(cl: CLMocker,
|
||||||
update: ForkchoiceStateV1): Result[ForkchoiceUpdatedResponse, string] =
|
eng: EngineEnv,
|
||||||
let version = cl.latestExecutedPayload.version
|
version: Version,
|
||||||
|
update: ForkchoiceStateV1):
|
||||||
|
Result[ForkchoiceUpdatedResponse, string] =
|
||||||
eng.client.forkchoiceUpdated(version, update, none(PayloadAttributes))
|
eng.client.forkchoiceUpdated(version, update, none(PayloadAttributes))
|
||||||
|
|
||||||
proc broadcastLatestForkchoice(cl: CLMocker): bool =
|
proc broadcastForkchoiceUpdated*(cl: CLMocker,
|
||||||
|
version: Version,
|
||||||
|
update: ForkchoiceStateV1): bool =
|
||||||
for eng in cl.clients:
|
for eng in cl.clients:
|
||||||
let res = cl.broadcastForkchoiceUpdated(eng, cl.latestForkchoice)
|
let res = cl.broadcastForkchoiceUpdated(eng, version, update)
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
error "CLMocker: broadcastForkchoiceUpdated Error", msg=res.error
|
error "CLMocker: broadcastForkchoiceUpdated Error", msg=res.error
|
||||||
return false
|
return false
|
||||||
|
@ -474,6 +478,10 @@ proc broadcastLatestForkchoice(cl: CLMocker): bool =
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
proc broadcastLatestForkchoice(cl: CLMocker): bool =
|
||||||
|
let version = cl.latestExecutedPayload.version
|
||||||
|
cl.broadcastForkchoiceUpdated(version, cl.latestForkchoice)
|
||||||
|
|
||||||
func w3Address(x: int): Web3Address =
|
func w3Address(x: int): Web3Address =
|
||||||
var res: array[20, byte]
|
var res: array[20, byte]
|
||||||
res[^1] = x.byte
|
res[^1] = x.byte
|
||||||
|
|
|
@ -39,13 +39,8 @@ method withMainFork(cs: InvalidMissingAncestorReOrgTest, fork: EngineFork): Base
|
||||||
return res
|
return res
|
||||||
|
|
||||||
method getName(cs: InvalidMissingAncestorReOrgTest): string =
|
method getName(cs: InvalidMissingAncestorReOrgTest): string =
|
||||||
var desc = "Invalid Missing Ancestor ReOrg Invalid" & $cs.invalidField
|
"Invalid Missing Ancestor ReOrg, $1, EmptyTxs=$2, Invalid P$3" % [
|
||||||
|
$cs.invalidField, $cs.emptyTransactions, $cs.invalidIndex]
|
||||||
if cs.emptyTransactions:
|
|
||||||
desc.add ", Empty Txs"
|
|
||||||
|
|
||||||
desc.add ", Invalid P" & $cs.invalidIndex & "'"
|
|
||||||
desc
|
|
||||||
|
|
||||||
method execute(cs: InvalidMissingAncestorReOrgTest, env: TestEnv): bool =
|
method execute(cs: InvalidMissingAncestorReOrgTest, env: TestEnv): bool =
|
||||||
# Wait until TTD is reached by this client
|
# Wait until TTD is reached by this client
|
||||||
|
@ -186,25 +181,14 @@ type
|
||||||
# or start chain (if specified).
|
# or start chain (if specified).
|
||||||
reOrgFromCanonical*: bool
|
reOrgFromCanonical*: bool
|
||||||
|
|
||||||
|
|
||||||
method withMainFork(cs: InvalidMissingAncestorReOrgSyncTest, fork: EngineFork): BaseSpec =
|
method withMainFork(cs: InvalidMissingAncestorReOrgSyncTest, fork: EngineFork): BaseSpec =
|
||||||
var res = cs.clone()
|
var res = cs.clone()
|
||||||
res.mainFork = fork
|
res.mainFork = fork
|
||||||
return res
|
return res
|
||||||
|
|
||||||
method getName(cs: InvalidMissingAncestorReOrgSyncTest): string =
|
method getName(cs: InvalidMissingAncestorReOrgSyncTest): string =
|
||||||
var name = "Invalid Missing Ancestor ReOrg"
|
"Invalid Missing Ancestor Syncing ReOrg, $1, EmptyTxs=$2, CanonicalReOrg=$3, Invalid P$4" % [
|
||||||
name.add ", Invalid " & $cs.invalidField
|
$cs.invalidField, $cs.emptyTransactions, $cs.reOrgFromCanonical, $cs.invalidIndex]
|
||||||
|
|
||||||
if cs.emptyTransactions:
|
|
||||||
name.add ", Empty Txs"
|
|
||||||
|
|
||||||
name.add ", Invalid P" & $cs.invalidIndex & "'"
|
|
||||||
name.add ", Reveal using sync"
|
|
||||||
|
|
||||||
if cs.reOrgFromCanonical:
|
|
||||||
name.add ", ReOrg from Canonical"
|
|
||||||
return name
|
|
||||||
|
|
||||||
method execute(cs: InvalidMissingAncestorReOrgSyncTest, env: TestEnv): bool =
|
method execute(cs: InvalidMissingAncestorReOrgSyncTest, env: TestEnv): bool =
|
||||||
var sec = env.addEngine(true, cs.reOrgFromCanonical)
|
var sec = env.addEngine(true, cs.reOrgFromCanonical)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import
|
||||||
std/strutils,
|
std/strutils,
|
||||||
eth/common,
|
eth/common,
|
||||||
chronicles,
|
chronicles,
|
||||||
|
stew/byteutils,
|
||||||
./engine_spec,
|
./engine_spec,
|
||||||
../cancun/customizer,
|
../cancun/customizer,
|
||||||
../helper
|
../helper
|
||||||
|
@ -117,6 +118,12 @@ type
|
||||||
transactionCount*: int
|
transactionCount*: int
|
||||||
scenario*: TransactionReOrgScenario
|
scenario*: TransactionReOrgScenario
|
||||||
|
|
||||||
|
ShadowTx = ref object
|
||||||
|
payload: ExecutionPayload
|
||||||
|
nextTx: Transaction
|
||||||
|
tx: Option[Transaction]
|
||||||
|
sendTransaction: proc(i: int): Transaction {.gcsafe.}
|
||||||
|
|
||||||
method withMainFork(cs: TransactionReOrgTest, fork: EngineFork): BaseSpec =
|
method withMainFork(cs: TransactionReOrgTest, fork: EngineFork): BaseSpec =
|
||||||
var res = cs.clone()
|
var res = cs.clone()
|
||||||
res.mainFork = fork
|
res.mainFork = fork
|
||||||
|
@ -128,6 +135,9 @@ method getName(cs: TransactionReOrgTest): string =
|
||||||
name.add ", " & $cs.scenario
|
name.add ", " & $cs.scenario
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
func txHash(shadow: ShadowTx): common.Hash256 =
|
||||||
|
shadow.tx.get.rlpHash
|
||||||
|
|
||||||
# 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
|
||||||
method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
|
method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
|
||||||
# Wait until this client catches up with latest PoS
|
# Wait until this client catches up with latest PoS
|
||||||
|
@ -136,259 +146,218 @@ 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.
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
txCount = cs.transactionCount
|
|
||||||
sstoreContractAddr = common.HexToAddress("0000000000000000000000000000000000000317")
|
|
||||||
)
|
|
||||||
|
|
||||||
if txCount == 0 (
|
# Create transactions that modify the state in order to check after the reorg.
|
||||||
# Default is to send 5 transactions
|
var shadow = ShadowTx()
|
||||||
txCount = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
# Send a transaction on each payload of the canonical chain
|
# Send a transaction on each payload of the canonical chain
|
||||||
sendTransaction = func(i int) (Transaction, error) (
|
shadow.sendTransaction = proc(i: int): Transaction {.gcsafe.} =
|
||||||
data = common.LeftPadBytes([]byte(byte(i)), 32)
|
let sstoreContractAddr = hexToByteArray[20]("0000000000000000000000000000000000000317")
|
||||||
t.Logf("transactionReorg, i=%v, data=%v\n", i, data)
|
var data: array[32, byte]
|
||||||
return t.sendNextTx(
|
data[^1] = i.byte
|
||||||
t.Engine,
|
info "transactionReorg", idx=i
|
||||||
BaseTx(
|
let tc = BaseTx(
|
||||||
recipient: &sstoreContractAddr,
|
recipient: some(sstoreContractAddr),
|
||||||
amount: big0,
|
amount: 0.u256,
|
||||||
payload: data,
|
payload: @data,
|
||||||
txType: cs.txType,
|
txType: cs.txType,
|
||||||
gasLimit: 75000,
|
gasLimit: 75000,
|
||||||
ForkConfig: t.ForkConfig,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
var tx = env.makeNextTx(tc)
|
||||||
|
doAssert env.sendTx(tx)
|
||||||
|
tx
|
||||||
|
|
||||||
)
|
var txCount = cs.transactionCount
|
||||||
|
if txCount == 0:
|
||||||
var (
|
# Default is to send 5 transactions
|
||||||
shadow.payload ExecutableData
|
txCount = 5
|
||||||
nextTx Transaction
|
|
||||||
tx Transaction
|
|
||||||
)
|
|
||||||
|
|
||||||
for i = 0; i < txCount; i++ (
|
|
||||||
|
|
||||||
|
for i in 0..<txCount:
|
||||||
# Generate two payloads, one with the transaction and the other one without it
|
# Generate two payloads, one with the transaction and the other one without it
|
||||||
env.clMock.produceSingleBlock(BlockProcessCallbacks(
|
let pbRes = 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 cs.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
|
var attr = env.clMock.latestPayloadAttributes
|
||||||
rand.Read(payloadAttributes.Random[:])
|
attr.prevRandao = Web3Hash.randomBytes()
|
||||||
r = env.engine.client.forkchoiceUpdated(env.clMock.latestForkchoice, payloadAttributes, env.clMock.latestHeader.Time)
|
|
||||||
|
var version = env.engine.version(env.clMock.latestHeader.timestamp)
|
||||||
|
let r = env.engine.client.forkchoiceUpdated(version, env.clMock.latestForkchoice, some(attr))
|
||||||
r.expectNoError()
|
r.expectNoError()
|
||||||
if r.Response.PayloadID == nil (
|
testCond r.get.payloadID.isSome:
|
||||||
fatal "No payload ID returned by forkchoiceUpdated", t.TestName)
|
fatal "No payload ID returned by forkchoiceUpdated"
|
||||||
)
|
|
||||||
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()
|
||||||
shadow.payload = &g.Payload
|
shadow.payload = g.get.executionPayload
|
||||||
|
|
||||||
if len(shadow.payload.transactions) != 0 (
|
testCond len(shadow.payload.transactions) == 0:
|
||||||
fatal "Empty payload contains transactions: %v", t.TestName, shadow.payload)
|
fatal "Empty payload contains transactions"
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if cs.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)
|
let tx = shadow.sendTransaction(i)
|
||||||
testCond ok:
|
|
||||||
fatal "Error trying to send transaction: %v", t.TestName, err)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the receipt
|
# Get the receipt
|
||||||
ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout)
|
let receipt = env.engine.client.txReceipt(tx.rlpHash)
|
||||||
defer cancel()
|
testCond receipt.isOk:
|
||||||
receipt, _ = t.Eth.TransactionReceipt(ctx, shadow.txHash)
|
fatal "Receipt obtained before tx included in block"
|
||||||
if receipt != nil (
|
|
||||||
fatal "Receipt obtained before tx included in block: %v", t.TestName, receipt)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
),
|
return true
|
||||||
|
,
|
||||||
onGetpayload: proc(): bool =
|
onGetpayload: proc(): bool =
|
||||||
# Check that indeed the payload contains the transaction
|
# Check that indeed the payload contains the transaction
|
||||||
if shadow.tx.isSome:
|
if shadow.tx.isSome:
|
||||||
if !TransactionInPayload(env.clMock.latestPayloadBuilt, tx) (
|
testCond txInPayload(env.clMock.latestPayloadBuilt, shadow.txHash):
|
||||||
fatal "Payload built does not contain the transaction: %v", t.TestName, env.clMock.latestPayloadBuilt)
|
fatal "Payload built does not contain the transaction"
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if cs.scenario == TransactionReOrgScenarioReOrgDifferentBlock || cs.scenario == TransactionReOrgScenarioNewPayloadOnRevert (
|
if cs.scenario in [TransactionReOrgScenarioReOrgDifferentBlock, TransactionReOrgScenarioNewPayloadOnRevert]:
|
||||||
# Create side payload with different hash
|
# Create side payload with different hash
|
||||||
var err error
|
let customizer = CustomPayloadData(
|
||||||
customizer = &CustomPayloadData(
|
extraData: some(@[0x01.byte])
|
||||||
extraData: &([]byte(0x01)),
|
|
||||||
)
|
|
||||||
shadow.payload, err = customizer.customizePayload(env.clMock.latestPayloadBuilt)
|
|
||||||
testCond ok:
|
|
||||||
fatal "Error creating reorg payload %v", err)
|
|
||||||
)
|
)
|
||||||
|
shadow.payload = customizer.customizePayload(env.clMock.latestExecutableData).basePayload
|
||||||
|
|
||||||
if shadow.payload.parentHash != env.clMock.latestPayloadBuilt.parentHash (
|
testCond shadow.payload.parentHash == env.clMock.latestPayloadBuilt.parentHash:
|
||||||
fatal "Incorrect parent hash for payloads: %v != %v", t.TestName, shadow.payload.parentHash, env.clMock.latestPayloadBuilt.parentHash)
|
fatal "Incorrect parent hash for payloads"
|
||||||
)
|
|
||||||
if shadow.payload.blockHash == env.clMock.latestPayloadBuilt.blockHash (
|
testCond shadow.payload.blockHash != env.clMock.latestPayloadBuilt.blockHash:
|
||||||
fatal "Incorrect hash for payloads: %v == %v", t.TestName, shadow.payload.blockHash, env.clMock.latestPayloadBuilt.blockHash)
|
fatal "Incorrect hash for payloads"
|
||||||
)
|
|
||||||
elif cs.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
|
||||||
shadow.nextTx, err = sendTransaction(i)
|
shadow.nextTx = shadow.sendTransaction(i)
|
||||||
testCond ok:
|
|
||||||
fatal "Error trying to send transaction: %v", t.TestName, err)
|
|
||||||
)
|
|
||||||
|
|
||||||
if i == 0 (
|
if i == 0:
|
||||||
# We actually can only do this once because the transaction carries over and we cannot
|
# We actually can only do this once because the transaction carries over and we cannot
|
||||||
# impede it from being included in the next payload
|
# impede it from being included in the next payload
|
||||||
forkchoiceUpdated = env.clMock.latestForkchoice
|
var forkchoiceUpdated = env.clMock.latestForkchoice
|
||||||
payloadAttributes = env.clMock.LatestPayloadAttributes
|
var payloadAttributes = env.clMock.latestPayloadAttributes
|
||||||
rand.Read(payloadAttributes.SuggestedFeeRecipient[:])
|
payloadAttributes.suggestedFeeRecipient = w3Addr EthAddress.randomBytes()
|
||||||
f = env.engine.client.forkchoiceUpdated(
|
|
||||||
&forkchoiceUpdated,
|
var version = env.engine.version(env.clMock.latestHeader.timestamp)
|
||||||
&payloadAttributes,
|
let f = env.engine.client.forkchoiceUpdated(version, forkchoiceUpdated, some(payloadAttributes))
|
||||||
env.clMock.latestHeader.Time,
|
|
||||||
)
|
|
||||||
f.expectPayloadStatus(PayloadExecutionStatus.valid)
|
f.expectPayloadStatus(PayloadExecutionStatus.valid)
|
||||||
|
|
||||||
# Wait a second for the client to prepare the payload with the included transaction
|
# Wait a second for the client to prepare the payload with the included transaction
|
||||||
|
let period = chronos.seconds(env.clMock.payloadProductionClientDelay)
|
||||||
|
waitFor sleepAsync(period)
|
||||||
|
|
||||||
await sleepAsync(env.clMock.PayloadProductionClientDelay)
|
version = env.engine.version(env.clMock.latestPayloadAttributes.timestamp)
|
||||||
|
let g = env.engine.client.getPayload(f.get.payloadID.get, version)
|
||||||
g = env.engine.client.getPayload(f.Response.PayloadID, env.clMock.LatestPayloadAttributes)
|
|
||||||
g.expectNoError()
|
g.expectNoError()
|
||||||
|
|
||||||
if !TransactionInPayload(g.Payload, shadow.nextTx) (
|
let payload = g.get.executionPayload
|
||||||
fatal "Payload built does not contain the transaction: %v", t.TestName, g.Payload)
|
testCond txInPayload(payload, shadow.nextTx.rlpHash):
|
||||||
)
|
fatal "Payload built does not contain the transaction"
|
||||||
|
|
||||||
# Send the new payload and forkchoiceUpdated to it
|
# Send the new payload and forkchoiceUpdated to it
|
||||||
n = env.engine.client.newPayload(g.Payload)
|
let n = env.engine.client.newPayload(payload)
|
||||||
n.expectStatus(PayloadExecutionStatus.valid)
|
n.expectStatus(PayloadExecutionStatus.valid)
|
||||||
|
|
||||||
forkchoiceUpdated.headBlockHash = g.Payload.blockHash
|
forkchoiceUpdated.headBlockHash = payload.blockHash
|
||||||
|
|
||||||
s = env.engine.client.forkchoiceUpdated(forkchoiceUpdated, nil, g.Payload.timestamp)
|
version = env.engine.version(payload.timestamp)
|
||||||
|
let s = env.engine.client.forkchoiceUpdated(version, forkchoiceUpdated)
|
||||||
s.expectPayloadStatus(PayloadExecutionStatus.valid)
|
s.expectPayloadStatus(PayloadExecutionStatus.valid)
|
||||||
)
|
return true
|
||||||
)
|
,
|
||||||
),
|
|
||||||
onNewPayloadBroadcast: proc(): bool =
|
onNewPayloadBroadcast: proc(): bool =
|
||||||
if shadow.tx.isSome:
|
if shadow.tx.isSome:
|
||||||
# Get the receipt
|
# Get the receipt
|
||||||
ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout)
|
let receipt = env.engine.client.txReceipt(shadow.txHash)
|
||||||
defer cancel()
|
testCond receipt.isOk:
|
||||||
receipt, _ = t.Eth.TransactionReceipt(ctx, shadow.txHash)
|
fatal "Receipt obtained before tx included in block (NewPayload)"
|
||||||
if receipt != nil (
|
return true
|
||||||
fatal "Receipt obtained before tx included in block (NewPayload): %v", t.TestName, receipt)
|
,
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
onForkchoiceBroadcast: proc(): bool =
|
onForkchoiceBroadcast: proc(): bool =
|
||||||
if cs.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(shadow.txHash)
|
var txt = env.engine.client.txReceipt(shadow.txHash)
|
||||||
txt.expectBlockHash(env.clMock.latestForkchoice.headBlockHash)
|
txt.expectBlockHash(ethHash env.clMock.latestForkchoice.headBlockHash)
|
||||||
|
|
||||||
if shadow.payload.parentHash != env.clMock.latestPayloadBuilt.parentHash (
|
testCond shadow.payload.parentHash == env.clMock.latestPayloadBuilt.parentHash:
|
||||||
fatal "Incorrect parent hash for payloads: %v != %v", t.TestName, shadow.payload.parentHash, env.clMock.latestPayloadBuilt.parentHash)
|
fatal "Incorrect parent hash for payloads"
|
||||||
)
|
|
||||||
if shadow.payload.blockHash == env.clMock.latestPayloadBuilt.blockHash (
|
|
||||||
fatal "Incorrect hash for payloads: %v == %v", t.TestName, shadow.payload.blockHash, env.clMock.latestPayloadBuilt.blockHash)
|
|
||||||
)
|
|
||||||
|
|
||||||
if shadow.payload == nil (
|
testCond shadow.payload.blockHash != env.clMock.latestPayloadBuilt.blockHash:
|
||||||
fatal "No payload to re-org to", t.TestName)
|
fatal "Incorrect hash for payloads"
|
||||||
)
|
|
||||||
r = env.engine.client.newPayload(shadow.payload)
|
#if shadow.payload == nil (
|
||||||
|
# fatal "No payload to re-org to", t.TestName)
|
||||||
|
|
||||||
|
let r = env.engine.client.newPayload(shadow.payload)
|
||||||
r.expectStatus(PayloadExecutionStatus.valid)
|
r.expectStatus(PayloadExecutionStatus.valid)
|
||||||
r.expectLatestValidHash(shadow.payload.blockHash)
|
r.expectLatestValidHash(shadow.payload.blockHash)
|
||||||
|
|
||||||
s = env.engine.client.forkchoiceUpdated(api.ForkchoiceStateV1(
|
let fcu = ForkchoiceStateV1(
|
||||||
headBlockHash: shadow.payload.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, shadow.payload.timestamp)
|
)
|
||||||
|
|
||||||
|
var version = env.engine.version(shadow.payload.timestamp)
|
||||||
|
let s = env.engine.client.forkchoiceUpdated(version, fcu)
|
||||||
s.expectPayloadStatus(PayloadExecutionStatus.valid)
|
s.expectPayloadStatus(PayloadExecutionStatus.valid)
|
||||||
|
|
||||||
p = env.engine.client.headerByNumber(Head)
|
let p = env.engine.client.namedHeader(Head)
|
||||||
p.expectHash(shadow.payload.blockHash)
|
p.expectHash(ethHash shadow.payload.blockHash)
|
||||||
|
|
||||||
txt = env.engine.client.txReceipt(shadow.txHash)
|
txt = env.engine.client.txReceipt(shadow.txHash)
|
||||||
if cs.scenario == TransactionReOrgScenarioReOrgOut (
|
if cs.scenario == TransactionReOrgScenarioReOrgOut:
|
||||||
if txt.Receipt != nil (
|
testCond txt.isErr:
|
||||||
receiptJson, _ = json.MarshalIndent(txt.Receipt, "", " ")
|
fatal "Receipt was obtained when the tx had been re-org'd out"
|
||||||
fatal "Receipt was obtained when the tx had been re-org'd out: %s", t.TestName, receiptJson)
|
elif cs.scenario in [TransactionReOrgScenarioReOrgDifferentBlock, TransactionReOrgScenarioNewPayloadOnRevert]:
|
||||||
)
|
txt.expectBlockHash(ethHash shadow.payload.blockHash)
|
||||||
elif cs.scenario == TransactionReOrgScenarioReOrgDifferentBlock || cs.scenario == TransactionReOrgScenarioNewPayloadOnRevert (
|
|
||||||
txt.expectBlockHash(shadow.payload.blockHash)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Re-org back
|
# Re-org back
|
||||||
if cs.scenario == TransactionReOrgScenarioNewPayloadOnRevert (
|
if cs.scenario == TransactionReOrgScenarioNewPayloadOnRevert:
|
||||||
r = env.engine.client.newPayload(env.clMock.latestPayloadBuilt)
|
let 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)
|
||||||
)
|
|
||||||
env.clMock.BroadcastForkchoiceUpdated(env.clMock.latestForkchoice, nil, 1)
|
testCond env.clMock.broadcastForkchoiceUpdated(Version.V1, env.clMock.latestForkchoice)
|
||||||
)
|
|
||||||
|
|
||||||
if shadow.tx.isSome:
|
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(shadow.txHash)
|
let txt = env.engine.client.txReceipt(shadow.txHash)
|
||||||
txt.expectBlockHash(env.clMock.latestForkchoice.headBlockHash)
|
txt.expectBlockHash(ethHash env.clMock.latestForkchoice.headBlockHash)
|
||||||
|
|
||||||
if cs.scenario != TransactionReOrgScenarioReOrgBackIn (
|
if cs.scenario != TransactionReOrgScenarioReOrgBackIn:
|
||||||
tx = nil
|
shadow.tx = none(Transaction)
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if cs.scenario == TransactionReOrgScenarioReOrgBackIn && i > 0 (
|
if cs.scenario == TransactionReOrgScenarioReOrgBackIn and 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 = shadow.nextTx
|
shadow.tx = some(shadow.nextTx)
|
||||||
)
|
return true
|
||||||
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
|
testCond pbRes
|
||||||
)
|
|
||||||
|
|
||||||
if shadow.tx.isSome:
|
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(
|
let pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
|
||||||
onForkchoiceBroadcast: proc(): bool =
|
onForkchoiceBroadcast: proc(): bool =
|
||||||
if !TransactionInPayload(env.clMock.latestPayloadBuilt, tx) (
|
testCond txInPayload(env.clMock.latestPayloadBuilt, shadow.txHash):
|
||||||
fatal "Payload built does not contain the transaction: %v", t.TestName, env.clMock.latestPayloadBuilt)
|
fatal "Payload built does not contain the transaction"
|
||||||
)
|
|
||||||
# Get the receipt
|
# Get the receipt
|
||||||
ctx, cancel = context.WithTimeout(t.TestContext, globals.RPCTimeout)
|
let receipt = env.engine.client.txReceipt(shadow.txHash)
|
||||||
defer cancel()
|
testCond receipt.isErr:
|
||||||
receipt, _ = t.Eth.TransactionReceipt(ctx, shadow.txHash)
|
fatal "Receipt not obtained after tx included in block"
|
||||||
if receipt == nil (
|
|
||||||
fatal "Receipt not obtained after tx included in block: %v", t.TestName, receipt)
|
return true
|
||||||
)
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
|
testCond pbRes
|
||||||
|
|
||||||
)
|
return true
|
||||||
|
|
||||||
)
|
|
||||||
]#
|
|
||||||
|
|
||||||
# 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.
|
||||||
|
@ -401,6 +370,13 @@ type
|
||||||
# Whether to execute a sidechain payload on the re-org
|
# Whether to execute a sidechain payload on the re-org
|
||||||
executeSidePayloadOnReOrg*: bool
|
executeSidePayloadOnReOrg*: bool
|
||||||
|
|
||||||
|
ShadowCanon = ref object
|
||||||
|
previousHash: Web3Hash
|
||||||
|
previousTimestamp: Web3Quantity
|
||||||
|
payload: ExecutionPayload
|
||||||
|
parentForkchoice: ForkchoiceStateV1
|
||||||
|
parentTimestamp: uint64
|
||||||
|
|
||||||
method withMainFork(cs: ReOrgBackToCanonicalTest, fork: EngineFork): BaseSpec =
|
method withMainFork(cs: ReOrgBackToCanonicalTest, fork: EngineFork): BaseSpec =
|
||||||
var res = cs.clone()
|
var res = cs.clone()
|
||||||
res.mainFork = fork
|
res.mainFork = fork
|
||||||
|
@ -435,99 +411,99 @@ method execute(cs: ReOrgBackToCanonicalTest, 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())
|
||||||
|
|
||||||
#[
|
|
||||||
# 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
|
var shadow = ShadowCanon(
|
||||||
previousTimestamp = env.clMock.latestPayloadBuilt.timestamp
|
previousHash: env.clMock.latestForkchoice.headBlockHash,
|
||||||
|
previousTimestamp: env.clMock.latestPayloadBuilt.timestamp
|
||||||
|
)
|
||||||
|
|
||||||
if cs.executeSidePayloadOnReOrg (
|
if cs.executeSidePayloadOnReOrg:
|
||||||
var (
|
var pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
|
||||||
shadow.payload ExecutableData
|
onPayloadAttributesGenerated: proc(): bool =
|
||||||
shadow.parentForkchoice api.ForkchoiceStateV1
|
var attr = env.clMock.latestPayloadAttributes
|
||||||
shadow.parentTimestamp uint64
|
attr.prevRandao = Web3Hash.randomBytes()
|
||||||
)
|
|
||||||
env.clMock.produceSingleBlock(BlockProcessCallbacks(
|
var version = env.engine.version(env.clMock.latestHeader.timestamp)
|
||||||
OnPayloadAttributesGenerated: proc(): bool =
|
let r = env.engine.client.forkchoiceUpdated(version, env.clMock.latestForkchoice, some(attr))
|
||||||
payloadAttributes = env.clMock.LatestPayloadAttributes
|
|
||||||
rand.Read(payloadAttributes.Random[:])
|
|
||||||
r = env.engine.client.forkchoiceUpdated(env.clMock.latestForkchoice, payloadAttributes, env.clMock.latestHeader.Time)
|
|
||||||
r.expectNoError()
|
r.expectNoError()
|
||||||
if r.Response.PayloadID == nil (
|
testcond r.get.payloadID.isSome:
|
||||||
fatal "No payload ID returned by forkchoiceUpdated", t.TestName)
|
fatal "No payload ID returned by forkchoiceUpdated"
|
||||||
)
|
|
||||||
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()
|
||||||
shadow.payload = &g.Payload
|
|
||||||
|
shadow.payload = g.get.executionPayload
|
||||||
shadow.parentForkchoice = env.clMock.latestForkchoice
|
shadow.parentForkchoice = env.clMock.latestForkchoice
|
||||||
shadow.parentTimestamp = env.clMock.latestHeader.Time
|
shadow.parentTimestamp = env.clMock.latestHeader.timestamp.uint64
|
||||||
),
|
return true
|
||||||
))
|
))
|
||||||
|
testCond pbRes
|
||||||
|
|
||||||
# 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(cs.GetDepth()-1), BlockProcessCallbacks(
|
pbRes = env.clMock.produceBlocks(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
|
let tc = BaseTx(
|
||||||
_, err = t.sendNextTxs(
|
recipient: some(ZeroAddr),
|
||||||
t.TestContext,
|
amount: 1.u256,
|
||||||
t.Engine,
|
|
||||||
BaseTx(
|
|
||||||
recipient: &ZeroAddr,
|
|
||||||
amount: big1,
|
|
||||||
payload: nil,
|
|
||||||
txType: cs.txType,
|
txType: cs.txType,
|
||||||
gasLimit: 75000,
|
gasLimit: 75000,
|
||||||
ForkConfig: t.ForkConfig,
|
|
||||||
),
|
|
||||||
cs.transactionPerPayload,
|
|
||||||
)
|
)
|
||||||
|
let ok = env.sendNextTxs(env.engine, tc, cs.transactionPerPayload)
|
||||||
testCond ok:
|
testCond ok:
|
||||||
fatal "Error trying to send transactions: %v", t.TestName, err)
|
fatal "Error trying to send transactions"
|
||||||
)
|
return true
|
||||||
),
|
|
||||||
))
|
))
|
||||||
|
testCond pbRes
|
||||||
|
|
||||||
# On the last block, before executing the next payload of the canonical chain,
|
# On the last block, before executing the next payload of the canonical chain,
|
||||||
# re-org back to the parent of the side payload and execute the side payload first
|
# re-org back to the parent of the side payload and execute the side payload first
|
||||||
env.clMock.produceSingleBlock(BlockProcessCallbacks(
|
pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
|
||||||
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(shadow.parentForkchoice, nil, shadow.parentTimestamp)
|
var version = env.engine.version(shadow.parentTimestamp)
|
||||||
|
let f = env.engine.client.forkchoiceUpdated(version, shadow.parentForkchoice)
|
||||||
f.expectPayloadStatus(PayloadExecutionStatus.valid)
|
f.expectPayloadStatus(PayloadExecutionStatus.valid)
|
||||||
f.expectLatestValidHash(shadow.parentForkchoice.headBlockHash)
|
f.expectLatestValidHash(shadow.parentForkchoice.headBlockHash)
|
||||||
|
|
||||||
# Execute the side payload
|
# Execute the side payload
|
||||||
n = env.engine.client.newPayload(shadow.payload)
|
let n = env.engine.client.newPayload(shadow.payload)
|
||||||
n.expectStatus(PayloadExecutionStatus.valid)
|
n.expectStatus(PayloadExecutionStatus.valid)
|
||||||
n.expectLatestValidHash(shadow.payload.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
|
||||||
),
|
return true
|
||||||
))
|
))
|
||||||
|
testCond pbRes
|
||||||
else:
|
else:
|
||||||
testCond env.clMock.produceBlocks(int(cs.GetDepth()), BlockProcessCallbacks(
|
let pbRes = env.clMock.produceBlocks(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(
|
let fcu = ForkchoiceStateV1(
|
||||||
headBlockHash: previousHash,
|
headBlockHash: shadow.previousHash,
|
||||||
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
|
||||||
r = env.engine.client.forkchoiceUpdated(forkchoiceUpdatedBack, nil, previousTimestamp)
|
var version = env.engine.version(shadow.previousTimestamp)
|
||||||
|
var r = env.engine.client.forkchoiceUpdated(version, fcu)
|
||||||
r.expectNoError()
|
r.expectNoError()
|
||||||
|
|
||||||
# Re-send the ForkchoiceUpdated that the CLMock had sent
|
# Re-send the ForkchoiceUpdated that the CLMock had sent
|
||||||
r = env.engine.client.forkchoiceUpdated(env.clMock.latestForkchoice, nil, env.clMock.LatestExecutedPayload.timestamp)
|
version = env.engine.version(env.clMock.latestExecutedPayload.timestamp)
|
||||||
|
r = env.engine.client.forkchoiceUpdated(version, env.clMock.latestForkchoice)
|
||||||
r.expectNoError()
|
r.expectNoError()
|
||||||
),
|
return true
|
||||||
))
|
))
|
||||||
)
|
testCond pbRes
|
||||||
|
|
||||||
# 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)
|
let r = env.engine.client.namedHeader(Head)
|
||||||
r.expectHash(env.clMock.latestPayloadBuilt.blockHash)
|
r.expectHash(ethHash env.clMock.latestPayloadBuilt.blockHash)
|
||||||
)
|
return true
|
||||||
]#
|
|
||||||
|
|
||||||
type
|
type
|
||||||
ReOrgBackFromSyncingTest* = ref object of EngineSpec
|
ReOrgBackFromSyncingTest* = ref object of EngineSpec
|
||||||
|
|
|
@ -165,12 +165,16 @@ template expectLatestValidHash*(res: untyped, expectedHash: Web3Hash) =
|
||||||
testCond s.latestValidHash.isSome:
|
testCond s.latestValidHash.isSome:
|
||||||
error "Expect latest valid hash isSome"
|
error "Expect latest valid hash isSome"
|
||||||
testCond s.latestValidHash.get == expectedHash:
|
testCond s.latestValidHash.get == expectedHash:
|
||||||
error "latest valid hash mismatch", expect=expectedHash, get=s.latestValidHash.get
|
error "latest valid hash mismatch",
|
||||||
|
expect=expectedHash.short,
|
||||||
|
get=s.latestValidHash.get.short
|
||||||
else:
|
else:
|
||||||
testCond s.payloadStatus.latestValidHash.isSome:
|
testCond s.payloadStatus.latestValidHash.isSome:
|
||||||
error "Expect latest valid hash isSome"
|
error "Expect latest valid hash isSome"
|
||||||
testCond s.payloadStatus.latestValidHash.get == expectedHash:
|
testCond s.payloadStatus.latestValidHash.get == expectedHash:
|
||||||
error "latest valid hash mismatch", expect=expectedHash, get=s.payloadStatus.latestValidHash.get
|
error "latest valid hash mismatch",
|
||||||
|
expect=expectedHash.short,
|
||||||
|
get=s.payloadStatus.latestValidHash.get.short
|
||||||
|
|
||||||
template expectLatestValidHash*(res: untyped) =
|
template expectLatestValidHash*(res: untyped) =
|
||||||
testCond res.isOk:
|
testCond res.isOk:
|
||||||
|
@ -297,6 +301,13 @@ template expectPayloadParentHash*(res: untyped, expected: Web3Hash) =
|
||||||
testCond rec.executionPayload.parentHash == expected:
|
testCond rec.executionPayload.parentHash == expected:
|
||||||
error "expectPayloadParentHash", expect=expected.short, get=rec.executionPayload.parentHash.short
|
error "expectPayloadParentHash", expect=expected.short, get=rec.executionPayload.parentHash.short
|
||||||
|
|
||||||
|
template expectBlockHash*(res: untyped, expected: common.Hash256) =
|
||||||
|
testCond res.isOk:
|
||||||
|
error "expectBlockHash", msg=res.error
|
||||||
|
let rec = res.get
|
||||||
|
testCond rec.blockHash == expected:
|
||||||
|
error "expectBlockHash", expect=expected.short, get=rec.blockHash.short
|
||||||
|
|
||||||
func timestamp*(x: ExecutableData): auto =
|
func timestamp*(x: ExecutableData): auto =
|
||||||
x.basePayload.timestamp
|
x.basePayload.timestamp
|
||||||
|
|
||||||
|
|
|
@ -126,8 +126,9 @@ proc newPayload*(ben: BeaconEngineRef,
|
||||||
|
|
||||||
if header.timestamp <= parent.timestamp:
|
if header.timestamp <= parent.timestamp:
|
||||||
warn "Invalid timestamp",
|
warn "Invalid timestamp",
|
||||||
parent = header.timestamp, header = header.timestamp
|
number = header.blockNumber, parentNumber = parent.blockNumber,
|
||||||
return invalidStatus(db.getHeadBlockHash(), "Invalid timestamp")
|
parent = parent.timestamp, header = header.timestamp
|
||||||
|
return invalidStatus(parent.blockHash, "Invalid timestamp")
|
||||||
|
|
||||||
if not db.haveBlockAndState(header.parentHash):
|
if not db.haveBlockAndState(header.parentHash):
|
||||||
ben.put(blockHash, header)
|
ben.put(blockHash, header)
|
||||||
|
|
Loading…
Reference in New Issue