@aztec/archiver 0.0.0-test.1 → 0.0.1-commit.24de95ac
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/README.md +27 -6
- package/dest/archiver/archiver.d.ts +126 -46
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +683 -261
- package/dest/archiver/archiver_store.d.ts +84 -49
- package/dest/archiver/archiver_store.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.js +707 -213
- package/dest/archiver/config.d.ts +4 -20
- package/dest/archiver/config.d.ts.map +1 -1
- package/dest/archiver/config.js +16 -12
- package/dest/archiver/data_retrieval.d.ts +25 -20
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +147 -68
- package/dest/archiver/errors.d.ts +8 -0
- package/dest/archiver/errors.d.ts.map +1 -1
- package/dest/archiver/errors.js +12 -0
- package/dest/archiver/index.d.ts +2 -3
- package/dest/archiver/index.d.ts.map +1 -1
- package/dest/archiver/index.js +1 -2
- package/dest/archiver/instrumentation.d.ts +9 -3
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/instrumentation.js +58 -17
- package/dest/archiver/kv_archiver_store/block_store.d.ts +47 -10
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +216 -63
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -2
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +12 -18
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +10 -7
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +30 -16
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +49 -34
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +88 -46
- package/dest/archiver/kv_archiver_store/log_store.d.ts +1 -1
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +18 -46
- package/dest/archiver/kv_archiver_store/message_store.d.ts +22 -16
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +150 -48
- package/dest/archiver/structs/inbox_message.d.ts +15 -0
- package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
- package/dest/archiver/structs/inbox_message.js +38 -0
- package/dest/archiver/structs/published.d.ts +1 -10
- package/dest/archiver/structs/published.d.ts.map +1 -1
- package/dest/archiver/structs/published.js +1 -1
- package/dest/archiver/validation.d.ts +11 -0
- package/dest/archiver/validation.d.ts.map +1 -0
- package/dest/archiver/validation.js +90 -0
- package/dest/factory.d.ts +7 -12
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +18 -49
- package/dest/rpc/index.d.ts +1 -2
- package/dest/rpc/index.d.ts.map +1 -1
- package/dest/rpc/index.js +1 -4
- package/dest/test/mock_archiver.d.ts +1 -1
- package/dest/test/mock_l1_to_l2_message_source.d.ts +4 -2
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
- package/dest/test/mock_l1_to_l2_message_source.js +14 -1
- package/dest/test/mock_l2_block_source.d.ts +32 -5
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +118 -7
- package/dest/test/mock_structs.d.ts +9 -0
- package/dest/test/mock_structs.d.ts.map +1 -0
- package/dest/test/mock_structs.js +37 -0
- package/package.json +25 -27
- package/src/archiver/archiver.ts +858 -317
- package/src/archiver/archiver_store.ts +97 -55
- package/src/archiver/archiver_store_test_suite.ts +663 -210
- package/src/archiver/config.ts +23 -41
- package/src/archiver/data_retrieval.ts +215 -92
- package/src/archiver/errors.ts +21 -0
- package/src/archiver/index.ts +2 -3
- package/src/archiver/instrumentation.ts +75 -20
- package/src/archiver/kv_archiver_store/block_store.ts +270 -72
- package/src/archiver/kv_archiver_store/contract_class_store.ts +13 -23
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +35 -27
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +127 -63
- package/src/archiver/kv_archiver_store/log_store.ts +24 -62
- package/src/archiver/kv_archiver_store/message_store.ts +209 -53
- package/src/archiver/structs/inbox_message.ts +41 -0
- package/src/archiver/structs/published.ts +1 -11
- package/src/archiver/validation.ts +99 -0
- package/src/factory.ts +24 -66
- package/src/rpc/index.ts +1 -5
- package/src/test/mock_archiver.ts +1 -1
- package/src/test/mock_l1_to_l2_message_source.ts +14 -3
- package/src/test/mock_l2_block_source.ts +152 -8
- package/src/test/mock_structs.ts +49 -0
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +0 -12
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +0 -1
- package/dest/archiver/kv_archiver_store/nullifier_store.js +0 -73
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +0 -175
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +0 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.js +0 -636
- package/src/archiver/kv_archiver_store/nullifier_store.ts +0 -97
- package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
- package/src/archiver/memory_archiver_store/memory_archiver_store.ts +0 -801
package/src/archiver/archiver.ts
CHANGED
|
@@ -1,42 +1,54 @@
|
|
|
1
1
|
import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
|
|
2
|
-
import {
|
|
2
|
+
import { EpochCache } from '@aztec/epoch-cache';
|
|
3
|
+
import {
|
|
4
|
+
BlockTagTooOldError,
|
|
5
|
+
InboxContract,
|
|
6
|
+
type L1BlockId,
|
|
7
|
+
RollupContract,
|
|
8
|
+
type ViemPublicClient,
|
|
9
|
+
createEthereumChain,
|
|
10
|
+
} from '@aztec/ethereum';
|
|
11
|
+
import { maxBigint } from '@aztec/foundation/bigint';
|
|
12
|
+
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
13
|
+
import { merge, pick } from '@aztec/foundation/collection';
|
|
3
14
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
15
|
import { Fr } from '@aztec/foundation/fields';
|
|
5
16
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
17
|
+
import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
|
|
6
18
|
import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
|
|
19
|
+
import { sleep } from '@aztec/foundation/sleep';
|
|
7
20
|
import { count } from '@aztec/foundation/string';
|
|
8
|
-
import { elapsed } from '@aztec/foundation/timer';
|
|
9
|
-
import {
|
|
21
|
+
import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
|
|
22
|
+
import type { CustomRange } from '@aztec/kv-store';
|
|
23
|
+
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
10
24
|
import {
|
|
11
|
-
|
|
25
|
+
ContractClassPublishedEvent,
|
|
12
26
|
PrivateFunctionBroadcastedEvent,
|
|
13
|
-
|
|
14
|
-
} from '@aztec/protocol-contracts/class-
|
|
27
|
+
UtilityFunctionBroadcastedEvent,
|
|
28
|
+
} from '@aztec/protocol-contracts/class-registry';
|
|
15
29
|
import {
|
|
16
|
-
|
|
30
|
+
ContractInstancePublishedEvent,
|
|
17
31
|
ContractInstanceUpdatedEvent,
|
|
18
|
-
} from '@aztec/protocol-contracts/instance-
|
|
32
|
+
} from '@aztec/protocol-contracts/instance-registry';
|
|
19
33
|
import type { FunctionSelector } from '@aztec/stdlib/abi';
|
|
20
34
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
21
35
|
import {
|
|
22
|
-
type
|
|
36
|
+
type ArchiverEmitter,
|
|
23
37
|
type L2Block,
|
|
24
38
|
type L2BlockId,
|
|
25
39
|
type L2BlockSource,
|
|
26
40
|
L2BlockSourceEvents,
|
|
27
41
|
type L2Tips,
|
|
28
|
-
type NullifierWithBlockSource,
|
|
29
42
|
} from '@aztec/stdlib/block';
|
|
30
43
|
import {
|
|
31
44
|
type ContractClassPublic,
|
|
32
45
|
type ContractDataSource,
|
|
33
46
|
type ContractInstanceWithAddress,
|
|
34
47
|
type ExecutablePrivateFunctionWithMembershipProof,
|
|
35
|
-
type
|
|
36
|
-
type UnconstrainedFunctionWithMembershipProof,
|
|
48
|
+
type UtilityFunctionWithMembershipProof,
|
|
37
49
|
computePublicBytecodeCommitment,
|
|
38
50
|
isValidPrivateFunctionMembershipProof,
|
|
39
|
-
|
|
51
|
+
isValidUtilityFunctionMembershipProof,
|
|
40
52
|
} from '@aztec/stdlib/contract';
|
|
41
53
|
import {
|
|
42
54
|
type L1RollupConstants,
|
|
@@ -49,49 +61,74 @@ import {
|
|
|
49
61
|
import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
|
|
50
62
|
import type { L2LogsSource } from '@aztec/stdlib/interfaces/server';
|
|
51
63
|
import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
52
|
-
import type {
|
|
53
|
-
import { type BlockHeader,
|
|
54
|
-
import
|
|
64
|
+
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
65
|
+
import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
|
|
66
|
+
import type { UInt64 } from '@aztec/stdlib/types';
|
|
67
|
+
import {
|
|
68
|
+
Attributes,
|
|
69
|
+
type TelemetryClient,
|
|
70
|
+
type Traceable,
|
|
71
|
+
type Tracer,
|
|
72
|
+
getTelemetryClient,
|
|
73
|
+
trackSpan,
|
|
74
|
+
} from '@aztec/telemetry-client';
|
|
55
75
|
|
|
56
76
|
import { EventEmitter } from 'events';
|
|
57
77
|
import groupBy from 'lodash.groupby';
|
|
58
|
-
import { type GetContractReturnType, createPublicClient, fallback,
|
|
78
|
+
import { type GetContractReturnType, createPublicClient, fallback, http } from 'viem';
|
|
59
79
|
|
|
60
80
|
import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
|
|
61
81
|
import type { ArchiverConfig } from './config.js';
|
|
62
|
-
import {
|
|
63
|
-
|
|
82
|
+
import {
|
|
83
|
+
retrieveBlocksFromRollup,
|
|
84
|
+
retrieveL1ToL2Message,
|
|
85
|
+
retrieveL1ToL2Messages,
|
|
86
|
+
retrievedBlockToPublishedL2Block,
|
|
87
|
+
} from './data_retrieval.js';
|
|
88
|
+
import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
|
|
64
89
|
import { ArchiverInstrumentation } from './instrumentation.js';
|
|
65
|
-
import type {
|
|
66
|
-
import type {
|
|
90
|
+
import type { InboxMessage } from './structs/inbox_message.js';
|
|
91
|
+
import type { PublishedL2Block } from './structs/published.js';
|
|
92
|
+
import { type ValidateBlockResult, validateBlockAttestations } from './validation.js';
|
|
67
93
|
|
|
68
94
|
/**
|
|
69
95
|
* Helper interface to combine all sources this archiver implementation provides.
|
|
70
96
|
*/
|
|
71
|
-
export type ArchiveSource = L2BlockSource &
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
export type ArchiveSource = L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource;
|
|
98
|
+
|
|
99
|
+
export type ArchiverDeps = {
|
|
100
|
+
telemetry?: TelemetryClient;
|
|
101
|
+
blobSinkClient: BlobSinkClientInterface;
|
|
102
|
+
epochCache?: EpochCache;
|
|
103
|
+
dateProvider?: DateProvider;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function mapArchiverConfig(config: Partial<ArchiverConfig>) {
|
|
107
|
+
return {
|
|
108
|
+
pollingIntervalMs: config.archiverPollingIntervalMS,
|
|
109
|
+
batchSize: config.archiverBatchSize,
|
|
110
|
+
skipValidateBlockAttestations: config.skipValidateBlockAttestations,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
76
113
|
|
|
77
114
|
/**
|
|
78
115
|
* Pulls L2 blocks in a non-blocking manner and provides interface for their retrieval.
|
|
79
116
|
* Responsible for handling robust L1 polling so that other components do not need to
|
|
80
117
|
* concern themselves with it.
|
|
81
118
|
*/
|
|
82
|
-
export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
83
|
-
/**
|
|
84
|
-
* A promise in which we will be continually fetching new L2 blocks.
|
|
85
|
-
*/
|
|
119
|
+
export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implements ArchiveSource, Traceable {
|
|
120
|
+
/** A loop in which we will be continually fetching new L2 blocks. */
|
|
86
121
|
private runningPromise?: RunningPromise;
|
|
87
122
|
|
|
88
|
-
private rollup:
|
|
89
|
-
private inbox:
|
|
123
|
+
private rollup: RollupContract;
|
|
124
|
+
private inbox: InboxContract;
|
|
90
125
|
|
|
91
126
|
private store: ArchiverStoreHelper;
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
private l1BlockNumber: bigint | undefined;
|
|
129
|
+
private l1Timestamp: bigint | undefined;
|
|
130
|
+
private initialSyncComplete: boolean = false;
|
|
131
|
+
private initialSyncPromise: PromiseWithResolvers<void>;
|
|
95
132
|
|
|
96
133
|
public readonly tracer: Tracer;
|
|
97
134
|
|
|
@@ -109,10 +146,11 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
109
146
|
private readonly publicClient: ViemPublicClient,
|
|
110
147
|
private readonly l1Addresses: { rollupAddress: EthAddress; inboxAddress: EthAddress; registryAddress: EthAddress },
|
|
111
148
|
readonly dataStore: ArchiverDataStore,
|
|
112
|
-
private
|
|
149
|
+
private config: { pollingIntervalMs: number; batchSize: number; skipValidateBlockAttestations?: boolean },
|
|
113
150
|
private readonly blobSinkClient: BlobSinkClientInterface,
|
|
151
|
+
private readonly epochCache: EpochCache,
|
|
114
152
|
private readonly instrumentation: ArchiverInstrumentation,
|
|
115
|
-
private readonly l1constants: L1RollupConstants,
|
|
153
|
+
private readonly l1constants: L1RollupConstants & { l1StartBlockHash: Buffer32; genesisArchiveRoot: Fr },
|
|
116
154
|
private readonly log: Logger = createLogger('archiver'),
|
|
117
155
|
) {
|
|
118
156
|
super();
|
|
@@ -120,17 +158,9 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
120
158
|
this.tracer = instrumentation.tracer;
|
|
121
159
|
this.store = new ArchiverStoreHelper(dataStore);
|
|
122
160
|
|
|
123
|
-
this.rollup =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
client: publicClient,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
this.inbox = getContract({
|
|
130
|
-
address: l1Addresses.inboxAddress.toString(),
|
|
131
|
-
abi: InboxAbi,
|
|
132
|
-
client: publicClient,
|
|
133
|
-
});
|
|
161
|
+
this.rollup = new RollupContract(publicClient, l1Addresses.rollupAddress);
|
|
162
|
+
this.inbox = new InboxContract(publicClient, l1Addresses.inboxAddress);
|
|
163
|
+
this.initialSyncPromise = promiseWithResolvers();
|
|
134
164
|
}
|
|
135
165
|
|
|
136
166
|
/**
|
|
@@ -143,7 +173,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
143
173
|
public static async createAndSync(
|
|
144
174
|
config: ArchiverConfig,
|
|
145
175
|
archiverStore: ArchiverDataStore,
|
|
146
|
-
deps:
|
|
176
|
+
deps: ArchiverDeps,
|
|
147
177
|
blockUntilSynced = true,
|
|
148
178
|
): Promise<Archiver> {
|
|
149
179
|
const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
@@ -153,35 +183,56 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
153
183
|
pollingInterval: config.viemPollingIntervalMS,
|
|
154
184
|
});
|
|
155
185
|
|
|
156
|
-
const rollup =
|
|
157
|
-
address: config.l1Contracts.rollupAddress.toString(),
|
|
158
|
-
abi: RollupAbi,
|
|
159
|
-
client: publicClient,
|
|
160
|
-
});
|
|
186
|
+
const rollup = new RollupContract(publicClient, config.l1Contracts.rollupAddress);
|
|
161
187
|
|
|
162
|
-
const [l1StartBlock, l1GenesisTime] = await Promise.all([
|
|
163
|
-
rollup.
|
|
164
|
-
rollup.
|
|
188
|
+
const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, genesisArchiveRoot] = await Promise.all([
|
|
189
|
+
rollup.getL1StartBlock(),
|
|
190
|
+
rollup.getL1GenesisTime(),
|
|
191
|
+
rollup.getProofSubmissionEpochs(),
|
|
192
|
+
rollup.getGenesisArchiveTreeRoot(),
|
|
165
193
|
] as const);
|
|
166
194
|
|
|
195
|
+
const l1StartBlockHash = await publicClient
|
|
196
|
+
.getBlock({ blockNumber: l1StartBlock, includeTransactions: false })
|
|
197
|
+
.then(block => Buffer32.fromString(block.hash));
|
|
198
|
+
|
|
167
199
|
const { aztecEpochDuration: epochDuration, aztecSlotDuration: slotDuration, ethereumSlotDuration } = config;
|
|
168
200
|
|
|
201
|
+
const l1Constants = {
|
|
202
|
+
l1StartBlockHash,
|
|
203
|
+
l1StartBlock,
|
|
204
|
+
l1GenesisTime,
|
|
205
|
+
epochDuration,
|
|
206
|
+
slotDuration,
|
|
207
|
+
ethereumSlotDuration,
|
|
208
|
+
proofSubmissionEpochs: Number(proofSubmissionEpochs),
|
|
209
|
+
genesisArchiveRoot: Fr.fromHexString(genesisArchiveRoot),
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const opts = merge({ pollingIntervalMs: 10_000, batchSize: 100 }, mapArchiverConfig(config));
|
|
213
|
+
|
|
214
|
+
const epochCache = deps.epochCache ?? (await EpochCache.create(config.l1Contracts.rollupAddress, config, deps));
|
|
215
|
+
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
216
|
+
|
|
169
217
|
const archiver = new Archiver(
|
|
170
218
|
publicClient,
|
|
171
219
|
config.l1Contracts,
|
|
172
220
|
archiverStore,
|
|
173
|
-
|
|
174
|
-
pollingIntervalMs: config.archiverPollingIntervalMS ?? 10_000,
|
|
175
|
-
batchSize: config.archiverBatchSize ?? 100,
|
|
176
|
-
},
|
|
221
|
+
opts,
|
|
177
222
|
deps.blobSinkClient,
|
|
178
|
-
|
|
179
|
-
|
|
223
|
+
epochCache,
|
|
224
|
+
await ArchiverInstrumentation.new(telemetry, () => archiverStore.estimateSize()),
|
|
225
|
+
l1Constants,
|
|
180
226
|
);
|
|
181
227
|
await archiver.start(blockUntilSynced);
|
|
182
228
|
return archiver;
|
|
183
229
|
}
|
|
184
230
|
|
|
231
|
+
/** Updates archiver config */
|
|
232
|
+
public updateConfig(newConfig: Partial<ArchiverConfig>) {
|
|
233
|
+
this.config = merge(this.config, mapArchiverConfig(newConfig));
|
|
234
|
+
}
|
|
235
|
+
|
|
185
236
|
/**
|
|
186
237
|
* Starts sync process.
|
|
187
238
|
* @param blockUntilSynced - If true, blocks until the archiver has fully synced.
|
|
@@ -191,8 +242,13 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
191
242
|
throw new Error('Archiver is already running');
|
|
192
243
|
}
|
|
193
244
|
|
|
245
|
+
await this.blobSinkClient.testSources();
|
|
246
|
+
|
|
194
247
|
if (blockUntilSynced) {
|
|
195
|
-
await this.syncSafe(
|
|
248
|
+
while (!(await this.syncSafe(true))) {
|
|
249
|
+
this.log.info(`Retrying initial archiver sync in ${this.config.pollingIntervalMs}ms`);
|
|
250
|
+
await sleep(this.config.pollingIntervalMs);
|
|
251
|
+
}
|
|
196
252
|
}
|
|
197
253
|
|
|
198
254
|
this.runningPromise = new RunningPromise(
|
|
@@ -210,11 +266,30 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
210
266
|
this.runningPromise.start();
|
|
211
267
|
}
|
|
212
268
|
|
|
269
|
+
public syncImmediate() {
|
|
270
|
+
if (!this.runningPromise) {
|
|
271
|
+
throw new Error('Archiver is not running');
|
|
272
|
+
}
|
|
273
|
+
return this.runningPromise.trigger();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public waitForInitialSync() {
|
|
277
|
+
return this.initialSyncPromise.promise;
|
|
278
|
+
}
|
|
279
|
+
|
|
213
280
|
private async syncSafe(initialRun: boolean) {
|
|
214
281
|
try {
|
|
215
282
|
await this.sync(initialRun);
|
|
283
|
+
return true;
|
|
216
284
|
} catch (error) {
|
|
217
|
-
|
|
285
|
+
if (error instanceof NoBlobBodiesFoundError) {
|
|
286
|
+
this.log.error(`Error syncing archiver: ${error.message}`);
|
|
287
|
+
} else if (error instanceof BlockTagTooOldError) {
|
|
288
|
+
this.log.warn(`Re-running archiver sync: ${error.message}`);
|
|
289
|
+
} else {
|
|
290
|
+
this.log.error('Error during archiver sync', error);
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
218
293
|
}
|
|
219
294
|
}
|
|
220
295
|
|
|
@@ -235,16 +310,21 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
235
310
|
*
|
|
236
311
|
* This code does not handle reorgs.
|
|
237
312
|
*/
|
|
238
|
-
const { l1StartBlock } = this.l1constants;
|
|
239
|
-
const {
|
|
240
|
-
|
|
313
|
+
const { l1StartBlock, l1StartBlockHash } = this.l1constants;
|
|
314
|
+
const {
|
|
315
|
+
blocksSynchedTo = l1StartBlock,
|
|
316
|
+
messagesSynchedTo = { l1BlockNumber: l1StartBlock, l1BlockHash: l1StartBlockHash },
|
|
317
|
+
} = await this.store.getSynchPoint();
|
|
318
|
+
|
|
319
|
+
const currentL1Block = await this.publicClient.getBlock({ includeTransactions: false });
|
|
320
|
+
const currentL1BlockNumber = currentL1Block.number;
|
|
321
|
+
const currentL1BlockHash = Buffer32.fromString(currentL1Block.hash);
|
|
241
322
|
|
|
242
323
|
if (initialRun) {
|
|
243
324
|
this.log.info(
|
|
244
|
-
`Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
)} to current L1 block ${currentL1BlockNumber}`,
|
|
325
|
+
`Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo}` +
|
|
326
|
+
` to current L1 block ${currentL1BlockNumber} with hash ${currentL1BlockHash.toString()}`,
|
|
327
|
+
{ blocksSynchedTo, messagesSynchedTo },
|
|
248
328
|
);
|
|
249
329
|
}
|
|
250
330
|
|
|
@@ -268,28 +348,49 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
268
348
|
*/
|
|
269
349
|
|
|
270
350
|
// ********** Events that are processed per L1 block **********
|
|
271
|
-
await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber);
|
|
351
|
+
await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber, currentL1BlockHash);
|
|
272
352
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
this.l1Timestamp
|
|
276
|
-
|
|
277
|
-
|
|
353
|
+
// Get L1 timestamp for the current block
|
|
354
|
+
const currentL1Timestamp =
|
|
355
|
+
!this.l1Timestamp || !this.l1BlockNumber || this.l1BlockNumber !== currentL1BlockNumber
|
|
356
|
+
? (await this.publicClient.getBlock({ blockNumber: currentL1BlockNumber })).timestamp
|
|
357
|
+
: this.l1Timestamp;
|
|
278
358
|
|
|
279
359
|
// ********** Events that are processed per L2 block **********
|
|
280
360
|
if (currentL1BlockNumber > blocksSynchedTo) {
|
|
281
|
-
// First we retrieve new L2 blocks
|
|
282
|
-
|
|
283
|
-
|
|
361
|
+
// First we retrieve new L2 blocks and store them in the DB. This will also update the
|
|
362
|
+
// pending chain validation status, proven block number, and synched L1 block number.
|
|
363
|
+
const rollupStatus = await this.handleL2blocks(blocksSynchedTo, currentL1BlockNumber);
|
|
364
|
+
// Then we prune the current epoch if it'd reorg on next submission.
|
|
284
365
|
// Note that we don't do this before retrieving L2 blocks because we may need to retrieve
|
|
285
366
|
// blocks from more than 2 epochs ago, so we want to make sure we have the latest view of
|
|
286
367
|
// the chain locally before we start unwinding stuff. This can be optimized by figuring out
|
|
287
368
|
// up to which point we're pruning, and then requesting L2 blocks up to that point only.
|
|
288
|
-
await this.handleEpochPrune(
|
|
369
|
+
const { rollupCanPrune } = await this.handleEpochPrune(
|
|
370
|
+
rollupStatus.provenBlockNumber,
|
|
371
|
+
currentL1BlockNumber,
|
|
372
|
+
currentL1Timestamp,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// And lastly we check if we are missing any L2 blocks behind us due to a possible L1 reorg.
|
|
376
|
+
// We only do this if rollup cant prune on the next submission. Otherwise we will end up
|
|
377
|
+
// re-syncing the blocks we have just unwound above. We also dont do this if the last block is invalid,
|
|
378
|
+
// since the archiver will rightfully refuse to sync up to it.
|
|
379
|
+
if (!rollupCanPrune && rollupStatus.validationResult?.valid) {
|
|
380
|
+
await this.checkForNewBlocksBeforeL1SyncPoint(rollupStatus, blocksSynchedTo, currentL1BlockNumber);
|
|
381
|
+
}
|
|
289
382
|
|
|
290
383
|
this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
|
|
291
384
|
}
|
|
292
385
|
|
|
386
|
+
// After syncing has completed, update the current l1 block number and timestamp,
|
|
387
|
+
// otherwise we risk announcing to the world that we've synced to a given point,
|
|
388
|
+
// but the corresponding blocks have not been processed (see #12631).
|
|
389
|
+
this.l1Timestamp = currentL1Timestamp;
|
|
390
|
+
this.l1BlockNumber = currentL1BlockNumber;
|
|
391
|
+
this.initialSyncComplete = true;
|
|
392
|
+
this.initialSyncPromise.resolve();
|
|
393
|
+
|
|
293
394
|
if (initialRun) {
|
|
294
395
|
this.log.info(`Initial archiver sync to L1 block ${currentL1BlockNumber} complete.`, {
|
|
295
396
|
l1BlockNumber: currentL1BlockNumber,
|
|
@@ -299,32 +400,51 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
299
400
|
}
|
|
300
401
|
}
|
|
301
402
|
|
|
302
|
-
/** Queries the rollup contract on whether a prune can be executed on the
|
|
303
|
-
private async canPrune(currentL1BlockNumber: bigint) {
|
|
304
|
-
const time = (
|
|
305
|
-
|
|
403
|
+
/** Queries the rollup contract on whether a prune can be executed on the immediate next L1 block. */
|
|
404
|
+
private async canPrune(currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
|
|
405
|
+
const time = (currentL1Timestamp ?? 0n) + BigInt(this.l1constants.ethereumSlotDuration);
|
|
406
|
+
const result = await this.rollup.canPruneAtTime(time, { blockNumber: currentL1BlockNumber });
|
|
407
|
+
if (result) {
|
|
408
|
+
this.log.debug(`Rollup contract allows pruning at L1 block ${currentL1BlockNumber} time ${time}`, {
|
|
409
|
+
currentL1Timestamp,
|
|
410
|
+
pruneTime: time,
|
|
411
|
+
currentL1BlockNumber,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
return result;
|
|
306
415
|
}
|
|
307
416
|
|
|
308
417
|
/** Checks if there'd be a reorg for the next block submission and start pruning now. */
|
|
309
|
-
private async handleEpochPrune(provenBlockNumber:
|
|
310
|
-
const
|
|
311
|
-
const
|
|
418
|
+
private async handleEpochPrune(provenBlockNumber: number, currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
|
|
419
|
+
const rollupCanPrune = await this.canPrune(currentL1BlockNumber, currentL1Timestamp);
|
|
420
|
+
const localPendingBlockNumber = await this.getBlockNumber();
|
|
421
|
+
const canPrune = localPendingBlockNumber > provenBlockNumber && rollupCanPrune;
|
|
312
422
|
|
|
313
423
|
if (canPrune) {
|
|
314
|
-
const
|
|
315
|
-
const
|
|
424
|
+
const timer = new Timer();
|
|
425
|
+
const pruneFrom = provenBlockNumber + 1;
|
|
426
|
+
|
|
427
|
+
const header = await this.getBlockHeader(Number(pruneFrom));
|
|
428
|
+
if (header === undefined) {
|
|
429
|
+
throw new Error(`Missing block header ${pruneFrom}`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const pruneFromSlotNumber = header.globalVariables.slotNumber.toBigInt();
|
|
433
|
+
const pruneFromEpochNumber = getEpochAtSlot(pruneFromSlotNumber, this.l1constants);
|
|
434
|
+
|
|
435
|
+
const blocksToUnwind = localPendingBlockNumber - provenBlockNumber;
|
|
436
|
+
|
|
437
|
+
const blocks = await this.getBlocks(Number(provenBlockNumber) + 1, Number(blocksToUnwind));
|
|
316
438
|
|
|
317
439
|
// Emit an event for listening services to react to the chain prune
|
|
318
440
|
this.emit(L2BlockSourceEvents.L2PruneDetected, {
|
|
319
441
|
type: L2BlockSourceEvents.L2PruneDetected,
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
epochNumber: localPendingEpochNumber,
|
|
442
|
+
epochNumber: pruneFromEpochNumber,
|
|
443
|
+
blocks,
|
|
323
444
|
});
|
|
324
445
|
|
|
325
|
-
const blocksToUnwind = localPendingBlockNumber - provenBlockNumber;
|
|
326
446
|
this.log.debug(
|
|
327
|
-
`L2 prune from ${provenBlockNumber +
|
|
447
|
+
`L2 prune from ${provenBlockNumber + 1} to ${localPendingBlockNumber} will occur on next block submission.`,
|
|
328
448
|
);
|
|
329
449
|
await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
|
|
330
450
|
this.log.warn(
|
|
@@ -332,11 +452,13 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
332
452
|
`to ${provenBlockNumber} due to predicted reorg at L1 block ${currentL1BlockNumber}. ` +
|
|
333
453
|
`Updated L2 latest block is ${await this.getBlockNumber()}.`,
|
|
334
454
|
);
|
|
335
|
-
this.instrumentation.processPrune();
|
|
455
|
+
this.instrumentation.processPrune(timer.ms());
|
|
336
456
|
// TODO(palla/reorg): Do we need to set the block synched L1 block number here?
|
|
337
457
|
// Seems like the next iteration should handle this.
|
|
338
458
|
// await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
339
459
|
}
|
|
460
|
+
|
|
461
|
+
return { rollupCanPrune };
|
|
340
462
|
}
|
|
341
463
|
|
|
342
464
|
private nextRange(end: bigint, limit: bigint): [bigint, bigint] {
|
|
@@ -349,49 +471,196 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
349
471
|
return [nextStart, nextEnd];
|
|
350
472
|
}
|
|
351
473
|
|
|
352
|
-
private async handleL1ToL2Messages(
|
|
353
|
-
|
|
354
|
-
|
|
474
|
+
private async handleL1ToL2Messages(
|
|
475
|
+
messagesSyncPoint: L1BlockId,
|
|
476
|
+
currentL1BlockNumber: bigint,
|
|
477
|
+
_currentL1BlockHash: Buffer32,
|
|
478
|
+
) {
|
|
479
|
+
this.log.trace(`Handling L1 to L2 messages from ${messagesSyncPoint.l1BlockNumber} to ${currentL1BlockNumber}.`);
|
|
480
|
+
if (currentL1BlockNumber <= messagesSyncPoint.l1BlockNumber) {
|
|
355
481
|
return;
|
|
356
482
|
}
|
|
357
483
|
|
|
358
|
-
|
|
359
|
-
const
|
|
484
|
+
// Load remote and local inbox states.
|
|
485
|
+
const localMessagesInserted = await this.store.getTotalL1ToL2MessageCount();
|
|
486
|
+
const localLastMessage = await this.store.getLastL1ToL2Message();
|
|
487
|
+
const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
|
|
360
488
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
489
|
+
this.log.trace(`Retrieved remote inbox state at L1 block ${currentL1BlockNumber}.`, {
|
|
490
|
+
localMessagesInserted,
|
|
491
|
+
localLastMessage,
|
|
492
|
+
remoteMessagesState,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Compare message count and rolling hash. If they match, no need to retrieve anything.
|
|
496
|
+
if (
|
|
497
|
+
remoteMessagesState.totalMessagesInserted === localMessagesInserted &&
|
|
498
|
+
remoteMessagesState.messagesRollingHash.equals(localLastMessage?.rollingHash ?? Buffer16.ZERO)
|
|
499
|
+
) {
|
|
500
|
+
this.log.debug(
|
|
501
|
+
`No L1 to L2 messages to query between L1 blocks ${messagesSyncPoint.l1BlockNumber} and ${currentL1BlockNumber}.`,
|
|
365
502
|
);
|
|
366
503
|
return;
|
|
367
504
|
}
|
|
368
505
|
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
506
|
+
// Check if our syncpoint is still valid. If not, there was an L1 reorg and we need to re-retrieve messages.
|
|
507
|
+
// Note that we need to fetch it from logs and not from inbox state at the syncpoint l1 block number, since it
|
|
508
|
+
// could be older than 128 blocks and non-archive nodes cannot resolve it.
|
|
509
|
+
if (localLastMessage) {
|
|
510
|
+
const remoteLastMessage = await this.retrieveL1ToL2Message(localLastMessage.leaf);
|
|
511
|
+
this.log.trace(`Retrieved remote message for local last`, { remoteLastMessage, localLastMessage });
|
|
512
|
+
if (!remoteLastMessage || !remoteLastMessage.rollingHash.equals(localLastMessage.rollingHash)) {
|
|
513
|
+
this.log.warn(`Rolling back L1 to L2 messages due to hash mismatch or msg not found.`, {
|
|
514
|
+
remoteLastMessage,
|
|
515
|
+
messagesSyncPoint,
|
|
516
|
+
localLastMessage,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
messagesSyncPoint = await this.rollbackL1ToL2Messages(localLastMessage, messagesSyncPoint);
|
|
520
|
+
this.log.debug(`Rolled back L1 to L2 messages to L1 block ${messagesSyncPoint.l1BlockNumber}.`, {
|
|
521
|
+
messagesSyncPoint,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Retrieve and save messages in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
|
|
527
|
+
let searchStartBlock: bigint = 0n;
|
|
528
|
+
let searchEndBlock: bigint = messagesSyncPoint.l1BlockNumber;
|
|
529
|
+
|
|
530
|
+
let lastMessage: InboxMessage | undefined;
|
|
531
|
+
let messageCount = 0;
|
|
532
|
+
|
|
372
533
|
do {
|
|
373
534
|
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
374
535
|
this.log.trace(`Retrieving L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`);
|
|
375
|
-
const
|
|
536
|
+
const messages = await retrieveL1ToL2Messages(this.inbox.getContract(), searchStartBlock, searchEndBlock);
|
|
376
537
|
this.log.verbose(
|
|
377
|
-
`Retrieved ${
|
|
538
|
+
`Retrieved ${messages.length} new L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`,
|
|
378
539
|
);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
540
|
+
const timer = new Timer();
|
|
541
|
+
await this.store.addL1ToL2Messages(messages);
|
|
542
|
+
const perMsg = timer.ms() / messages.length;
|
|
543
|
+
this.instrumentation.processNewMessages(messages.length, perMsg);
|
|
544
|
+
for (const msg of messages) {
|
|
545
|
+
this.log.debug(`Downloaded L1 to L2 message`, { ...msg, leaf: msg.leaf.toString() });
|
|
546
|
+
lastMessage = msg;
|
|
547
|
+
messageCount++;
|
|
382
548
|
}
|
|
383
549
|
} while (searchEndBlock < currentL1BlockNumber);
|
|
550
|
+
|
|
551
|
+
// Log stats for messages retrieved (if any).
|
|
552
|
+
if (messageCount > 0) {
|
|
553
|
+
this.log.info(
|
|
554
|
+
`Retrieved ${messageCount} new L1 to L2 messages up to message with index ${lastMessage?.index} for L2 block ${lastMessage?.l2BlockNumber}`,
|
|
555
|
+
{ lastMessage, messageCount },
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Warn if the resulting rolling hash does not match the remote state we had retrieved.
|
|
560
|
+
if (lastMessage && !lastMessage.rollingHash.equals(remoteMessagesState.messagesRollingHash)) {
|
|
561
|
+
this.log.warn(`Last message retrieved rolling hash does not match remote state.`, {
|
|
562
|
+
lastMessage,
|
|
563
|
+
remoteMessagesState,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
384
566
|
}
|
|
385
567
|
|
|
386
|
-
private async
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
568
|
+
private async retrieveL1ToL2Message(leaf: Fr): Promise<InboxMessage | undefined> {
|
|
569
|
+
const currentL1BlockNumber = await this.publicClient.getBlockNumber();
|
|
570
|
+
let searchStartBlock: bigint = 0n;
|
|
571
|
+
let searchEndBlock: bigint = this.l1constants.l1StartBlock - 1n;
|
|
572
|
+
|
|
573
|
+
do {
|
|
574
|
+
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
575
|
+
|
|
576
|
+
const message = await retrieveL1ToL2Message(this.inbox.getContract(), leaf, searchStartBlock, searchEndBlock);
|
|
577
|
+
|
|
578
|
+
if (message) {
|
|
579
|
+
return message;
|
|
580
|
+
}
|
|
581
|
+
} while (searchEndBlock < currentL1BlockNumber);
|
|
582
|
+
|
|
583
|
+
return undefined;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private async rollbackL1ToL2Messages(localLastMessage: InboxMessage, messagesSyncPoint: L1BlockId) {
|
|
587
|
+
// Slowly go back through our messages until we find the last common message.
|
|
588
|
+
// We could query the logs in batch as an optimization, but the depth of the reorg should not be deep, and this
|
|
589
|
+
// is a very rare case, so it's fine to query one log at a time.
|
|
590
|
+
let commonMsg: undefined | InboxMessage;
|
|
591
|
+
this.log.verbose(`Searching most recent common L1 to L2 message at or before index ${localLastMessage.index}`);
|
|
592
|
+
for await (const msg of this.store.iterateL1ToL2Messages({ reverse: true, end: localLastMessage.index })) {
|
|
593
|
+
const remoteMsg = await this.retrieveL1ToL2Message(msg.leaf);
|
|
594
|
+
const logCtx = { remoteMsg, localMsg: msg };
|
|
595
|
+
if (remoteMsg && remoteMsg.rollingHash.equals(msg.rollingHash)) {
|
|
596
|
+
this.log.verbose(
|
|
597
|
+
`Found most recent common L1 to L2 message at index ${msg.index} on L1 block ${msg.l1BlockNumber}`,
|
|
598
|
+
logCtx,
|
|
599
|
+
);
|
|
600
|
+
commonMsg = remoteMsg;
|
|
601
|
+
break;
|
|
602
|
+
} else if (remoteMsg) {
|
|
603
|
+
this.log.debug(`Local L1 to L2 message with index ${msg.index} has different rolling hash`, logCtx);
|
|
604
|
+
} else {
|
|
605
|
+
this.log.debug(`Local L1 to L2 message with index ${msg.index} not found on L1`, logCtx);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Delete everything after the common message we found.
|
|
610
|
+
const lastGoodIndex = commonMsg?.index;
|
|
611
|
+
this.log.warn(`Deleting all local L1 to L2 messages after index ${lastGoodIndex ?? 'undefined'}`);
|
|
612
|
+
await this.store.removeL1ToL2Messages(lastGoodIndex !== undefined ? lastGoodIndex + 1n : 0n);
|
|
613
|
+
|
|
614
|
+
// Update the syncpoint so the loop below reprocesses the changed messages. We go to the block before
|
|
615
|
+
// the last common one, so we force reprocessing it, in case new messages were added on that same L1 block
|
|
616
|
+
// after the last common message.
|
|
617
|
+
const syncPointL1BlockNumber = commonMsg ? commonMsg.l1BlockNumber - 1n : this.l1constants.l1StartBlock;
|
|
618
|
+
const syncPointL1BlockHash = await this.getL1BlockHash(syncPointL1BlockNumber);
|
|
619
|
+
messagesSyncPoint = { l1BlockNumber: syncPointL1BlockNumber, l1BlockHash: syncPointL1BlockHash };
|
|
620
|
+
await this.store.setMessageSynchedL1Block(messagesSyncPoint);
|
|
621
|
+
return messagesSyncPoint;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private async getL1BlockHash(l1BlockNumber: bigint): Promise<Buffer32> {
|
|
625
|
+
const block = await this.publicClient.getBlock({ blockNumber: l1BlockNumber, includeTransactions: false });
|
|
626
|
+
if (!block) {
|
|
627
|
+
throw new Error(`Missing L1 block ${l1BlockNumber}`);
|
|
628
|
+
}
|
|
629
|
+
return Buffer32.fromString(block.hash);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
private async handleL2blocks(blocksSynchedTo: bigint, currentL1BlockNumber: bigint) {
|
|
633
|
+
const localPendingBlockNumber = await this.getBlockNumber();
|
|
634
|
+
const initialValidationResult: ValidateBlockResult | undefined = await this.store.getPendingChainValidationStatus();
|
|
391
635
|
const [provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive, archiveForLocalPendingBlockNumber] =
|
|
392
|
-
await this.rollup.
|
|
636
|
+
await this.rollup.status(BigInt(localPendingBlockNumber), { blockNumber: currentL1BlockNumber });
|
|
637
|
+
const rollupStatus = {
|
|
638
|
+
provenBlockNumber: Number(provenBlockNumber),
|
|
639
|
+
provenArchive,
|
|
640
|
+
pendingBlockNumber: Number(pendingBlockNumber),
|
|
641
|
+
pendingArchive,
|
|
642
|
+
validationResult: initialValidationResult,
|
|
643
|
+
};
|
|
644
|
+
this.log.trace(`Retrieved rollup status at current L1 block ${currentL1BlockNumber}.`, {
|
|
645
|
+
localPendingBlockNumber,
|
|
646
|
+
blocksSynchedTo,
|
|
647
|
+
currentL1BlockNumber,
|
|
648
|
+
archiveForLocalPendingBlockNumber,
|
|
649
|
+
...rollupStatus,
|
|
650
|
+
});
|
|
393
651
|
|
|
394
652
|
const updateProvenBlock = async () => {
|
|
653
|
+
// Annoying edge case: if proven block is moved back to 0 due to a reorg at the beginning of the chain,
|
|
654
|
+
// we need to set it to zero. This is an edge case because we dont have a block zero (initial block is one),
|
|
655
|
+
// so localBlockForDestinationProvenBlockNumber would not be found below.
|
|
656
|
+
if (provenBlockNumber === 0n) {
|
|
657
|
+
const localProvenBlockNumber = await this.store.getProvenL2BlockNumber();
|
|
658
|
+
if (localProvenBlockNumber !== Number(provenBlockNumber)) {
|
|
659
|
+
await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
|
|
660
|
+
this.log.info(`Rolled back proven chain to block ${provenBlockNumber}`, { provenBlockNumber });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
395
664
|
const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber));
|
|
396
665
|
|
|
397
666
|
// Sanity check. I've hit what seems to be a state where the proven block is set to a value greater than the latest
|
|
@@ -403,6 +672,12 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
403
672
|
);
|
|
404
673
|
}
|
|
405
674
|
|
|
675
|
+
this.log.trace(
|
|
676
|
+
`Local block for remote proven block ${provenBlockNumber} is ${
|
|
677
|
+
localBlockForDestinationProvenBlockNumber?.archive.root.toString() ?? 'undefined'
|
|
678
|
+
}`,
|
|
679
|
+
);
|
|
680
|
+
|
|
406
681
|
if (
|
|
407
682
|
localBlockForDestinationProvenBlockNumber &&
|
|
408
683
|
provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString()
|
|
@@ -413,6 +688,17 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
413
688
|
this.log.info(`Updated proven chain to block ${provenBlockNumber}`, {
|
|
414
689
|
provenBlockNumber,
|
|
415
690
|
});
|
|
691
|
+
const provenSlotNumber =
|
|
692
|
+
localBlockForDestinationProvenBlockNumber.header.globalVariables.slotNumber.toBigInt();
|
|
693
|
+
const provenEpochNumber = getEpochAtSlot(provenSlotNumber, this.l1constants);
|
|
694
|
+
this.emit(L2BlockSourceEvents.L2BlockProven, {
|
|
695
|
+
type: L2BlockSourceEvents.L2BlockProven,
|
|
696
|
+
blockNumber: provenBlockNumber,
|
|
697
|
+
slotNumber: provenSlotNumber,
|
|
698
|
+
epochNumber: provenEpochNumber,
|
|
699
|
+
});
|
|
700
|
+
} else {
|
|
701
|
+
this.log.trace(`Proven block ${provenBlockNumber} already stored.`);
|
|
416
702
|
}
|
|
417
703
|
}
|
|
418
704
|
this.instrumentation.updateLastProvenBlock(Number(provenBlockNumber));
|
|
@@ -420,11 +706,13 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
420
706
|
|
|
421
707
|
// This is an edge case that we only hit if there are no proposed blocks.
|
|
422
708
|
// If we have 0 blocks locally and there are no blocks onchain there is nothing to do.
|
|
423
|
-
const noBlocks = localPendingBlockNumber ===
|
|
709
|
+
const noBlocks = localPendingBlockNumber === 0 && pendingBlockNumber === 0n;
|
|
424
710
|
if (noBlocks) {
|
|
425
711
|
await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
426
|
-
this.log.debug(
|
|
427
|
-
|
|
712
|
+
this.log.debug(
|
|
713
|
+
`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no blocks on chain`,
|
|
714
|
+
);
|
|
715
|
+
return rollupStatus;
|
|
428
716
|
}
|
|
429
717
|
|
|
430
718
|
await updateProvenBlock();
|
|
@@ -432,25 +720,36 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
432
720
|
// Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
|
|
433
721
|
// are any state that could be impacted by it. If we have no blocks, there is no impact.
|
|
434
722
|
if (localPendingBlockNumber > 0) {
|
|
435
|
-
const localPendingBlock = await this.getBlock(
|
|
723
|
+
const localPendingBlock = await this.getBlock(localPendingBlockNumber);
|
|
436
724
|
if (localPendingBlock === undefined) {
|
|
437
725
|
throw new Error(`Missing block ${localPendingBlockNumber}`);
|
|
438
726
|
}
|
|
439
727
|
|
|
440
|
-
const
|
|
728
|
+
const localPendingArchiveRoot = localPendingBlock.archive.root.toString();
|
|
729
|
+
const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingArchiveRoot;
|
|
441
730
|
if (noBlockSinceLast) {
|
|
442
|
-
|
|
731
|
+
// We believe the following line causes a problem when we encounter L1 re-orgs.
|
|
732
|
+
// Basically, by setting the synched L1 block number here, we are saying that we have
|
|
733
|
+
// processed all blocks up to the current L1 block number and we will not attempt to retrieve logs from
|
|
734
|
+
// this block again (or any blocks before).
|
|
735
|
+
// However, in the re-org scenario, our L1 node is temporarily lying to us and we end up potentially missing blocks
|
|
736
|
+
// We must only set this block number based on actually retrieved logs.
|
|
737
|
+
// TODO(#8621): Tackle this properly when we handle L1 Re-orgs.
|
|
738
|
+
// await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
443
739
|
this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
444
|
-
return
|
|
740
|
+
return rollupStatus;
|
|
445
741
|
}
|
|
446
742
|
|
|
447
|
-
const localPendingBlockInChain = archiveForLocalPendingBlockNumber ===
|
|
743
|
+
const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingArchiveRoot;
|
|
448
744
|
if (!localPendingBlockInChain) {
|
|
449
745
|
// If our local pending block tip is not in the chain on L1 a "prune" must have happened
|
|
450
746
|
// or the L1 have reorged.
|
|
451
747
|
// In any case, we have to figure out how far into the past the action will take us.
|
|
452
748
|
// For simplicity here, we will simply rewind until we end in a block that is also on the chain on L1.
|
|
453
|
-
this.log.debug(
|
|
749
|
+
this.log.debug(
|
|
750
|
+
`L2 prune has been detected due to local pending block ${localPendingBlockNumber} not in chain`,
|
|
751
|
+
{ localPendingBlockNumber, localPendingArchiveRoot, archiveForLocalPendingBlockNumber },
|
|
752
|
+
);
|
|
454
753
|
|
|
455
754
|
let tipAfterUnwind = localPendingBlockNumber;
|
|
456
755
|
while (true) {
|
|
@@ -459,7 +758,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
459
758
|
break;
|
|
460
759
|
}
|
|
461
760
|
|
|
462
|
-
const archiveAtContract = await this.rollup.
|
|
761
|
+
const archiveAtContract = await this.rollup.archiveAt(BigInt(candidateBlock.number));
|
|
463
762
|
|
|
464
763
|
if (archiveAtContract === candidateBlock.archive.root.toString()) {
|
|
465
764
|
break;
|
|
@@ -478,19 +777,20 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
478
777
|
}
|
|
479
778
|
}
|
|
480
779
|
|
|
481
|
-
// Retrieve L2 blocks in batches. Each batch is estimated to
|
|
780
|
+
// Retrieve L2 blocks in batches. Each batch is estimated to accommodate up to L2 'blockBatchSize' blocks,
|
|
482
781
|
// computed using the L2 block time vs the L1 block time.
|
|
483
782
|
let searchStartBlock: bigint = blocksSynchedTo;
|
|
484
783
|
let searchEndBlock: bigint = blocksSynchedTo;
|
|
784
|
+
let lastRetrievedBlock: PublishedL2Block | undefined;
|
|
485
785
|
|
|
486
786
|
do {
|
|
487
787
|
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
488
788
|
|
|
489
789
|
this.log.trace(`Retrieving L2 blocks from L1 block ${searchStartBlock} to ${searchEndBlock}`);
|
|
490
790
|
|
|
491
|
-
// TODO(md):
|
|
791
|
+
// TODO(md): Retrieve from blob sink then from consensus client, then from peers
|
|
492
792
|
const retrievedBlocks = await retrieveBlocksFromRollup(
|
|
493
|
-
this.rollup,
|
|
793
|
+
this.rollup.getContract() as GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
|
|
494
794
|
this.publicClient,
|
|
495
795
|
this.blobSinkClient,
|
|
496
796
|
searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
|
|
@@ -510,35 +810,159 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
510
810
|
`Retrieved ${retrievedBlocks.length} new L2 blocks between L1 blocks ${searchStartBlock} and ${searchEndBlock} with last processed L1 block ${lastProcessedL1BlockNumber}.`,
|
|
511
811
|
);
|
|
512
812
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
813
|
+
const publishedBlocks = await Promise.all(retrievedBlocks.map(b => retrievedBlockToPublishedL2Block(b)));
|
|
814
|
+
const validBlocks: PublishedL2Block[] = [];
|
|
815
|
+
|
|
816
|
+
for (const block of publishedBlocks) {
|
|
817
|
+
const validationResult = this.config.skipValidateBlockAttestations
|
|
818
|
+
? { valid: true as const }
|
|
819
|
+
: await validateBlockAttestations(block, this.epochCache, this.l1constants, this.log);
|
|
820
|
+
|
|
821
|
+
// Only update the validation result if it has changed, so we can keep track of the first invalid block
|
|
822
|
+
// in case there is a sequence of more than one invalid block, as we need to invalidate the first one.
|
|
823
|
+
// There is an exception though: if an invalid block is invalidated and replaced with another invalid block,
|
|
824
|
+
// we need to update the validation result, since we need to be able to invalidate the new one.
|
|
825
|
+
// See test 'chain progresses if an invalid block is invalidated with an invalid one' for more info.
|
|
826
|
+
if (
|
|
827
|
+
rollupStatus.validationResult?.valid !== validationResult.valid ||
|
|
828
|
+
(!rollupStatus.validationResult.valid &&
|
|
829
|
+
!validationResult.valid &&
|
|
830
|
+
rollupStatus.validationResult.block.blockNumber === validationResult.block.blockNumber)
|
|
831
|
+
) {
|
|
832
|
+
rollupStatus.validationResult = validationResult;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (!validationResult.valid) {
|
|
836
|
+
this.log.warn(`Skipping block ${block.block.number} due to invalid attestations`, {
|
|
837
|
+
blockHash: block.block.hash(),
|
|
838
|
+
l1BlockNumber: block.l1.blockNumber,
|
|
839
|
+
...pick(validationResult, 'reason'),
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
// Emit event for invalid block detection
|
|
843
|
+
this.emit(L2BlockSourceEvents.InvalidAttestationsBlockDetected, {
|
|
844
|
+
type: L2BlockSourceEvents.InvalidAttestationsBlockDetected,
|
|
845
|
+
validationResult,
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// We keep consuming blocks if we find an invalid one, since we do not listen for BlockInvalidated events
|
|
849
|
+
// We just pretend the invalid ones are not there and keep consuming the next blocks
|
|
850
|
+
// Note that this breaks if the committee ever attests to a descendant of an invalid block
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
validBlocks.push(block);
|
|
855
|
+
this.log.debug(`Ingesting new L2 block ${block.block.number} with ${block.block.body.txEffects.length} txs`, {
|
|
856
|
+
blockHash: block.block.hash(),
|
|
516
857
|
l1BlockNumber: block.l1.blockNumber,
|
|
517
|
-
...block.
|
|
518
|
-
...block.
|
|
858
|
+
...block.block.header.globalVariables.toInspect(),
|
|
859
|
+
...block.block.getStats(),
|
|
519
860
|
});
|
|
520
861
|
}
|
|
521
862
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
863
|
+
try {
|
|
864
|
+
const updatedValidationResult =
|
|
865
|
+
rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
|
|
866
|
+
const [processDuration] = await elapsed(() => this.store.addBlocks(validBlocks, updatedValidationResult));
|
|
867
|
+
this.instrumentation.processNewBlocks(
|
|
868
|
+
processDuration / validBlocks.length,
|
|
869
|
+
validBlocks.map(b => b.block),
|
|
870
|
+
);
|
|
871
|
+
} catch (err) {
|
|
872
|
+
if (err instanceof InitialBlockNumberNotSequentialError) {
|
|
873
|
+
const { previousBlockNumber, newBlockNumber } = err;
|
|
874
|
+
const previousBlock = previousBlockNumber
|
|
875
|
+
? await this.store.getPublishedBlock(previousBlockNumber)
|
|
876
|
+
: undefined;
|
|
877
|
+
const updatedL1SyncPoint = previousBlock?.l1.blockNumber ?? this.l1constants.l1StartBlock;
|
|
878
|
+
await this.store.setBlockSynchedL1BlockNumber(updatedL1SyncPoint);
|
|
879
|
+
this.log.warn(
|
|
880
|
+
`Attempting to insert block ${newBlockNumber} with previous block ${previousBlockNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
|
|
881
|
+
{
|
|
882
|
+
previousBlockNumber,
|
|
883
|
+
previousBlockHash: await previousBlock?.block.hash(),
|
|
884
|
+
newBlockNumber,
|
|
885
|
+
updatedL1SyncPoint,
|
|
886
|
+
},
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
throw err;
|
|
890
|
+
}
|
|
527
891
|
|
|
528
|
-
for (const block of
|
|
529
|
-
this.log.info(`Downloaded L2 block ${block.
|
|
530
|
-
blockHash: block.
|
|
531
|
-
blockNumber: block.
|
|
532
|
-
txCount: block.
|
|
533
|
-
globalVariables: block.
|
|
892
|
+
for (const block of validBlocks) {
|
|
893
|
+
this.log.info(`Downloaded L2 block ${block.block.number}`, {
|
|
894
|
+
blockHash: await block.block.hash(),
|
|
895
|
+
blockNumber: block.block.number,
|
|
896
|
+
txCount: block.block.body.txEffects.length,
|
|
897
|
+
globalVariables: block.block.header.globalVariables.toInspect(),
|
|
898
|
+
archiveRoot: block.block.archive.root.toString(),
|
|
899
|
+
archiveNextLeafIndex: block.block.archive.nextAvailableLeafIndex,
|
|
534
900
|
});
|
|
535
901
|
}
|
|
902
|
+
lastRetrievedBlock = validBlocks.at(-1) ?? lastRetrievedBlock;
|
|
536
903
|
} while (searchEndBlock < currentL1BlockNumber);
|
|
537
904
|
|
|
538
905
|
// Important that we update AFTER inserting the blocks.
|
|
539
906
|
await updateProvenBlock();
|
|
540
907
|
|
|
541
|
-
return {
|
|
908
|
+
return { ...rollupStatus, lastRetrievedBlock };
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
private async checkForNewBlocksBeforeL1SyncPoint(
|
|
912
|
+
status: {
|
|
913
|
+
lastRetrievedBlock?: PublishedL2Block;
|
|
914
|
+
pendingBlockNumber: number;
|
|
915
|
+
},
|
|
916
|
+
blocksSynchedTo: bigint,
|
|
917
|
+
currentL1BlockNumber: bigint,
|
|
918
|
+
) {
|
|
919
|
+
const { lastRetrievedBlock, pendingBlockNumber } = status;
|
|
920
|
+
// Compare the last L2 block we have (either retrieved in this round or loaded from store) with what the
|
|
921
|
+
// rollup contract told us was the latest one (pinned at the currentL1BlockNumber).
|
|
922
|
+
const latestLocalL2BlockNumber = lastRetrievedBlock?.block.number ?? (await this.store.getSynchedL2BlockNumber());
|
|
923
|
+
if (latestLocalL2BlockNumber < pendingBlockNumber) {
|
|
924
|
+
// Here we have consumed all logs until the `currentL1Block` we pinned at the beginning of the archiver loop,
|
|
925
|
+
// but still havent reached the pending block according to the call to the rollup contract.
|
|
926
|
+
// We suspect an L1 reorg that added blocks *behind* us. If that is the case, it must have happened between the
|
|
927
|
+
// last L2 block we saw and the current one, so we reset the last synched L1 block number. In the edge case we
|
|
928
|
+
// don't have one, we go back 2 L1 epochs, which is the deepest possible reorg (assuming Casper is working).
|
|
929
|
+
const latestLocalL2Block =
|
|
930
|
+
lastRetrievedBlock ??
|
|
931
|
+
(latestLocalL2BlockNumber > 0
|
|
932
|
+
? await this.store.getPublishedBlocks(latestLocalL2BlockNumber, 1).then(([b]) => b)
|
|
933
|
+
: undefined);
|
|
934
|
+
const targetL1BlockNumber = latestLocalL2Block?.l1.blockNumber ?? maxBigint(currentL1BlockNumber - 64n, 0n);
|
|
935
|
+
const latestLocalL2BlockArchive = latestLocalL2Block?.block.archive.root.toString();
|
|
936
|
+
this.log.warn(
|
|
937
|
+
`Failed to reach L2 block ${pendingBlockNumber} at ${currentL1BlockNumber} (latest is ${latestLocalL2BlockNumber}). ` +
|
|
938
|
+
`Rolling back last synched L1 block number to ${targetL1BlockNumber}.`,
|
|
939
|
+
{
|
|
940
|
+
latestLocalL2BlockNumber,
|
|
941
|
+
latestLocalL2BlockArchive,
|
|
942
|
+
blocksSynchedTo,
|
|
943
|
+
currentL1BlockNumber,
|
|
944
|
+
...status,
|
|
945
|
+
},
|
|
946
|
+
);
|
|
947
|
+
await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
|
|
948
|
+
} else {
|
|
949
|
+
this.log.trace(`No new blocks behind L1 sync point to retrieve.`, {
|
|
950
|
+
latestLocalL2BlockNumber,
|
|
951
|
+
pendingBlockNumber,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/** Resumes the archiver after a stop. */
|
|
957
|
+
public resume() {
|
|
958
|
+
if (!this.runningPromise) {
|
|
959
|
+
throw new Error(`Archiver was never started`);
|
|
960
|
+
}
|
|
961
|
+
if (this.runningPromise.isRunning()) {
|
|
962
|
+
this.log.warn(`Archiver already running`);
|
|
963
|
+
}
|
|
964
|
+
this.log.info(`Restarting archiver`);
|
|
965
|
+
this.runningPromise.start();
|
|
542
966
|
}
|
|
543
967
|
|
|
544
968
|
/**
|
|
@@ -553,10 +977,18 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
553
977
|
return Promise.resolve();
|
|
554
978
|
}
|
|
555
979
|
|
|
980
|
+
public backupTo(destPath: string): Promise<string> {
|
|
981
|
+
return this.dataStore.backupTo(destPath);
|
|
982
|
+
}
|
|
983
|
+
|
|
556
984
|
public getL1Constants(): Promise<L1RollupConstants> {
|
|
557
985
|
return Promise.resolve(this.l1constants);
|
|
558
986
|
}
|
|
559
987
|
|
|
988
|
+
public getGenesisValues(): Promise<{ genesisArchiveRoot: Fr }> {
|
|
989
|
+
return Promise.resolve({ genesisArchiveRoot: this.l1constants.genesisArchiveRoot });
|
|
990
|
+
}
|
|
991
|
+
|
|
560
992
|
public getRollupAddress(): Promise<EthAddress> {
|
|
561
993
|
return Promise.resolve(this.l1Addresses.rollupAddress);
|
|
562
994
|
}
|
|
@@ -565,28 +997,24 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
565
997
|
return Promise.resolve(this.l1Addresses.registryAddress);
|
|
566
998
|
}
|
|
567
999
|
|
|
568
|
-
public getL1BlockNumber(): bigint {
|
|
569
|
-
|
|
570
|
-
if (!l1BlockNumber) {
|
|
571
|
-
throw new Error('L1 block number not yet available. Complete an initial sync first.');
|
|
572
|
-
}
|
|
573
|
-
return l1BlockNumber;
|
|
1000
|
+
public getL1BlockNumber(): bigint | undefined {
|
|
1001
|
+
return this.l1BlockNumber;
|
|
574
1002
|
}
|
|
575
1003
|
|
|
576
|
-
public getL1Timestamp(): bigint {
|
|
577
|
-
|
|
578
|
-
if (!l1Timestamp) {
|
|
579
|
-
throw new Error('L1 timestamp not yet available. Complete an initial sync first.');
|
|
580
|
-
}
|
|
581
|
-
return l1Timestamp;
|
|
1004
|
+
public getL1Timestamp(): Promise<bigint | undefined> {
|
|
1005
|
+
return Promise.resolve(this.l1Timestamp);
|
|
582
1006
|
}
|
|
583
1007
|
|
|
584
|
-
public getL2SlotNumber(): Promise<bigint> {
|
|
585
|
-
return Promise.resolve(
|
|
1008
|
+
public getL2SlotNumber(): Promise<bigint | undefined> {
|
|
1009
|
+
return Promise.resolve(
|
|
1010
|
+
this.l1Timestamp === undefined ? undefined : getSlotAtTimestamp(this.l1Timestamp, this.l1constants),
|
|
1011
|
+
);
|
|
586
1012
|
}
|
|
587
1013
|
|
|
588
|
-
public getL2EpochNumber(): Promise<bigint> {
|
|
589
|
-
return Promise.resolve(
|
|
1014
|
+
public getL2EpochNumber(): Promise<bigint | undefined> {
|
|
1015
|
+
return Promise.resolve(
|
|
1016
|
+
this.l1Timestamp === undefined ? undefined : getEpochNumberAtTimestamp(this.l1Timestamp, this.l1constants),
|
|
1017
|
+
);
|
|
590
1018
|
}
|
|
591
1019
|
|
|
592
1020
|
public async getBlocksForEpoch(epochNumber: bigint): Promise<L2Block[]> {
|
|
@@ -607,6 +1035,24 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
607
1035
|
return blocks.reverse();
|
|
608
1036
|
}
|
|
609
1037
|
|
|
1038
|
+
public async getBlockHeadersForEpoch(epochNumber: bigint): Promise<BlockHeader[]> {
|
|
1039
|
+
const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
|
|
1040
|
+
const blocks: BlockHeader[] = [];
|
|
1041
|
+
|
|
1042
|
+
// Walk the list of blocks backwards and filter by slots matching the requested epoch.
|
|
1043
|
+
// We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
|
|
1044
|
+
let number = await this.store.getSynchedL2BlockNumber();
|
|
1045
|
+
let header = await this.getBlockHeader(number);
|
|
1046
|
+
const slot = (b: BlockHeader) => b.globalVariables.slotNumber.toBigInt();
|
|
1047
|
+
while (header && slot(header) >= start) {
|
|
1048
|
+
if (slot(header) <= end) {
|
|
1049
|
+
blocks.push(header);
|
|
1050
|
+
}
|
|
1051
|
+
header = await this.getBlockHeader(--number);
|
|
1052
|
+
}
|
|
1053
|
+
return blocks.reverse();
|
|
1054
|
+
}
|
|
1055
|
+
|
|
610
1056
|
public async isEpochComplete(epochNumber: bigint): Promise<boolean> {
|
|
611
1057
|
// The epoch is complete if the current L2 block is the last one in the epoch (or later)
|
|
612
1058
|
const header = await this.getBlockHeader('latest');
|
|
@@ -635,6 +1081,11 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
635
1081
|
return l1Timestamp + leeway >= endTimestamp;
|
|
636
1082
|
}
|
|
637
1083
|
|
|
1084
|
+
/** Returns whether the archiver has completed an initial sync run successfully. */
|
|
1085
|
+
public isInitialSyncComplete(): boolean {
|
|
1086
|
+
return this.initialSyncComplete;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
638
1089
|
/**
|
|
639
1090
|
* Gets up to `limit` amount of L2 blocks starting from `from`.
|
|
640
1091
|
* @param from - Number of the first block to return (inclusive).
|
|
@@ -642,11 +1093,32 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
642
1093
|
* @param proven - If true, only return blocks that have been proven.
|
|
643
1094
|
* @returns The requested L2 blocks.
|
|
644
1095
|
*/
|
|
645
|
-
public
|
|
1096
|
+
public getBlocks(from: number, limit: number, proven?: boolean): Promise<L2Block[]> {
|
|
1097
|
+
return this.getPublishedBlocks(from, limit, proven).then(blocks => blocks.map(b => b.block));
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/** Equivalent to getBlocks but includes publish data. */
|
|
1101
|
+
public async getPublishedBlocks(from: number, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
|
|
646
1102
|
const limitWithProven = proven
|
|
647
1103
|
? Math.min(limit, Math.max((await this.store.getProvenL2BlockNumber()) - from + 1, 0))
|
|
648
1104
|
: limit;
|
|
649
|
-
return limitWithProven === 0 ? [] :
|
|
1105
|
+
return limitWithProven === 0 ? [] : await this.store.getPublishedBlocks(from, limitWithProven);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
public getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
|
|
1109
|
+
return this.store.getPublishedBlockByHash(blockHash);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
public getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
|
|
1113
|
+
return this.store.getPublishedBlockByArchive(archive);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
public getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
|
|
1117
|
+
return this.store.getBlockHeaderByHash(blockHash);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
public getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
|
|
1121
|
+
return this.store.getBlockHeaderByArchive(archive);
|
|
650
1122
|
}
|
|
651
1123
|
|
|
652
1124
|
/**
|
|
@@ -659,11 +1131,11 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
659
1131
|
if (number < 0) {
|
|
660
1132
|
number = await this.store.getSynchedL2BlockNumber();
|
|
661
1133
|
}
|
|
662
|
-
if (number
|
|
1134
|
+
if (number === 0) {
|
|
663
1135
|
return undefined;
|
|
664
1136
|
}
|
|
665
|
-
const
|
|
666
|
-
return
|
|
1137
|
+
const publishedBlock = await this.store.getPublishedBlock(number);
|
|
1138
|
+
return publishedBlock?.block;
|
|
667
1139
|
}
|
|
668
1140
|
|
|
669
1141
|
public async getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
|
|
@@ -685,29 +1157,6 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
685
1157
|
return this.store.getSettledTxReceipt(txHash);
|
|
686
1158
|
}
|
|
687
1159
|
|
|
688
|
-
/**
|
|
689
|
-
* Gets the public function data for a contract.
|
|
690
|
-
* @param address - The contract address containing the function to fetch.
|
|
691
|
-
* @param selector - The function selector of the function to fetch.
|
|
692
|
-
* @returns The public function data (if found).
|
|
693
|
-
*/
|
|
694
|
-
public async getPublicFunction(
|
|
695
|
-
address: AztecAddress,
|
|
696
|
-
selector: FunctionSelector,
|
|
697
|
-
): Promise<PublicFunction | undefined> {
|
|
698
|
-
const instance = await this.getContract(address);
|
|
699
|
-
if (!instance) {
|
|
700
|
-
throw new Error(`Contract ${address.toString()} not found`);
|
|
701
|
-
}
|
|
702
|
-
const contractClass = await this.getContractClass(instance.currentContractClassId);
|
|
703
|
-
if (!contractClass) {
|
|
704
|
-
throw new Error(
|
|
705
|
-
`Contract class ${instance.currentContractClassId.toString()} for ${address.toString()} not found`,
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
return contractClass.publicFunctions.find(f => f.selector.equals(selector));
|
|
709
|
-
}
|
|
710
|
-
|
|
711
1160
|
/**
|
|
712
1161
|
* Retrieves all private logs from up to `limit` blocks, starting from the block number `from`.
|
|
713
1162
|
* @param from - The block number from which to begin retrieving logs.
|
|
@@ -728,17 +1177,6 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
728
1177
|
return this.store.getLogsByTags(tags);
|
|
729
1178
|
}
|
|
730
1179
|
|
|
731
|
-
/**
|
|
732
|
-
* Returns the provided nullifier indexes scoped to the block
|
|
733
|
-
* they were first included in, or undefined if they're not present in the tree
|
|
734
|
-
* @param blockNumber Max block number to search for the nullifiers
|
|
735
|
-
* @param nullifiers Nullifiers to get
|
|
736
|
-
* @returns The block scoped indexes of the provided nullifiers, or undefined if the nullifier doesn't exist in the tree
|
|
737
|
-
*/
|
|
738
|
-
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
|
|
739
|
-
return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
1180
|
/**
|
|
743
1181
|
* Gets public logs based on the provided filter.
|
|
744
1182
|
* @param filter - The filter to apply to the logs.
|
|
@@ -782,8 +1220,20 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
782
1220
|
return this.store.getBytecodeCommitment(id);
|
|
783
1221
|
}
|
|
784
1222
|
|
|
785
|
-
public getContract(
|
|
786
|
-
|
|
1223
|
+
public async getContract(
|
|
1224
|
+
address: AztecAddress,
|
|
1225
|
+
maybeTimestamp?: UInt64,
|
|
1226
|
+
): Promise<ContractInstanceWithAddress | undefined> {
|
|
1227
|
+
let timestamp;
|
|
1228
|
+
if (maybeTimestamp === undefined) {
|
|
1229
|
+
const latestBlockHeader = await this.getBlockHeader('latest');
|
|
1230
|
+
// If we get undefined block header, it means that the archiver has not yet synced any block so we default to 0.
|
|
1231
|
+
timestamp = latestBlockHeader ? latestBlockHeader.globalVariables.timestamp : 0n;
|
|
1232
|
+
} else {
|
|
1233
|
+
timestamp = maybeTimestamp;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
return this.store.getContractInstance(address, timestamp);
|
|
787
1237
|
}
|
|
788
1238
|
|
|
789
1239
|
/**
|
|
@@ -791,7 +1241,7 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
791
1241
|
* @param blockNumber - L2 block number to get messages for.
|
|
792
1242
|
* @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found).
|
|
793
1243
|
*/
|
|
794
|
-
getL1ToL2Messages(blockNumber:
|
|
1244
|
+
getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
|
|
795
1245
|
return this.store.getL1ToL2Messages(blockNumber);
|
|
796
1246
|
}
|
|
797
1247
|
|
|
@@ -808,22 +1258,20 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
808
1258
|
return this.store.getContractClassIds();
|
|
809
1259
|
}
|
|
810
1260
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
await this.store.addContractClasses(
|
|
814
|
-
[contractClass],
|
|
815
|
-
[await computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
816
|
-
0,
|
|
817
|
-
);
|
|
818
|
-
return;
|
|
1261
|
+
registerContractFunctionSignatures(signatures: string[]): Promise<void> {
|
|
1262
|
+
return this.store.registerContractFunctionSignatures(signatures);
|
|
819
1263
|
}
|
|
820
1264
|
|
|
821
|
-
|
|
822
|
-
return this.store.
|
|
1265
|
+
getDebugFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
|
|
1266
|
+
return this.store.getDebugFunctionName(address, selector);
|
|
823
1267
|
}
|
|
824
1268
|
|
|
825
|
-
|
|
826
|
-
return this.store.
|
|
1269
|
+
async getPendingChainValidationStatus(): Promise<ValidateBlockResult> {
|
|
1270
|
+
return (await this.store.getPendingChainValidationStatus()) ?? { valid: true };
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
isPendingChainInvalid(): Promise<boolean> {
|
|
1274
|
+
return this.getPendingChainValidationStatus().then(status => !status.valid);
|
|
827
1275
|
}
|
|
828
1276
|
|
|
829
1277
|
async getL2Tips(): Promise<L2Tips> {
|
|
@@ -832,9 +1280,15 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
832
1280
|
this.getProvenBlockNumber(),
|
|
833
1281
|
] as const);
|
|
834
1282
|
|
|
835
|
-
|
|
1283
|
+
// TODO(#13569): Compute proper finalized block number based on L1 finalized block.
|
|
1284
|
+
// We just force it 2 epochs worth of proven data for now.
|
|
1285
|
+
// NOTE: update end-to-end/src/e2e_epochs/epochs_empty_blocks.test.ts as that uses finalized blocks in computations
|
|
1286
|
+
const finalizedBlockNumber = Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0);
|
|
1287
|
+
|
|
1288
|
+
const [latestBlockHeader, provenBlockHeader, finalizedBlockHeader] = await Promise.all([
|
|
836
1289
|
latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
|
|
837
1290
|
provenBlockNumber > 0 ? this.getBlockHeader(provenBlockNumber) : undefined,
|
|
1291
|
+
finalizedBlockNumber > 0 ? this.getBlockHeader(finalizedBlockNumber) : undefined,
|
|
838
1292
|
] as const);
|
|
839
1293
|
|
|
840
1294
|
if (latestBlockNumber > 0 && !latestBlockHeader) {
|
|
@@ -847,9 +1301,16 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
847
1301
|
);
|
|
848
1302
|
}
|
|
849
1303
|
|
|
1304
|
+
if (finalizedBlockNumber > 0 && !finalizedBlockHeader) {
|
|
1305
|
+
throw new Error(
|
|
1306
|
+
`Failed to retrieve finalized block header for block ${finalizedBlockNumber} (latest block is ${latestBlockNumber})`,
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
850
1310
|
const latestBlockHeaderHash = await latestBlockHeader?.hash();
|
|
851
1311
|
const provenBlockHeaderHash = await provenBlockHeader?.hash();
|
|
852
|
-
const finalizedBlockHeaderHash = await
|
|
1312
|
+
const finalizedBlockHeaderHash = await finalizedBlockHeader?.hash();
|
|
1313
|
+
|
|
853
1314
|
return {
|
|
854
1315
|
latest: {
|
|
855
1316
|
number: latestBlockNumber,
|
|
@@ -860,11 +1321,45 @@ export class Archiver extends EventEmitter implements ArchiveSource, Traceable {
|
|
|
860
1321
|
hash: provenBlockHeaderHash?.toString(),
|
|
861
1322
|
} as L2BlockId,
|
|
862
1323
|
finalized: {
|
|
863
|
-
number:
|
|
1324
|
+
number: finalizedBlockNumber,
|
|
864
1325
|
hash: finalizedBlockHeaderHash?.toString(),
|
|
865
1326
|
} as L2BlockId,
|
|
866
1327
|
};
|
|
867
1328
|
}
|
|
1329
|
+
|
|
1330
|
+
public async rollbackTo(targetL2BlockNumber: number): Promise<void> {
|
|
1331
|
+
const currentBlocks = await this.getL2Tips();
|
|
1332
|
+
const currentL2Block = currentBlocks.latest.number;
|
|
1333
|
+
const currentProvenBlock = currentBlocks.proven.number;
|
|
1334
|
+
// const currentFinalizedBlock = currentBlocks.finalized.number;
|
|
1335
|
+
|
|
1336
|
+
if (targetL2BlockNumber >= currentL2Block) {
|
|
1337
|
+
throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
|
|
1338
|
+
}
|
|
1339
|
+
const blocksToUnwind = currentL2Block - targetL2BlockNumber;
|
|
1340
|
+
const targetL2Block = await this.store.getPublishedBlock(targetL2BlockNumber);
|
|
1341
|
+
if (!targetL2Block) {
|
|
1342
|
+
throw new Error(`Target L2 block ${targetL2BlockNumber} not found`);
|
|
1343
|
+
}
|
|
1344
|
+
const targetL1BlockNumber = targetL2Block.l1.blockNumber;
|
|
1345
|
+
const targetL1BlockHash = await this.getL1BlockHash(targetL1BlockNumber);
|
|
1346
|
+
this.log.info(`Unwinding ${blocksToUnwind} blocks from L2 block ${currentL2Block}`);
|
|
1347
|
+
await this.store.unwindBlocks(currentL2Block, blocksToUnwind);
|
|
1348
|
+
this.log.info(`Unwinding L1 to L2 messages to ${targetL2BlockNumber}`);
|
|
1349
|
+
await this.store.rollbackL1ToL2MessagesToL2Block(targetL2BlockNumber);
|
|
1350
|
+
this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
|
|
1351
|
+
await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
|
|
1352
|
+
await this.store.setMessageSynchedL1Block({ l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash });
|
|
1353
|
+
if (targetL2BlockNumber < currentProvenBlock) {
|
|
1354
|
+
this.log.info(`Clearing proven L2 block number`);
|
|
1355
|
+
await this.store.setProvenL2BlockNumber(0);
|
|
1356
|
+
}
|
|
1357
|
+
// TODO(palla/reorg): Set the finalized block when we add support for it.
|
|
1358
|
+
// if (targetL2BlockNumber < currentFinalizedBlock) {
|
|
1359
|
+
// this.log.info(`Clearing finalized L2 block number`);
|
|
1360
|
+
// await this.store.setFinalizedL2BlockNumber(0);
|
|
1361
|
+
// }
|
|
1362
|
+
}
|
|
868
1363
|
}
|
|
869
1364
|
|
|
870
1365
|
enum Operation {
|
|
@@ -878,14 +1373,12 @@ enum Operation {
|
|
|
878
1373
|
* I would have preferred to not have this type. But it is useful for handling the logic that any
|
|
879
1374
|
* store would need to include otherwise while exposing fewer functions and logic directly to the archiver.
|
|
880
1375
|
*/
|
|
881
|
-
class ArchiverStoreHelper
|
|
1376
|
+
export class ArchiverStoreHelper
|
|
882
1377
|
implements
|
|
883
1378
|
Omit<
|
|
884
1379
|
ArchiverDataStore,
|
|
885
1380
|
| 'addLogs'
|
|
886
1381
|
| 'deleteLogs'
|
|
887
|
-
| 'addNullifiers'
|
|
888
|
-
| 'deleteNullifiers'
|
|
889
1382
|
| 'addContractClasses'
|
|
890
1383
|
| 'deleteContractClasses'
|
|
891
1384
|
| 'addContractInstances'
|
|
@@ -893,31 +1386,26 @@ class ArchiverStoreHelper
|
|
|
893
1386
|
| 'addContractInstanceUpdates'
|
|
894
1387
|
| 'deleteContractInstanceUpdates'
|
|
895
1388
|
| 'addFunctions'
|
|
1389
|
+
| 'backupTo'
|
|
1390
|
+
| 'close'
|
|
1391
|
+
| 'transactionAsync'
|
|
1392
|
+
| 'addBlocks'
|
|
896
1393
|
>
|
|
897
1394
|
{
|
|
898
1395
|
#log = createLogger('archiver:block-helper');
|
|
899
1396
|
|
|
900
|
-
constructor(
|
|
901
|
-
|
|
902
|
-
// TODO(#10007): Remove this method
|
|
903
|
-
addContractClasses(
|
|
904
|
-
contractClasses: ContractClassPublic[],
|
|
905
|
-
bytecodeCommitments: Fr[],
|
|
906
|
-
blockNum: number,
|
|
907
|
-
): Promise<boolean> {
|
|
908
|
-
return this.store.addContractClasses(contractClasses, bytecodeCommitments, blockNum);
|
|
909
|
-
}
|
|
1397
|
+
constructor(protected readonly store: ArchiverDataStore) {}
|
|
910
1398
|
|
|
911
1399
|
/**
|
|
912
|
-
* Extracts and stores contract classes out of
|
|
1400
|
+
* Extracts and stores contract classes out of ContractClassPublished events emitted by the class registry contract.
|
|
913
1401
|
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
914
1402
|
*/
|
|
915
|
-
async #
|
|
916
|
-
const
|
|
917
|
-
.filter(log =>
|
|
918
|
-
.map(log =>
|
|
1403
|
+
async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: number, operation: Operation) {
|
|
1404
|
+
const contractClassPublishedEvents = allLogs
|
|
1405
|
+
.filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
|
|
1406
|
+
.map(log => ContractClassPublishedEvent.fromLog(log));
|
|
919
1407
|
|
|
920
|
-
const contractClasses = await Promise.all(
|
|
1408
|
+
const contractClasses = await Promise.all(contractClassPublishedEvents.map(e => e.toContractClassPublic()));
|
|
921
1409
|
if (contractClasses.length > 0) {
|
|
922
1410
|
contractClasses.forEach(c => this.#log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
|
|
923
1411
|
if (operation == Operation.Store) {
|
|
@@ -934,13 +1422,13 @@ class ArchiverStoreHelper
|
|
|
934
1422
|
}
|
|
935
1423
|
|
|
936
1424
|
/**
|
|
937
|
-
* Extracts and stores contract instances out of
|
|
1425
|
+
* Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
|
|
938
1426
|
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
939
1427
|
*/
|
|
940
1428
|
async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: number, operation: Operation) {
|
|
941
1429
|
const contractInstances = allLogs
|
|
942
|
-
.filter(log =>
|
|
943
|
-
.map(log =>
|
|
1430
|
+
.filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
|
|
1431
|
+
.map(log => ContractInstancePublishedEvent.fromLog(log))
|
|
944
1432
|
.map(e => e.toContractInstance());
|
|
945
1433
|
if (contractInstances.length > 0) {
|
|
946
1434
|
contractInstances.forEach(c =>
|
|
@@ -956,10 +1444,12 @@ class ArchiverStoreHelper
|
|
|
956
1444
|
}
|
|
957
1445
|
|
|
958
1446
|
/**
|
|
959
|
-
* Extracts and stores contract instances out of
|
|
1447
|
+
* Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
|
|
960
1448
|
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
1449
|
+
* @param timestamp - Timestamp at which the updates were scheduled.
|
|
1450
|
+
* @param operation - The operation to perform on the contract instance updates (Store or Delete).
|
|
961
1451
|
*/
|
|
962
|
-
async #updateUpdatedContractInstances(allLogs: PublicLog[],
|
|
1452
|
+
async #updateUpdatedContractInstances(allLogs: PublicLog[], timestamp: UInt64, operation: Operation) {
|
|
963
1453
|
const contractUpdates = allLogs
|
|
964
1454
|
.filter(log => ContractInstanceUpdatedEvent.isContractInstanceUpdatedEvent(log))
|
|
965
1455
|
.map(log => ContractInstanceUpdatedEvent.fromLog(log))
|
|
@@ -970,16 +1460,16 @@ class ArchiverStoreHelper
|
|
|
970
1460
|
this.#log.verbose(`${Operation[operation]} contract instance update at ${c.address.toString()}`),
|
|
971
1461
|
);
|
|
972
1462
|
if (operation == Operation.Store) {
|
|
973
|
-
return await this.store.addContractInstanceUpdates(contractUpdates,
|
|
1463
|
+
return await this.store.addContractInstanceUpdates(contractUpdates, timestamp);
|
|
974
1464
|
} else if (operation == Operation.Delete) {
|
|
975
|
-
return await this.store.deleteContractInstanceUpdates(contractUpdates,
|
|
1465
|
+
return await this.store.deleteContractInstanceUpdates(contractUpdates, timestamp);
|
|
976
1466
|
}
|
|
977
1467
|
}
|
|
978
1468
|
return true;
|
|
979
1469
|
}
|
|
980
1470
|
|
|
981
1471
|
/**
|
|
982
|
-
* Stores the functions that
|
|
1472
|
+
* Stores the functions that were broadcasted individually
|
|
983
1473
|
*
|
|
984
1474
|
* @dev Beware that there is not a delete variant of this, since they are added to contract classes
|
|
985
1475
|
* and will be deleted as part of the class if needed.
|
|
@@ -989,17 +1479,17 @@ class ArchiverStoreHelper
|
|
|
989
1479
|
* @returns
|
|
990
1480
|
*/
|
|
991
1481
|
async #storeBroadcastedIndividualFunctions(allLogs: ContractClassLog[], _blockNum: number) {
|
|
992
|
-
// Filter out private and
|
|
1482
|
+
// Filter out private and utility function broadcast events
|
|
993
1483
|
const privateFnEvents = allLogs
|
|
994
1484
|
.filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
|
|
995
1485
|
.map(log => PrivateFunctionBroadcastedEvent.fromLog(log));
|
|
996
|
-
const
|
|
997
|
-
.filter(log =>
|
|
998
|
-
.map(log =>
|
|
1486
|
+
const utilityFnEvents = allLogs
|
|
1487
|
+
.filter(log => UtilityFunctionBroadcastedEvent.isUtilityFunctionBroadcastedEvent(log))
|
|
1488
|
+
.map(log => UtilityFunctionBroadcastedEvent.fromLog(log));
|
|
999
1489
|
|
|
1000
1490
|
// Group all events by contract class id
|
|
1001
1491
|
for (const [classIdString, classEvents] of Object.entries(
|
|
1002
|
-
groupBy([...privateFnEvents, ...
|
|
1492
|
+
groupBy([...privateFnEvents, ...utilityFnEvents], e => e.contractClassId.toString()),
|
|
1003
1493
|
)) {
|
|
1004
1494
|
const contractClassId = Fr.fromHexString(classIdString);
|
|
1005
1495
|
const contractClass = await this.getContractClass(contractClassId);
|
|
@@ -1008,27 +1498,27 @@ class ArchiverStoreHelper
|
|
|
1008
1498
|
continue;
|
|
1009
1499
|
}
|
|
1010
1500
|
|
|
1011
|
-
// Split private and
|
|
1501
|
+
// Split private and utility functions, and filter out invalid ones
|
|
1012
1502
|
const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
|
|
1013
1503
|
const privateFns = allFns.filter(
|
|
1014
|
-
(fn): fn is ExecutablePrivateFunctionWithMembershipProof => '
|
|
1504
|
+
(fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'utilityFunctionsTreeRoot' in fn,
|
|
1015
1505
|
);
|
|
1016
|
-
const
|
|
1017
|
-
(fn): fn is
|
|
1506
|
+
const utilityFns = allFns.filter(
|
|
1507
|
+
(fn): fn is UtilityFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
|
|
1018
1508
|
);
|
|
1019
1509
|
|
|
1020
1510
|
const privateFunctionsWithValidity = await Promise.all(
|
|
1021
1511
|
privateFns.map(async fn => ({ fn, valid: await isValidPrivateFunctionMembershipProof(fn, contractClass) })),
|
|
1022
1512
|
);
|
|
1023
1513
|
const validPrivateFns = privateFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1514
|
+
const utilityFunctionsWithValidity = await Promise.all(
|
|
1515
|
+
utilityFns.map(async fn => ({
|
|
1026
1516
|
fn,
|
|
1027
|
-
valid: await
|
|
1517
|
+
valid: await isValidUtilityFunctionMembershipProof(fn, contractClass),
|
|
1028
1518
|
})),
|
|
1029
1519
|
);
|
|
1030
|
-
const
|
|
1031
|
-
const validFnCount = validPrivateFns.length +
|
|
1520
|
+
const validUtilityFns = utilityFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
|
|
1521
|
+
const validFnCount = validPrivateFns.length + validUtilityFns.length;
|
|
1032
1522
|
if (validFnCount !== allFns.length) {
|
|
1033
1523
|
this.#log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
|
|
1034
1524
|
}
|
|
@@ -1037,85 +1527,120 @@ class ArchiverStoreHelper
|
|
|
1037
1527
|
if (validFnCount > 0) {
|
|
1038
1528
|
this.#log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
|
|
1039
1529
|
}
|
|
1040
|
-
return await this.store.addFunctions(contractClassId, validPrivateFns,
|
|
1530
|
+
return await this.store.addFunctions(contractClassId, validPrivateFns, validUtilityFns);
|
|
1041
1531
|
}
|
|
1042
1532
|
return true;
|
|
1043
1533
|
}
|
|
1044
1534
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1535
|
+
public addBlocks(blocks: PublishedL2Block[], pendingChainValidationStatus?: ValidateBlockResult): Promise<boolean> {
|
|
1536
|
+
// Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
|
|
1537
|
+
// or if the previous block is not in the store.
|
|
1538
|
+
return this.store.transactionAsync(async () => {
|
|
1539
|
+
await this.store.addBlocks(blocks);
|
|
1540
|
+
|
|
1541
|
+
const opResults = await Promise.all([
|
|
1542
|
+
// Update the pending chain validation status if provided
|
|
1543
|
+
pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
|
|
1544
|
+
// Add any logs emitted during the retrieved blocks
|
|
1545
|
+
this.store.addLogs(blocks.map(block => block.block)),
|
|
1546
|
+
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
1547
|
+
...blocks.map(async block => {
|
|
1548
|
+
const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
|
|
1549
|
+
// ContractInstancePublished event logs are broadcast in privateLogs.
|
|
1550
|
+
const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
|
|
1551
|
+
const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
|
|
1552
|
+
return (
|
|
1553
|
+
await Promise.all([
|
|
1554
|
+
this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Store),
|
|
1555
|
+
this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
|
|
1556
|
+
this.#updateUpdatedContractInstances(
|
|
1557
|
+
publicLogs,
|
|
1558
|
+
block.block.header.globalVariables.timestamp,
|
|
1559
|
+
Operation.Store,
|
|
1560
|
+
),
|
|
1561
|
+
this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.block.number),
|
|
1562
|
+
])
|
|
1563
|
+
).every(Boolean);
|
|
1564
|
+
}),
|
|
1565
|
+
]);
|
|
1566
|
+
|
|
1567
|
+
return opResults.every(Boolean);
|
|
1568
|
+
});
|
|
1068
1569
|
}
|
|
1069
1570
|
|
|
1070
|
-
async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
|
|
1571
|
+
public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
|
|
1071
1572
|
const last = await this.getSynchedL2BlockNumber();
|
|
1072
1573
|
if (from != last) {
|
|
1073
|
-
throw new Error(`
|
|
1574
|
+
throw new Error(`Cannot unwind blocks from block ${from} when the last block is ${last}`);
|
|
1575
|
+
}
|
|
1576
|
+
if (blocksToUnwind <= 0) {
|
|
1577
|
+
throw new Error(`Cannot unwind ${blocksToUnwind} blocks`);
|
|
1074
1578
|
}
|
|
1075
1579
|
|
|
1076
1580
|
// from - blocksToUnwind = the new head, so + 1 for what we need to remove
|
|
1077
|
-
const blocks = await this.
|
|
1581
|
+
const blocks = await this.getPublishedBlocks(from - blocksToUnwind + 1, blocksToUnwind);
|
|
1078
1582
|
|
|
1079
1583
|
const opResults = await Promise.all([
|
|
1584
|
+
// Prune rolls back to the last proven block, which is by definition valid
|
|
1585
|
+
this.store.setPendingChainValidationStatus({ valid: true }),
|
|
1080
1586
|
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
1081
1587
|
...blocks.map(async block => {
|
|
1082
|
-
const contractClassLogs = block.
|
|
1083
|
-
//
|
|
1084
|
-
const privateLogs = block.
|
|
1085
|
-
const publicLogs = block.
|
|
1588
|
+
const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
|
|
1589
|
+
// ContractInstancePublished event logs are broadcast in privateLogs.
|
|
1590
|
+
const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
|
|
1591
|
+
const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
|
|
1086
1592
|
|
|
1087
1593
|
return (
|
|
1088
1594
|
await Promise.all([
|
|
1089
|
-
this.#
|
|
1090
|
-
this.#updateDeployedContractInstances(privateLogs, block.
|
|
1091
|
-
this.#updateUpdatedContractInstances(
|
|
1595
|
+
this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Delete),
|
|
1596
|
+
this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Delete),
|
|
1597
|
+
this.#updateUpdatedContractInstances(
|
|
1598
|
+
publicLogs,
|
|
1599
|
+
block.block.header.globalVariables.timestamp,
|
|
1600
|
+
Operation.Delete,
|
|
1601
|
+
),
|
|
1092
1602
|
])
|
|
1093
1603
|
).every(Boolean);
|
|
1094
1604
|
}),
|
|
1095
1605
|
|
|
1096
|
-
this.store.deleteLogs(blocks.map(b => b.
|
|
1606
|
+
this.store.deleteLogs(blocks.map(b => b.block)),
|
|
1097
1607
|
this.store.unwindBlocks(from, blocksToUnwind),
|
|
1098
1608
|
]);
|
|
1099
1609
|
|
|
1100
1610
|
return opResults.every(Boolean);
|
|
1101
1611
|
}
|
|
1102
1612
|
|
|
1103
|
-
|
|
1104
|
-
return this.store.
|
|
1613
|
+
getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
|
|
1614
|
+
return this.store.getPublishedBlocks(from, limit);
|
|
1615
|
+
}
|
|
1616
|
+
getPublishedBlock(number: number): Promise<PublishedL2Block | undefined> {
|
|
1617
|
+
return this.store.getPublishedBlock(number);
|
|
1618
|
+
}
|
|
1619
|
+
getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
|
|
1620
|
+
return this.store.getPublishedBlockByHash(blockHash);
|
|
1621
|
+
}
|
|
1622
|
+
getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
|
|
1623
|
+
return this.store.getPublishedBlockByArchive(archive);
|
|
1105
1624
|
}
|
|
1106
1625
|
getBlockHeaders(from: number, limit: number): Promise<BlockHeader[]> {
|
|
1107
1626
|
return this.store.getBlockHeaders(from, limit);
|
|
1108
1627
|
}
|
|
1109
|
-
|
|
1628
|
+
getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
|
|
1629
|
+
return this.store.getBlockHeaderByHash(blockHash);
|
|
1630
|
+
}
|
|
1631
|
+
getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
|
|
1632
|
+
return this.store.getBlockHeaderByArchive(archive);
|
|
1633
|
+
}
|
|
1634
|
+
getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
|
|
1110
1635
|
return this.store.getTxEffect(txHash);
|
|
1111
1636
|
}
|
|
1112
1637
|
getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
|
|
1113
1638
|
return this.store.getSettledTxReceipt(txHash);
|
|
1114
1639
|
}
|
|
1115
|
-
addL1ToL2Messages(messages:
|
|
1640
|
+
addL1ToL2Messages(messages: InboxMessage[]): Promise<void> {
|
|
1116
1641
|
return this.store.addL1ToL2Messages(messages);
|
|
1117
1642
|
}
|
|
1118
|
-
getL1ToL2Messages(blockNumber:
|
|
1643
|
+
getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
|
|
1119
1644
|
return this.store.getL1ToL2Messages(blockNumber);
|
|
1120
1645
|
}
|
|
1121
1646
|
getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise<bigint | undefined> {
|
|
@@ -1124,11 +1649,8 @@ class ArchiverStoreHelper
|
|
|
1124
1649
|
getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
|
|
1125
1650
|
return this.store.getPrivateLogs(from, limit);
|
|
1126
1651
|
}
|
|
1127
|
-
getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
|
|
1128
|
-
return this.store.getLogsByTags(tags);
|
|
1129
|
-
}
|
|
1130
|
-
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
|
|
1131
|
-
return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
|
|
1652
|
+
getLogsByTags(tags: Fr[], logsPerTag?: number): Promise<TxScopedL2Log[][]> {
|
|
1653
|
+
return this.store.getLogsByTags(tags, logsPerTag);
|
|
1132
1654
|
}
|
|
1133
1655
|
getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
|
|
1134
1656
|
return this.store.getPublicLogs(filter);
|
|
@@ -1148,8 +1670,8 @@ class ArchiverStoreHelper
|
|
|
1148
1670
|
setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
|
|
1149
1671
|
return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
|
|
1150
1672
|
}
|
|
1151
|
-
|
|
1152
|
-
return this.store.
|
|
1673
|
+
setMessageSynchedL1Block(l1Block: L1BlockId): Promise<void> {
|
|
1674
|
+
return this.store.setMessageSynchedL1Block(l1Block);
|
|
1153
1675
|
}
|
|
1154
1676
|
getSynchPoint(): Promise<ArchiverL1SynchPoint> {
|
|
1155
1677
|
return this.store.getSynchPoint();
|
|
@@ -1160,22 +1682,41 @@ class ArchiverStoreHelper
|
|
|
1160
1682
|
getBytecodeCommitment(contractClassId: Fr): Promise<Fr | undefined> {
|
|
1161
1683
|
return this.store.getBytecodeCommitment(contractClassId);
|
|
1162
1684
|
}
|
|
1163
|
-
getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
|
|
1164
|
-
return this.store.getContractInstance(address);
|
|
1685
|
+
getContractInstance(address: AztecAddress, timestamp: UInt64): Promise<ContractInstanceWithAddress | undefined> {
|
|
1686
|
+
return this.store.getContractInstance(address, timestamp);
|
|
1165
1687
|
}
|
|
1166
1688
|
getContractClassIds(): Promise<Fr[]> {
|
|
1167
1689
|
return this.store.getContractClassIds();
|
|
1168
1690
|
}
|
|
1169
|
-
registerContractFunctionSignatures(
|
|
1170
|
-
return this.store.registerContractFunctionSignatures(
|
|
1691
|
+
registerContractFunctionSignatures(signatures: string[]): Promise<void> {
|
|
1692
|
+
return this.store.registerContractFunctionSignatures(signatures);
|
|
1171
1693
|
}
|
|
1172
|
-
|
|
1173
|
-
return this.store.
|
|
1694
|
+
getDebugFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
|
|
1695
|
+
return this.store.getDebugFunctionName(address, selector);
|
|
1174
1696
|
}
|
|
1175
1697
|
getTotalL1ToL2MessageCount(): Promise<bigint> {
|
|
1176
1698
|
return this.store.getTotalL1ToL2MessageCount();
|
|
1177
1699
|
}
|
|
1178
|
-
estimateSize(): Promise<{ mappingSize: number; actualSize: number; numItems: number }> {
|
|
1700
|
+
estimateSize(): Promise<{ mappingSize: number; physicalFileSize: number; actualSize: number; numItems: number }> {
|
|
1179
1701
|
return this.store.estimateSize();
|
|
1180
1702
|
}
|
|
1703
|
+
rollbackL1ToL2MessagesToL2Block(targetBlockNumber: number): Promise<void> {
|
|
1704
|
+
return this.store.rollbackL1ToL2MessagesToL2Block(targetBlockNumber);
|
|
1705
|
+
}
|
|
1706
|
+
iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {
|
|
1707
|
+
return this.store.iterateL1ToL2Messages(range);
|
|
1708
|
+
}
|
|
1709
|
+
removeL1ToL2Messages(startIndex: bigint): Promise<void> {
|
|
1710
|
+
return this.store.removeL1ToL2Messages(startIndex);
|
|
1711
|
+
}
|
|
1712
|
+
getLastL1ToL2Message(): Promise<InboxMessage | undefined> {
|
|
1713
|
+
return this.store.getLastL1ToL2Message();
|
|
1714
|
+
}
|
|
1715
|
+
getPendingChainValidationStatus(): Promise<ValidateBlockResult | undefined> {
|
|
1716
|
+
return this.store.getPendingChainValidationStatus();
|
|
1717
|
+
}
|
|
1718
|
+
setPendingChainValidationStatus(status: ValidateBlockResult | undefined): Promise<void> {
|
|
1719
|
+
this.#log.debug(`Setting pending chain validation status to valid ${status?.valid}`, status);
|
|
1720
|
+
return this.store.setPendingChainValidationStatus(status);
|
|
1721
|
+
}
|
|
1181
1722
|
}
|