Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ export AZTEC_EPOCH_DURATION=16
export AZTEC_TARGET_COMMITTEE_SIZE=48
export AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=2
export AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=2
export AZTEC_INBOX_LAG=2
export AZTEC_PROOF_SUBMISSION_EPOCHS=2
export AZTEC_LOCAL_EJECTION_THRESHOLD=0
export AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS=1
Expand Down
1 change: 0 additions & 1 deletion l1-contracts/scripts/network-defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN": 48,
"AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT": 2,
"AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE": 48,
"AZTEC_INBOX_LAG": 1,
"AZTEC_PROOF_SUBMISSION_EPOCHS": 1,
"AZTEC_MANA_TARGET": 100000000,
"AZTEC_PROVING_COST_PER_MANA": 100,
Expand Down
1 change: 0 additions & 1 deletion l1-contracts/test/script/DeployAztecL1Contracts.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ contract DeployAztecL1ContractsTest is Test {
// Timing config
vm.setEnv("AZTEC_SLOT_DURATION", vm.toString(json.readUint(".AZTEC_SLOT_DURATION")));
vm.setEnv("AZTEC_EPOCH_DURATION", vm.toString(json.readUint(".AZTEC_EPOCH_DURATION")));
vm.setEnv("AZTEC_INBOX_LAG", vm.toString(json.readUint(".AZTEC_INBOX_LAG")));
vm.setEnv("AZTEC_PROOF_SUBMISSION_EPOCHS", vm.toString(json.readUint(".AZTEC_PROOF_SUBMISSION_EPOCHS")));

// Validator config
Expand Down
1 change: 0 additions & 1 deletion l1-contracts/test/script/DeployRollupForUpgrade.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ contract DeployRollupForUpgradeTest is Test {
// Timing config
vm.setEnv("AZTEC_SLOT_DURATION", vm.toString(json.readUint(".AZTEC_SLOT_DURATION")));
vm.setEnv("AZTEC_EPOCH_DURATION", vm.toString(json.readUint(".AZTEC_EPOCH_DURATION")));
vm.setEnv("AZTEC_INBOX_LAG", vm.toString(json.readUint(".AZTEC_INBOX_LAG")));
vm.setEnv("AZTEC_PROOF_SUBMISSION_EPOCHS", vm.toString(json.readUint(".AZTEC_PROOF_SUBMISSION_EPOCHS")));

// Validator config
Expand Down
1 change: 0 additions & 1 deletion spartan/environments/bench-10tps.env
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ AZTEC_SLOT_DURATION=72
AZTEC_PROOF_SUBMISSION_EPOCHS=2
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=1
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=1
AZTEC_INBOX_LAG=2

# 2B mana target - good for about ~800 txs at 2.5M mana each
AZTEC_MANA_TARGET=2000000000
Expand Down
1 change: 0 additions & 1 deletion spartan/environments/bench-inclusion-sweep.env
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ AZTEC_SLOT_DURATION=72
AZTEC_PROOF_SUBMISSION_EPOCHS=100
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=1
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=1
AZTEC_INBOX_LAG=2

AZTEC_MANA_TARGET=2000000000

Expand Down
2 changes: 0 additions & 2 deletions spartan/environments/network-defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ l1-contracts: &l1-contracts-defaults
#---------------------------------------------------------------------------
# Proof & Block Configuration
#---------------------------------------------------------------------------
# Checkpoints to lag in inbox (prevents sequencer DOS attacks).
AZTEC_INBOX_LAG: 2
# Epochs after end that proofs are still accepted.
AZTEC_PROOF_SUBMISSION_EPOCHS: 1
# Target mana consumption per block.
Expand Down
1 change: 0 additions & 1 deletion spartan/environments/next-net.env
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ SEQ_MAX_TX_PER_CHECKPOINT=12
SEQ_BUILD_CHECKPOINT_IF_EMPTY=true
SEQ_BLOCK_DURATION_MS=5500

AZTEC_INBOX_LAG=2

VALIDATOR_REPLICAS=2
VALIDATORS_PER_NODE=24
Expand Down
1 change: 0 additions & 1 deletion spartan/environments/next-scenario.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ DESTROY_ETH_DEVNET=true
CREATE_ETH_DEVNET=true
AZTEC_EPOCH_DURATION=32
AZTEC_SLOT_DURATION=36
AZTEC_INBOX_LAG=2
ETHEREUM_CHAIN_ID=1337
LABS_INFRA_MNEMONIC="test test test test test test test test test test test junk"
FUNDING_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
Expand Down
1 change: 0 additions & 1 deletion spartan/environments/staging-internal.env
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ SPONSORED_FPC=false
# Rollup
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=2
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=2
AZTEC_INBOX_LAG=2
AZTEC_MANA_TARGET=75000000
AZTEC_PROVING_COST_PER_MANA=12500000

Expand Down
1 change: 0 additions & 1 deletion spartan/environments/staging-public.env
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ SPONSORED_FPC=false
# Rollup
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=2
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=2
AZTEC_INBOX_LAG=2
AZTEC_MANA_TARGET=75000000
AZTEC_PROVING_COST_PER_MANA=12500000

Expand Down
1 change: 0 additions & 1 deletion spartan/scripts/deploy_network.sh
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,6 @@ AZTEC_LAG_IN_EPOCHS_FOR_RANDAO = ${AZTEC_LAG_IN_EPOCHS_FOR_RANDAO:-null}
AZTEC_SLOT_DURATION = ${AZTEC_SLOT_DURATION:-null}
AZTEC_EPOCH_DURATION = ${AZTEC_EPOCH_DURATION:-null}
AZTEC_TARGET_COMMITTEE_SIZE = ${AZTEC_TARGET_COMMITTEE_SIZE:-null}
AZTEC_INBOX_LAG = ${AZTEC_INBOX_LAG:-null}
AZTEC_PROOF_SUBMISSION_EPOCHS = ${AZTEC_PROOF_SUBMISSION_EPOCHS:-null}
AZTEC_ACTIVATION_THRESHOLD = ${AZTEC_ACTIVATION_THRESHOLD:-null}
AZTEC_EJECTION_THRESHOLD = ${AZTEC_EJECTION_THRESHOLD:-null}
Expand Down
1 change: 0 additions & 1 deletion spartan/terraform/deploy-rollup-contracts/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ locals {
AZTEC_SLOT_DURATION = var.AZTEC_SLOT_DURATION
AZTEC_EPOCH_DURATION = var.AZTEC_EPOCH_DURATION
AZTEC_TARGET_COMMITTEE_SIZE = var.AZTEC_TARGET_COMMITTEE_SIZE
AZTEC_INBOX_LAG = var.AZTEC_INBOX_LAG
AZTEC_PROOF_SUBMISSION_EPOCHS = var.AZTEC_PROOF_SUBMISSION_EPOCHS
AZTEC_ACTIVATION_THRESHOLD = var.AZTEC_ACTIVATION_THRESHOLD
AZTEC_EJECTION_THRESHOLD = var.AZTEC_EJECTION_THRESHOLD
Expand Down
6 changes: 0 additions & 6 deletions spartan/terraform/deploy-rollup-contracts/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,6 @@ variable "AZTEC_PROOF_SUBMISSION_EPOCHS" {
nullable = true
}

variable "AZTEC_INBOX_LAG" {
description = "Checkpoints to lag in inbox (prevents sequencer DOS attacks)"
type = string
nullable = true
}

variable "AZTEC_ACTIVATION_THRESHOLD" {
description = "Aztec activation threshold"
type = string
Expand Down
9 changes: 5 additions & 4 deletions yarn-project/THREAT_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,9 @@ L1 does **not** verify committee attestations at propose time (gas optimization)
invalid attestations is rejected without touching potentially malformed blob data
([validation.ts](archiver/src/modules/validation.ts)).
- **C2 — Sync never bricks.** The archiver skips invalid checkpoints, advances its syncpoint past them, and keeps
processing. The `inHash` check cannot be weaponized: L1 enforces `header.inHash == inbox.consume(...)` at propose
time, so a local mismatch indicates a node bug, not attacker-controlled input (`ProposeLib`).
processing. The consensus rolling-hash check cannot be weaponized: L1 enforces at propose time that the checkpoint
header's `inboxRollingHash` matches the rolling hash of the Inbox bucket the checkpoint consumes through, so a local
mismatch indicates a node bug, not attacker-controlled input (`ProposeLib`).
- **C3 — Bad-attestation checkpoints are removable and unprovable.** Anyone can call `invalidateBadAttestation` /
`invalidateInsufficientAttestations` to purge them (no rebate; expected callers: next proposer, then committee, then
any validator, with timed fallbacks in the sequencer). They cannot enter the proven chain: `submitEpochRootProof`
Expand Down Expand Up @@ -236,8 +237,8 @@ L1 does **not** verify committee attestations at propose time (gas optimization)

- **S1 — Sync everything provable.** Nodes must be able to sync from L1 any checkpoint that provers can prove, even if
local validator policy would have refused to attest to it. Validator-side caps (`VALIDATOR_MAX_*`) apply only to p2p
proposal validation; the archiver's L1 path enforces only attestation validity, `inHash` consistency, and structural
blob decoding. Rationale: escape-hatch and malicious-quorum checkpoints bypass validator policy but can still reach
proposal validation; the archiver's L1 path enforces only attestation validity, consensus rolling-hash consistency,
and structural blob decoding. Rationale: escape-hatch and malicious-quorum checkpoints bypass validator policy but can still reach
the proven chain, and a node that refuses to sync them forks itself off.
- **S2 — L1 reorg resilience.** Message and checkpoint sync detect L1 reorgs (rolling hashes, archive root comparison),
unwind to the common ancestor, and re-fetch — including the subtle case of checkpoints added *behind* the syncpoint.
Expand Down
9 changes: 4 additions & 5 deletions yarn-project/archiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ Two independent syncpoints track progress on L1:

### L1-to-L2 Messages

Messages are synced from the Inbox contract. The sync compares local state (message count and rolling hash) against the Inbox contract state on L1, downloads any missing messages, and verifies consistency afterwards. On success, the syncpoint advances to the current L1 block. On failure (L1 reorg or inconsistency), the syncpoint rolls back to the last known-good message and the operation retries (up to 3 times within the same sync iteration).
Messages are synced from the Inbox contract. The sync compares local state (message count and consensus rolling hash) against the Inbox's current rolling-hash bucket on L1, downloads any missing messages, and verifies consistency afterwards. On success, the syncpoint advances to the current L1 block. On failure (L1 reorg or inconsistency), the syncpoint rolls back to the last known-good message and the operation retries (up to 3 times within the same sync iteration).

1. Query Inbox state at the current L1 block (message count + rolling hash)
1. Query the Inbox's current bucket at the current L1 block (cumulative message count + consensus rolling hash)
2. Compare local state against remote
3. If they match, advance syncpoint and return
4. If mismatch, fetch `MessageSent` events in batches and store them
Expand All @@ -55,7 +55,7 @@ Messages are synced from the Inbox contract. The sync compares local state (mess
- If still mismatched (e.g., messages missed due to a concurrent L1 reorg), rollback and retry
6. On success, advance the syncpoint

The syncpoint and the `inboxTreeInProgress` marker (which tracks which checkpoint's messages are currently being filled on L1) are updated atomically. The marker is only advanced after messages are stored, so concurrent reads don't see an unsealed checkpoint as readable before its messages are available.
Messages are stored with compact (unpadded) global indices matching the Inbox's insertion order, and each carries the consensus rolling hash (a truncated sha256 chain) and the sequence of the Inbox bucket it was absorbed into (AZIP-22 Fast Inbox). Bucket snapshots let the sequencer and validator resolve message bundles per block.

### Checkpoints

Expand All @@ -70,13 +70,12 @@ Checkpoints are synced from the Rollup contract via `handleCheckpoints()`:
- Verify archive matches (checkpoint still in chain)
- Validate attestations (2/3 + 1 committee signatures required)
- Skip invalid checkpoints (see "Invalid Checkpoints" in Edge Cases)
- Verify `inHash` matches expected value (see below)
- Store valid checkpoints with their blocks
6. Update proven checkpoint again (may have advanced after storing new checkpoints)
7. Handle epoch prune if applicable
8. Check for checkpoints behind syncpoint (L1 reorg case)

The `inHash` is a hash of all L1-to-L2 messages consumed by a checkpoint. The archiver computes the expected `inHash` from locally stored messages and compares it against the checkpoint header. A mismatch indicates a bug (messages out of sync with checkpoints) and causes a fatal error.
L1 enforces at propose time that a checkpoint header's consensus rolling hash matches the Inbox bucket the checkpoint consumes through (AZIP-22 Fast Inbox), so the archiver does not re-derive or cross-check a per-checkpoint message hash while syncing.

The `blocksSynchedTo` syncpoint is updated:
- When checkpoints are stored: set to the L1 block of the last stored checkpoint
Expand Down
59 changes: 23 additions & 36 deletions yarn-project/archiver/src/archiver-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { sum, times } from '@aztec/foundation/collection';
import { Secp256k1Signer } from '@aztec/foundation/crypto/secp256k1-signer';
import { Fr } from '@aztec/foundation/curves/bn254';
import { EthAddress } from '@aztec/foundation/eth-address';
import { toArray } from '@aztec/foundation/iterable';
import { type Logger, createLogger } from '@aztec/foundation/log';
import { retryFastUntil } from '@aztec/foundation/retry';
import { TestDateProvider } from '@aztec/foundation/timer';
Expand All @@ -36,7 +37,7 @@ import { type MockProxy, mock } from 'jest-mock-extended';
import type { GetBlockReturnType } from 'viem';

import { Archiver, type ArchiverEmitter } from './archiver.js';
import { BlockOrCheckpointSlotExpiredError, L1ToL2MessagesNotReadyError } from './errors.js';
import { BlockOrCheckpointSlotExpiredError } from './errors.js';
import type { ArchiverInstrumentation } from './modules/instrumentation.js';
import { ArchiverL1Synchronizer } from './modules/l1_synchronizer.js';
import { type ArchiverDataStores, createArchiverDataStores } from './store/data_stores.js';
Expand Down Expand Up @@ -186,6 +187,11 @@ describe('Archiver Sync', () => {
await archiver?.stop();
});

// Returns every stored L1-to-L2 message leaf (as hex), in insertion order (compact indexing, AZIP-22 Fast Inbox).
const getStoredLeaves = async () =>
(await toArray(archiverStore.messages.iterateL1ToL2Messages())).map(m => m.leaf.toString());
const asHex = (leaves: Fr[]) => leaves.map(l => l.toString());

describe('basic sync', () => {
it('syncs l1 to l2 messages and checkpoints', async () => {
expect(await archiver.getCheckpointNumber()).toEqual(CheckpointNumber(0));
Expand Down Expand Up @@ -220,7 +226,7 @@ describe('Archiver Sync', () => {
expect(await archiver.getCheckpointNumber()).toEqual(CheckpointNumber(1));

// Verify messages for checkpoint 1
expect(await archiver.getL1ToL2Messages(CheckpointNumber(1))).toEqual(msgs1);
expect(await getStoredLeaves()).toEqual(asHex(msgs1));

// Mark checkpoint 1 as proven
fake.markCheckpointAsProven(CheckpointNumber(1));
Expand All @@ -233,11 +239,8 @@ describe('Archiver Sync', () => {
await archiver.syncImmediate();
expect(await archiver.getCheckpointNumber()).toEqual(CheckpointNumber(3));

// Verify messages for all checkpoints
expect(await archiver.getL1ToL2Messages(CheckpointNumber(1))).toEqual(msgs1);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(2))).toEqual(msgs2);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(3))).toEqual(msgs3);
await expect(archiver.getL1ToL2Messages(CheckpointNumber(4))).rejects.toThrow(L1ToL2MessagesNotReadyError);
// Verify messages for all checkpoints, stored contiguously in insertion order.
expect(await getStoredLeaves()).toEqual(asHex([...msgs1, ...msgs2, ...msgs3]));

// Verify private logs are surfaced through the block body.
for (const checkpoint of [cp1, cp2, cp3]) {
Expand Down Expand Up @@ -440,29 +443,29 @@ describe('Archiver Sync', () => {
logger.warn('Initial sync');
await archiver.syncImmediate();

expect(inboxContract.getState).toHaveBeenCalledTimes(1);
expect(inboxContract.getCurrentBucket).toHaveBeenCalledTimes(1);
expect(rollupContract.status).toHaveBeenCalledTimes(1);
inboxContract.getState.mockClear();
inboxContract.getCurrentBucket.mockClear();
rollupContract.status.mockClear();

// We sync again, but since chain didn't move, no new calls should be expected
logger.warn('Sync with no L1 advancement');
await archiver.syncImmediate();
expect(inboxContract.getState).toHaveBeenCalledTimes(0);
expect(inboxContract.getCurrentBucket).toHaveBeenCalledTimes(0);
expect(rollupContract.status).toHaveBeenCalledTimes(0);

// Advance the chain and we should see calls again
fake.setL1BlockNumber(150n);
logger.warn('Sync after L1 advancement');
await archiver.syncImmediate();
expect(inboxContract.getState).toHaveBeenCalledTimes(1);
expect(inboxContract.getCurrentBucket).toHaveBeenCalledTimes(1);
expect(rollupContract.status).toHaveBeenCalledTimes(1);
});

it('does not fetch messages when local and remote state both have zero messages', async () => {
// When there are no messages on L1, the remote inbox state has messagesRollingHash = Buffer16.ZERO
// and totalMessagesInserted = 0. The local store also returns 0 messages and undefined lastMessage.
// The fallback for the local rolling hash must use Buffer16.ZERO (not Buffer32.ZERO) to match.
// When there are no messages on L1, the remote Inbox current bucket is genesis (rolling hash Fr.ZERO,
// total 0). The local store also returns 0 messages and undefined lastMessage, whose rolling-hash fallback
// is Fr.ZERO — so local and remote state match and no message fetch is attempted.
fake.setL1BlockNumber(100n);

// Add a checkpoint with zero messages so the sync has something to process
Expand Down Expand Up @@ -1094,10 +1097,7 @@ describe('Archiver Sync', () => {
// Sync
await archiver.syncImmediate();

expect(await archiver.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(2);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(2))).toHaveLength(0);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(3))).toHaveLength(4);
await expect(archiver.getL1ToL2Messages(CheckpointNumber(4))).rejects.toThrow(L1ToL2MessagesNotReadyError);
expect(await getStoredLeaves()).toEqual(asHex([...msgs1, ...msgs3]));

// Simulate L1 reorg: remove last 2 messages from checkpoint 3, add new messages for checkpoints 4 and 5
logger.warn('Reorging L1 to L2 messages');
Expand All @@ -1114,18 +1114,8 @@ describe('Archiver Sync', () => {
fake.setL1BlockNumber(111n);
await archiver.syncImmediate();

expect(await archiver.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(2);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(2))).toHaveLength(0);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(3))).toHaveLength(2); // Reduced from 4 to 2
expect(await archiver.getL1ToL2Messages(CheckpointNumber(4))).toHaveLength(1);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(5))).toHaveLength(2);

expect((await archiver.getL1ToL2Messages(CheckpointNumber(4))).map(leaf => leaf.toString())).toEqual(
[msg40].map(leaf => leaf.toString()),
);
expect((await archiver.getL1ToL2Messages(CheckpointNumber(5))).map(leaf => leaf.toString())).toEqual(
[msg50, msg51].map(leaf => leaf.toString()),
);
// The reorg kept the first 4 messages (2 from CP1, 2 from CP3) and appended the new ones.
expect(await getStoredLeaves()).toEqual(asHex([msgs1[0], msgs1[1], msgs3[0], msgs3[1], msg40, msg50, msg51]));
});

it('short-circuits rollback at the finalized L1 block', async () => {
Expand All @@ -1141,8 +1131,7 @@ describe('Archiver Sync', () => {
fake.setL1BlockNumber(110n);
await archiver.syncImmediate();

expect(await archiver.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(2);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(3))).toHaveLength(4);
expect(await getStoredLeaves()).toEqual(asHex([...msgs1, ...msgs3]));

// Simulate L1 reorg: remove the last 2 messages from checkpoint 3 and add new ones.
fake.removeMessagesAfter(4);
Expand All @@ -1163,8 +1152,7 @@ describe('Archiver Sync', () => {
);
expect(callsAtFinalizedOrBelow).toHaveLength(0);

expect(await archiver.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(2);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(4))).toHaveLength(1);
expect(await getStoredLeaves()).toEqual(asHex([msgs1[0], msgs1[1], msgs3[0], msgs3[1], msg40]));
});

it('falls back to per-message log queries when finalized block is undefined', async () => {
Expand Down Expand Up @@ -1193,8 +1181,7 @@ describe('Archiver Sync', () => {
// 2 messages mismatch on remote (msgs3[2], msgs3[3]) and one matches (msgs3[1]) before we break.
expect(eventByHashSpy).toHaveBeenCalledTimes(3);

expect(await archiver.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(2);
expect(await archiver.getL1ToL2Messages(CheckpointNumber(4))).toHaveLength(1);
expect(await getStoredLeaves()).toEqual(asHex([msgs1[0], msgs1[1], msgs3[0], msgs3[1], msg40]));
});

it('persists the finalized L1 block monotonically after message sync', async () => {
Expand Down
Loading
Loading