@aztec/archiver 2.1.8 → 2.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/archiver/archiver.d.ts +6 -5
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +26 -9
- package/dest/archiver/config.d.ts.map +1 -1
- package/dest/archiver/config.js +5 -0
- package/dest/archiver/instrumentation.d.ts +2 -0
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/instrumentation.js +11 -0
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts +3 -0
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts.map +1 -0
- package/dest/archiver/l1/bin/retrieve-calldata.js +149 -0
- package/dest/archiver/l1/calldata_retriever.d.ts +87 -0
- package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -0
- package/dest/archiver/l1/calldata_retriever.js +406 -0
- package/dest/archiver/{data_retrieval.d.ts → l1/data_retrieval.d.ts} +13 -6
- package/dest/archiver/l1/data_retrieval.d.ts.map +1 -0
- package/dest/archiver/{data_retrieval.js → l1/data_retrieval.js} +22 -99
- package/dest/archiver/l1/debug_tx.d.ts +19 -0
- package/dest/archiver/l1/debug_tx.d.ts.map +1 -0
- package/dest/archiver/l1/debug_tx.js +73 -0
- package/dest/archiver/l1/spire_proposer.d.ts +70 -0
- package/dest/archiver/l1/spire_proposer.d.ts.map +1 -0
- package/dest/archiver/l1/spire_proposer.js +157 -0
- package/dest/archiver/l1/trace_tx.d.ts +97 -0
- package/dest/archiver/l1/trace_tx.d.ts.map +1 -0
- package/dest/archiver/l1/trace_tx.js +91 -0
- package/dest/archiver/l1/types.d.ts +12 -0
- package/dest/archiver/l1/types.d.ts.map +1 -0
- package/dest/archiver/l1/types.js +3 -0
- package/dest/archiver/l1/validate_trace.d.ts +29 -0
- package/dest/archiver/l1/validate_trace.d.ts.map +1 -0
- package/dest/archiver/l1/validate_trace.js +150 -0
- package/dest/index.d.ts +1 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/package.json +15 -14
- package/src/archiver/archiver.ts +42 -12
- package/src/archiver/config.ts +5 -0
- package/src/archiver/instrumentation.ts +14 -0
- package/src/archiver/l1/README.md +98 -0
- package/src/archiver/l1/bin/retrieve-calldata.ts +186 -0
- package/src/archiver/l1/calldata_retriever.ts +528 -0
- package/src/archiver/{data_retrieval.ts → l1/data_retrieval.ts} +45 -155
- package/src/archiver/l1/debug_tx.ts +99 -0
- package/src/archiver/l1/spire_proposer.ts +160 -0
- package/src/archiver/l1/trace_tx.ts +128 -0
- package/src/archiver/l1/types.ts +13 -0
- package/src/archiver/l1/validate_trace.ts +211 -0
- package/src/index.ts +1 -1
- package/src/test/fixtures/debug_traceTransaction-multicall3.json +88 -0
- package/src/test/fixtures/debug_traceTransaction-multiplePropose.json +153 -0
- package/src/test/fixtures/debug_traceTransaction-proxied.json +122 -0
- package/src/test/fixtures/trace_transaction-multicall3.json +65 -0
- package/src/test/fixtures/trace_transaction-multiplePropose.json +319 -0
- package/src/test/fixtures/trace_transaction-proxied.json +128 -0
- package/src/test/fixtures/trace_transaction-randomRevert.json +216 -0
- package/dest/archiver/data_retrieval.d.ts.map +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import { callTraceSchema } from './debug_tx.js';
|
|
3
|
+
import { traceTransactionResponseSchema } from './trace_tx.js';
|
|
4
|
+
const logger = createLogger('aztec:archiver:validate_trace');
|
|
5
|
+
/**
|
|
6
|
+
* Helper function to test a trace method with validation
|
|
7
|
+
*
|
|
8
|
+
* @param client - The Viem public debug client
|
|
9
|
+
* @param txHash - Transaction hash to trace
|
|
10
|
+
* @param schema - Zod schema to validate the response
|
|
11
|
+
* @param method - Name of the RPC method ('debug_traceTransaction' or 'trace_transaction')
|
|
12
|
+
* @param blockType - Type of block being tested ('recent' or 'old')
|
|
13
|
+
* @returns true if the method works and validation passes, false otherwise
|
|
14
|
+
*/ async function testTraceMethod(client, txHash, schema, method, blockType) {
|
|
15
|
+
try {
|
|
16
|
+
// Make request with appropriate params based on method name
|
|
17
|
+
const result = await client.request(method === 'debug_traceTransaction' ? {
|
|
18
|
+
method,
|
|
19
|
+
params: [
|
|
20
|
+
txHash,
|
|
21
|
+
{
|
|
22
|
+
tracer: 'callTracer'
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
} : {
|
|
26
|
+
method,
|
|
27
|
+
params: [
|
|
28
|
+
txHash
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
schema.parse(result);
|
|
32
|
+
logger.debug(`${method} works for ${blockType} blocks`);
|
|
33
|
+
return true;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
logger.warn(`${method} failed for ${blockType} blocks: ${error}`);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Validates the availability of debug/trace methods on the Ethereum client.
|
|
41
|
+
*
|
|
42
|
+
* @param client - The Viem public debug client
|
|
43
|
+
* @returns Object indicating which trace methods are available for recent and old blocks
|
|
44
|
+
*/ export async function validateTraceAvailability(client) {
|
|
45
|
+
const result = {
|
|
46
|
+
debugTraceRecent: false,
|
|
47
|
+
traceTransactionRecent: false,
|
|
48
|
+
debugTraceOld: false,
|
|
49
|
+
traceTransactionOld: false
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
// Get the latest block
|
|
53
|
+
let latestBlock = await client.getBlock({
|
|
54
|
+
blockTag: 'latest'
|
|
55
|
+
});
|
|
56
|
+
let attempts = 10;
|
|
57
|
+
// Loop back to find a block with transactions (handles dev/test scenarios)
|
|
58
|
+
while(!hasTxs(latestBlock) && latestBlock.number && latestBlock.number > 0n && --attempts > 0){
|
|
59
|
+
logger.debug(`Block ${latestBlock.number} has no transactions, checking previous block`);
|
|
60
|
+
latestBlock = await client.getBlock({
|
|
61
|
+
blockNumber: latestBlock.number - 1n
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (!hasTxs(latestBlock)) {
|
|
65
|
+
logger.warn('No blocks with transactions found from latest back to genesis, cannot validate trace availability');
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
// Get a transaction from the found block
|
|
69
|
+
const recentTxHash = latestBlock.transactions[0];
|
|
70
|
+
// Test debug_traceTransaction with recent block
|
|
71
|
+
result.debugTraceRecent = await testTraceMethod(client, recentTxHash, callTraceSchema, 'debug_traceTransaction', 'recent');
|
|
72
|
+
// Test trace_transaction with recent block
|
|
73
|
+
result.traceTransactionRecent = await testTraceMethod(client, recentTxHash, traceTransactionResponseSchema, 'trace_transaction', 'recent');
|
|
74
|
+
// Get a block from 512 blocks ago
|
|
75
|
+
const oldBlockNumber = latestBlock.number ? latestBlock.number - 512n : null;
|
|
76
|
+
if (!oldBlockNumber || oldBlockNumber < 0n) {
|
|
77
|
+
logger.debug('Cannot test old blocks, blockchain too short');
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
let oldBlock = await client.getBlock({
|
|
81
|
+
blockNumber: oldBlockNumber
|
|
82
|
+
});
|
|
83
|
+
attempts = 10;
|
|
84
|
+
// Loop back to find a block with transactions (handles dev/test scenarios)
|
|
85
|
+
while(!hasTxs(oldBlock) && oldBlock.number && oldBlock.number > 0n && --attempts > 0){
|
|
86
|
+
logger.debug(`Block ${oldBlock.number} has no transactions, checking previous block`);
|
|
87
|
+
oldBlock = await client.getBlock({
|
|
88
|
+
blockNumber: oldBlock.number - 1n
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (!hasTxs(oldBlock)) {
|
|
92
|
+
logger.debug('No blocks with transactions found from old block back to genesis, cannot validate trace availability for old blocks');
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
const oldTxHash = oldBlock.transactions[0];
|
|
96
|
+
// Test debug_traceTransaction with old block
|
|
97
|
+
result.debugTraceOld = await testTraceMethod(client, oldTxHash, callTraceSchema, 'debug_traceTransaction', 'old');
|
|
98
|
+
// Test trace_transaction with old block
|
|
99
|
+
result.traceTransactionOld = await testTraceMethod(client, oldTxHash, traceTransactionResponseSchema, 'trace_transaction', 'old');
|
|
100
|
+
} catch (error) {
|
|
101
|
+
logger.warn(`Error validating debug_traceTransaction and trace_transaction availability: ${error}`);
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
function hasTxs(block) {
|
|
106
|
+
return Array.isArray(block.transactions) && block.transactions.length > 0;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Validates trace availability and logs appropriate messages based on the results.
|
|
110
|
+
* Optionally throws an error if no trace methods are available and ethereumAllowNoDebugHosts is false.
|
|
111
|
+
*
|
|
112
|
+
* @param client - The Viem public debug client
|
|
113
|
+
* @param ethereumAllowNoDebugHosts - If false, throws an error when no trace methods are available
|
|
114
|
+
* @throws Error if ethereumAllowNoDebugHosts is false and no trace methods are available
|
|
115
|
+
*/ export async function validateAndLogTraceAvailability(client, ethereumAllowNoDebugHosts) {
|
|
116
|
+
logger.info('Validating trace/debug method availability...');
|
|
117
|
+
const availability = await validateTraceAvailability(client);
|
|
118
|
+
// Check if we have support for old blocks (either debug or trace)
|
|
119
|
+
const hasOldBlockSupport = availability.debugTraceOld || availability.traceTransactionOld;
|
|
120
|
+
if (hasOldBlockSupport) {
|
|
121
|
+
// Ideal case: we have trace support for old blocks
|
|
122
|
+
const methods = [];
|
|
123
|
+
if (availability.debugTraceOld) {
|
|
124
|
+
methods.push('debug_traceTransaction');
|
|
125
|
+
}
|
|
126
|
+
if (availability.traceTransactionOld) {
|
|
127
|
+
methods.push('trace_transaction');
|
|
128
|
+
}
|
|
129
|
+
logger.info(`Ethereum client supports trace methods for old blocks (${methods.join(', ')}). Archiver can retrieve historical transaction traces.`);
|
|
130
|
+
} else if (availability.debugTraceRecent || availability.traceTransactionRecent) {
|
|
131
|
+
// Warning case: only recent block support
|
|
132
|
+
const methods = [];
|
|
133
|
+
if (availability.debugTraceRecent) {
|
|
134
|
+
methods.push('debug_traceTransaction');
|
|
135
|
+
}
|
|
136
|
+
if (availability.traceTransactionRecent) {
|
|
137
|
+
methods.push('trace_transaction');
|
|
138
|
+
}
|
|
139
|
+
logger.warn(`Ethereum client only supports trace methods for recent blocks (${methods.join(', ')}). Historical transaction traces may not be available.`);
|
|
140
|
+
} else {
|
|
141
|
+
// Error case: no support at all
|
|
142
|
+
const errorMessage = 'Ethereum debug client does not support debug_traceTransaction or trace_transaction methods. Transaction tracing will not be available. This may impact archiver syncing.';
|
|
143
|
+
if (ethereumAllowNoDebugHosts) {
|
|
144
|
+
logger.warn(`${errorMessage} Continuing because ETHEREUM_ALLOW_NO_DEBUG_HOSTS is set to true.`);
|
|
145
|
+
} else {
|
|
146
|
+
logger.error(errorMessage);
|
|
147
|
+
throw new Error(`${errorMessage} Set ETHEREUM_ALLOW_NO_DEBUG_HOSTS=true to bypass this check.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
package/dest/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './archiver/index.js';
|
|
2
2
|
export * from './factory.js';
|
|
3
3
|
export * from './rpc/index.js';
|
|
4
|
-
export { retrieveBlocksFromRollup, retrieveL2ProofVerifiedEvents } from './archiver/data_retrieval.js';
|
|
4
|
+
export { retrieveBlocksFromRollup, retrieveL2ProofVerifiedEvents } from './archiver/l1/data_retrieval.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dest/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAE/B,OAAO,EAAE,wBAAwB,EAAE,6BAA6B,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAE/B,OAAO,EAAE,wBAAwB,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC"}
|
package/dest/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from './archiver/index.js';
|
|
2
2
|
export * from './factory.js';
|
|
3
3
|
export * from './rpc/index.js';
|
|
4
|
-
export { retrieveBlocksFromRollup, retrieveL2ProofVerifiedEvents } from './archiver/data_retrieval.js';
|
|
4
|
+
export { retrieveBlocksFromRollup, retrieveL2ProofVerifiedEvents } from './archiver/l1/data_retrieval.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/archiver",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -66,23 +66,24 @@
|
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@aztec/blob-lib": "2.1.
|
|
70
|
-
"@aztec/blob-sink": "2.1.
|
|
71
|
-
"@aztec/constants": "2.1.
|
|
72
|
-
"@aztec/epoch-cache": "2.1.
|
|
73
|
-
"@aztec/ethereum": "2.1.
|
|
74
|
-
"@aztec/foundation": "2.1.
|
|
75
|
-
"@aztec/kv-store": "2.1.
|
|
76
|
-
"@aztec/l1-artifacts": "2.1.
|
|
77
|
-
"@aztec/noir-protocol-circuits-types": "2.1.
|
|
78
|
-
"@aztec/protocol-contracts": "2.1.
|
|
79
|
-
"@aztec/stdlib": "2.1.
|
|
80
|
-
"@aztec/telemetry-client": "2.1.
|
|
69
|
+
"@aztec/blob-lib": "2.1.9",
|
|
70
|
+
"@aztec/blob-sink": "2.1.9",
|
|
71
|
+
"@aztec/constants": "2.1.9",
|
|
72
|
+
"@aztec/epoch-cache": "2.1.9",
|
|
73
|
+
"@aztec/ethereum": "2.1.9",
|
|
74
|
+
"@aztec/foundation": "2.1.9",
|
|
75
|
+
"@aztec/kv-store": "2.1.9",
|
|
76
|
+
"@aztec/l1-artifacts": "2.1.9",
|
|
77
|
+
"@aztec/noir-protocol-circuits-types": "2.1.9",
|
|
78
|
+
"@aztec/protocol-contracts": "2.1.9",
|
|
79
|
+
"@aztec/stdlib": "2.1.9",
|
|
80
|
+
"@aztec/telemetry-client": "2.1.9",
|
|
81
81
|
"lodash.groupby": "^4.6.0",
|
|
82
82
|
"lodash.omit": "^4.5.0",
|
|
83
83
|
"tsc-watch": "^6.0.0",
|
|
84
84
|
"tslib": "^2.5.0",
|
|
85
|
-
"viem": "npm:@aztec/viem@2.38.2"
|
|
85
|
+
"viem": "npm:@aztec/viem@2.38.2",
|
|
86
|
+
"zod": "^3.23.8"
|
|
86
87
|
},
|
|
87
88
|
"devDependencies": {
|
|
88
89
|
"@jest/globals": "^30.0.0",
|
package/src/archiver/archiver.ts
CHANGED
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
BlockTagTooOldError,
|
|
5
5
|
InboxContract,
|
|
6
6
|
type L1BlockId,
|
|
7
|
+
type L1ContractAddresses,
|
|
7
8
|
RollupContract,
|
|
8
9
|
type ViemPublicClient,
|
|
10
|
+
type ViemPublicDebugClient,
|
|
9
11
|
createEthereumChain,
|
|
10
12
|
} from '@aztec/ethereum';
|
|
11
13
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
@@ -77,14 +79,15 @@ import { type GetContractReturnType, type Hex, createPublicClient, fallback, htt
|
|
|
77
79
|
|
|
78
80
|
import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
|
|
79
81
|
import type { ArchiverConfig } from './config.js';
|
|
82
|
+
import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
|
|
83
|
+
import { ArchiverInstrumentation } from './instrumentation.js';
|
|
80
84
|
import {
|
|
81
85
|
retrieveBlocksFromRollup,
|
|
82
86
|
retrieveL1ToL2Message,
|
|
83
87
|
retrieveL1ToL2Messages,
|
|
84
88
|
retrievedBlockToPublishedL2Block,
|
|
85
|
-
} from './data_retrieval.js';
|
|
86
|
-
import {
|
|
87
|
-
import { ArchiverInstrumentation } from './instrumentation.js';
|
|
89
|
+
} from './l1/data_retrieval.js';
|
|
90
|
+
import { validateAndLogTraceAvailability } from './l1/validate_trace.js';
|
|
88
91
|
import type { InboxMessage } from './structs/inbox_message.js';
|
|
89
92
|
import type { PublishedL2Block } from './structs/published.js';
|
|
90
93
|
import { type ValidateBlockResult, validateBlockAttestations } from './validation.js';
|
|
@@ -107,6 +110,7 @@ function mapArchiverConfig(config: Partial<ArchiverConfig>) {
|
|
|
107
110
|
batchSize: config.archiverBatchSize,
|
|
108
111
|
skipValidateBlockAttestations: config.skipValidateBlockAttestations,
|
|
109
112
|
maxAllowedEthClientDriftSeconds: config.maxAllowedEthClientDriftSeconds,
|
|
113
|
+
ethereumAllowNoDebugHosts: config.ethereumAllowNoDebugHosts,
|
|
110
114
|
};
|
|
111
115
|
}
|
|
112
116
|
|
|
@@ -144,6 +148,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
144
148
|
/**
|
|
145
149
|
* Creates a new instance of the Archiver.
|
|
146
150
|
* @param publicClient - A client for interacting with the Ethereum node.
|
|
151
|
+
* @param debugClient - A client for interacting with the Ethereum node for debug/trace methods.
|
|
147
152
|
* @param rollupAddress - Ethereum address of the rollup contract.
|
|
148
153
|
* @param inboxAddress - Ethereum address of the inbox contract.
|
|
149
154
|
* @param registryAddress - Ethereum address of the registry contract.
|
|
@@ -153,13 +158,18 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
153
158
|
*/
|
|
154
159
|
constructor(
|
|
155
160
|
private readonly publicClient: ViemPublicClient,
|
|
156
|
-
private readonly
|
|
161
|
+
private readonly debugClient: ViemPublicDebugClient,
|
|
162
|
+
private readonly l1Addresses: Pick<
|
|
163
|
+
L1ContractAddresses,
|
|
164
|
+
'rollupAddress' | 'inboxAddress' | 'registryAddress' | 'governanceProposerAddress' | 'slashFactoryAddress'
|
|
165
|
+
> & { slashingProposerAddress: EthAddress },
|
|
157
166
|
readonly dataStore: ArchiverDataStore,
|
|
158
167
|
private config: {
|
|
159
168
|
pollingIntervalMs: number;
|
|
160
169
|
batchSize: number;
|
|
161
170
|
skipValidateBlockAttestations?: boolean;
|
|
162
171
|
maxAllowedEthClientDriftSeconds: number;
|
|
172
|
+
ethereumAllowNoDebugHosts?: boolean;
|
|
163
173
|
},
|
|
164
174
|
private readonly blobSinkClient: BlobSinkClientInterface,
|
|
165
175
|
private readonly epochCache: EpochCache,
|
|
@@ -207,14 +217,24 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
207
217
|
pollingInterval: config.viemPollingIntervalMS,
|
|
208
218
|
});
|
|
209
219
|
|
|
220
|
+
// Create debug client using debug RPC URLs if available, otherwise fall back to regular RPC URLs
|
|
221
|
+
const debugRpcUrls = config.l1DebugRpcUrls.length > 0 ? config.l1DebugRpcUrls : config.l1RpcUrls;
|
|
222
|
+
const debugClient = createPublicClient({
|
|
223
|
+
chain: chain.chainInfo,
|
|
224
|
+
transport: fallback(debugRpcUrls.map(url => http(url))),
|
|
225
|
+
pollingInterval: config.viemPollingIntervalMS,
|
|
226
|
+
}) as ViemPublicDebugClient;
|
|
227
|
+
|
|
210
228
|
const rollup = new RollupContract(publicClient, config.l1Contracts.rollupAddress);
|
|
211
229
|
|
|
212
|
-
const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, genesisArchiveRoot] =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
230
|
+
const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, genesisArchiveRoot, slashingProposerAddress] =
|
|
231
|
+
await Promise.all([
|
|
232
|
+
rollup.getL1StartBlock(),
|
|
233
|
+
rollup.getL1GenesisTime(),
|
|
234
|
+
rollup.getProofSubmissionEpochs(),
|
|
235
|
+
rollup.getGenesisArchiveTreeRoot(),
|
|
236
|
+
rollup.getSlashingProposerAddress(),
|
|
237
|
+
] as const);
|
|
218
238
|
|
|
219
239
|
const l1StartBlockHash = await publicClient
|
|
220
240
|
.getBlock({ blockNumber: l1StartBlock, includeTransactions: false })
|
|
@@ -234,7 +254,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
234
254
|
};
|
|
235
255
|
|
|
236
256
|
const opts = merge(
|
|
237
|
-
{
|
|
257
|
+
{
|
|
258
|
+
pollingIntervalMs: 10_000,
|
|
259
|
+
batchSize: 100,
|
|
260
|
+
maxAllowedEthClientDriftSeconds: 300,
|
|
261
|
+
ethereumAllowNoDebugHosts: false,
|
|
262
|
+
},
|
|
238
263
|
mapArchiverConfig(config),
|
|
239
264
|
);
|
|
240
265
|
|
|
@@ -243,7 +268,8 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
243
268
|
|
|
244
269
|
const archiver = new Archiver(
|
|
245
270
|
publicClient,
|
|
246
|
-
|
|
271
|
+
debugClient,
|
|
272
|
+
{ ...config.l1Contracts, slashingProposerAddress },
|
|
247
273
|
archiverStore,
|
|
248
274
|
opts,
|
|
249
275
|
deps.blobSinkClient,
|
|
@@ -272,6 +298,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
272
298
|
|
|
273
299
|
await this.blobSinkClient.testSources();
|
|
274
300
|
await this.testEthereumNodeSynced();
|
|
301
|
+
await validateAndLogTraceAvailability(this.debugClient, this.config.ethereumAllowNoDebugHosts ?? false);
|
|
275
302
|
|
|
276
303
|
// Log initial state for the archiver
|
|
277
304
|
const { l1StartBlock } = this.l1constants;
|
|
@@ -832,9 +859,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
832
859
|
const retrievedBlocks = await retrieveBlocksFromRollup(
|
|
833
860
|
this.rollup.getContract() as GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
|
|
834
861
|
this.publicClient,
|
|
862
|
+
this.debugClient,
|
|
835
863
|
this.blobSinkClient,
|
|
836
864
|
searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
|
|
837
865
|
searchEndBlock,
|
|
866
|
+
this.l1Addresses,
|
|
867
|
+
this.instrumentation,
|
|
838
868
|
this.log,
|
|
839
869
|
);
|
|
840
870
|
|
package/src/archiver/config.ts
CHANGED
|
@@ -55,6 +55,11 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
|
|
|
55
55
|
description: 'Maximum allowed drift in seconds between the Ethereum client and current time.',
|
|
56
56
|
...numberConfigHelper(300),
|
|
57
57
|
},
|
|
58
|
+
ethereumAllowNoDebugHosts: {
|
|
59
|
+
env: 'ETHEREUM_ALLOW_NO_DEBUG_HOSTS',
|
|
60
|
+
description: 'Whether to allow starting the archiver without debug/trace method support on Ethereum hosts',
|
|
61
|
+
...booleanConfigHelper(true),
|
|
62
|
+
},
|
|
58
63
|
...chainConfigMappings,
|
|
59
64
|
...l1ReaderConfigMappings,
|
|
60
65
|
viemPollingIntervalMS: {
|
|
@@ -34,6 +34,8 @@ export class ArchiverInstrumentation {
|
|
|
34
34
|
private syncDurationPerMessage: Histogram;
|
|
35
35
|
private syncMessageCount: UpDownCounter;
|
|
36
36
|
|
|
37
|
+
private blockProposalTxTargetCount: UpDownCounter;
|
|
38
|
+
|
|
37
39
|
private log = createLogger('archiver:instrumentation');
|
|
38
40
|
|
|
39
41
|
private constructor(
|
|
@@ -114,6 +116,11 @@ export class ArchiverInstrumentation {
|
|
|
114
116
|
valueType: ValueType.INT,
|
|
115
117
|
});
|
|
116
118
|
|
|
119
|
+
this.blockProposalTxTargetCount = meter.createUpDownCounter(Metrics.ARCHIVER_BLOCK_PROPOSAL_TX_TARGET_COUNT, {
|
|
120
|
+
description: 'Number of block proposals by tx target',
|
|
121
|
+
valueType: ValueType.INT,
|
|
122
|
+
});
|
|
123
|
+
|
|
117
124
|
this.dbMetrics = new LmdbMetrics(
|
|
118
125
|
meter,
|
|
119
126
|
{
|
|
@@ -184,4 +191,11 @@ export class ArchiverInstrumentation {
|
|
|
184
191
|
public updateL1BlockHeight(blockNumber: bigint) {
|
|
185
192
|
this.l1BlockHeight.record(Number(blockNumber));
|
|
186
193
|
}
|
|
194
|
+
|
|
195
|
+
public recordBlockProposalTxTarget(target: string, usedTrace: boolean) {
|
|
196
|
+
this.blockProposalTxTargetCount.add(1, {
|
|
197
|
+
[Attributes.L1_BLOCK_PROPOSAL_TX_TARGET]: target.toLowerCase(),
|
|
198
|
+
[Attributes.L1_BLOCK_PROPOSAL_USED_TRACE]: usedTrace,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
187
201
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Archiver L1 Data Retrieval
|
|
2
|
+
|
|
3
|
+
Modules and classes to handle data retrieval from L1 for the archiver.
|
|
4
|
+
|
|
5
|
+
## Calldata Retriever
|
|
6
|
+
|
|
7
|
+
The sequencer publisher bundles multiple operations into a single multicall3 transaction for gas
|
|
8
|
+
efficiency. A typical transaction includes:
|
|
9
|
+
|
|
10
|
+
1. Attestation invalidations (if needed): `invalidateBadAttestation`, `invalidateInsufficientAttestations`
|
|
11
|
+
2. Block proposal: `propose` (exactly one per transaction to the rollup contract)
|
|
12
|
+
3. Governance and slashing (if needed): votes, payload creation/execution
|
|
13
|
+
|
|
14
|
+
The archiver needs to extract the `propose` calldata from these bundled transactions to reconstruct
|
|
15
|
+
L2 blocks. This class needs to handle scenarios where the transaction was submitted via multicall3,
|
|
16
|
+
as well as alternative ways for submitting the `propose` call that other clients might use.
|
|
17
|
+
|
|
18
|
+
### Multicall3 Validation and Decoding
|
|
19
|
+
|
|
20
|
+
First attempt to decode the transaction as a multicall3 `aggregate3` call with validation:
|
|
21
|
+
|
|
22
|
+
- Check if transaction is to multicall3 address (`0xcA11bde05977b3631167028862bE2a173976CA11`)
|
|
23
|
+
- Decode as `aggregate3(Call3[] calldata calls)`
|
|
24
|
+
- Allow calls to known addresses and methods (rollup, governance, slashing contracts, etc.)
|
|
25
|
+
- Find the single `propose` call to the rollup contract
|
|
26
|
+
- Verify exactly one `propose` call exists
|
|
27
|
+
- Extract and return the propose calldata
|
|
28
|
+
|
|
29
|
+
This step handles the common case efficiently without requiring expensive trace or debug RPC calls.
|
|
30
|
+
Any validation failure triggers fallback to the next step.
|
|
31
|
+
|
|
32
|
+
### Direct Propose Call
|
|
33
|
+
|
|
34
|
+
Second attempt to decode the transaction as a direct `propose` call to the rollup contract:
|
|
35
|
+
|
|
36
|
+
- Check if transaction is to the rollup address
|
|
37
|
+
- Decode as `propose` function call
|
|
38
|
+
- Verify the function is indeed `propose`
|
|
39
|
+
- Return the transaction input as the propose calldata
|
|
40
|
+
|
|
41
|
+
This handles scenarios where clients submit transactions directly to the rollup contract without
|
|
42
|
+
using multicall3 for bundling. Any validation failure triggers fallback to the next step.
|
|
43
|
+
|
|
44
|
+
### Spire Proposer Call
|
|
45
|
+
|
|
46
|
+
Given existing attempts to route the call via the Spire proposer, we also check if the tx is `to` the
|
|
47
|
+
proposer known address, and if so, we try decoding it as either a multicall3 or a direct call to the
|
|
48
|
+
rollup contract.
|
|
49
|
+
|
|
50
|
+
Similar as with the multicall3 check, we check that there are no other calls in the Spire proposer, so
|
|
51
|
+
we are absolutely sure that the only call is the successful one to the rollup. Any extraneous call would
|
|
52
|
+
imply an unexpected path to calling `propose` in the rollup contract, and since we cannot verify if the
|
|
53
|
+
calldata arguments we extracted are the correct ones (see the section below), we cannot know for sure which
|
|
54
|
+
one is the call that succeeded, so we don't know which calldata to process.
|
|
55
|
+
|
|
56
|
+
Furthermore, since the Spire proposer is upgradeable, we check if the implementation has not changed in
|
|
57
|
+
order to decode. As usual, any validation failure triggers fallback to the next step.
|
|
58
|
+
|
|
59
|
+
### Verifying Multicall3 Arguments
|
|
60
|
+
|
|
61
|
+
**This is NOT implemented for simplicity's sake**
|
|
62
|
+
|
|
63
|
+
If the checks above don't hold, such as when there are multiple calls to `propose`, then we cannot
|
|
64
|
+
reliably extract the `propose` calldata from the multicall3 arguments alone. We can try a best-effort
|
|
65
|
+
where we try all `propose` calls we see and validate them against on-chain data. Note that we can use these
|
|
66
|
+
same strategies if we were to obtain the calldata from another source.
|
|
67
|
+
|
|
68
|
+
#### TempBlockLog Verification
|
|
69
|
+
|
|
70
|
+
Read the stored `TempBlockLog` for the L2 block number from L1 and verify it matches our decoded header hash,
|
|
71
|
+
since the `TempBlockLog` stores the hash of the proposed block header, the payload commitment, and the attestations.
|
|
72
|
+
|
|
73
|
+
However, `TempBlockLog` is only stored temporarily and deleted after proven, so this method only works for recent
|
|
74
|
+
blocks, not for historical data syncing.
|
|
75
|
+
|
|
76
|
+
#### Archive Verification
|
|
77
|
+
|
|
78
|
+
Verify that the archive root in the decoded propose is correct with regard to the block header. This requires
|
|
79
|
+
hashing the block header we have retrieved, inserting it into the archive tree, and checking the resulting root
|
|
80
|
+
against the one we got from L1.
|
|
81
|
+
|
|
82
|
+
However, this requires that the archive keeps a reference to world-state, which is not the case in the current
|
|
83
|
+
system.
|
|
84
|
+
|
|
85
|
+
#### Emit Commitments in Rollup Contract
|
|
86
|
+
|
|
87
|
+
Modify rollup contract to emit commitments to the block header in the `L2BlockProposed` event, allowing us to easily
|
|
88
|
+
verify the calldata we obtained vs the emitted event.
|
|
89
|
+
|
|
90
|
+
However, modifying the rollup contract is out of scope for this change. But we can implement this approach in `v2`.
|
|
91
|
+
|
|
92
|
+
### Debug and Trace Transaction Fallback
|
|
93
|
+
|
|
94
|
+
Last, we use L1 node's trace/debug RPC methods to definitively identify the one successful `propose` call within the tx.
|
|
95
|
+
We can then extract the exact calldata that hit the `propose` function in the rollup contract.
|
|
96
|
+
|
|
97
|
+
This approach requires access to a debug-enabled L1 node, which may be more resource-intensive, so we only
|
|
98
|
+
use it as a fallback when the first step fails, which should be rare in practice.
|