feat: check calldata against emitted hashes#20486
feat: check calldata against emitted hashes#20486mrzeszutko wants to merge 1 commit 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.
| 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.
| * 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.
| 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
proposecalldata from multicalls containing unrecognized calls by verifying candidates againstattestationsHashandpayloadDigestemitted inCheckpointProposedeventsproposeselector, then returns the uniquely hash-verified matchdebug_traceTransaction/trace_transactionRPC fallback for calldata extractiongetCheckpointFromRollupTxscenariosDetails
Hash verification helper
Added
verifyProposeCalldataHasheswhich decodes propose calldata, computesattestationsHash(keccak of ABI-encoded signatures) andpayloadDigest(keccak of ABI-encoded propose args), and returnstrueonly when both computed hashes match the expected values from the event. Returnsfalseon any decode error, missing hash, or mismatch.Extended
tryDecodeMulticall3The method now accepts optional
expectedHashesandrollupAddressparameters:proposecalls (matching rollup address + selector), verifies each against expected hashes, and returns the uniquely verified candidateundefinedif zero or multiple candidates verify, or if hashes are incompletePlumbing
expectedHashesandrollupAddressare threaded throughgetCheckpointFromRollupTx->getProposeCallData->tryDecodeMulticall3andtryDecodeSpireProposer, so relaxed verification works for both direct multicall3 and Spire-wrapped multicall3 transactions.Backwards compatibility
When hashes are missing (older events before TMNT-461), relaxed mode is skipped entirely and behavior is identical to before. The final
decodeAndBuildCheckpointhash validation remains as a safety net.Fixes A-408