@aztec/end-to-end 5.0.0-rc.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -27
- package/dest/automine/automine_test_context.d.ts +85 -0
- package/dest/automine/automine_test_context.d.ts.map +1 -0
- package/dest/automine/automine_test_context.js +98 -0
- package/dest/automine/contracts/fixtures/storage_proof_fetcher.d.ts +2 -0
- package/dest/automine/contracts/fixtures/storage_proof_fetcher.d.ts.map +1 -0
- package/dest/{e2e_storage_proof → automine/contracts}/fixtures/storage_proof_fixture.d.ts +1 -1
- package/dest/automine/contracts/fixtures/storage_proof_fixture.d.ts.map +1 -0
- package/dest/automine/delivery/interactive_handshake_responder.d.ts +48 -0
- package/dest/automine/delivery/interactive_handshake_responder.d.ts.map +1 -0
- package/dest/automine/delivery/interactive_handshake_responder.js +76 -0
- package/dest/automine/delivery/onchain_delivery_harness.d.ts +19 -0
- package/dest/automine/delivery/onchain_delivery_harness.d.ts.map +1 -0
- package/dest/automine/delivery/onchain_delivery_harness.js +141 -0
- package/dest/automine/token/blacklist_token_contract_test.d.ts +49 -0
- package/dest/automine/token/blacklist_token_contract_test.d.ts.map +1 -0
- package/dest/{e2e_blacklist_token_contract → automine/token}/blacklist_token_contract_test.js +17 -24
- package/dest/automine/token/token_contract_test.d.ts +40 -0
- package/dest/automine/token/token_contract_test.d.ts.map +1 -0
- package/dest/{e2e_token_contract → automine/token}/token_contract_test.js +22 -28
- package/dest/automine/token/token_test_helpers.d.ts +73 -0
- package/dest/automine/token/token_test_helpers.d.ts.map +1 -0
- package/dest/automine/token/token_test_helpers.js +97 -0
- package/dest/bench/client_flows/benchmark.d.ts +1 -1
- package/dest/bench/client_flows/benchmark.d.ts.map +1 -1
- package/dest/bench/client_flows/benchmark.js +3 -1
- package/dest/bench/client_flows/client_flows_benchmark.d.ts +4 -4
- package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
- package/dest/bench/client_flows/client_flows_benchmark.js +25 -10
- package/dest/bench/utils.d.ts +1 -1
- package/dest/composed/ha/ha_full_setup.d.ts +77 -0
- package/dest/composed/ha/ha_full_setup.d.ts.map +1 -0
- package/dest/composed/ha/ha_full_setup.js +383 -0
- package/dest/composed/web3signer/multi_validator_keystore_utils.d.ts +12 -0
- package/dest/composed/web3signer/multi_validator_keystore_utils.d.ts.map +1 -0
- package/dest/fixtures/e2e_prover_test.d.ts +10 -7
- package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
- package/dest/fixtures/e2e_prover_test.js +33 -14
- package/dest/fixtures/fixtures.d.ts +8 -5
- package/dest/fixtures/fixtures.d.ts.map +1 -1
- package/dest/fixtures/fixtures.js +12 -4
- package/dest/fixtures/ha_setup.d.ts +3 -3
- package/dest/fixtures/ha_setup.d.ts.map +1 -1
- package/dest/fixtures/ha_setup.js +4 -1
- package/dest/fixtures/index.d.ts +2 -1
- package/dest/fixtures/index.d.ts.map +1 -1
- package/dest/fixtures/index.js +1 -0
- package/dest/fixtures/setup.d.ts +35 -9
- package/dest/fixtures/setup.d.ts.map +1 -1
- package/dest/fixtures/setup.js +175 -142
- package/dest/fixtures/setup_p2p_test.d.ts +1 -1
- package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
- package/dest/fixtures/setup_p2p_test.js +3 -3
- package/dest/fixtures/standard_contracts_genesis.d.ts +16 -0
- package/dest/fixtures/standard_contracts_genesis.d.ts.map +1 -0
- package/dest/fixtures/standard_contracts_genesis.js +25 -0
- package/dest/fixtures/timing.d.ts +42 -0
- package/dest/fixtures/timing.d.ts.map +1 -0
- package/dest/fixtures/timing.js +72 -0
- package/dest/fixtures/token_utils.d.ts +22 -4
- package/dest/fixtures/token_utils.d.ts.map +1 -1
- package/dest/fixtures/token_utils.js +21 -1
- package/dest/fixtures/wait_helpers.d.ts +135 -0
- package/dest/fixtures/wait_helpers.d.ts.map +1 -0
- package/dest/fixtures/wait_helpers.js +154 -0
- package/dest/forward-compatibility/wallet_service.js +1 -1
- package/dest/multi-node/block-production/setup.d.ts +97 -0
- package/dest/multi-node/block-production/setup.d.ts.map +1 -0
- package/dest/multi-node/block-production/setup.js +158 -0
- package/dest/multi-node/governance/setup.d.ts +67 -0
- package/dest/multi-node/governance/setup.d.ts.map +1 -0
- package/dest/multi-node/governance/setup.js +126 -0
- package/dest/multi-node/multi_node_test_context.d.ts +212 -0
- package/dest/multi-node/multi_node_test_context.d.ts.map +1 -0
- package/dest/multi-node/multi_node_test_context.js +347 -0
- package/dest/multi-node/slashing/inactivity_setup.d.ts +27 -0
- package/dest/multi-node/slashing/inactivity_setup.d.ts.map +1 -0
- package/dest/multi-node/slashing/inactivity_setup.js +111 -0
- package/dest/multi-node/slashing/setup.d.ts +167 -0
- package/dest/multi-node/slashing/setup.d.ts.map +1 -0
- package/dest/multi-node/slashing/setup.js +275 -0
- package/dest/{e2e_p2p → p2p}/p2p_network.d.ts +10 -2
- package/dest/p2p/p2p_network.d.ts.map +1 -0
- package/dest/{e2e_p2p → p2p}/p2p_network.js +36 -9
- package/dest/p2p/reqresp/utils.d.ts +19 -0
- package/dest/p2p/reqresp/utils.d.ts.map +1 -0
- package/dest/{e2e_p2p → p2p}/reqresp/utils.js +4 -15
- package/dest/p2p/shared.d.ts +72 -0
- package/dest/p2p/shared.d.ts.map +1 -0
- package/dest/p2p/shared.js +212 -0
- package/dest/shared/cross_chain_test_harness.d.ts +3 -3
- package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
- package/dest/shared/cross_chain_test_harness.js +24 -16
- package/dest/shared/gas_portal_test_harness.d.ts +1 -1
- package/dest/shared/gas_portal_test_harness.d.ts.map +1 -1
- package/dest/shared/gas_portal_test_harness.js +8 -5
- package/dest/shared/submit-transactions.d.ts +1 -1
- package/dest/shared/submit-transactions.d.ts.map +1 -1
- package/dest/shared/submit-transactions.js +12 -5
- package/dest/shared/timing_env.d.js +5 -0
- package/dest/shared/timing_env.js +365 -0
- package/dest/shared/wait_for_l1_to_l2_message.js +1 -1
- package/dest/simulators/lending_simulator.js +1 -1
- package/dest/single-node/cross-chain/cross_chain_messaging_test.d.ts +75 -0
- package/dest/single-node/cross-chain/cross_chain_messaging_test.d.ts.map +1 -0
- package/dest/{e2e_cross_chain_messaging → single-node/cross-chain}/cross_chain_messaging_test.js +77 -27
- package/dest/single-node/cross-chain/message_test_helpers.d.ts +38 -0
- package/dest/single-node/cross-chain/message_test_helpers.d.ts.map +1 -0
- package/dest/single-node/cross-chain/message_test_helpers.js +70 -0
- package/dest/{e2e_fees → single-node/fees}/bridging_race.notest.d.ts +1 -1
- package/dest/single-node/fees/bridging_race.notest.d.ts.map +1 -0
- package/dest/{e2e_fees → single-node/fees}/bridging_race.notest.js +17 -4
- package/dest/single-node/fees/fees_test.d.ts +95 -0
- package/dest/single-node/fees/fees_test.d.ts.map +1 -0
- package/dest/{e2e_fees → single-node/fees}/fees_test.js +124 -46
- package/dest/single-node/l1-reorgs/setup.d.ts +42 -0
- package/dest/single-node/l1-reorgs/setup.d.ts.map +1 -0
- package/dest/single-node/l1-reorgs/setup.js +57 -0
- package/dest/single-node/partial-proofs/setup.d.ts +5 -0
- package/dest/single-node/partial-proofs/setup.d.ts.map +1 -0
- package/dest/single-node/partial-proofs/setup.js +5 -0
- package/dest/single-node/proving/setup.d.ts +5 -0
- package/dest/single-node/proving/setup.d.ts.map +1 -0
- package/dest/single-node/proving/setup.js +5 -0
- package/dest/single-node/recovery/setup.d.ts +5 -0
- package/dest/single-node/recovery/setup.d.ts.map +1 -0
- package/dest/single-node/recovery/setup.js +5 -0
- package/dest/single-node/setup.d.ts +49 -0
- package/dest/single-node/setup.d.ts.map +1 -0
- package/dest/single-node/setup.js +56 -0
- package/dest/single-node/single_node_test_context.d.ts +373 -0
- package/dest/single-node/single_node_test_context.d.ts.map +1 -0
- package/dest/single-node/single_node_test_context.js +845 -0
- package/dest/spartan/setup_test_wallets.d.ts +1 -1
- package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
- package/dest/spartan/setup_test_wallets.js +18 -8
- package/dest/spartan/tx_metrics.d.ts +10 -1
- package/dest/spartan/tx_metrics.d.ts.map +1 -1
- package/dest/spartan/tx_metrics.js +32 -0
- package/dest/test-wallet/test_wallet.d.ts +23 -4
- package/dest/test-wallet/test_wallet.d.ts.map +1 -1
- package/dest/test-wallet/test_wallet.js +20 -4
- package/dest/test-wallet/utils.d.ts +36 -1
- package/dest/test-wallet/utils.d.ts.map +1 -1
- package/dest/test-wallet/utils.js +64 -4
- package/dest/test-wallet/wallet_worker_script.js +5 -2
- package/dest/test-wallet/worker_wallet.d.ts +5 -5
- package/dest/test-wallet/worker_wallet.d.ts.map +1 -1
- package/dest/test-wallet/worker_wallet.js +2 -2
- package/dest/test-wallet/worker_wallet_schema.js +2 -1
- package/package.json +41 -41
- package/src/automine/README.md +60 -0
- package/src/automine/automine_test_context.ts +153 -0
- package/src/automine/delivery/interactive_handshake_responder.ts +125 -0
- package/src/automine/delivery/onchain_delivery_harness.ts +213 -0
- package/src/{e2e_blacklist_token_contract → automine/token}/blacklist_token_contract_test.ts +19 -39
- package/src/{e2e_token_contract → automine/token}/token_contract_test.ts +24 -43
- package/src/automine/token/token_test_helpers.ts +138 -0
- package/src/bench/client_flows/benchmark.ts +3 -1
- package/src/bench/client_flows/client_flows_benchmark.ts +39 -12
- package/src/composed/ha/ha_full_setup.ts +466 -0
- package/src/fixtures/e2e_prover_test.ts +40 -23
- package/src/fixtures/fixtures.ts +14 -4
- package/src/fixtures/ha_setup.ts +7 -3
- package/src/fixtures/index.ts +1 -0
- package/src/fixtures/setup.ts +217 -146
- package/src/fixtures/setup_p2p_test.ts +3 -3
- package/src/fixtures/standard_contracts_genesis.ts +28 -0
- package/src/fixtures/timing.ts +97 -0
- package/src/fixtures/token_utils.ts +39 -4
- package/src/fixtures/wait_helpers.ts +333 -0
- package/src/forward-compatibility/wallet_service.ts +2 -2
- package/src/guides/up_quick_start.sh +5 -0
- package/src/infra/README.md +12 -0
- package/src/multi-node/README.md +96 -0
- package/src/multi-node/block-production/setup.ts +272 -0
- package/src/multi-node/governance/setup.ts +172 -0
- package/src/multi-node/multi_node_test_context.ts +447 -0
- package/src/multi-node/slashing/inactivity_setup.ts +139 -0
- package/src/multi-node/slashing/setup.ts +433 -0
- package/src/p2p/README.md +78 -0
- package/src/{e2e_p2p → p2p}/p2p_network.ts +36 -7
- package/src/{e2e_p2p → p2p}/reqresp/utils.ts +5 -14
- package/src/p2p/shared.ts +329 -0
- package/src/shared/cross_chain_test_harness.ts +22 -13
- package/src/shared/gas_portal_test_harness.ts +13 -5
- package/src/shared/submit-transactions.ts +13 -5
- package/src/shared/timing_env.d.mts +39 -0
- package/src/shared/timing_env.mjs +363 -0
- package/src/shared/wait_for_l1_to_l2_message.ts +1 -1
- package/src/simulators/lending_simulator.ts +1 -1
- package/src/single-node/README.md +66 -0
- package/src/{e2e_cross_chain_messaging → single-node/cross-chain}/cross_chain_messaging_test.ts +110 -39
- package/src/single-node/cross-chain/message_test_helpers.ts +125 -0
- package/src/{e2e_fees → single-node/fees}/bridging_race.notest.ts +19 -5
- package/src/{e2e_fees → single-node/fees}/fees_test.ts +141 -48
- package/src/single-node/l1-reorgs/setup.ts +93 -0
- package/src/single-node/partial-proofs/setup.ts +8 -0
- package/src/single-node/proving/setup.ts +15 -0
- package/src/single-node/recovery/setup.ts +8 -0
- package/src/single-node/setup.ts +63 -0
- package/src/single-node/single_node_test_context.ts +1054 -0
- package/src/spartan/setup_test_wallets.ts +23 -8
- package/src/spartan/tx_metrics.ts +26 -0
- package/src/test-wallet/test_wallet.ts +28 -6
- package/src/test-wallet/utils.ts +102 -5
- package/src/test-wallet/wallet_worker_script.ts +6 -3
- package/src/test-wallet/worker_wallet.ts +5 -9
- package/src/test-wallet/worker_wallet_schema.ts +1 -1
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +0 -56
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +0 -1
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +0 -58
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +0 -1
- package/dest/e2e_deploy_contract/deploy_test.d.ts +0 -36
- package/dest/e2e_deploy_contract/deploy_test.d.ts.map +0 -1
- package/dest/e2e_deploy_contract/deploy_test.js +0 -42
- package/dest/e2e_epochs/epochs_test.d.ts +0 -137
- package/dest/e2e_epochs/epochs_test.d.ts.map +0 -1
- package/dest/e2e_epochs/epochs_test.js +0 -485
- package/dest/e2e_fees/bridging_race.notest.d.ts.map +0 -1
- package/dest/e2e_fees/fees_test.d.ts +0 -89
- package/dest/e2e_fees/fees_test.d.ts.map +0 -1
- package/dest/e2e_l1_publisher/write_json.d.ts +0 -11
- package/dest/e2e_l1_publisher/write_json.d.ts.map +0 -1
- package/dest/e2e_l1_publisher/write_json.js +0 -56
- package/dest/e2e_multi_validator/utils.d.ts +0 -12
- package/dest/e2e_multi_validator/utils.d.ts.map +0 -1
- package/dest/e2e_nested_contract/nested_contract_test.d.ts +0 -22
- package/dest/e2e_nested_contract/nested_contract_test.d.ts.map +0 -1
- package/dest/e2e_nested_contract/nested_contract_test.js +0 -40
- package/dest/e2e_p2p/inactivity_slash_test.d.ts +0 -31
- package/dest/e2e_p2p/inactivity_slash_test.d.ts.map +0 -1
- package/dest/e2e_p2p/inactivity_slash_test.js +0 -138
- package/dest/e2e_p2p/p2p_network.d.ts.map +0 -1
- package/dest/e2e_p2p/reqresp/utils.d.ts +0 -22
- package/dest/e2e_p2p/reqresp/utils.d.ts.map +0 -1
- package/dest/e2e_p2p/shared.d.ts +0 -75
- package/dest/e2e_p2p/shared.d.ts.map +0 -1
- package/dest/e2e_p2p/shared.js +0 -217
- package/dest/e2e_storage_proof/fixtures/storage_proof_fetcher.d.ts +0 -2
- package/dest/e2e_storage_proof/fixtures/storage_proof_fetcher.d.ts.map +0 -1
- package/dest/e2e_storage_proof/fixtures/storage_proof_fixture.d.ts.map +0 -1
- package/dest/e2e_token_contract/token_contract_test.d.ts +0 -44
- package/dest/e2e_token_contract/token_contract_test.d.ts.map +0 -1
- package/src/e2e_deploy_contract/deploy_test.ts +0 -73
- package/src/e2e_epochs/epochs_test.ts +0 -596
- package/src/e2e_l1_publisher/write_json.ts +0 -77
- package/src/e2e_nested_contract/nested_contract_test.ts +0 -51
- package/src/e2e_p2p/inactivity_slash_test.ts +0 -181
- package/src/e2e_p2p/shared.ts +0 -332
- /package/dest/{e2e_storage_proof → automine/contracts}/fixtures/storage_proof_fetcher.js +0 -0
- /package/dest/{e2e_storage_proof → automine/contracts}/fixtures/storage_proof_fixture.js +0 -0
- /package/dest/{e2e_multi_validator/utils.js → composed/web3signer/multi_validator_keystore_utils.js} +0 -0
- /package/src/{e2e_storage_proof → automine/contracts}/fixtures/storage_proof.json +0 -0
- /package/src/{e2e_storage_proof → automine/contracts}/fixtures/storage_proof_fetcher.ts +0 -0
- /package/src/{e2e_storage_proof → automine/contracts}/fixtures/storage_proof_fixture.ts +0 -0
- /package/src/{e2e_multi_validator/utils.ts → composed/web3signer/multi_validator_keystore_utils.ts} +0 -0
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
import { createAztecNodeService } from '@aztec/aztec-node';
|
|
2
|
+
import { getAccountContractAddress } from '@aztec/aztec.js/account';
|
|
3
|
+
import { getTimestampRangeForEpoch } from '@aztec/aztec.js/block';
|
|
4
|
+
import { getContractInstanceFromInstantiationParams } from '@aztec/aztec.js/contracts';
|
|
5
|
+
import { Fr } from '@aztec/aztec.js/fields';
|
|
6
|
+
import { MerkleTreeId } from '@aztec/aztec.js/trees';
|
|
7
|
+
import { EpochCache } from '@aztec/epoch-cache';
|
|
8
|
+
import { createExtendedL1Client } from '@aztec/ethereum/client';
|
|
9
|
+
import { DefaultL1ContractsConfig } from '@aztec/ethereum/config';
|
|
10
|
+
import { RollupContract } from '@aztec/ethereum/contracts';
|
|
11
|
+
import { createDelayer, waitUntilL1Timestamp, wrapClientWithDelayer } from '@aztec/ethereum/l1-tx-utils';
|
|
12
|
+
import { ChainMonitor } from '@aztec/ethereum/test';
|
|
13
|
+
import { CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
14
|
+
import { SecretValue } from '@aztec/foundation/config';
|
|
15
|
+
import { randomBytes } from '@aztec/foundation/crypto/random';
|
|
16
|
+
import { withLoggerBindings } from '@aztec/foundation/log/server';
|
|
17
|
+
import { retryUntil } from '@aztec/foundation/retry';
|
|
18
|
+
import { sleep } from '@aztec/foundation/sleep';
|
|
19
|
+
import { executeTimeout } from '@aztec/foundation/timer';
|
|
20
|
+
import { SpamContract } from '@aztec/noir-test-contracts.js/Spam';
|
|
21
|
+
import { TestContract } from '@aztec/noir-test-contracts.js/Test';
|
|
22
|
+
import { getMockPubSubP2PServiceFactory } from '@aztec/p2p/test-helpers';
|
|
23
|
+
import { SequencerState } from '@aztec/sequencer-client';
|
|
24
|
+
import { EthAddress } from '@aztec/stdlib/block';
|
|
25
|
+
import { getProofSubmissionDeadlineTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
26
|
+
import { tryStop } from '@aztec/stdlib/interfaces/server';
|
|
27
|
+
import { join } from 'path';
|
|
28
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
29
|
+
import { SCHNORR_HARDCODED_PRIVATE_KEY, SchnorrHardcodedKeyAccountContract } from '../fixtures/schnorr_hardcoded_account_contract.js';
|
|
30
|
+
import { testSpan } from '../fixtures/timing.js';
|
|
31
|
+
import { createAndSyncProverNode, getPrivateKeyFromIndex, setup } from '../fixtures/utils.js';
|
|
32
|
+
export const WORLD_STATE_CHECKPOINT_HISTORY = 2;
|
|
33
|
+
export const WORLD_STATE_BLOCK_CHECK_INTERVAL = 50;
|
|
34
|
+
export const ARCHIVER_POLL_INTERVAL = 50;
|
|
35
|
+
/**
|
|
36
|
+
* Default L1 (ethereum) slot duration in seconds for single-node e2e tests. Kept at 8s, the fast-profile
|
|
37
|
+
* boundary (`FAST_PROFILE_ETHEREUM_SLOT_DURATION`): at 8s the proposer still uses the production operational
|
|
38
|
+
* budgets (fast-profile clamping only kicks in strictly below 8s), so the default single-node L2 slot is
|
|
39
|
+
* `2 x 8 = 16s`. CI previously ran at 12s (24s L2 slots); unifying it with the local value removes a
|
|
40
|
+
* CI-vs-local cadence asymmetry and cuts every default-cadence single-node suite by a third.
|
|
41
|
+
*/ export const DEFAULT_L1_BLOCK_TIME = 8;
|
|
42
|
+
/**
|
|
43
|
+
* The 24s-slot reorg cadence shared by every reorg/prune/HA test, regardless of single-node vs
|
|
44
|
+
* multi-validator topology: a 24s L2 slot, 5s blocks, and a 4-slot epoch. The 5s block duration is chosen
|
|
45
|
+
* so the fast-profile budgets both reorg profiles run under (eth < 8s: p2p 0.5s, prepare 0.5s, init 1s)
|
|
46
|
+
* still fit ~3 full block sub-slots per checkpoint — `floor((24 - 1 - 5 - 2*0.5 - 0.5) / 5) = 3` — which
|
|
47
|
+
* the l1-reorgs suites' `assertMultipleBlocksPerSlot(2)` assertions require. The two concrete reorg
|
|
48
|
+
* profiles ({@link FAST_REORG_TIMING}, {@link MULTI_VALIDATOR_REORG_TIMING}) extend this with their topology's L1
|
|
49
|
+
* slot duration and any extra knobs. Kept timing-only — `maxSpeedUpAttempts`, `cancelTxOnTimeout`, and
|
|
50
|
+
* `aztecProofSubmissionEpochs` encode per-test scenario intent and stay explicit at the call site.
|
|
51
|
+
*/ export const REORG_TIMING_BASE = {
|
|
52
|
+
aztecSlotDuration: 24,
|
|
53
|
+
blockDurationMs: 5000,
|
|
54
|
+
aztecEpochDuration: 4
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Timing-only profile shared by the fast single-node L1-reorg tests (`proving/optimistic`'s reorg
|
|
58
|
+
* cases and `l1-reorgs/`). Extends {@link REORG_TIMING_BASE} with mainnet-style 32-slot anvil epochs
|
|
59
|
+
* and a 4s L1 slot. Note: `ethereumSlotDuration` stays at 4 here (not unified to
|
|
60
|
+
* {@link MULTI_VALIDATOR_REORG_TIMING}'s 6) — at eth=6 the proof-submission-window timing in the
|
|
61
|
+
* proof-removal/proof-restore reorg assertions in `l1-reorgs/blocks` starves and times out, so the 4s
|
|
62
|
+
* L1 slot is required for the single-node reorg path. Tests that need a different epoch length (e.g. 8
|
|
63
|
+
* for the "with replacement" case) override `aztecEpochDuration` after the spread.
|
|
64
|
+
*/ export const FAST_REORG_TIMING = {
|
|
65
|
+
...REORG_TIMING_BASE,
|
|
66
|
+
ethereumSlotDuration: 4,
|
|
67
|
+
anvilSlotsInAnEpoch: 32
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Timing-only profile naming the 24s/6s reorg-and-prune cadence copied verbatim across the
|
|
71
|
+
* multi-validator recovery and high-availability tests (`recovery/proposal_failure_recovery`,
|
|
72
|
+
* `recovery/equivocation_recovery`, `high-availability/ha_sync`,
|
|
73
|
+
* `high-availability/ha_checkpoint_handoff`). The multi-validator analogue of
|
|
74
|
+
* {@link FAST_REORG_TIMING}, adding the 0.5s attestation-propagation budget those committee tests
|
|
75
|
+
* need. Timing-only: committee size, `aztecProofSubmissionEpochs`, and the slasher block stay
|
|
76
|
+
* per-test. Spread BEFORE per-test overrides so a test can still bump e.g. `aztecEpochDuration`.
|
|
77
|
+
*/ export const MULTI_VALIDATOR_REORG_TIMING = {
|
|
78
|
+
...REORG_TIMING_BASE,
|
|
79
|
+
ethereumSlotDuration: 6,
|
|
80
|
+
attestationPropagationTime: 0.5
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Timing-only profile naming the 24s/12s multi-validator block-production cadence copied across
|
|
84
|
+
* `block-production/` (`simple`, `first_slot`, and `proof_boundary`). Uses `aztecSlotDurationInL1Slots: 2`
|
|
85
|
+
* rather than an explicit `aztecSlotDuration: 24` so the L2 slot stays coupled to `ethereumSlotDuration`
|
|
86
|
+
* if a test overrides eth. The 4s block duration keeps enough full block sub-slots per checkpoint under
|
|
87
|
+
* the production budgets these eth=12 tests run with (init 1s, prepare 1s, min-block 2s, p2p =
|
|
88
|
+
* attestationPropagationTime): `floor((24 - 1 - 4 - 2P - 1) / 4)` = 4 blocks at P<=1, 3 blocks at P=2
|
|
89
|
+
* (the default). Deliberately omits `attestationPropagationTime` (per-scenario: default 2, 0.5, or 1) —
|
|
90
|
+
* set it per test. `high_tps` pins the old 36s/6s cadence at its own call site because its 2-txs-x-2.5s
|
|
91
|
+
* per-block budget does not fit a 4s block. Spread BEFORE per-test overrides.
|
|
92
|
+
*/ export const MULTI_VALIDATOR_BLOCK_PRODUCTION_TIMING = {
|
|
93
|
+
ethereumSlotDuration: 12,
|
|
94
|
+
aztecSlotDurationInL1Slots: 2,
|
|
95
|
+
blockDurationMs: 4000
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Timing-only profile naming the 72s wide-slot multiple-blocks-per-slot cadence copied across the
|
|
99
|
+
* block-production and recovery tests (`block-production/`'s `setupBlockProductionWithProver`, `block-production/blob_promotion`,
|
|
100
|
+
* `recovery/pipeline_prune`). A-914:
|
|
101
|
+
* pipelined multiple-blocks-per-slot needs this 72s/12s cadence (not the tighter 36s/4s), otherwise non-proposer nodes hit
|
|
102
|
+
* `CheckpointNumberNotSequentialError` when the pipelined proposer races ahead of L1 confirmation. The
|
|
103
|
+
* larger `perBlockAllocationMultiplier` lets each of the several blocks per slot fit non-trivial txs.
|
|
104
|
+
* Spread BEFORE per-test overrides (e.g. `mockGossipSubNetworkLatency`, `maxTxsPerCheckpoint`).
|
|
105
|
+
*/ export const WIDE_SLOT_TIMING = {
|
|
106
|
+
ethereumSlotDuration: 12,
|
|
107
|
+
aztecSlotDuration: 72,
|
|
108
|
+
blockDurationMs: 5500,
|
|
109
|
+
aztecEpochDuration: 4,
|
|
110
|
+
perBlockAllocationMultiplier: 8,
|
|
111
|
+
aztecTargetCommitteeSize: 3
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Base class for the prod-sequencer single-node test topology: one node running the production
|
|
115
|
+
* sequencer with fast block times and short epochs, an optional fake-proof prover node, and the
|
|
116
|
+
* environment it runs in (in-proc anvil + L1 deploy). Owns node spawning
|
|
117
|
+
* (`createNonValidatorNode` / `createProverNode` incl. the mock-gossip `p2pServiceFactory` wiring,
|
|
118
|
+
* which is harmless with a single node), the `ChainMonitor`, and the epoch / proof-window / reorg
|
|
119
|
+
* waiters and assertion helpers shared by every test in the category.
|
|
120
|
+
*
|
|
121
|
+
* {@link MultiNodeTestContext} extends this with the N-validator topology (validator-node spawning,
|
|
122
|
+
* committee/proposal/attestation convergence helpers). Single-node-topology tests use this base
|
|
123
|
+
* directly from the sibling `single-node/` category.
|
|
124
|
+
*/ export class SingleNodeTestContext {
|
|
125
|
+
context;
|
|
126
|
+
l1Client;
|
|
127
|
+
rollup;
|
|
128
|
+
constants;
|
|
129
|
+
logger;
|
|
130
|
+
monitor;
|
|
131
|
+
epochCache;
|
|
132
|
+
proverDelayer;
|
|
133
|
+
sequencerDelayer;
|
|
134
|
+
proverNodes = [];
|
|
135
|
+
nodes = [];
|
|
136
|
+
epochDuration;
|
|
137
|
+
L1_BLOCK_TIME_IN_S;
|
|
138
|
+
L2_SLOT_DURATION_IN_S;
|
|
139
|
+
static async setup(opts = {}) {
|
|
140
|
+
const test = new this();
|
|
141
|
+
await test.setup(opts);
|
|
142
|
+
return test;
|
|
143
|
+
}
|
|
144
|
+
static getSlotDurations(opts = {}) {
|
|
145
|
+
const envEthereumSlotDuration = process.env.L1_BLOCK_TIME ? parseInt(process.env.L1_BLOCK_TIME) : DEFAULT_L1_BLOCK_TIME;
|
|
146
|
+
const ethereumSlotDuration = opts.ethereumSlotDuration ?? envEthereumSlotDuration;
|
|
147
|
+
const aztecSlotDuration = opts.aztecSlotDuration ?? (opts.aztecSlotDurationInL1Slots ?? 2) * ethereumSlotDuration;
|
|
148
|
+
const aztecEpochDuration = opts.aztecEpochDuration ?? 6;
|
|
149
|
+
const aztecProofSubmissionEpochs = opts.aztecProofSubmissionEpochs ?? 1;
|
|
150
|
+
return {
|
|
151
|
+
ethereumSlotDuration,
|
|
152
|
+
aztecSlotDuration,
|
|
153
|
+
aztecEpochDuration,
|
|
154
|
+
aztecProofSubmissionEpochs
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async setup(opts = {}) {
|
|
158
|
+
const { ethereumSlotDuration, aztecSlotDuration, aztecEpochDuration, aztecProofSubmissionEpochs } = SingleNodeTestContext.getSlotDurations(opts);
|
|
159
|
+
// Auto-create a hardcoded account funded via genesis when:
|
|
160
|
+
// - skipInitialSequencer is set (no sequencer to deploy on-chain), or
|
|
161
|
+
// - useHardcodedAccount is explicitly requested (e.g. tight per-block gas budgets that
|
|
162
|
+
// can't fit a full account-deploy tx).
|
|
163
|
+
const useHardcodedAccount = (opts.skipInitialSequencer || opts.useHardcodedAccount) && !opts.skipHardcodedAccount;
|
|
164
|
+
let hardcodedAccountData;
|
|
165
|
+
if (useHardcodedAccount) {
|
|
166
|
+
hardcodedAccountData = await SingleNodeTestContext.getHardcodedAccountData(Fr.random(), Fr.random());
|
|
167
|
+
}
|
|
168
|
+
// Set up system without any account nor protocol contracts
|
|
169
|
+
// and with faster block times and shorter epochs.
|
|
170
|
+
const context = await setup(useHardcodedAccount ? 0 : opts.numberOfAccounts ?? 0, {
|
|
171
|
+
automineL1Setup: true,
|
|
172
|
+
checkIntervalMs: 50,
|
|
173
|
+
archiverPollingIntervalMS: ARCHIVER_POLL_INTERVAL,
|
|
174
|
+
worldStateBlockCheckIntervalMS: WORLD_STATE_BLOCK_CHECK_INTERVAL,
|
|
175
|
+
aztecEpochDuration,
|
|
176
|
+
aztecSlotDuration,
|
|
177
|
+
ethereumSlotDuration,
|
|
178
|
+
aztecProofSubmissionEpochs,
|
|
179
|
+
aztecTargetCommitteeSize: opts.initialValidators?.length ?? 0,
|
|
180
|
+
minTxsPerBlock: 0,
|
|
181
|
+
realProofs: false,
|
|
182
|
+
startProverNode: true,
|
|
183
|
+
proverTestDelayMs: opts.proverTestDelayMs ?? 0,
|
|
184
|
+
proverId: EthAddress.fromNumber(1),
|
|
185
|
+
worldStateCheckpointHistory: WORLD_STATE_CHECKPOINT_HISTORY,
|
|
186
|
+
exitDelaySeconds: DefaultL1ContractsConfig.exitDelaySeconds,
|
|
187
|
+
slasherEnabled: false,
|
|
188
|
+
// `inboxLag: 2` is the intended value when running with pipelining (the production config
|
|
189
|
+
// default of 1 is a separate bug). Set before `...opts` so tests can still override.
|
|
190
|
+
inboxLag: 2,
|
|
191
|
+
...opts,
|
|
192
|
+
...hardcodedAccountData ? {
|
|
193
|
+
additionallyFundedAccounts: [
|
|
194
|
+
hardcodedAccountData
|
|
195
|
+
],
|
|
196
|
+
numberOfAccounts: 0
|
|
197
|
+
} : {}
|
|
198
|
+
}, // Use checkpointed chain tip for PXE by default to avoid issues with blocks being dropped due to pruned anchor blocks.
|
|
199
|
+
// Can be overridden via opts.pxeOpts.
|
|
200
|
+
{
|
|
201
|
+
syncChainTip: 'checkpointed',
|
|
202
|
+
...opts.pxeOpts
|
|
203
|
+
});
|
|
204
|
+
// Register the hardcoded account in PXE (local only, no on-chain deployment needed).
|
|
205
|
+
if (hardcodedAccountData) {
|
|
206
|
+
this.context = context;
|
|
207
|
+
await this.registerHardcodedAccount(hardcodedAccountData);
|
|
208
|
+
}
|
|
209
|
+
await this.hydrateFromContext(context);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Populates the context-derived state (tracked nodes, L1 client, rollup, epoch cache, chain
|
|
213
|
+
* monitor, delayers, timing constants) from an already-built {@link EndToEndContext}. Split out of
|
|
214
|
+
* {@link setup} so prover-specific subclasses that build the environment with their own bespoke
|
|
215
|
+
* `setup(...)` opts (e.g. `FullProverTest`) can still reuse the base node/teardown machinery
|
|
216
|
+
* without inheriting the base's default node config. Slot/epoch durations are read from the
|
|
217
|
+
* resolved `context.config` so the recorded constants match whatever the environment was deployed
|
|
218
|
+
* with.
|
|
219
|
+
*/ async hydrateFromContext(context) {
|
|
220
|
+
this.context = context;
|
|
221
|
+
const { ethereumSlotDuration, aztecSlotDuration, aztecEpochDuration } = context.config;
|
|
222
|
+
this.L1_BLOCK_TIME_IN_S = ethereumSlotDuration;
|
|
223
|
+
this.L2_SLOT_DURATION_IN_S = aztecSlotDuration;
|
|
224
|
+
this.proverNodes = context.proverNode ? [
|
|
225
|
+
context.proverNode
|
|
226
|
+
] : [];
|
|
227
|
+
this.nodes = context.aztecNode ? [
|
|
228
|
+
context.aztecNode
|
|
229
|
+
] : [];
|
|
230
|
+
this.logger = context.logger;
|
|
231
|
+
this.l1Client = context.deployL1ContractsValues.l1Client;
|
|
232
|
+
this.rollup = RollupContract.getFromConfig(context.config);
|
|
233
|
+
this.epochCache = await EpochCache.create(this.rollup, context.config, {
|
|
234
|
+
dateProvider: context.dateProvider
|
|
235
|
+
});
|
|
236
|
+
// Loop that tracks L1 and L2 block numbers and logs whenever there's a new one.
|
|
237
|
+
this.monitor = new ChainMonitor(this.rollup, context.dateProvider, this.logger).start();
|
|
238
|
+
this.proverDelayer = context.proverDelayer;
|
|
239
|
+
this.sequencerDelayer = context.sequencerDelayer;
|
|
240
|
+
if (context.proverNode && !this.proverDelayer || context.sequencer && !this.sequencerDelayer) {
|
|
241
|
+
throw new Error(`Could not find prover or sequencer delayer`);
|
|
242
|
+
}
|
|
243
|
+
// Constants used for time calculation
|
|
244
|
+
this.epochDuration = aztecEpochDuration;
|
|
245
|
+
this.constants = {
|
|
246
|
+
epochDuration: aztecEpochDuration,
|
|
247
|
+
slotDuration: aztecSlotDuration,
|
|
248
|
+
l1StartBlock: await this.rollup.getL1StartBlock(),
|
|
249
|
+
l1GenesisTime: await this.rollup.getL1GenesisTime(),
|
|
250
|
+
ethereumSlotDuration,
|
|
251
|
+
proofSubmissionEpochs: Number(await this.rollup.getProofSubmissionEpochs()),
|
|
252
|
+
targetCommitteeSize: await this.rollup.getTargetCommitteeSize(),
|
|
253
|
+
rollupManaLimit: Number(await this.rollup.getManaLimit())
|
|
254
|
+
};
|
|
255
|
+
this.logger.info(`L2 genesis at L1 block ${this.constants.l1StartBlock} (timestamp ${this.constants.l1GenesisTime})`);
|
|
256
|
+
}
|
|
257
|
+
async teardown() {
|
|
258
|
+
await this.monitor.stop();
|
|
259
|
+
await Promise.all(this.proverNodes.map((node)=>tryStop(node, this.logger)));
|
|
260
|
+
await Promise.all(this.nodes.map((node)=>tryStop(node, this.logger)));
|
|
261
|
+
await this.context.teardown();
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Computes InitialAccountData for a SchnorrHardcodedKeyAccountContract.
|
|
265
|
+
* This contract has a hardcoded signing key and no initializer, so it can be used without
|
|
266
|
+
* on-chain deployment. Pass the returned data in `additionallyFundedAccounts` so the address
|
|
267
|
+
* gets funded with fee juice in genesis.
|
|
268
|
+
*/ static async getHardcodedAccountData(secret, salt) {
|
|
269
|
+
const contract = new SchnorrHardcodedKeyAccountContract();
|
|
270
|
+
const address = await getAccountContractAddress(contract, secret, salt);
|
|
271
|
+
const signingKey = SCHNORR_HARDCODED_PRIVATE_KEY;
|
|
272
|
+
return {
|
|
273
|
+
secret,
|
|
274
|
+
salt,
|
|
275
|
+
signingKey,
|
|
276
|
+
address
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Registers a SchnorrHardcodedKeyAccountContract in PXE. The account must have been funded
|
|
281
|
+
* at genesis (via getHardcodedAccountData). No on-chain deployment or block mining needed.
|
|
282
|
+
*/ async registerHardcodedAccount(accountData) {
|
|
283
|
+
const contract = new SchnorrHardcodedKeyAccountContract();
|
|
284
|
+
const wallet = this.context.wallet;
|
|
285
|
+
const accountManager = await wallet.createAccount({
|
|
286
|
+
secret: accountData.secret,
|
|
287
|
+
salt: accountData.salt,
|
|
288
|
+
contract
|
|
289
|
+
});
|
|
290
|
+
this.context.accounts = [
|
|
291
|
+
accountManager.address
|
|
292
|
+
];
|
|
293
|
+
return accountManager.address;
|
|
294
|
+
}
|
|
295
|
+
async createProverNode(opts = {}) {
|
|
296
|
+
this.logger.warn('Creating and syncing a simulated prover node...');
|
|
297
|
+
const proverNodePrivateKey = this.getNextPrivateKey();
|
|
298
|
+
const proverIndex = this.proverNodes.length + 1;
|
|
299
|
+
const { mockGossipSubNetwork } = this.context;
|
|
300
|
+
const { proverNode } = await testSpan('setup:node', ()=>withLoggerBindings({
|
|
301
|
+
actor: `prover-${proverIndex}`
|
|
302
|
+
}, ()=>createAndSyncProverNode(proverNodePrivateKey, {
|
|
303
|
+
...this.context.config,
|
|
304
|
+
p2pEnabled: this.context.config.p2pEnabled || mockGossipSubNetwork !== undefined,
|
|
305
|
+
proverId: EthAddress.fromNumber(proverIndex),
|
|
306
|
+
dontStart: opts.dontStart,
|
|
307
|
+
...opts
|
|
308
|
+
}, {
|
|
309
|
+
dataDirectory: join(this.context.config.dataDirectory, randomBytes(8).toString('hex'))
|
|
310
|
+
}, {
|
|
311
|
+
dateProvider: this.context.dateProvider,
|
|
312
|
+
p2pClientDeps: {
|
|
313
|
+
p2pServiceFactory: mockGossipSubNetwork ? getMockPubSubP2PServiceFactory(mockGossipSubNetwork) : undefined,
|
|
314
|
+
rpcTxProviders: [
|
|
315
|
+
this.context.aztecNode
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
}, {
|
|
319
|
+
genesis: this.context.genesis,
|
|
320
|
+
dontStart: opts.dontStart
|
|
321
|
+
})));
|
|
322
|
+
this.proverNodes.push(proverNode);
|
|
323
|
+
return proverNode;
|
|
324
|
+
}
|
|
325
|
+
createNonValidatorNode(opts = {}) {
|
|
326
|
+
this.logger.warn('Creating and syncing a node without a validator...');
|
|
327
|
+
return this.createNode({
|
|
328
|
+
...opts,
|
|
329
|
+
disableValidator: true
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
async createNode(opts = {}) {
|
|
333
|
+
const nodeIndex = this.nodes.length + 1;
|
|
334
|
+
const actorPrefix = opts.disableValidator ? 'node' : 'validator';
|
|
335
|
+
const { mockGossipSubNetwork } = this.context;
|
|
336
|
+
const resolvedConfig = {
|
|
337
|
+
...this.context.config,
|
|
338
|
+
...opts
|
|
339
|
+
};
|
|
340
|
+
const p2pEnabled = resolvedConfig.p2pEnabled || mockGossipSubNetwork !== undefined;
|
|
341
|
+
const p2pIp = resolvedConfig.p2pIp ?? (p2pEnabled ? '127.0.0.1' : undefined);
|
|
342
|
+
const node = await testSpan('setup:node', ()=>withLoggerBindings({
|
|
343
|
+
actor: `${actorPrefix}-${nodeIndex}`
|
|
344
|
+
}, ()=>createAztecNodeService({
|
|
345
|
+
...resolvedConfig,
|
|
346
|
+
dataDirectory: join(this.context.config.dataDirectory, randomBytes(8).toString('hex')),
|
|
347
|
+
validatorPrivateKeys: opts.validatorPrivateKeys ?? new SecretValue([]),
|
|
348
|
+
nodeId: resolvedConfig.nodeId || `${actorPrefix}-${nodeIndex}`,
|
|
349
|
+
p2pEnabled,
|
|
350
|
+
p2pIp
|
|
351
|
+
}, {
|
|
352
|
+
dateProvider: this.context.dateProvider,
|
|
353
|
+
p2pClientDeps: {
|
|
354
|
+
p2pServiceFactory: mockGossipSubNetwork ? getMockPubSubP2PServiceFactory(mockGossipSubNetwork) : undefined
|
|
355
|
+
},
|
|
356
|
+
slashingProtectionDb: opts.slashingProtectionDb
|
|
357
|
+
}, {
|
|
358
|
+
genesis: this.context.genesis,
|
|
359
|
+
...opts
|
|
360
|
+
})));
|
|
361
|
+
this.nodes.push(node);
|
|
362
|
+
return node;
|
|
363
|
+
}
|
|
364
|
+
getNextPrivateKey() {
|
|
365
|
+
const key = getPrivateKeyFromIndex(this.nodes.length + this.proverNodes.length + 1);
|
|
366
|
+
return `0x${key.toString('hex')}`;
|
|
367
|
+
}
|
|
368
|
+
/** Waits until the epoch begins (ie until the immediately previous L1 block is mined). */ async waitUntilEpochStarts(epoch) {
|
|
369
|
+
const [start] = getTimestampRangeForEpoch(EpochNumber(epoch), this.constants);
|
|
370
|
+
this.logger.info(`Waiting until L1 timestamp ${start} is reached as the start of epoch ${epoch}`);
|
|
371
|
+
// Cover at least two full epochs of wall time so callers issuing the wait mid-epoch
|
|
372
|
+
// still have headroom — the prior `30 * epochDuration` mixed units (slots vs seconds)
|
|
373
|
+
// and timed out at 120s for configs whose epoch wall time is 144s+.
|
|
374
|
+
await testSpan('wait:epoch', ()=>waitUntilL1Timestamp(this.l1Client, start - BigInt(this.L1_BLOCK_TIME_IN_S), undefined, 2 * this.epochDuration * this.L2_SLOT_DURATION_IN_S));
|
|
375
|
+
return start;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Waits until the next epoch boundary and returns that epoch's number. Anchors tests
|
|
379
|
+
* on a guaranteed-fresh epoch regardless of how much wall time `beforeEach` consumed —
|
|
380
|
+
* `waitUntilEpochStarts(1)` returns immediately when the chain has already advanced past
|
|
381
|
+
* slot 4, which under CI load can leave only seconds of the target epoch remaining.
|
|
382
|
+
*
|
|
383
|
+
* If the chain has more than two slots of headroom before the target boundary, warps
|
|
384
|
+
* the L1 clock to within two slots of the boundary instead of waiting in wall-clock.
|
|
385
|
+
* The two-slot tail is intentional — it lets the sequencer/builder settle so the first
|
|
386
|
+
* checkpoint of the target epoch lands correctly.
|
|
387
|
+
*/ async waitUntilNextEpochStarts() {
|
|
388
|
+
const { epoch } = this.epochCache.getEpochAndSlotNow();
|
|
389
|
+
const target = EpochNumber(Number(epoch) + 1);
|
|
390
|
+
const [targetTs] = getTimestampRangeForEpoch(target, this.constants);
|
|
391
|
+
const safeTs = targetTs - BigInt(2 * this.L2_SLOT_DURATION_IN_S);
|
|
392
|
+
const currentTs = BigInt(await this.context.cheatCodes.eth.lastBlockTimestamp());
|
|
393
|
+
if (currentTs < safeTs) {
|
|
394
|
+
this.logger.info(`Warping L1 from ${currentTs} to ${safeTs} (2 slots before epoch ${target})`);
|
|
395
|
+
await this.context.cheatCodes.eth.warp(Number(safeTs), {
|
|
396
|
+
resetBlockInterval: true
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
await this.waitUntilEpochStarts(Number(target));
|
|
400
|
+
return target;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Warps the L1 clock to within `leadSlots` slots of the start of `epoch`, then waits for that boundary in
|
|
404
|
+
* wall-clock, skipping the dead stretch where the chain just advances one L1 block per slot until an epoch
|
|
405
|
+
* ends. Anything the post-warp assertions depend on must already be produced before calling this — warping
|
|
406
|
+
* the tail away does not change what gets proven, and the shared `TestDateProvider` moves the
|
|
407
|
+
* prover/node/sequencer clocks together so the epoch finalizes right after the warp. The `leadSlots` tail
|
|
408
|
+
* (default 2) is left in real time so the sequencer can publish the epoch's final checkpoint before the
|
|
409
|
+
* boundary, and so any post-warp sampler observes an in-epoch slot rather than the epoch's last slot.
|
|
410
|
+
* Forward-only, so a no-op when already within `leadSlots` of the boundary.
|
|
411
|
+
*/ async warpToEpochStart(epoch, opts = {}) {
|
|
412
|
+
const leadSlots = opts.leadSlots ?? 2;
|
|
413
|
+
const [targetTs] = getTimestampRangeForEpoch(EpochNumber(epoch), this.constants);
|
|
414
|
+
const safeTs = targetTs - BigInt(leadSlots * this.L2_SLOT_DURATION_IN_S);
|
|
415
|
+
const currentTs = BigInt(await this.context.cheatCodes.eth.lastBlockTimestamp());
|
|
416
|
+
if (currentTs < safeTs) {
|
|
417
|
+
this.logger.info(`Warping L1 from ${currentTs} to ${safeTs} (${leadSlots} slots before epoch ${epoch})`);
|
|
418
|
+
await this.context.cheatCodes.eth.warp(Number(safeTs), {
|
|
419
|
+
resetBlockInterval: true
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
return this.waitUntilEpochStarts(epoch);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Most of a proof-submission window is dead wall-clock time, so warp the L1 clock forward to `leadSlots`
|
|
426
|
+
* (default 2) L2 slots before the window's last slot. A subsequent
|
|
427
|
+
* {@link waitUntilLastSlotOfProofSubmissionWindow} then only sleeps out the few remaining real slots,
|
|
428
|
+
* leaving enough real time for any in-flight proving, pruning, and recovery to happen organically. Only
|
|
429
|
+
* warps forward, so it is a no-op when the chain is already within `leadSlots`+1 slots of the window end.
|
|
430
|
+
*/ async warpNearSubmissionWindowEnd(epoch, opts = {}) {
|
|
431
|
+
const leadSlots = opts.leadSlots ?? 2;
|
|
432
|
+
const { slotDuration } = this.constants;
|
|
433
|
+
const deadline = getProofSubmissionDeadlineTimestamp(EpochNumber(epoch), this.constants);
|
|
434
|
+
// Mirror waitUntilLastSlotOfProofSubmissionWindow's target (one slot before the deadline).
|
|
435
|
+
const lastSlotTs = deadline - BigInt(slotDuration);
|
|
436
|
+
const target = lastSlotTs - BigInt(leadSlots * slotDuration);
|
|
437
|
+
const currentTs = BigInt(await this.context.cheatCodes.eth.lastBlockTimestamp());
|
|
438
|
+
if (currentTs < target) {
|
|
439
|
+
this.logger.warn(`Warping L1 to ${leadSlots} slots before end of epoch ${epoch} submission window`, {
|
|
440
|
+
currentTs,
|
|
441
|
+
target,
|
|
442
|
+
epoch
|
|
443
|
+
});
|
|
444
|
+
await this.context.cheatCodes.eth.warp(Number(target), {
|
|
445
|
+
resetBlockInterval: true
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/** Waits until the given checkpoint number is mined. */ async waitUntilCheckpointNumber(target, timeout = 120) {
|
|
450
|
+
await testSpan('wait:checkpoint', ()=>retryUntil(()=>Promise.resolve(target <= this.monitor.checkpointNumber), `Wait until checkpoint ${target}`, timeout, 0.1));
|
|
451
|
+
}
|
|
452
|
+
/** Waits until the given checkpoint number is marked as proven. */ async waitUntilProvenCheckpointNumber(target, timeout = 120) {
|
|
453
|
+
await testSpan('wait:proven-checkpoint', ()=>retryUntil(()=>Promise.resolve(target <= this.monitor.provenCheckpointNumber), `Wait proven checkpoint ${target}`, timeout, 0.1));
|
|
454
|
+
return this.monitor.provenCheckpointNumber;
|
|
455
|
+
}
|
|
456
|
+
/** Waits until the last slot of the proof submission window for a given epoch. */ async waitUntilLastSlotOfProofSubmissionWindow(epochNumber) {
|
|
457
|
+
const deadline = getProofSubmissionDeadlineTimestamp(EpochNumber.fromBigInt(BigInt(epochNumber)), this.constants);
|
|
458
|
+
const oneSlotBefore = deadline - BigInt(this.constants.slotDuration);
|
|
459
|
+
const date = new Date(Number(oneSlotBefore) * 1000);
|
|
460
|
+
this.logger.info(`Waiting until last slot of submission window for epoch ${epochNumber} at ${date}`, {
|
|
461
|
+
oneSlotBefore
|
|
462
|
+
});
|
|
463
|
+
// Use a timeout that accounts for the full proof submission window
|
|
464
|
+
const proofSubmissionWindowDuration = this.constants.proofSubmissionEpochs * this.epochDuration * this.L2_SLOT_DURATION_IN_S;
|
|
465
|
+
await testSpan('wait:proof-window', ()=>waitUntilL1Timestamp(this.l1Client, oneSlotBefore, undefined, proofSubmissionWindowDuration * 2));
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Timestamp one L1 block before L2 `slot` begins — the instant a pipelined proposer starts
|
|
469
|
+
* building for that slot. `opts.lead` overrides the default one-L1-block lead (e.g. when a test
|
|
470
|
+
* needs an extra block-duration margin to guarantee sub-slot 1 is reachable).
|
|
471
|
+
*/ buildWindowTimestampForSlot(slot, opts = {}) {
|
|
472
|
+
const lead = opts.lead ?? this.L1_BLOCK_TIME_IN_S;
|
|
473
|
+
return getTimestampForSlot(slot, this.constants) - BigInt(lead);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Warps the L1 clock to the build window of `slot` (one L1 block before the slot begins) with
|
|
477
|
+
* `resetBlockInterval`, so a pipelined proposer engages cleanly when its sequencer starts.
|
|
478
|
+
* @returns The slot warped to.
|
|
479
|
+
*/ async warpToBuildWindowForSlot(slot) {
|
|
480
|
+
const target = this.buildWindowTimestampForSlot(slot);
|
|
481
|
+
this.logger.info(`Warping L1 to build window of slot ${slot}`, {
|
|
482
|
+
slot,
|
|
483
|
+
target
|
|
484
|
+
});
|
|
485
|
+
await this.context.cheatCodes.eth.warp(Number(target), {
|
|
486
|
+
resetBlockInterval: true
|
|
487
|
+
});
|
|
488
|
+
return slot;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Waits (in wall-clock, without warping) until the L1 clock reaches the build window of `slot`.
|
|
492
|
+
* For production-sequencer tests that can't warp. `opts.lead` extends the lead beyond the default
|
|
493
|
+
* one L1 block; `opts.timeout` bounds the wait (defaults to three L2 slots).
|
|
494
|
+
* @returns The slot waited for.
|
|
495
|
+
*/ async waitForBuildWindowForSlot(slot, opts = {}) {
|
|
496
|
+
const target = this.buildWindowTimestampForSlot(slot, {
|
|
497
|
+
lead: opts.lead
|
|
498
|
+
});
|
|
499
|
+
const timeout = opts.timeout ?? this.L2_SLOT_DURATION_IN_S * 3;
|
|
500
|
+
this.logger.info(`Waiting until L1 reaches build window of slot ${slot}`, {
|
|
501
|
+
slot,
|
|
502
|
+
target
|
|
503
|
+
});
|
|
504
|
+
await testSpan('wait:slot', ()=>waitUntilL1Timestamp(this.l1Client, target, undefined, timeout));
|
|
505
|
+
return slot;
|
|
506
|
+
}
|
|
507
|
+
/** Waits for the aztec node to sync to the target block number. */ async waitForNodeToSync(blockNumber, type) {
|
|
508
|
+
const waitTime = ARCHIVER_POLL_INTERVAL + WORLD_STATE_BLOCK_CHECK_INTERVAL;
|
|
509
|
+
await testSpan('wait:node-sync', async ()=>{
|
|
510
|
+
let synched = false;
|
|
511
|
+
while(!synched){
|
|
512
|
+
await sleep(waitTime);
|
|
513
|
+
const [syncState, tips] = await Promise.all([
|
|
514
|
+
this.context.aztecNode.getWorldStateSyncStatus(),
|
|
515
|
+
await this.context.aztecNode.getChainTips()
|
|
516
|
+
]);
|
|
517
|
+
this.logger.info(`Wait for node synch ${blockNumber} ${type}`, {
|
|
518
|
+
blockNumber,
|
|
519
|
+
type,
|
|
520
|
+
syncState,
|
|
521
|
+
tips
|
|
522
|
+
});
|
|
523
|
+
if (type === 'proven') {
|
|
524
|
+
synched = tips.proven.block.number >= blockNumber && syncState.latestBlockNumber >= blockNumber;
|
|
525
|
+
} else if (type === 'finalized') {
|
|
526
|
+
synched = syncState.finalizedBlockNumber >= blockNumber;
|
|
527
|
+
} else {
|
|
528
|
+
synched = syncState.oldestHistoricBlockNumber >= blockNumber;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Waits until every prover node has submitted a proof for `epoch` (of length `epochLength`
|
|
535
|
+
* checkpoints) on L1. Polls `rollup.getHasSubmittedProof` for each prover registered in
|
|
536
|
+
* {@link proverNodes}.
|
|
537
|
+
*/ async waitForAllProversToSubmit(epoch, epochLength, opts = {}) {
|
|
538
|
+
const proverIds = this.proverNodes.map((node)=>node.getProverNode().getProverId());
|
|
539
|
+
await testSpan('wait:proof-submitted', ()=>retryUntil(async ()=>{
|
|
540
|
+
const haveSubmitted = await Promise.all(proverIds.map((proverId)=>this.rollup.getHasSubmittedProof(epoch, epochLength, proverId)));
|
|
541
|
+
this.logger.info(`Proof submissions: ${haveSubmitted.join(', ')}`);
|
|
542
|
+
return haveSubmitted.every((submitted)=>submitted);
|
|
543
|
+
}, 'Provers have submitted proofs', opts.timeout ?? 120));
|
|
544
|
+
}
|
|
545
|
+
/** Registers the SpamContract on the given wallet. */ async registerSpamContract(wallet, salt = Fr.ZERO) {
|
|
546
|
+
const instance = await getContractInstanceFromInstantiationParams(SpamContract.artifact, {
|
|
547
|
+
constructorArgs: [],
|
|
548
|
+
constructorArtifact: undefined,
|
|
549
|
+
salt,
|
|
550
|
+
publicKeys: undefined,
|
|
551
|
+
deployer: undefined
|
|
552
|
+
});
|
|
553
|
+
await wallet.registerContract(instance, SpamContract.artifact);
|
|
554
|
+
return SpamContract.at(instance.address, wallet);
|
|
555
|
+
}
|
|
556
|
+
/** Registers the TestContract on the given wallet. */ async registerTestContract(wallet, salt = Fr.ZERO) {
|
|
557
|
+
const instance = await getContractInstanceFromInstantiationParams(TestContract.artifact, {
|
|
558
|
+
constructorArgs: [],
|
|
559
|
+
constructorArtifact: undefined,
|
|
560
|
+
salt,
|
|
561
|
+
publicKeys: undefined,
|
|
562
|
+
deployer: undefined
|
|
563
|
+
});
|
|
564
|
+
await wallet.registerContract(instance, TestContract.artifact);
|
|
565
|
+
return TestContract.at(instance.address, wallet);
|
|
566
|
+
}
|
|
567
|
+
/** Creates an L1 client using a fresh account with funds from anvil, with a tx delayer already set up. */ async createL1Client() {
|
|
568
|
+
const rawClient = createExtendedL1Client([
|
|
569
|
+
...this.l1Client.chain.rpcUrls.default.http
|
|
570
|
+
], privateKeyToAccount(this.getNextPrivateKey()), this.l1Client.chain);
|
|
571
|
+
const delayer = createDelayer(this.context.dateProvider, {
|
|
572
|
+
ethereumSlotDuration: this.L1_BLOCK_TIME_IN_S
|
|
573
|
+
}, {});
|
|
574
|
+
const client = wrapClientWithDelayer(rawClient, delayer);
|
|
575
|
+
expect(await client.getBalance({
|
|
576
|
+
address: client.account.address
|
|
577
|
+
})).toBeGreaterThan(0n);
|
|
578
|
+
return {
|
|
579
|
+
client,
|
|
580
|
+
delayer
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
/** Verifies whether the given block number is found on the aztec node. */ async verifyHistoricBlock(blockNumber, expectedSuccess) {
|
|
584
|
+
// We use `findLeavesIndexes` here, but could use any function that queries the world-state
|
|
585
|
+
// at a particular block, so we know whether that historic block is available or has been
|
|
586
|
+
// pruned. Note that `getBlock` would not work here, since it only hits the archiver.
|
|
587
|
+
const result = await this.context.aztecNode.findLeavesIndexes(blockNumber, MerkleTreeId.NULLIFIER_TREE, [
|
|
588
|
+
Fr.ZERO
|
|
589
|
+
]).then((_)=>true).catch((_)=>false);
|
|
590
|
+
expect(result).toBe(expectedSuccess);
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Verifies that at least one checkpoint has `targetBlockCount` blocks and that block numbering is
|
|
594
|
+
* contiguous within every checkpoint (MBPS validation).
|
|
595
|
+
*
|
|
596
|
+
* Two optional wait modes (both poll before reading checkpoints):
|
|
597
|
+
* - `opts.wait`: waits until some checkpoint reaches `targetBlockCount` blocks (the proposed-tip
|
|
598
|
+
* MBPS setup case), and
|
|
599
|
+
* - `opts.targetBlock`: waits until the archiver's checkpointed tip reaches that block number
|
|
600
|
+
* (the pipeline-prune recovery case).
|
|
601
|
+
*
|
|
602
|
+
* Reads from `opts.archiver` when given (e.g. a specific validator node's block source); otherwise
|
|
603
|
+
* from the initial node's archiver.
|
|
604
|
+
* @returns The number of the first checkpoint with at least `targetBlockCount` blocks.
|
|
605
|
+
*/ async assertMultipleBlocksPerSlot(targetBlockCount, opts = {}) {
|
|
606
|
+
const archiver = opts.archiver ?? this.context.aztecNode.getBlockSource();
|
|
607
|
+
const waitTimeout = opts.timeout ?? this.L2_SLOT_DURATION_IN_S * 3;
|
|
608
|
+
if (opts.targetBlock !== undefined) {
|
|
609
|
+
const targetBlock = opts.targetBlock;
|
|
610
|
+
await retryUntil(async ()=>{
|
|
611
|
+
const checkpointed = await archiver.getBlockNumber({
|
|
612
|
+
tag: 'checkpointed'
|
|
613
|
+
});
|
|
614
|
+
return checkpointed !== undefined && checkpointed >= targetBlock;
|
|
615
|
+
}, `archiver checkpointed block ${targetBlock}`, 10, 0.1);
|
|
616
|
+
}
|
|
617
|
+
if (opts.wait) {
|
|
618
|
+
await retryUntil(async ()=>{
|
|
619
|
+
const found = await archiver.getCheckpoints({
|
|
620
|
+
from: CheckpointNumber(1),
|
|
621
|
+
limit: 50
|
|
622
|
+
});
|
|
623
|
+
return found.some((pc)=>pc.checkpoint.blocks.length >= targetBlockCount) || undefined;
|
|
624
|
+
}, `checkpoint with at least ${targetBlockCount} blocks`, waitTimeout, 0.5);
|
|
625
|
+
}
|
|
626
|
+
const checkpoints = await archiver.getCheckpoints({
|
|
627
|
+
from: CheckpointNumber(1),
|
|
628
|
+
limit: 50
|
|
629
|
+
});
|
|
630
|
+
this.logger.warn(`Retrieved ${checkpoints.length} checkpoints from archiver`, {
|
|
631
|
+
checkpoints: checkpoints.map((pc)=>pc.checkpoint.getStats())
|
|
632
|
+
});
|
|
633
|
+
let expectedBlockNumber = checkpoints[0].checkpoint.blocks[0].number;
|
|
634
|
+
let multiBlockCheckpointNumber;
|
|
635
|
+
for (const checkpoint of checkpoints){
|
|
636
|
+
const blockCount = checkpoint.checkpoint.blocks.length;
|
|
637
|
+
if (blockCount >= targetBlockCount && multiBlockCheckpointNumber === undefined) {
|
|
638
|
+
multiBlockCheckpointNumber = checkpoint.checkpoint.number;
|
|
639
|
+
}
|
|
640
|
+
this.logger.verbose(`Checkpoint ${checkpoint.checkpoint.number} has ${blockCount} blocks`, {
|
|
641
|
+
checkpoint: checkpoint.checkpoint.getStats()
|
|
642
|
+
});
|
|
643
|
+
for(let i = 0; i < blockCount; i++){
|
|
644
|
+
const block = checkpoint.checkpoint.blocks[i];
|
|
645
|
+
expect(block.indexWithinCheckpoint).toBe(i);
|
|
646
|
+
expect(block.checkpointNumber).toBe(checkpoint.checkpoint.number);
|
|
647
|
+
expect(block.number).toBe(expectedBlockNumber);
|
|
648
|
+
expectedBlockNumber++;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
expect(multiBlockCheckpointNumber).toBeDefined();
|
|
652
|
+
return multiBlockCheckpointNumber;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Asserts pipelining by comparing the build slot (from block-proposed events) against the
|
|
656
|
+
* submission slot (from block headers). With pipelining, the block is built in slot N but its
|
|
657
|
+
* header carries submission slot N+1. Also checks each block's coinbase matches the expected
|
|
658
|
+
* proposer for its submission slot. Reads checkpoints from `archiver` (typically a validator
|
|
659
|
+
* node's block source).
|
|
660
|
+
*/ async assertProposerPipelining(archiver, blockProposedEvents, logger) {
|
|
661
|
+
const checkpoints = await archiver.getCheckpoints({
|
|
662
|
+
from: CheckpointNumber(1),
|
|
663
|
+
limit: 50
|
|
664
|
+
});
|
|
665
|
+
const allBlocks = checkpoints.flatMap((pc)=>pc.checkpoint.blocks);
|
|
666
|
+
logger.warn(`assertProposerPipelining: ${allBlocks.length} blocks, ${blockProposedEvents.length} events`, {
|
|
667
|
+
blockNumbers: allBlocks.map((b)=>b.number),
|
|
668
|
+
eventBlockNumbers: blockProposedEvents.map((e)=>e.blockNumber)
|
|
669
|
+
});
|
|
670
|
+
let foundPipelining = false;
|
|
671
|
+
for (const block of allBlocks){
|
|
672
|
+
const headerSlot = block.header.globalVariables.slotNumber; // submission slot (N+1)
|
|
673
|
+
const coinbase = block.header.globalVariables.coinbase;
|
|
674
|
+
// Find the block-proposed event for this block (use Number() for safe comparison)
|
|
675
|
+
const event = blockProposedEvents.find((e)=>Number(e.blockNumber) === Number(block.number));
|
|
676
|
+
// if there is no event, then it was probably block number one - which was proposed in setup
|
|
677
|
+
if (!event) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
const buildSlot = event.buildSlot; // build slot (N)
|
|
681
|
+
// Verify the pipelining offset: block built in slot N, submitted in slot N+1
|
|
682
|
+
expect(Number(headerSlot)).toBe(Number(buildSlot) + 1);
|
|
683
|
+
foundPipelining = true;
|
|
684
|
+
// Verify coinbase matches the expected proposer for the submission slot
|
|
685
|
+
const expectedProposer = await this.rollup.getProposerAt(getTimestampForSlot(headerSlot, this.constants));
|
|
686
|
+
expect(coinbase).toEqual(expectedProposer);
|
|
687
|
+
logger.warn(`Block ${block.number}: buildSlot=${buildSlot}, submissionSlot=${headerSlot}, coinbase=${coinbase}`, {
|
|
688
|
+
blockNumber: block.number,
|
|
689
|
+
buildSlot,
|
|
690
|
+
headerSlot,
|
|
691
|
+
coinbase: coinbase.toString(),
|
|
692
|
+
expectedProposer: expectedProposer.toString()
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
expect(foundPipelining).toBe(true);
|
|
696
|
+
logger.warn(`Pipelining assertion passed for ${allBlocks.length} blocks`);
|
|
697
|
+
}
|
|
698
|
+
watchSequencerEvents(sequencers, getMetadata = ()=>({}), additionalFailEventKeys = []) {
|
|
699
|
+
const stateChanges = [];
|
|
700
|
+
const failEvents = [];
|
|
701
|
+
// Note we do not include the 'block-tx-count-check-failed' event here, since it is fine if we dont build
|
|
702
|
+
// due to lack of txs available.
|
|
703
|
+
const failEventsKeys = [
|
|
704
|
+
'block-build-failed',
|
|
705
|
+
'checkpoint-publish-failed',
|
|
706
|
+
'proposer-rollup-check-failed',
|
|
707
|
+
'checkpoint-error',
|
|
708
|
+
'checkpoint-publish-failed',
|
|
709
|
+
'header-validation-failed',
|
|
710
|
+
'pipelined-checkpoint-discarded',
|
|
711
|
+
...additionalFailEventKeys
|
|
712
|
+
];
|
|
713
|
+
const makeEvent = (i, eventName, args)=>({
|
|
714
|
+
...args,
|
|
715
|
+
type: eventName,
|
|
716
|
+
sequencerIndex: i + 2,
|
|
717
|
+
...getMetadata(i)
|
|
718
|
+
});
|
|
719
|
+
sequencers.forEach((sequencer, i)=>{
|
|
720
|
+
const sequencerIndex = i + 2;
|
|
721
|
+
sequencer.getSequencer().on('state-changed', (args)=>{
|
|
722
|
+
const noisyStates = [
|
|
723
|
+
SequencerState.IDLE,
|
|
724
|
+
SequencerState.PROPOSER_CHECK,
|
|
725
|
+
SequencerState.SYNCHRONIZING
|
|
726
|
+
];
|
|
727
|
+
if (!noisyStates.includes(args.newState)) {
|
|
728
|
+
const evt = makeEvent(i, 'state-changed', args);
|
|
729
|
+
stateChanges.push(evt);
|
|
730
|
+
this.logger.verbose(`Sequencer ${sequencerIndex} transitioned from state ${args.oldState} to state ${args.newState}`, evt);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
failEventsKeys.forEach((eventName)=>{
|
|
734
|
+
sequencer.getSequencer().on(eventName, (args)=>{
|
|
735
|
+
// Skip benign block-build-failed events where the builder rejected the block because it
|
|
736
|
+
// could not collect enough valid txs. This is the same "not enough txs" case as
|
|
737
|
+
// block-tx-count-check-failed (which is already excluded above), just detected after we
|
|
738
|
+
// started processing txs rather than before.
|
|
739
|
+
if (eventName === 'block-build-failed' && args.reason === 'Insufficient valid txs') {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const evt = makeEvent(i, eventName, args);
|
|
743
|
+
failEvents.push(evt);
|
|
744
|
+
this.logger.error(`Failed event ${eventName} from sequencer ${sequencerIndex}`, undefined, evt);
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
return {
|
|
749
|
+
failEvents,
|
|
750
|
+
stateChanges
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Resolves with the event args the first time `sequencer` emits `event` with args matching
|
|
755
|
+
* `match`. Rejects after `opts.timeout` ms (default 60s). Wraps the
|
|
756
|
+
* `executeTimeout(signal => new Promise(...))` one-shot subscription boilerplate, cleaning up
|
|
757
|
+
* the listener on both the resolve and the abort paths.
|
|
758
|
+
*/ waitForSequencerEvent(sequencer, event, match = ()=>true, opts = {}) {
|
|
759
|
+
const timeout = opts.timeout ?? 60_000;
|
|
760
|
+
return testSpan('wait:sequencer-state', ()=>executeTimeout((signal)=>new Promise((resolve)=>{
|
|
761
|
+
const listener = (args)=>{
|
|
762
|
+
if (match(args)) {
|
|
763
|
+
sequencer.off(event, listener);
|
|
764
|
+
resolve(args);
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
signal.addEventListener('abort', ()=>sequencer.off(event, listener), {
|
|
768
|
+
once: true
|
|
769
|
+
});
|
|
770
|
+
sequencer.on(event, listener);
|
|
771
|
+
}), timeout, `wait for sequencer event ${String(event)}`));
|
|
772
|
+
}
|
|
773
|
+
/** Returns the {@link SequencerClient} of each given node, throwing if any node has no sequencer. */ getSequencers(nodes) {
|
|
774
|
+
return nodes.map((node)=>{
|
|
775
|
+
const sequencer = node.getSequencer();
|
|
776
|
+
if (!sequencer) {
|
|
777
|
+
throw new Error('Node has no sequencer');
|
|
778
|
+
}
|
|
779
|
+
return sequencer;
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
/** Starts the sequencer on each given node in parallel. */ async startSequencers(nodes) {
|
|
783
|
+
await Promise.all(this.getSequencers(nodes).map((sequencer)=>sequencer.start()));
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Resolves once `sequencer` is in `state`, returning immediately if it is already there. Use to
|
|
787
|
+
* flush in-flight work (e.g. wait for `IDLE` so pending L1 publishes have been issued) before
|
|
788
|
+
* sampling chain state. Builds on {@link waitForSequencerEvent} for the not-yet-there path.
|
|
789
|
+
*/ async waitForSequencerState(sequencer, state, opts = {}) {
|
|
790
|
+
if (sequencer.status().state === state) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
await this.waitForSequencerEvent(sequencer, 'state-changed', (args)=>args.newState === state, opts);
|
|
794
|
+
}
|
|
795
|
+
assertNoFailuresFromSequencers(failEvents) {
|
|
796
|
+
if (failEvents.length > 0) {
|
|
797
|
+
this.logger.error(`Failed events from sequencers`, failEvents);
|
|
798
|
+
}
|
|
799
|
+
expect(failEvents).toEqual([]);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Warps the L1 clock to `target` with every given node's sequencer paused, then resumes them
|
|
803
|
+
* (pass `restart: false` to leave them paused, e.g. until some clock-driven effect is confirmed).
|
|
804
|
+
*
|
|
805
|
+
* Warping the shared date provider under live sequencers interrupts whatever iteration is mid-build,
|
|
806
|
+
* producing spurious `block-build-failed` / `checkpoint-error` events and dropped checkpoints. The
|
|
807
|
+
* sequencers are therefore paused first: the poll loop halts and the in-flight iteration, its pending L1
|
|
808
|
+
* submission, and any pending fallback vote all finish untouched before the warp, so nothing fires with a
|
|
809
|
+
* stale slot afterwards. Pausing leaves the validator clients (and their slashing-protection stores) and
|
|
810
|
+
* publishers running, so a later {@link SequencerClient.start} cleanly resumes; archivers, provers, and
|
|
811
|
+
* the chain monitor keep running throughout, so clock-driven effects of the warp (e.g. an orphan-block
|
|
812
|
+
* prune) still fire.
|
|
813
|
+
*
|
|
814
|
+
* The warp is performed here (rather than via a caller-supplied callback) so it happens only after the
|
|
815
|
+
* pause has drained. Draining can take several slots, so a `target` computed before the pause may already
|
|
816
|
+
* lie in the past by the time the sequencers are down. The warp is therefore skipped when the L1 clock has
|
|
817
|
+
* already reached or passed `target` — `evm_setNextBlockTimestamp` rejects a non-advancing timestamp, so
|
|
818
|
+
* warping there would throw "timestamp in the past".
|
|
819
|
+
*/ async warpWithSequencersPaused(nodes, cheatCodes, target, opts = {}) {
|
|
820
|
+
const sequencers = this.getSequencers(nodes);
|
|
821
|
+
await testSpan('warp:sequencers-paused', async ()=>{
|
|
822
|
+
this.logger.warn(`Pausing ${sequencers.length} sequencers before warp`);
|
|
823
|
+
await Promise.all(sequencers.map((sequencer)=>sequencer.pause()));
|
|
824
|
+
const currentTs = BigInt(await cheatCodes.eth.lastBlockTimestamp());
|
|
825
|
+
if (currentTs < target) {
|
|
826
|
+
this.logger.warn(`Warping L1 to ${target} with all sequencers paused`, {
|
|
827
|
+
currentTs,
|
|
828
|
+
target
|
|
829
|
+
});
|
|
830
|
+
await cheatCodes.eth.warp(Number(target), {
|
|
831
|
+
resetBlockInterval: true
|
|
832
|
+
});
|
|
833
|
+
} else {
|
|
834
|
+
this.logger.verbose(`Skipping warp: L1 clock ${currentTs} already at or past target ${target}`, {
|
|
835
|
+
currentTs,
|
|
836
|
+
target
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
if (opts.restart ?? true) {
|
|
840
|
+
this.logger.warn(`Resuming ${sequencers.length} sequencers after warp`);
|
|
841
|
+
await Promise.all(sequencers.map((sequencer)=>sequencer.start()));
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|