feat: check calldata against emitted hashes#20486
feat: check calldata against emitted hashes#20486mrzeszutko wants to merge 3 commits intomerge-train/spartanfrom
Conversation
| expectedHashes?: { attestationsHash?: Hex; payloadDigest?: Hex }, | ||
| ): Promise<Hex | undefined> { | ||
| // Try to decode as Spire Proposer multicall (extracts the wrapped call) | ||
| const spireWrappedCall = await getCallFromSpireProposer(tx, this.publicClient, this.logger); |
There was a problem hiding this comment.
We need to extend the multicall strict/relaxed logic to the spire proposer as well. See Spire Proposer multicall must contain exactly one call in archiver/src/l1/spire_proposer.ts. If we can now identify which one is the correct call, we should do it.
There was a problem hiding this comment.
fixed - I missed this before
| if (proposeCalls.length > 1) { | ||
| this.logger.warn(`Multiple propose calls found in multicall3 (${proposeCalls.length})`, { txHash }); | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
IIUC, this means that if there are two propose calls on the multicall, and all other calls are whitelisted, we'll still fail rather than fall back to comparing by expected hashes. Granted, we should not have more than one propose call per multicall, but still - not failing here means we don't need to fall back to debug traceTx.
There was a problem hiding this comment.
I changed this to return the first call if there is more than one with the same hashes, if there is more than 1 it is now just logged
| * Attempts to decode transaction input as multicall3 and extract propose calldata. | ||
| * Returns undefined if validation fails. | ||
| * Tries strict validation first (all calls must be on the allowlist). If strict fails due to | ||
| * unrecognized calls and both expected hashes are available, falls back to relaxed mode which | ||
| * filters candidate propose calls by target address + selector and verifies them against hashes. |
There was a problem hiding this comment.
I think we can just drop the strict/relaxed differentiation, fetch all propose calls we find in the multicall (ignoring the ones that don't match, and removing altogether the "valid functions" check), and then filter by the ones that match the expected hashes. There's no point in keeping backwards compatibility for L1 rollup contracts that don't emit the expected hashes in the event, since we're developing on next directly without having to backport.
There was a problem hiding this comment.
I removed backwards compatiblity and refactored the code to remove doubled hash validation
| if (!attestationsMatch) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Let's add some warn-level logging here, since this should not happen
Summary
proposecalls by rollup address + selector and verifies them againstattestationsHashandpayloadDigestfromCheckpointProposedeventsCalldataRetrieverconstructor from a contract addresses object to justrollupAddressValidContractCall,computeValidContractCalls, all non-propose selector constants, and unused ABI imports (EmpireSlashingProposerAbi,GovernanceProposerAbi,SlashFactoryAbi,TallySlashingProposerAbi)retrieve-calldata.ts) to extract hashes fromCheckpointProposedevent logs and pass them to the retrieverl1Addressesconstructor parameter fromArchiverL1SynchronizerandcontractAddressesfrom data retrieval functionsDetails
Hash-only matching in
tryDecodeMulticall3Previously, 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 +
proposeselector, 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
attestationsHashandpayloadDigestare now required (not optional) in:CheckpointProposedArgstype inrollup.tsCalldataRetrievermethods (getCheckpointFromRollupTx,getCheckpointFromTx,tryDecodeMulticall3,tryDecodeDirectPropose,tryDecodeSpireProposer,tryDecodeAndVerifyPropose)Runtime guards in
getCheckpointProposedEventsthrow if either field is missing from the event log.Simplified constructor
CalldataRetrievernow takes justrollupAddress: EthAddressinstead of{ rollupAddress, governanceProposerAddress, slashingProposerAddress, slashFactoryAddress? }. This eliminates the need forl1AddressesinArchiverL1SynchronizerandcontractAddressesin data retrieval functions.Spire Proposer multi-call support
getCallFromSpireProposerrenamed togetCallsFromSpireProposer, now returns all wrapped calls as an array.tryDecodeSpireProposeriterates each call and tries it throughtryDecodeMulticall3or direct propose + hash verification, returning the first verified match.CLI debug tool
retrieve-calldata.tsnow usesdecodeEventLogfrom viem to extractattestationsHashandpayloadDigestfrom theCheckpointProposedevent log, passing real hashes instead of an empty object.Test plan
Fixes A-408