422 lines
12 KiB
Nim
422 lines
12 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
# http://opensource.org/licenses/MIT)
|
|
# at your option. This file may not be copied, modified, or distributed except
|
|
# according to those terms.
|
|
|
|
import
|
|
eth/common/eth_types,
|
|
./engine/engine_spec,
|
|
./types,
|
|
./test_env,
|
|
./base_spec,
|
|
./cancun/customizer,
|
|
../../../nimbus/common/chain_config
|
|
|
|
import
|
|
./engine/suggested_fee_recipient,
|
|
./engine/payload_attributes,
|
|
./engine/payload_execution,
|
|
./engine/invalid_ancestor,
|
|
./engine/invalid_payload,
|
|
./engine/prev_randao,
|
|
./engine/payload_id,
|
|
./engine/forkchoice,
|
|
./engine/versioning,
|
|
./engine/bad_hash,
|
|
./engine/fork_id,
|
|
./engine/reorg,
|
|
./engine/misc,
|
|
./engine/rpc
|
|
|
|
proc getGenesis(cs: EngineSpec, param: NetworkParams) =
|
|
# Set the terminal total difficulty
|
|
let realTTD = param.genesis.difficulty + cs.ttd.u256
|
|
param.config.terminalTotalDifficulty = some(realTTD)
|
|
if param.genesis.difficulty <= realTTD:
|
|
param.config.terminalTotalDifficultyPassed = some(true)
|
|
|
|
# Set the genesis timestamp if provided
|
|
if cs.genesisTimestamp != 0:
|
|
param.genesis.timestamp = cs.genesisTimestamp.EthTime
|
|
|
|
proc specExecute(ws: BaseSpec): bool =
|
|
let
|
|
cs = EngineSpec(ws)
|
|
forkConfig = ws.getForkConfig()
|
|
|
|
if forkConfig.isNil:
|
|
echo "because fork configuration is not possible, skip test: ", cs.getName()
|
|
return true
|
|
|
|
let conf = envConfig(forkConfig)
|
|
cs.getGenesis(conf.networkParams)
|
|
let env = TestEnv.new(conf)
|
|
env.engine.setRealTTD()
|
|
env.setupCLMock()
|
|
if cs.enableConfigureCLMock:
|
|
cs.configureCLMock(env.clMock)
|
|
result = cs.execute(env)
|
|
env.close()
|
|
|
|
# Execution specification reference:
|
|
# https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md
|
|
|
|
# Register all test combinations for Paris
|
|
proc makeEngineTest*(): seq[EngineSpec] =
|
|
# Misc Tests
|
|
# Pre-merge & merge fork occur at block 1, post-merge forks occur at block 2
|
|
result.add NonZeroPreMergeFork(forkHeight: 2)
|
|
|
|
# Payload Attributes Tests
|
|
block:
|
|
let list = [
|
|
InvalidPayloadAttributesTest(
|
|
description: "Zero timestamp",
|
|
customizer: BasePayloadAttributesCustomizer(
|
|
timestamp: some(0'u64),
|
|
),
|
|
),
|
|
InvalidPayloadAttributesTest(
|
|
description: "Parent timestamp",
|
|
customizer: TimestampDeltaPayloadAttributesCustomizer(
|
|
timestampDelta: -1,
|
|
),
|
|
),
|
|
]
|
|
|
|
for x in list:
|
|
result.add x
|
|
let y = x.clone()
|
|
y.syncing = true
|
|
result.add y
|
|
|
|
# Invalid Transaction ChainID Tests
|
|
result.add InvalidTxChainIDTest(
|
|
txType: some(TxLegacy),
|
|
)
|
|
|
|
result.add InvalidTxChainIDTest(
|
|
txType: some(TxEip1559),
|
|
)
|
|
|
|
# Invalid Ancestor Re-Org Tests (Reveal Via NewPayload)
|
|
for invalidIndex in [1, 9, 10]:
|
|
for emptyTxs in [false, true]:
|
|
result.add InvalidMissingAncestorReOrgTest(
|
|
slotsToSafe: 32,
|
|
slotsToFinalized: 64,
|
|
sidechainLength: 10,
|
|
invalidIndex: invalidIndex,
|
|
invalidField: InvalidStateRoot,
|
|
emptyTransactions: emptyTxs,
|
|
enableConfigureCLMock: true
|
|
)
|
|
|
|
# Invalid Payload Tests
|
|
const
|
|
invalidPayloadBlockFields = [
|
|
InvalidParentHash,
|
|
InvalidStateRoot,
|
|
InvalidReceiptsRoot,
|
|
InvalidNumber,
|
|
InvalidGasLimit,
|
|
InvalidGasUsed,
|
|
InvalidTimestamp,
|
|
InvalidPrevRandao,
|
|
RemoveTransaction,
|
|
]
|
|
|
|
for invalidField in invalidPayloadBlockFields:
|
|
for syncing in [false, true]:
|
|
if invalidField == InvalidStateRoot:
|
|
result.add InvalidPayloadTestCase(
|
|
invalidField: invalidField,
|
|
syncing: syncing,
|
|
emptyTransactions: true,
|
|
)
|
|
|
|
result.add InvalidPayloadTestCase(
|
|
invalidField: invalidField,
|
|
syncing: syncing,
|
|
)
|
|
|
|
# Register bad hash tests
|
|
for syncing in [false, true]:
|
|
for sidechain in [false, true]:
|
|
result.add BadHashOnNewPayload(
|
|
syncing: syncing,
|
|
sidechain: sidechain,
|
|
)
|
|
|
|
# Parent hash == block hash tests
|
|
result.add ParentHashOnNewPayload(syncing: false)
|
|
result.add ParentHashOnNewPayload(syncing: true)
|
|
|
|
result.add PayloadBuildAfterInvalidPayloadTest(
|
|
invalidField: InvalidStateRoot,
|
|
)
|
|
|
|
const forkchoiceStateField = [
|
|
HeadBlockHash,
|
|
SafeBlockHash,
|
|
FinalizedBlockHash,
|
|
]
|
|
|
|
# Register ForkchoiceUpdate tests
|
|
for field in forkchoiceStateField:
|
|
result.add InconsistentForkchoiceTest(field: field)
|
|
result.add ForkchoiceUpdatedUnknownBlockHashTest(field: field)
|
|
|
|
# PrevRandao opcode tests
|
|
result.add PrevRandaoTransactionTest(
|
|
txType: some(TxLegacy)
|
|
)
|
|
|
|
result.add PrevRandaoTransactionTest(
|
|
txType: some(TxEip1559),
|
|
)
|
|
|
|
# Suggested Fee Recipient Tests
|
|
result.add SuggestedFeeRecipientTest(
|
|
txType: some(TxLegacy),
|
|
transactionCount: 20,
|
|
)
|
|
|
|
result.add SuggestedFeeRecipientTest(
|
|
txType: some(TxEip1559),
|
|
transactionCount: 20,
|
|
)
|
|
|
|
# Payload Execution Tests
|
|
result.add ReExecutePayloadTest()
|
|
result.add InOrderPayloadExecutionTest()
|
|
result.add MultiplePayloadsExtendingCanonicalChainTest(
|
|
setHeadToFirstPayloadReceived: true,
|
|
)
|
|
|
|
result.add MultiplePayloadsExtendingCanonicalChainTest(
|
|
setHeadToFirstPayloadReceived: false,
|
|
)
|
|
|
|
result.add NewPayloadOnSyncingClientTest()
|
|
result.add NewPayloadWithMissingFcUTest()
|
|
|
|
const invalidPayloadBlockField = [
|
|
InvalidTransactionSignature,
|
|
InvalidTransactionNonce,
|
|
InvalidTransactionGasPrice,
|
|
InvalidTransactionGasTipPrice,
|
|
InvalidTransactionGas,
|
|
InvalidTransactionValue,
|
|
InvalidTransactionChainID,
|
|
]
|
|
|
|
# Invalid Transaction Payload Tests
|
|
for invalidField in invalidPayloadBlockField:
|
|
let invalidDetectedOnSync = invalidField == InvalidTransactionChainID
|
|
for syncing in [false, true]:
|
|
if invalidField != InvalidTransactionGasTipPrice:
|
|
for testTxType in [TxLegacy, TxEip1559]:
|
|
result.add InvalidPayloadTestCase(
|
|
txType: some(testTxType),
|
|
invalidField: invalidField,
|
|
syncing: syncing,
|
|
invalidDetectedOnSync: invalidDetectedOnSync,
|
|
)
|
|
else:
|
|
result.add InvalidPayloadTestCase(
|
|
txType: some(TxEip1559),
|
|
invalidField: invalidField,
|
|
syncing: syncing,
|
|
invalidDetectedOnSync: invalidDetectedOnSync,
|
|
)
|
|
|
|
const payloadAttributesFieldChange = [
|
|
PayloadAttributesIncreaseTimestamp,
|
|
PayloadAttributesRandom,
|
|
PayloadAttributesSuggestedFeeRecipient,
|
|
]
|
|
|
|
# Payload ID Tests
|
|
for payloadAttributeFieldChange in payloadAttributesFieldChange:
|
|
result.add UniquePayloadIDTest(
|
|
fieldModification: payloadAttributeFieldChange,
|
|
)
|
|
|
|
# Endpoint Versions Tests
|
|
# Early upgrade of ForkchoiceUpdated when requesting a payload
|
|
result.add ForkchoiceUpdatedOnPayloadRequestTest(
|
|
name: "Early upgrade",
|
|
about: """
|
|
Early upgrade of ForkchoiceUpdated when requesting a payload.
|
|
The test sets the fork height to 1, and the block timestamp increments to 2
|
|
seconds each block.
|
|
CL Mock prepares the payload attributes for the first block, which should contain
|
|
the attributes of the next fork.
|
|
The test then reduces the timestamp by 1, but still uses the next forkchoice updated
|
|
version, which should result in UNSUPPORTED_FORK_ERROR error.
|
|
""",
|
|
forkHeight: 1,
|
|
blockTimestampIncrement: 2,
|
|
forkchoiceUpdatedCustomizer: UpgradeForkchoiceUpdatedVersion(
|
|
expectedError: engineApiUnsupportedFork,
|
|
),
|
|
payloadAttributescustomizer: TimestampDeltaPayloadAttributesCustomizer(
|
|
timestampDelta: -1,
|
|
),
|
|
)
|
|
|
|
# Register RPC tests
|
|
let blockStatusRPCCheckType = [
|
|
LatestOnNewPayload,
|
|
LatestOnHeadBlockHash,
|
|
SafeOnSafeBlockHash,
|
|
FinalizedOnFinalizedBlockHash,
|
|
]
|
|
|
|
for field in blockStatusRPCCheckType:
|
|
result.add BlockStatus(checkType: field)
|
|
|
|
# Fork ID Tests
|
|
for genesisTimestamp in 0..1:
|
|
for forkTime in 0..2:
|
|
for prevForkTime in 0..forkTime:
|
|
for currentBlock in 0..1:
|
|
result.add ForkIDSpec(
|
|
mainFork: ForkParis,
|
|
genesistimestamp: genesisTimestamp,
|
|
forkTime: forkTime.uint64,
|
|
previousForkTime: prevForkTime.uint64,
|
|
produceBlocksBeforePeering: currentBlock,
|
|
)
|
|
|
|
# Re-org using the Engine API tests
|
|
|
|
# Sidechain re-org tests
|
|
result.add SidechainReOrgTest()
|
|
result.add ReOrgBackFromSyncingTest(
|
|
slotsToSafe: 32,
|
|
slotsToFinalized: 64,
|
|
enableConfigureCLMock: true,
|
|
)
|
|
|
|
result.add ReOrgPrevValidatedPayloadOnSideChainTest(
|
|
slotsToSafe: 32,
|
|
slotsToFinalized: 64,
|
|
enableConfigureCLMock: true,
|
|
)
|
|
|
|
result.add SafeReOrgToSideChainTest(
|
|
slotsToSafe: 1,
|
|
slotsToFinalized: 2,
|
|
enableConfigureCLMock: true,
|
|
)
|
|
|
|
# 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,
|
|
enableConfigureCLMock: true,
|
|
)
|
|
|
|
result.add ReOrgBackToCanonicalTest(
|
|
slotsToSafe: 32,
|
|
slotsToFinalized: 64,
|
|
timeoutSeconds: 120,
|
|
transactionPerPayload: 50,
|
|
reOrgDepth: 10,
|
|
executeSidePayloadOnReOrg: true,
|
|
enableConfigureCLMock: true,
|
|
)
|
|
|
|
const
|
|
invalidReorgList = [
|
|
InvalidStateRoot,
|
|
InvalidReceiptsRoot,
|
|
# TODO: InvalidNumber, Test is causing a panic on the secondary node, disabling for now.
|
|
InvalidGasLimit,
|
|
InvalidGasUsed,
|
|
InvalidTimestamp,
|
|
# TODO: InvalidPrevRandao, Test consistently fails with Failed to set invalid block: missing trie node.
|
|
RemoveTransaction,
|
|
InvalidTransactionSignature,
|
|
InvalidTransactionNonce,
|
|
InvalidTransactionGas,
|
|
InvalidTransactionGasPrice,
|
|
InvalidTransactionValue,
|
|
# InvalidOmmers, Unsupported now
|
|
]
|
|
|
|
eightList = [
|
|
InvalidReceiptsRoot,
|
|
InvalidGasLimit,
|
|
InvalidGasUsed,
|
|
InvalidTimestamp,
|
|
InvalidPrevRandao
|
|
]
|
|
|
|
# Invalid Ancestor Re-Org Tests (Reveal Via Sync)
|
|
for invalidField in invalidReorgList:
|
|
for reOrgFromCanonical in [false, true]:
|
|
var invalidIndex = 9
|
|
if invalidField in eightList:
|
|
invalidIndex = 8
|
|
|
|
if invalidField == InvalidStateRoot:
|
|
result.add InvalidMissingAncestorReOrgSyncTest(
|
|
timeoutSeconds: 60,
|
|
slotsToSafe: 32,
|
|
slotsToFinalized: 64,
|
|
invalidField: invalidField,
|
|
reOrgFromCanonical: reOrgFromCanonical,
|
|
emptyTransactions: true,
|
|
invalidIndex: invalidIndex,
|
|
enableConfigureCLMock: true,
|
|
)
|
|
|
|
result.add InvalidMissingAncestorReOrgSyncTest(
|
|
timeoutSeconds: 60,
|
|
slotsToSafe: 32,
|
|
slotsToFinalized: 64,
|
|
invalidField: invalidField,
|
|
reOrgFromCanonical: reOrgFromCanonical,
|
|
invalidIndex: invalidIndex,
|
|
enableConfigureCLMock: true,
|
|
)
|
|
|
|
proc fillEngineTests*(): seq[TestDesc] =
|
|
let list = makeEngineTest()
|
|
for x in list:
|
|
result.add TestDesc(
|
|
name: x.getName(),
|
|
run: specExecute,
|
|
spec: x,
|
|
)
|
|
|
|
let engineTestList* = fillEngineTests()
|