From 853d17df79b8d2994b43820384ec654d82e19f4b Mon Sep 17 00:00:00 2001 From: spypsy <6403450+spypsy@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:09:22 +0000 Subject: [PATCH] chore: log L1 trace errors once in debug Fixes [A-393](https://linear.app/aztec-labs/issue/A-393/non-debug-tx-nodes-spam-logs-when-encountering-bad-blocks) --- .../src/l1/calldata_retriever.test.ts | 27 +++++++++++++++++++ .../archiver/src/l1/calldata_retriever.ts | 22 +++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/yarn-project/archiver/src/l1/calldata_retriever.test.ts b/yarn-project/archiver/src/l1/calldata_retriever.test.ts index 9dd7f5e7187a..0cb5bdf04650 100644 --- a/yarn-project/archiver/src/l1/calldata_retriever.test.ts +++ b/yarn-project/archiver/src/l1/calldata_retriever.test.ts @@ -14,6 +14,7 @@ import { GasFees } from '@aztec/stdlib/gas'; import { ConsensusPayload, SignatureDomainSeparator } from '@aztec/stdlib/p2p'; import { CheckpointHeader } from '@aztec/stdlib/rollup'; +import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; import { type Hex, @@ -1017,6 +1018,32 @@ describe('CalldataRetriever', () => { expect(debugClient.request).toHaveBeenCalledTimes(2); }); + it('should log trace+debug failure warn only once per tx hash', async () => { + CalldataRetriever.resetTraceFailureWarnedForTesting(); + const warnSpy = jest.spyOn(logger, 'warn'); + + // First attempt: both trace and debug fail + debugClient.request.mockRejectedValueOnce(new Error('trace_transaction not supported')); + debugClient.request.mockRejectedValueOnce(new Error('debug_traceTransaction not supported')); + + await expect(retriever.extractCalldataViaTrace(txHash)).rejects.toThrow( + 'Failed to trace transaction ' + txHash + ' to extract propose calldata', + ); + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Cannot decode L1 tx')); + + // Second attempt: same tx, both fail again - should not log warn again + debugClient.request.mockRejectedValueOnce(new Error('trace_transaction not supported')); + debugClient.request.mockRejectedValueOnce(new Error('debug_traceTransaction not supported')); + + await expect(retriever.extractCalldataViaTrace(txHash)).rejects.toThrow( + 'Failed to trace transaction ' + txHash + ' to extract propose calldata', + ); + expect(warnSpy).toHaveBeenCalledTimes(1); + + warnSpy.mockRestore(); + }); + it('should throw when no propose calls found', async () => { // Mock debug client to return empty trace debugClient.request.mockResolvedValueOnce([]); diff --git a/yarn-project/archiver/src/l1/calldata_retriever.ts b/yarn-project/archiver/src/l1/calldata_retriever.ts index 84f227b9617b..160123c13338 100644 --- a/yarn-project/archiver/src/l1/calldata_retriever.ts +++ b/yarn-project/archiver/src/l1/calldata_retriever.ts @@ -39,6 +39,14 @@ import type { CallInfo } from './types.js'; * in order to reconstruct an L2 block header. */ export class CalldataRetriever { + /** Tx hashes we've already logged for trace+debug failure (log once per tx per process). */ + private static readonly traceFailureWarnedTxHashes = new Set(); + + /** Clears the trace-failure warned set. For testing only. */ + static resetTraceFailureWarnedForTesting(): void { + CalldataRetriever.traceFailureWarnedTxHashes.clear(); + } + /** Pre-computed valid contract calls for validation */ private readonly validContractCalls: ValidContractCall[]; @@ -313,7 +321,8 @@ export class CalldataRetriever { this.logger.debug(`Successfully traced using trace_transaction, found ${calls.length} calls`); } catch (err) { const traceError = err instanceof Error ? err : new Error(String(err)); - this.logger.verbose(`Failed trace_transaction for ${txHash}`, { traceError }); + this.logger.verbose(`Failed trace_transaction for ${txHash}: ${traceError.message}`); + this.logger.debug(`Trace failure details for ${txHash}`, { traceError }); try { // Fall back to debug_traceTransaction (Geth RPC) @@ -322,7 +331,16 @@ export class CalldataRetriever { this.logger.debug(`Successfully traced using debug_traceTransaction, found ${calls.length} calls`); } catch (debugErr) { const debugError = debugErr instanceof Error ? debugErr : new Error(String(debugErr)); - this.logger.warn(`All tracing methods failed for tx ${txHash}`, { + // Log once per tx so we don't spam on every sync cycle when sync point doesn't advance + if (!CalldataRetriever.traceFailureWarnedTxHashes.has(txHash)) { + CalldataRetriever.traceFailureWarnedTxHashes.add(txHash); + this.logger.warn( + `Cannot decode L1 tx ${txHash}: trace and debug RPC failed or unavailable. ` + + `trace_transaction: ${traceError.message}; debug_traceTransaction: ${debugError.message}`, + ); + } + // Full error objects can be very long; keep at debug only + this.logger.debug(`Trace/debug failure details for tx ${txHash}`, { traceError, debugError, txHash,