Skip to content

feat: merge-train/spartan#20719

Merged
AztecBot merged 18 commits intonextfrom
merge-train/spartan
Feb 21, 2026
Merged

feat: merge-train/spartan#20719
AztecBot merged 18 commits intonextfrom
merge-train/spartan

Conversation

@AztecBot
Copy link
Collaborator

@AztecBot AztecBot commented Feb 20, 2026

BEGIN_COMMIT_OVERRIDE
feat: check calldata against emitted hashes (#20486)
feat: trim attestations to the minimum required length (#20591)
feat: call syncImmediate without block number during chain prune (#20717)
chore(metrics): add L1 inclusion timing metrics to archiver (#20722)
fix: pxe native prover log level (#20724)
chore: fix worker wallet in jest (#20725)
fix: rpc deployment name used to restart gke pods (#20736)
END_COMMIT_OVERRIDE

## Summary

- Replace the allowlist-based "strict" multicall3 validation with
hash-only matching: the archiver now finds `propose` calls by rollup
address + selector and verifies them against `attestationsHash` and
`payloadDigest` from `CheckpointProposed` events
- Make expected hashes required throughout — drop backwards
compatibility for older events without hashes
- Simplify `CalldataRetriever` constructor from a contract addresses
object to just `rollupAddress`
- Remove the allowlist infrastructure: `ValidContractCall`,
`computeValidContractCalls`, all non-propose selector constants, and
unused ABI imports (`EmpireSlashingProposerAbi`,
`GovernanceProposerAbi`, `SlashFactoryAbi`, `TallySlashingProposerAbi`)
- Update Spire Proposer to return all wrapped calls (not just exactly
one), enabling hash matching across multi-call Spire transactions
- Fix the CLI debug tool (`retrieve-calldata.ts`) to extract hashes from
`CheckpointProposed` event logs and pass them to the retriever
- Remove `l1Addresses` constructor parameter from
`ArchiverL1Synchronizer` and `contractAddresses` from data retrieval
functions

## Details

### Hash-only matching in `tryDecodeMulticall3`

Previously, the method first tried "strict" validation (all calls must
be on an allowlist of known addresses and selectors), then fell back to
"relaxed" hash matching. Now there's a single path: find all calls
matching rollup address + `propose` selector, verify each candidate
against expected hashes, return the uniquely verified one. If multiple
candidates verify (identical data), the first is returned with a
warning.

### Required expected hashes

`attestationsHash` and `payloadDigest` are now required (not optional)
in:
- `CheckpointProposedArgs` type in `rollup.ts`
- All `CalldataRetriever` methods (`getCheckpointFromRollupTx`,
`getCheckpointFromTx`, `tryDecodeMulticall3`, `tryDecodeDirectPropose`,
`tryDecodeSpireProposer`, `tryDecodeAndVerifyPropose`)

Runtime guards in `getCheckpointProposedEvents` throw if either field is
missing from the event log.

### Simplified constructor

`CalldataRetriever` now takes just `rollupAddress: EthAddress` instead
of `{ rollupAddress, governanceProposerAddress, slashingProposerAddress,
slashFactoryAddress? }`. This eliminates the need for `l1Addresses` in
`ArchiverL1Synchronizer` and `contractAddresses` in data retrieval
functions.

### Spire Proposer multi-call support

`getCallFromSpireProposer` renamed to `getCallsFromSpireProposer`, now
returns all wrapped calls as an array. `tryDecodeSpireProposer` iterates
each call and tries it through `tryDecodeMulticall3` or direct propose +
hash verification, returning the first verified match.

### CLI debug tool

`retrieve-calldata.ts` now uses `decodeEventLog` from viem to extract
`attestationsHash` and `payloadDigest` from the `CheckpointProposed`
event log, passing real hashes instead of an empty object.

## Test plan

- All 55 calldata retriever unit tests pass (hash matching, multicall3,
direct propose, Spire Proposer, trace fallback, integration)
- All 17 spire proposer unit tests pass (single/multi call, validation
failures, array return type)
- Build, format, and lint pass cleanly

Fixes A-408

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
AztecBot and others added 17 commits February 20, 2026 13:37
## Summary

- Trim attestations to the minimum required (2/3 + 1 of committee)
before submitting to L1, saving calldata gas
- The proposer's own attestation is always preserved (L1 reverts with
`MissingProposerSignature` without it)
- Attestations from the local node's validator keys are prioritized over
external ones
- No L1 contract changes needed — the existing packed format already
handles positions without signatures by storing addresses instead (20
bytes vs 65 bytes per signature)

## Changes

- `yarn-project/stdlib/src/p2p/attestation_utils.ts`: Added
`trimAttestations()` utility that partitions attestations into
proposer/local/external buckets and keeps only the minimum required,
prioritizing proposer and local validators
-
`yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.ts`:
Call `trimAttestations()` between `collectAttestations()` and
`orderAttestations()` in `waitForAttestations()`
- `yarn-project/stdlib/src/p2p/attestation_utils.test.ts`: New test file
with 7 unit tests covering trimming behavior, priority ordering, edge
cases
-
`yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.test.ts`:
Added `getValidatorAddresses` mock
-
`yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.timing.test.ts`:
Added `getValidatorAddresses` mock

## Test plan

- [x] `trimAttestations` unit tests (7/7 pass): no-op when under
threshold, trims to required count, keeps proposer, prioritizes local,
fills with external, no double-counting, handles bad signatures
- [x] `checkpoint_proposal_job.test.ts` (26/26 pass)
- [x] `checkpoint_proposal_job.timing.test.ts` (16/16 pass)
- [x] `sequencer.test.ts` (22/22 pass)
- [ ] E2E multi-validator tests (assertions use `>= quorum` so should be
safe)
)

## Summary

- Update `syncImmediate` calls in `FeePayerBalanceEvictionRule` and
`InvalidTxsAfterReorgRule` to call without a block number during
`CHAIN_PRUNED` events
- `syncImmediate(blockNumber)` returns early when the world state is
already at or past the target, skipping `blockStream.sync()` — calling
without args ensures the prune event is always processed

Note: despite the original issue description (A-545) suggesting that
validations may produce incorrect results, the eviction rules were
already correct. Both rules use `getSnapshot(blockNumber)` which serves
historical data via the native content-addressed store's root hash,
regardless of whether `unwindBlocks` has run. Additionally,
`#revalidateMetadata` (step 5 in `handlePrunedBlocks`) already calls
`syncImmediate()` without args through `createPoolTxValidator`, so the
world state processes the prune before the eviction rules run. This
change makes the eviction rules' intent explicit rather than relying on
the earlier sync.

Fixes A-545
Copy link
Collaborator

@ludamad ludamad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Auto-approved

@AztecBot AztecBot added this pull request to the merge queue Feb 20, 2026
@AztecBot
Copy link
Collaborator Author

🤖 Auto-merge enabled after 4 hours of inactivity. This PR will be merged automatically once all checks pass.

Merged via the queue into next with commit 321b439 Feb 21, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants