@aztec/archiver 0.76.4 → 0.77.0-testnet-ignition.21
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 +1 -1
- package/dest/archiver/archiver.d.ts +22 -10
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +762 -713
- package/dest/archiver/archiver_store.d.ts +20 -7
- package/dest/archiver/archiver_store.d.ts.map +1 -1
- package/dest/archiver/archiver_store.js +4 -2
- package/dest/archiver/archiver_store_test_suite.d.ts +2 -2
- package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.js +398 -227
- package/dest/archiver/config.d.ts +1 -1
- package/dest/archiver/config.d.ts.map +1 -1
- package/dest/archiver/config.js +10 -12
- package/dest/archiver/data_retrieval.d.ts +17 -14
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +90 -88
- package/dest/archiver/errors.js +1 -2
- package/dest/archiver/index.d.ts +1 -1
- package/dest/archiver/index.d.ts.map +1 -1
- package/dest/archiver/index.js +0 -1
- package/dest/archiver/instrumentation.d.ts +3 -1
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/instrumentation.js +37 -17
- package/dest/archiver/kv_archiver_store/block_store.d.ts +5 -3
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +125 -130
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +45 -37
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +10 -2
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +54 -15
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +16 -9
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +143 -160
- package/dest/archiver/kv_archiver_store/log_store.d.ts +5 -3
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +296 -255
- package/dest/archiver/kv_archiver_store/message_store.d.ts +3 -3
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +45 -50
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +2 -2
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/nullifier_store.js +36 -43
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +2 -2
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +1 -1
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +17 -26
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +16 -7
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +1 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.js +287 -247
- package/dest/archiver/structs/data_retrieval.js +5 -2
- package/dest/archiver/structs/published.js +1 -2
- package/dest/factory.d.ts +20 -6
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +54 -30
- package/dest/index.js +0 -1
- package/dest/rpc/index.d.ts +2 -1
- package/dest/rpc/index.d.ts.map +1 -1
- package/dest/rpc/index.js +8 -4
- package/dest/test/index.js +0 -1
- package/dest/test/mock_archiver.d.ts +3 -2
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_archiver.js +8 -13
- package/dest/test/mock_l1_to_l2_message_source.d.ts +2 -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 +4 -4
- package/dest/test/mock_l2_block_source.d.ts +5 -3
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +71 -68
- package/package.json +15 -16
- package/src/archiver/archiver.ts +149 -89
- package/src/archiver/archiver_store.ts +27 -27
- package/src/archiver/archiver_store_test_suite.ts +22 -15
- package/src/archiver/config.ts +1 -1
- package/src/archiver/data_retrieval.ts +32 -44
- package/src/archiver/index.ts +1 -1
- package/src/archiver/instrumentation.ts +11 -1
- package/src/archiver/kv_archiver_store/block_store.ts +10 -4
- package/src/archiver/kv_archiver_store/contract_class_store.ts +9 -9
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +81 -3
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +44 -29
- package/src/archiver/kv_archiver_store/log_store.ts +56 -32
- package/src/archiver/kv_archiver_store/message_store.ts +4 -3
- package/src/archiver/kv_archiver_store/nullifier_store.ts +3 -2
- package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +3 -3
- package/src/archiver/memory_archiver_store/memory_archiver_store.ts +110 -57
- package/src/factory.ts +44 -25
- package/src/rpc/index.ts +2 -6
- package/src/test/mock_archiver.ts +3 -2
- package/src/test/mock_l1_to_l2_message_source.ts +2 -2
- package/src/test/mock_l2_block_source.ts +16 -15
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
5
7
|
import { createEthereumChain } from '@aztec/ethereum';
|
|
6
8
|
import { Fr } from '@aztec/foundation/fields';
|
|
7
9
|
import { createLogger } from '@aztec/foundation/log';
|
|
8
|
-
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
10
|
+
import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
|
|
9
11
|
import { count } from '@aztec/foundation/string';
|
|
10
12
|
import { elapsed } from '@aztec/foundation/timer';
|
|
11
13
|
import { InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
12
|
-
import { ContractClassRegisteredEvent, PrivateFunctionBroadcastedEvent, UnconstrainedFunctionBroadcastedEvent
|
|
13
|
-
import { ContractInstanceDeployedEvent } from '@aztec/protocol-contracts/instance-deployer';
|
|
14
|
+
import { ContractClassRegisteredEvent, PrivateFunctionBroadcastedEvent, UnconstrainedFunctionBroadcastedEvent } from '@aztec/protocol-contracts/class-registerer';
|
|
15
|
+
import { ContractInstanceDeployedEvent, ContractInstanceUpdatedEvent } from '@aztec/protocol-contracts/instance-deployer';
|
|
16
|
+
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
17
|
+
import { computePublicBytecodeCommitment, isValidPrivateFunctionMembershipProof, isValidUnconstrainedFunctionMembershipProof } from '@aztec/stdlib/contract';
|
|
18
|
+
import { getEpochAtSlot, getEpochNumberAtTimestamp, getSlotAtTimestamp, getSlotRangeForEpoch, getTimestampRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
|
|
14
19
|
import { Attributes, trackSpan } from '@aztec/telemetry-client';
|
|
20
|
+
import { EventEmitter } from 'events';
|
|
15
21
|
import groupBy from 'lodash.groupby';
|
|
16
|
-
import { createPublicClient, getContract, http
|
|
22
|
+
import { createPublicClient, fallback, getContract, http } from 'viem';
|
|
17
23
|
import { retrieveBlocksFromRollup, retrieveL1ToL2Messages } from './data_retrieval.js';
|
|
18
24
|
import { NoBlobBodiesFoundError } from './errors.js';
|
|
19
25
|
import { ArchiverInstrumentation } from './instrumentation.js';
|
|
@@ -21,642 +27,778 @@ import { ArchiverInstrumentation } from './instrumentation.js';
|
|
|
21
27
|
* Pulls L2 blocks in a non-blocking manner and provides interface for their retrieval.
|
|
22
28
|
* Responsible for handling robust L1 polling so that other components do not need to
|
|
23
29
|
* concern themselves with it.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
30
|
+
*/ export class Archiver extends EventEmitter {
|
|
31
|
+
publicClient;
|
|
32
|
+
l1Addresses;
|
|
33
|
+
dataStore;
|
|
34
|
+
config;
|
|
35
|
+
blobSinkClient;
|
|
36
|
+
instrumentation;
|
|
37
|
+
l1constants;
|
|
38
|
+
log;
|
|
39
|
+
/**
|
|
40
|
+
* A promise in which we will be continually fetching new L2 blocks.
|
|
41
|
+
*/ runningPromise;
|
|
42
|
+
rollup;
|
|
43
|
+
inbox;
|
|
44
|
+
store;
|
|
45
|
+
l1BlockNumber;
|
|
46
|
+
l1Timestamp;
|
|
47
|
+
tracer;
|
|
48
|
+
/**
|
|
49
|
+
* Creates a new instance of the Archiver.
|
|
50
|
+
* @param publicClient - A client for interacting with the Ethereum node.
|
|
51
|
+
* @param rollupAddress - Ethereum address of the rollup contract.
|
|
52
|
+
* @param inboxAddress - Ethereum address of the inbox contract.
|
|
53
|
+
* @param registryAddress - Ethereum address of the registry contract.
|
|
54
|
+
* @param pollingIntervalMs - The interval for polling for L1 logs (in milliseconds).
|
|
55
|
+
* @param store - An archiver data store for storage & retrieval of blocks, encrypted logs & contract data.
|
|
56
|
+
* @param log - A logger.
|
|
57
|
+
*/ constructor(publicClient, l1Addresses, dataStore, config, blobSinkClient, instrumentation, l1constants, log = createLogger('archiver')){
|
|
58
|
+
super(), this.publicClient = publicClient, this.l1Addresses = l1Addresses, this.dataStore = dataStore, this.config = config, this.blobSinkClient = blobSinkClient, this.instrumentation = instrumentation, this.l1constants = l1constants, this.log = log;
|
|
59
|
+
this.tracer = instrumentation.tracer;
|
|
60
|
+
this.store = new ArchiverStoreHelper(dataStore);
|
|
61
|
+
this.rollup = getContract({
|
|
62
|
+
address: l1Addresses.rollupAddress.toString(),
|
|
63
|
+
abi: RollupAbi,
|
|
64
|
+
client: publicClient
|
|
65
|
+
});
|
|
66
|
+
this.inbox = getContract({
|
|
67
|
+
address: l1Addresses.inboxAddress.toString(),
|
|
68
|
+
abi: InboxAbi,
|
|
69
|
+
client: publicClient
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates a new instance of the Archiver and blocks until it syncs from chain.
|
|
74
|
+
* @param config - The archiver's desired configuration.
|
|
75
|
+
* @param archiverStore - The backing store for the archiver.
|
|
76
|
+
* @param blockUntilSynced - If true, blocks until the archiver has fully synced.
|
|
77
|
+
* @returns - An instance of the archiver.
|
|
78
|
+
*/ static async createAndSync(config, archiverStore, deps, blockUntilSynced = true) {
|
|
79
|
+
const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
80
|
+
const publicClient = createPublicClient({
|
|
81
|
+
chain: chain.chainInfo,
|
|
82
|
+
transport: fallback(config.l1RpcUrls.map((url)=>http(url))),
|
|
83
|
+
pollingInterval: config.viemPollingIntervalMS
|
|
84
|
+
});
|
|
85
|
+
const rollup = getContract({
|
|
86
|
+
address: config.l1Contracts.rollupAddress.toString(),
|
|
87
|
+
abi: RollupAbi,
|
|
88
|
+
client: publicClient
|
|
89
|
+
});
|
|
90
|
+
const [l1StartBlock, l1GenesisTime] = await Promise.all([
|
|
91
|
+
rollup.read.L1_BLOCK_AT_GENESIS(),
|
|
92
|
+
rollup.read.getGenesisTime()
|
|
93
|
+
]);
|
|
94
|
+
const { aztecEpochDuration: epochDuration, aztecSlotDuration: slotDuration, ethereumSlotDuration } = config;
|
|
95
|
+
const archiver = new Archiver(publicClient, config.l1Contracts, archiverStore, {
|
|
96
|
+
pollingIntervalMs: config.archiverPollingIntervalMS ?? 10_000,
|
|
97
|
+
batchSize: config.archiverBatchSize ?? 100
|
|
98
|
+
}, deps.blobSinkClient, await ArchiverInstrumentation.new(deps.telemetry, ()=>archiverStore.estimateSize()), {
|
|
99
|
+
l1StartBlock,
|
|
100
|
+
l1GenesisTime,
|
|
101
|
+
epochDuration,
|
|
102
|
+
slotDuration,
|
|
103
|
+
ethereumSlotDuration
|
|
104
|
+
});
|
|
105
|
+
await archiver.start(blockUntilSynced);
|
|
106
|
+
return archiver;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Starts sync process.
|
|
110
|
+
* @param blockUntilSynced - If true, blocks until the archiver has fully synced.
|
|
111
|
+
*/ async start(blockUntilSynced) {
|
|
112
|
+
if (this.runningPromise) {
|
|
113
|
+
throw new Error('Archiver is already running');
|
|
114
|
+
}
|
|
115
|
+
if (blockUntilSynced) {
|
|
116
|
+
await this.syncSafe(blockUntilSynced);
|
|
117
|
+
}
|
|
118
|
+
this.runningPromise = new RunningPromise(()=>this.sync(false), this.log, this.config.pollingIntervalMs, makeLoggingErrorHandler(this.log, // Ignored errors will not log to the console
|
|
119
|
+
// We ignore NoBlobBodiesFound as the message may not have been passed to the blob sink yet
|
|
120
|
+
NoBlobBodiesFoundError));
|
|
121
|
+
this.runningPromise.start();
|
|
122
|
+
}
|
|
123
|
+
async syncSafe(initialRun) {
|
|
124
|
+
try {
|
|
125
|
+
await this.sync(initialRun);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
this.log.error('Error during sync', {
|
|
128
|
+
error
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Fetches logs from L1 contracts and processes them.
|
|
134
|
+
*/ async sync(initialRun) {
|
|
135
|
+
/**
|
|
136
|
+
* We keep track of three "pointers" to L1 blocks:
|
|
137
|
+
* 1. the last L1 block that published an L2 block
|
|
138
|
+
* 2. the last L1 block that added L1 to L2 messages
|
|
139
|
+
* 3. the last L1 block that cancelled L1 to L2 messages
|
|
140
|
+
*
|
|
141
|
+
* We do this to deal with L1 data providers that are eventually consistent (e.g. Infura).
|
|
142
|
+
* We guard against seeing block X with no data at one point, and later, the provider processes the block and it has data.
|
|
143
|
+
* The archiver will stay back, until there's data on L1 that will move the pointers forward.
|
|
144
|
+
*
|
|
145
|
+
* This code does not handle reorgs.
|
|
146
|
+
*/ const { l1StartBlock } = this.l1constants;
|
|
147
|
+
const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
|
|
148
|
+
const currentL1BlockNumber = await this.publicClient.getBlockNumber();
|
|
149
|
+
if (initialRun) {
|
|
150
|
+
this.log.info(`Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${Math.min(Number(blocksSynchedTo), Number(messagesSynchedTo))} to current L1 block ${currentL1BlockNumber}`);
|
|
151
|
+
}
|
|
152
|
+
// ********** Ensuring Consistency of data pulled from L1 **********
|
|
153
|
+
/**
|
|
154
|
+
* There are a number of calls in this sync operation to L1 for retrieving
|
|
155
|
+
* events and transaction data. There are a couple of things we need to bear in mind
|
|
156
|
+
* to ensure that data is read exactly once.
|
|
157
|
+
*
|
|
158
|
+
* The first is the problem of eventually consistent ETH service providers like Infura.
|
|
159
|
+
* Each L1 read operation will query data from the last L1 block that it saw emit its kind of data.
|
|
160
|
+
* (so pending L1 to L2 messages will read from the last L1 block that emitted a message and so on)
|
|
161
|
+
* This will mean the archiver will lag behind L1 and will only advance when there's L2-relevant activity on the chain.
|
|
162
|
+
*
|
|
163
|
+
* The second is that in between the various calls to L1, the block number can move meaning some
|
|
164
|
+
* of the following calls will return data for blocks that were not present during earlier calls.
|
|
165
|
+
* To combat this for the time being we simply ensure that all data retrieval methods only retrieve
|
|
166
|
+
* data up to the currentBlockNumber captured at the top of this function. We might want to improve on this
|
|
167
|
+
* in future but for the time being it should give us the guarantees that we need
|
|
168
|
+
*/ // ********** Events that are processed per L1 block **********
|
|
169
|
+
await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber);
|
|
170
|
+
// Store latest l1 block number and timestamp seen. Used for epoch and slots calculations.
|
|
171
|
+
if (!this.l1BlockNumber || this.l1BlockNumber < currentL1BlockNumber) {
|
|
172
|
+
this.l1Timestamp = (await this.publicClient.getBlock({
|
|
173
|
+
blockNumber: currentL1BlockNumber
|
|
174
|
+
})).timestamp;
|
|
175
|
+
this.l1BlockNumber = currentL1BlockNumber;
|
|
176
|
+
}
|
|
177
|
+
// ********** Events that are processed per L2 block **********
|
|
178
|
+
if (currentL1BlockNumber > blocksSynchedTo) {
|
|
179
|
+
// First we retrieve new L2 blocks
|
|
180
|
+
const { provenBlockNumber } = await this.handleL2blocks(blocksSynchedTo, currentL1BlockNumber);
|
|
181
|
+
// And then we prune the current epoch if it'd reorg on next submission.
|
|
182
|
+
// Note that we don't do this before retrieving L2 blocks because we may need to retrieve
|
|
183
|
+
// blocks from more than 2 epochs ago, so we want to make sure we have the latest view of
|
|
184
|
+
// the chain locally before we start unwinding stuff. This can be optimized by figuring out
|
|
185
|
+
// up to which point we're pruning, and then requesting L2 blocks up to that point only.
|
|
186
|
+
await this.handleEpochPrune(provenBlockNumber, currentL1BlockNumber);
|
|
187
|
+
this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
|
|
188
|
+
}
|
|
189
|
+
if (initialRun) {
|
|
190
|
+
this.log.info(`Initial archiver sync to L1 block ${currentL1BlockNumber} complete.`, {
|
|
191
|
+
l1BlockNumber: currentL1BlockNumber,
|
|
192
|
+
syncPoint: await this.store.getSynchPoint(),
|
|
193
|
+
...await this.getL2Tips()
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** Queries the rollup contract on whether a prune can be executed on the immediatenext L1 block. */ async canPrune(currentL1BlockNumber) {
|
|
198
|
+
const time = (this.l1Timestamp ?? 0n) + BigInt(this.l1constants.ethereumSlotDuration);
|
|
199
|
+
return await this.rollup.read.canPruneAtTime([
|
|
200
|
+
time
|
|
201
|
+
], {
|
|
202
|
+
blockNumber: currentL1BlockNumber
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/** Checks if there'd be a reorg for the next block submission and start pruning now. */ async handleEpochPrune(provenBlockNumber, currentL1BlockNumber) {
|
|
206
|
+
const localPendingBlockNumber = BigInt(await this.getBlockNumber());
|
|
207
|
+
const canPrune = localPendingBlockNumber > provenBlockNumber && await this.canPrune(currentL1BlockNumber);
|
|
208
|
+
if (canPrune) {
|
|
209
|
+
const localPendingSlotNumber = await this.getL2SlotNumber();
|
|
210
|
+
const localPendingEpochNumber = getEpochAtSlot(localPendingSlotNumber, this.l1constants);
|
|
211
|
+
// Emit an event for listening services to react to the chain prune
|
|
212
|
+
this.emit(L2BlockSourceEvents.L2PruneDetected, {
|
|
213
|
+
type: L2BlockSourceEvents.L2PruneDetected,
|
|
214
|
+
blockNumber: localPendingBlockNumber,
|
|
215
|
+
slotNumber: localPendingSlotNumber,
|
|
216
|
+
epochNumber: localPendingEpochNumber
|
|
217
|
+
});
|
|
218
|
+
const blocksToUnwind = localPendingBlockNumber - provenBlockNumber;
|
|
219
|
+
this.log.debug(`L2 prune from ${provenBlockNumber + 1n} to ${localPendingBlockNumber} will occur on next block submission.`);
|
|
220
|
+
await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
|
|
221
|
+
this.log.warn(`Unwound ${count(blocksToUnwind, 'block')} from L2 block ${localPendingBlockNumber} ` + `to ${provenBlockNumber} due to predicted reorg at L1 block ${currentL1BlockNumber}. ` + `Updated L2 latest block is ${await this.getBlockNumber()}.`);
|
|
222
|
+
this.instrumentation.processPrune();
|
|
223
|
+
// TODO(palla/reorg): Do we need to set the block synched L1 block number here?
|
|
224
|
+
// Seems like the next iteration should handle this.
|
|
225
|
+
// await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
nextRange(end, limit) {
|
|
229
|
+
const batchSize = this.config.batchSize * this.l1constants.slotDuration / this.l1constants.ethereumSlotDuration;
|
|
230
|
+
const nextStart = end + 1n;
|
|
231
|
+
const nextEnd = nextStart + BigInt(batchSize);
|
|
232
|
+
if (nextEnd > limit) {
|
|
233
|
+
return [
|
|
234
|
+
nextStart,
|
|
235
|
+
limit
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
return [
|
|
239
|
+
nextStart,
|
|
240
|
+
nextEnd
|
|
241
|
+
];
|
|
242
|
+
}
|
|
243
|
+
async handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber) {
|
|
244
|
+
this.log.trace(`Handling L1 to L2 messages from ${messagesSynchedTo} to ${currentL1BlockNumber}.`);
|
|
245
|
+
if (currentL1BlockNumber <= messagesSynchedTo) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const localTotalMessageCount = await this.store.getTotalL1ToL2MessageCount();
|
|
249
|
+
const destinationTotalMessageCount = await this.inbox.read.totalMessagesInserted();
|
|
250
|
+
if (localTotalMessageCount === destinationTotalMessageCount) {
|
|
251
|
+
await this.store.setMessageSynchedL1BlockNumber(currentL1BlockNumber);
|
|
252
|
+
this.log.trace(`Retrieved no new L1 to L2 messages between L1 blocks ${messagesSynchedTo + 1n} and ${currentL1BlockNumber}.`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// Retrieve messages in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
|
|
256
|
+
let searchStartBlock = messagesSynchedTo;
|
|
257
|
+
let searchEndBlock = messagesSynchedTo;
|
|
258
|
+
do {
|
|
259
|
+
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
260
|
+
this.log.trace(`Retrieving L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`);
|
|
261
|
+
const retrievedL1ToL2Messages = await retrieveL1ToL2Messages(this.inbox, searchStartBlock, searchEndBlock);
|
|
262
|
+
this.log.verbose(`Retrieved ${retrievedL1ToL2Messages.retrievedData.length} new L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`);
|
|
263
|
+
await this.store.addL1ToL2Messages(retrievedL1ToL2Messages);
|
|
264
|
+
for (const msg of retrievedL1ToL2Messages.retrievedData){
|
|
265
|
+
this.log.debug(`Downloaded L1 to L2 message`, {
|
|
266
|
+
leaf: msg.leaf.toString(),
|
|
267
|
+
index: msg.index
|
|
80
268
|
});
|
|
81
|
-
const [l1StartBlock, l1GenesisTime] = await Promise.all([
|
|
82
|
-
rollup.read.L1_BLOCK_AT_GENESIS(),
|
|
83
|
-
rollup.read.getGenesisTime(),
|
|
84
|
-
]);
|
|
85
|
-
const { aztecEpochDuration: epochDuration, aztecSlotDuration: slotDuration, ethereumSlotDuration } = config;
|
|
86
|
-
const archiver = new _a(publicClient, config.l1Contracts, archiverStore, {
|
|
87
|
-
pollingIntervalMs: config.archiverPollingIntervalMS ?? 10000,
|
|
88
|
-
batchSize: config.archiverBatchSize ?? 100,
|
|
89
|
-
}, deps.blobSinkClient, await ArchiverInstrumentation.new(deps.telemetry, () => archiverStore.estimateSize()), { l1StartBlock, l1GenesisTime, epochDuration, slotDuration, ethereumSlotDuration });
|
|
90
|
-
await archiver.start(blockUntilSynced);
|
|
91
|
-
return archiver;
|
|
92
269
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
270
|
+
}while (searchEndBlock < currentL1BlockNumber)
|
|
271
|
+
}
|
|
272
|
+
async handleL2blocks(blocksSynchedTo, currentL1BlockNumber) {
|
|
273
|
+
const localPendingBlockNumber = BigInt(await this.getBlockNumber());
|
|
274
|
+
const [provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive, archiveForLocalPendingBlockNumber, provenEpochNumber] = await this.rollup.read.status([
|
|
275
|
+
localPendingBlockNumber
|
|
276
|
+
], {
|
|
277
|
+
blockNumber: currentL1BlockNumber
|
|
278
|
+
});
|
|
279
|
+
const updateProvenBlock = async ()=>{
|
|
280
|
+
const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber));
|
|
281
|
+
// Sanity check. I've hit what seems to be a state where the proven block is set to a value greater than the latest
|
|
282
|
+
// synched block when requesting L2Tips from the archiver. This is the only place where the proven block is set.
|
|
283
|
+
const synched = await this.store.getSynchedL2BlockNumber();
|
|
284
|
+
if (localBlockForDestinationProvenBlockNumber && synched < localBlockForDestinationProvenBlockNumber?.number) {
|
|
285
|
+
this.log.error(`Hit local block greater than last synched block: ${localBlockForDestinationProvenBlockNumber.number} > ${synched}`);
|
|
286
|
+
}
|
|
287
|
+
if (localBlockForDestinationProvenBlockNumber && provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString()) {
|
|
288
|
+
const [localProvenEpochNumber, localProvenBlockNumber] = await Promise.all([
|
|
289
|
+
this.store.getProvenL2EpochNumber(),
|
|
290
|
+
this.store.getProvenL2BlockNumber()
|
|
108
291
|
]);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* We keep track of three "pointers" to L1 blocks:
|
|
117
|
-
* 1. the last L1 block that published an L2 block
|
|
118
|
-
* 2. the last L1 block that added L1 to L2 messages
|
|
119
|
-
* 3. the last L1 block that cancelled L1 to L2 messages
|
|
120
|
-
*
|
|
121
|
-
* We do this to deal with L1 data providers that are eventually consistent (e.g. Infura).
|
|
122
|
-
* We guard against seeing block X with no data at one point, and later, the provider processes the block and it has data.
|
|
123
|
-
* The archiver will stay back, until there's data on L1 that will move the pointers forward.
|
|
124
|
-
*
|
|
125
|
-
* This code does not handle reorgs.
|
|
126
|
-
*/
|
|
127
|
-
const { l1StartBlock } = this.l1constants;
|
|
128
|
-
const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
|
|
129
|
-
const currentL1BlockNumber = await this.publicClient.getBlockNumber();
|
|
130
|
-
if (initialRun) {
|
|
131
|
-
this.log.info(`Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${Math.min(Number(blocksSynchedTo), Number(messagesSynchedTo))} to current L1 block ${currentL1BlockNumber}`);
|
|
132
|
-
}
|
|
133
|
-
// ********** Ensuring Consistency of data pulled from L1 **********
|
|
134
|
-
/**
|
|
135
|
-
* There are a number of calls in this sync operation to L1 for retrieving
|
|
136
|
-
* events and transaction data. There are a couple of things we need to bear in mind
|
|
137
|
-
* to ensure that data is read exactly once.
|
|
138
|
-
*
|
|
139
|
-
* The first is the problem of eventually consistent ETH service providers like Infura.
|
|
140
|
-
* Each L1 read operation will query data from the last L1 block that it saw emit its kind of data.
|
|
141
|
-
* (so pending L1 to L2 messages will read from the last L1 block that emitted a message and so on)
|
|
142
|
-
* This will mean the archiver will lag behind L1 and will only advance when there's L2-relevant activity on the chain.
|
|
143
|
-
*
|
|
144
|
-
* The second is that in between the various calls to L1, the block number can move meaning some
|
|
145
|
-
* of the following calls will return data for blocks that were not present during earlier calls.
|
|
146
|
-
* To combat this for the time being we simply ensure that all data retrieval methods only retrieve
|
|
147
|
-
* data up to the currentBlockNumber captured at the top of this function. We might want to improve on this
|
|
148
|
-
* in future but for the time being it should give us the guarantees that we need
|
|
149
|
-
*/
|
|
150
|
-
// ********** Events that are processed per L1 block **********
|
|
151
|
-
await this.handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber);
|
|
152
|
-
// Store latest l1 block number and timestamp seen. Used for epoch and slots calculations.
|
|
153
|
-
if (!this.l1BlockNumber || this.l1BlockNumber < currentL1BlockNumber) {
|
|
154
|
-
this.l1Timestamp = (await this.publicClient.getBlock({ blockNumber: currentL1BlockNumber })).timestamp;
|
|
155
|
-
this.l1BlockNumber = currentL1BlockNumber;
|
|
156
|
-
}
|
|
157
|
-
// ********** Events that are processed per L2 block **********
|
|
158
|
-
if (currentL1BlockNumber > blocksSynchedTo) {
|
|
159
|
-
// First we retrieve new L2 blocks
|
|
160
|
-
const { provenBlockNumber } = await this.handleL2blocks(blocksSynchedTo, currentL1BlockNumber);
|
|
161
|
-
// And then we prune the current epoch if it'd reorg on next submission.
|
|
162
|
-
// Note that we don't do this before retrieving L2 blocks because we may need to retrieve
|
|
163
|
-
// blocks from more than 2 epochs ago, so we want to make sure we have the latest view of
|
|
164
|
-
// the chain locally before we start unwinding stuff. This can be optimized by figuring out
|
|
165
|
-
// up to which point we're pruning, and then requesting L2 blocks up to that point only.
|
|
166
|
-
await this.handleEpochPrune(provenBlockNumber, currentL1BlockNumber);
|
|
167
|
-
}
|
|
168
|
-
if (initialRun) {
|
|
169
|
-
this.log.info(`Initial archiver sync to L1 block ${currentL1BlockNumber} complete.`, {
|
|
170
|
-
l1BlockNumber: currentL1BlockNumber,
|
|
171
|
-
...(await this.getL2Tips()),
|
|
292
|
+
if (localProvenEpochNumber !== Number(provenEpochNumber) || localProvenBlockNumber !== Number(provenBlockNumber)) {
|
|
293
|
+
await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
|
|
294
|
+
await this.store.setProvenL2EpochNumber(Number(provenEpochNumber));
|
|
295
|
+
this.log.info(`Updated proven chain to block ${provenBlockNumber} (epoch ${provenEpochNumber})`, {
|
|
296
|
+
provenBlockNumber,
|
|
297
|
+
provenEpochNumber
|
|
172
298
|
});
|
|
173
299
|
}
|
|
174
300
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return [nextStart, nextEnd];
|
|
202
|
-
}
|
|
203
|
-
async handleL1ToL2Messages(messagesSynchedTo, currentL1BlockNumber) {
|
|
204
|
-
this.log.trace(`Handling L1 to L2 messages from ${messagesSynchedTo} to ${currentL1BlockNumber}.`);
|
|
205
|
-
if (currentL1BlockNumber <= messagesSynchedTo) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
const localTotalMessageCount = await this.store.getTotalL1ToL2MessageCount();
|
|
209
|
-
const destinationTotalMessageCount = await this.inbox.read.totalMessagesInserted();
|
|
210
|
-
if (localTotalMessageCount === destinationTotalMessageCount) {
|
|
211
|
-
await this.store.setMessageSynchedL1BlockNumber(currentL1BlockNumber);
|
|
212
|
-
this.log.trace(`Retrieved no new L1 to L2 messages between L1 blocks ${messagesSynchedTo + 1n} and ${currentL1BlockNumber}.`);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
// Retrieve messages in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
|
|
216
|
-
let searchStartBlock = messagesSynchedTo;
|
|
217
|
-
let searchEndBlock = messagesSynchedTo;
|
|
218
|
-
do {
|
|
219
|
-
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
220
|
-
this.log.trace(`Retrieving L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`);
|
|
221
|
-
const retrievedL1ToL2Messages = await retrieveL1ToL2Messages(this.inbox, searchStartBlock, searchEndBlock);
|
|
222
|
-
this.log.verbose(`Retrieved ${retrievedL1ToL2Messages.retrievedData.length} new L1 to L2 messages between L1 blocks ${searchStartBlock} and ${searchEndBlock}.`);
|
|
223
|
-
await this.store.addL1ToL2Messages(retrievedL1ToL2Messages);
|
|
224
|
-
for (const msg of retrievedL1ToL2Messages.retrievedData) {
|
|
225
|
-
this.log.debug(`Downloaded L1 to L2 message`, { leaf: msg.leaf.toString(), index: msg.index });
|
|
226
|
-
}
|
|
227
|
-
} while (searchEndBlock < currentL1BlockNumber);
|
|
228
|
-
}
|
|
229
|
-
async handleL2blocks(blocksSynchedTo, currentL1BlockNumber) {
|
|
230
|
-
const localPendingBlockNumber = BigInt(await this.getBlockNumber());
|
|
231
|
-
const [provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive, archiveForLocalPendingBlockNumber, provenEpochNumber,] = await this.rollup.read.status([localPendingBlockNumber], { blockNumber: currentL1BlockNumber });
|
|
232
|
-
const updateProvenBlock = async () => {
|
|
233
|
-
const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber));
|
|
234
|
-
// Sanity check. I've hit what seems to be a state where the proven block is set to a value greater than the latest
|
|
235
|
-
// synched block when requesting L2Tips from the archiver. This is the only place where the proven block is set.
|
|
236
|
-
const synched = await this.store.getSynchedL2BlockNumber();
|
|
237
|
-
if (localBlockForDestinationProvenBlockNumber && synched < localBlockForDestinationProvenBlockNumber?.number) {
|
|
238
|
-
this.log.error(`Hit local block greater than last synched block: ${localBlockForDestinationProvenBlockNumber.number} > ${synched}`);
|
|
239
|
-
}
|
|
240
|
-
if (localBlockForDestinationProvenBlockNumber &&
|
|
241
|
-
provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString()) {
|
|
242
|
-
const [localProvenEpochNumber, localProvenBlockNumber] = await Promise.all([
|
|
243
|
-
this.store.getProvenL2EpochNumber(),
|
|
244
|
-
this.store.getProvenL2BlockNumber(),
|
|
245
|
-
]);
|
|
246
|
-
if (localProvenEpochNumber !== Number(provenEpochNumber) ||
|
|
247
|
-
localProvenBlockNumber !== Number(provenBlockNumber)) {
|
|
248
|
-
await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
|
|
249
|
-
await this.store.setProvenL2EpochNumber(Number(provenEpochNumber));
|
|
250
|
-
this.log.info(`Updated proven chain to block ${provenBlockNumber} (epoch ${provenEpochNumber})`, {
|
|
251
|
-
provenBlockNumber,
|
|
252
|
-
provenEpochNumber,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
this.instrumentation.updateLastProvenBlock(Number(provenBlockNumber));
|
|
301
|
+
this.instrumentation.updateLastProvenBlock(Number(provenBlockNumber));
|
|
302
|
+
};
|
|
303
|
+
// This is an edge case that we only hit if there are no proposed blocks.
|
|
304
|
+
// If we have 0 blocks locally and there are no blocks onchain there is nothing to do.
|
|
305
|
+
const noBlocks = localPendingBlockNumber === 0n && pendingBlockNumber === 0n;
|
|
306
|
+
if (noBlocks) {
|
|
307
|
+
await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
308
|
+
this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
309
|
+
return {
|
|
310
|
+
provenBlockNumber
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
await updateProvenBlock();
|
|
314
|
+
// Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
|
|
315
|
+
// are any state that could be impacted by it. If we have no blocks, there is no impact.
|
|
316
|
+
if (localPendingBlockNumber > 0) {
|
|
317
|
+
const localPendingBlock = await this.getBlock(Number(localPendingBlockNumber));
|
|
318
|
+
if (localPendingBlock === undefined) {
|
|
319
|
+
throw new Error(`Missing block ${localPendingBlockNumber}`);
|
|
320
|
+
}
|
|
321
|
+
const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingBlock.archive.root.toString();
|
|
322
|
+
if (noBlockSinceLast) {
|
|
323
|
+
await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
324
|
+
this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
325
|
+
return {
|
|
326
|
+
provenBlockNumber
|
|
257
327
|
};
|
|
258
|
-
// This is an edge case that we only hit if there are no proposed blocks.
|
|
259
|
-
// If we have 0 blocks locally and there are no blocks onchain there is nothing to do.
|
|
260
|
-
const noBlocks = localPendingBlockNumber === 0n && pendingBlockNumber === 0n;
|
|
261
|
-
if (noBlocks) {
|
|
262
|
-
await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
263
|
-
this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
264
|
-
return { provenBlockNumber };
|
|
265
|
-
}
|
|
266
|
-
await updateProvenBlock();
|
|
267
|
-
// Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
|
|
268
|
-
// are any state that could be impacted by it. If we have no blocks, there is no impact.
|
|
269
|
-
if (localPendingBlockNumber > 0) {
|
|
270
|
-
const localPendingBlock = await this.getBlock(Number(localPendingBlockNumber));
|
|
271
|
-
if (localPendingBlock === undefined) {
|
|
272
|
-
throw new Error(`Missing block ${localPendingBlockNumber}`);
|
|
273
|
-
}
|
|
274
|
-
const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingBlock.archive.root.toString();
|
|
275
|
-
if (noBlockSinceLast) {
|
|
276
|
-
await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
277
|
-
this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
278
|
-
return { provenBlockNumber };
|
|
279
|
-
}
|
|
280
|
-
const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingBlock.archive.root.toString();
|
|
281
|
-
if (!localPendingBlockInChain) {
|
|
282
|
-
// If our local pending block tip is not in the chain on L1 a "prune" must have happened
|
|
283
|
-
// or the L1 have reorged.
|
|
284
|
-
// In any case, we have to figure out how far into the past the action will take us.
|
|
285
|
-
// For simplicity here, we will simply rewind until we end in a block that is also on the chain on L1.
|
|
286
|
-
this.log.debug(`L2 prune has been detected.`);
|
|
287
|
-
let tipAfterUnwind = localPendingBlockNumber;
|
|
288
|
-
while (true) {
|
|
289
|
-
const candidateBlock = await this.getBlock(Number(tipAfterUnwind));
|
|
290
|
-
if (candidateBlock === undefined) {
|
|
291
|
-
break;
|
|
292
|
-
}
|
|
293
|
-
const archiveAtContract = await this.rollup.read.archiveAt([BigInt(candidateBlock.number)]);
|
|
294
|
-
if (archiveAtContract === candidateBlock.archive.root.toString()) {
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
tipAfterUnwind--;
|
|
298
|
-
}
|
|
299
|
-
const blocksToUnwind = localPendingBlockNumber - tipAfterUnwind;
|
|
300
|
-
await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
|
|
301
|
-
this.log.warn(`Unwound ${count(blocksToUnwind, 'block')} from L2 block ${localPendingBlockNumber} ` +
|
|
302
|
-
`due to mismatched block hashes at L1 block ${currentL1BlockNumber}. ` +
|
|
303
|
-
`Updated L2 latest block is ${await this.getBlockNumber()}.`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
// Retrieve L2 blocks in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
|
|
307
|
-
// computed using the L2 block time vs the L1 block time.
|
|
308
|
-
let searchStartBlock = blocksSynchedTo;
|
|
309
|
-
let searchEndBlock = blocksSynchedTo;
|
|
310
|
-
do {
|
|
311
|
-
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
312
|
-
this.log.trace(`Retrieving L2 blocks from L1 block ${searchStartBlock} to ${searchEndBlock}`);
|
|
313
|
-
// TODO(md): Retreive from blob sink then from consensus client, then from peers
|
|
314
|
-
const retrievedBlocks = await retrieveBlocksFromRollup(this.rollup, this.publicClient, this.blobSinkClient, searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
|
|
315
|
-
searchEndBlock, this.log);
|
|
316
|
-
if (retrievedBlocks.length === 0) {
|
|
317
|
-
// We are not calling `setBlockSynchedL1BlockNumber` because it may cause sync issues if based off infura.
|
|
318
|
-
// See further details in earlier comments.
|
|
319
|
-
this.log.trace(`Retrieved no new L2 blocks from L1 block ${searchStartBlock} to ${searchEndBlock}`);
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
const lastProcessedL1BlockNumber = retrievedBlocks[retrievedBlocks.length - 1].l1.blockNumber;
|
|
323
|
-
this.log.debug(`Retrieved ${retrievedBlocks.length} new L2 blocks between L1 blocks ${searchStartBlock} and ${searchEndBlock} with last processed L1 block ${lastProcessedL1BlockNumber}.`);
|
|
324
|
-
for (const block of retrievedBlocks) {
|
|
325
|
-
this.log.debug(`Ingesting new L2 block ${block.data.number} with ${block.data.body.txEffects.length} txs`, {
|
|
326
|
-
blockHash: block.data.hash(),
|
|
327
|
-
l1BlockNumber: block.l1.blockNumber,
|
|
328
|
-
...block.data.header.globalVariables.toInspect(),
|
|
329
|
-
...block.data.getStats(),
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
const [processDuration] = await elapsed(() => this.store.addBlocks(retrievedBlocks));
|
|
333
|
-
this.instrumentation.processNewBlocks(processDuration / retrievedBlocks.length, retrievedBlocks.map(b => b.data));
|
|
334
|
-
for (const block of retrievedBlocks) {
|
|
335
|
-
this.log.info(`Downloaded L2 block ${block.data.number}`, {
|
|
336
|
-
blockHash: block.data.hash(),
|
|
337
|
-
blockNumber: block.data.number,
|
|
338
|
-
txCount: block.data.body.txEffects.length,
|
|
339
|
-
globalVariables: block.data.header.globalVariables.toInspect(),
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
} while (searchEndBlock < currentL1BlockNumber);
|
|
343
|
-
// Important that we update AFTER inserting the blocks.
|
|
344
|
-
await updateProvenBlock();
|
|
345
|
-
return { provenBlockNumber };
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Stops the archiver.
|
|
349
|
-
* @returns A promise signalling completion of the stop process.
|
|
350
|
-
*/
|
|
351
|
-
async stop() {
|
|
352
|
-
this.log.debug('Stopping...');
|
|
353
|
-
await this.runningPromise?.stop();
|
|
354
|
-
this.log.info('Stopped.');
|
|
355
|
-
return Promise.resolve();
|
|
356
328
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
throw new Error('L1 block number not yet available. Complete an initial sync first.');
|
|
370
|
-
}
|
|
371
|
-
return l1BlockNumber;
|
|
372
|
-
}
|
|
373
|
-
getL1Timestamp() {
|
|
374
|
-
const l1Timestamp = this.l1Timestamp;
|
|
375
|
-
if (!l1Timestamp) {
|
|
376
|
-
throw new Error('L1 timestamp not yet available. Complete an initial sync first.');
|
|
377
|
-
}
|
|
378
|
-
return l1Timestamp;
|
|
379
|
-
}
|
|
380
|
-
getL2SlotNumber() {
|
|
381
|
-
return Promise.resolve(getSlotAtTimestamp(this.getL1Timestamp(), this.l1constants));
|
|
382
|
-
}
|
|
383
|
-
getL2EpochNumber() {
|
|
384
|
-
return Promise.resolve(getEpochNumberAtTimestamp(this.getL1Timestamp(), this.l1constants));
|
|
385
|
-
}
|
|
386
|
-
async getBlocksForEpoch(epochNumber) {
|
|
387
|
-
const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
|
|
388
|
-
const blocks = [];
|
|
389
|
-
// Walk the list of blocks backwards and filter by slots matching the requested epoch.
|
|
390
|
-
// We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
|
|
391
|
-
let block = await this.getBlock(await this.store.getSynchedL2BlockNumber());
|
|
392
|
-
const slot = (b) => b.header.globalVariables.slotNumber.toBigInt();
|
|
393
|
-
while (block && slot(block) >= start) {
|
|
394
|
-
if (slot(block) <= end) {
|
|
395
|
-
blocks.push(block);
|
|
329
|
+
const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingBlock.archive.root.toString();
|
|
330
|
+
if (!localPendingBlockInChain) {
|
|
331
|
+
// If our local pending block tip is not in the chain on L1 a "prune" must have happened
|
|
332
|
+
// or the L1 have reorged.
|
|
333
|
+
// In any case, we have to figure out how far into the past the action will take us.
|
|
334
|
+
// For simplicity here, we will simply rewind until we end in a block that is also on the chain on L1.
|
|
335
|
+
this.log.debug(`L2 prune has been detected.`);
|
|
336
|
+
let tipAfterUnwind = localPendingBlockNumber;
|
|
337
|
+
while(true){
|
|
338
|
+
const candidateBlock = await this.getBlock(Number(tipAfterUnwind));
|
|
339
|
+
if (candidateBlock === undefined) {
|
|
340
|
+
break;
|
|
396
341
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const slot = header?.globalVariables.slotNumber.toBigInt();
|
|
405
|
-
const [_startSlot, endSlot] = getSlotRangeForEpoch(epochNumber, this.l1constants);
|
|
406
|
-
if (slot && slot >= endSlot) {
|
|
407
|
-
return true;
|
|
408
|
-
}
|
|
409
|
-
// If we haven't run an initial sync, just return false.
|
|
410
|
-
const l1Timestamp = this.l1Timestamp;
|
|
411
|
-
if (l1Timestamp === undefined) {
|
|
412
|
-
return false;
|
|
413
|
-
}
|
|
414
|
-
// If not, the epoch may also be complete if the L2 slot has passed without a block
|
|
415
|
-
// We compute this based on the end timestamp for the given epoch and the timestamp of the last L1 block
|
|
416
|
-
const [_startTimestamp, endTimestamp] = getTimestampRangeForEpoch(epochNumber, this.l1constants);
|
|
417
|
-
// For this computation, we throw in a few extra seconds just for good measure,
|
|
418
|
-
// since we know the next L1 block won't be mined within this range. Remember that
|
|
419
|
-
// l1timestamp is the timestamp of the last l1 block we've seen, so this relies on
|
|
420
|
-
// the fact that L1 won't mine two blocks within this time of each other.
|
|
421
|
-
// TODO(palla/reorg): Is the above a safe assumption?
|
|
422
|
-
const leeway = 1n;
|
|
423
|
-
return l1Timestamp + leeway >= endTimestamp;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Gets up to `limit` amount of L2 blocks starting from `from`.
|
|
427
|
-
* @param from - Number of the first block to return (inclusive).
|
|
428
|
-
* @param limit - The number of blocks to return.
|
|
429
|
-
* @param proven - If true, only return blocks that have been proven.
|
|
430
|
-
* @returns The requested L2 blocks.
|
|
431
|
-
*/
|
|
432
|
-
async getBlocks(from, limit, proven) {
|
|
433
|
-
const limitWithProven = proven
|
|
434
|
-
? Math.min(limit, Math.max((await this.store.getProvenL2BlockNumber()) - from + 1, 0))
|
|
435
|
-
: limit;
|
|
436
|
-
return limitWithProven === 0 ? [] : (await this.store.getBlocks(from, limitWithProven)).map(b => b.data);
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Gets an l2 block.
|
|
440
|
-
* @param number - The block number to return.
|
|
441
|
-
* @returns The requested L2 block.
|
|
442
|
-
*/
|
|
443
|
-
async getBlock(number) {
|
|
444
|
-
// If the number provided is -ve, then return the latest block.
|
|
445
|
-
if (number < 0) {
|
|
446
|
-
number = await this.store.getSynchedL2BlockNumber();
|
|
447
|
-
}
|
|
448
|
-
if (number == 0) {
|
|
449
|
-
return undefined;
|
|
450
|
-
}
|
|
451
|
-
const blocks = await this.store.getBlocks(number, 1);
|
|
452
|
-
return blocks.length === 0 ? undefined : blocks[0].data;
|
|
453
|
-
}
|
|
454
|
-
async getBlockHeader(number) {
|
|
455
|
-
if (number === 'latest') {
|
|
456
|
-
number = await this.store.getSynchedL2BlockNumber();
|
|
457
|
-
}
|
|
458
|
-
if (number === 0) {
|
|
459
|
-
return undefined;
|
|
460
|
-
}
|
|
461
|
-
const headers = await this.store.getBlockHeaders(number, 1);
|
|
462
|
-
return headers.length === 0 ? undefined : headers[0];
|
|
463
|
-
}
|
|
464
|
-
getTxEffect(txHash) {
|
|
465
|
-
return this.store.getTxEffect(txHash);
|
|
466
|
-
}
|
|
467
|
-
getSettledTxReceipt(txHash) {
|
|
468
|
-
return this.store.getSettledTxReceipt(txHash);
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* Gets the public function data for a contract.
|
|
472
|
-
* @param address - The contract address containing the function to fetch.
|
|
473
|
-
* @param selector - The function selector of the function to fetch.
|
|
474
|
-
* @returns The public function data (if found).
|
|
475
|
-
*/
|
|
476
|
-
async getPublicFunction(address, selector) {
|
|
477
|
-
const instance = await this.getContract(address);
|
|
478
|
-
if (!instance) {
|
|
479
|
-
throw new Error(`Contract ${address.toString()} not found`);
|
|
480
|
-
}
|
|
481
|
-
const contractClass = await this.getContractClass(instance.contractClassId);
|
|
482
|
-
if (!contractClass) {
|
|
483
|
-
throw new Error(`Contract class ${instance.contractClassId.toString()} for ${address.toString()} not found`);
|
|
342
|
+
const archiveAtContract = await this.rollup.read.archiveAt([
|
|
343
|
+
BigInt(candidateBlock.number)
|
|
344
|
+
]);
|
|
345
|
+
if (archiveAtContract === candidateBlock.archive.root.toString()) {
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
tipAfterUnwind--;
|
|
484
349
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
* Retrieves all private logs from up to `limit` blocks, starting from the block number `from`.
|
|
489
|
-
* @param from - The block number from which to begin retrieving logs.
|
|
490
|
-
* @param limit - The maximum number of blocks to retrieve logs from.
|
|
491
|
-
* @returns An array of private logs from the specified range of blocks.
|
|
492
|
-
*/
|
|
493
|
-
getPrivateLogs(from, limit) {
|
|
494
|
-
return this.store.getPrivateLogs(from, limit);
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
|
|
498
|
-
* @param tags - The tags to filter the logs by.
|
|
499
|
-
* @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
|
|
500
|
-
* that tag.
|
|
501
|
-
*/
|
|
502
|
-
getLogsByTags(tags) {
|
|
503
|
-
return this.store.getLogsByTags(tags);
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Returns the provided nullifier indexes scoped to the block
|
|
507
|
-
* they were first included in, or undefined if they're not present in the tree
|
|
508
|
-
* @param blockNumber Max block number to search for the nullifiers
|
|
509
|
-
* @param nullifiers Nullifiers to get
|
|
510
|
-
* @returns The block scoped indexes of the provided nullifiers, or undefined if the nullifier doesn't exist in the tree
|
|
511
|
-
*/
|
|
512
|
-
findNullifiersIndexesWithBlock(blockNumber, nullifiers) {
|
|
513
|
-
return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Gets public logs based on the provided filter.
|
|
517
|
-
* @param filter - The filter to apply to the logs.
|
|
518
|
-
* @returns The requested logs.
|
|
519
|
-
*/
|
|
520
|
-
getPublicLogs(filter) {
|
|
521
|
-
return this.store.getPublicLogs(filter);
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Gets contract class logs based on the provided filter.
|
|
525
|
-
* @param filter - The filter to apply to the logs.
|
|
526
|
-
* @returns The requested logs.
|
|
527
|
-
*/
|
|
528
|
-
getContractClassLogs(filter) {
|
|
529
|
-
return this.store.getContractClassLogs(filter);
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
* Gets the number of the latest L2 block processed by the block source implementation.
|
|
533
|
-
* @returns The number of the latest L2 block processed by the block source implementation.
|
|
534
|
-
*/
|
|
535
|
-
getBlockNumber() {
|
|
536
|
-
return this.store.getSynchedL2BlockNumber();
|
|
537
|
-
}
|
|
538
|
-
getProvenBlockNumber() {
|
|
539
|
-
return this.store.getProvenL2BlockNumber();
|
|
540
|
-
}
|
|
541
|
-
getProvenL2EpochNumber() {
|
|
542
|
-
return this.store.getProvenL2EpochNumber();
|
|
543
|
-
}
|
|
544
|
-
/** Forcefully updates the last proven block number. Use for testing. */
|
|
545
|
-
setProvenBlockNumber(blockNumber) {
|
|
546
|
-
return this.store.setProvenL2BlockNumber(blockNumber);
|
|
547
|
-
}
|
|
548
|
-
getContractClass(id) {
|
|
549
|
-
return this.store.getContractClass(id);
|
|
550
|
-
}
|
|
551
|
-
getBytecodeCommitment(id) {
|
|
552
|
-
return this.store.getBytecodeCommitment(id);
|
|
350
|
+
const blocksToUnwind = localPendingBlockNumber - tipAfterUnwind;
|
|
351
|
+
await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
|
|
352
|
+
this.log.warn(`Unwound ${count(blocksToUnwind, 'block')} from L2 block ${localPendingBlockNumber} ` + `due to mismatched block hashes at L1 block ${currentL1BlockNumber}. ` + `Updated L2 latest block is ${await this.getBlockNumber()}.`);
|
|
553
353
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
registerContractFunctionSignatures(address, signatures) {
|
|
582
|
-
return this.store.registerContractFunctionSignatures(address, signatures);
|
|
583
|
-
}
|
|
584
|
-
getContractFunctionName(address, selector) {
|
|
585
|
-
return this.store.getContractFunctionName(address, selector);
|
|
354
|
+
}
|
|
355
|
+
// Retrieve L2 blocks in batches. Each batch is estimated to acommodate up to L2 'blockBatchSize' blocks,
|
|
356
|
+
// computed using the L2 block time vs the L1 block time.
|
|
357
|
+
let searchStartBlock = blocksSynchedTo;
|
|
358
|
+
let searchEndBlock = blocksSynchedTo;
|
|
359
|
+
do {
|
|
360
|
+
[searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
|
|
361
|
+
this.log.trace(`Retrieving L2 blocks from L1 block ${searchStartBlock} to ${searchEndBlock}`);
|
|
362
|
+
// TODO(md): Retreive from blob sink then from consensus client, then from peers
|
|
363
|
+
const retrievedBlocks = await retrieveBlocksFromRollup(this.rollup, this.publicClient, this.blobSinkClient, searchStartBlock, searchEndBlock, this.log);
|
|
364
|
+
if (retrievedBlocks.length === 0) {
|
|
365
|
+
// We are not calling `setBlockSynchedL1BlockNumber` because it may cause sync issues if based off infura.
|
|
366
|
+
// See further details in earlier comments.
|
|
367
|
+
this.log.trace(`Retrieved no new L2 blocks from L1 block ${searchStartBlock} to ${searchEndBlock}`);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const lastProcessedL1BlockNumber = retrievedBlocks[retrievedBlocks.length - 1].l1.blockNumber;
|
|
371
|
+
this.log.debug(`Retrieved ${retrievedBlocks.length} new L2 blocks between L1 blocks ${searchStartBlock} and ${searchEndBlock} with last processed L1 block ${lastProcessedL1BlockNumber}.`);
|
|
372
|
+
for (const block of retrievedBlocks){
|
|
373
|
+
this.log.debug(`Ingesting new L2 block ${block.data.number} with ${block.data.body.txEffects.length} txs`, {
|
|
374
|
+
blockHash: block.data.hash(),
|
|
375
|
+
l1BlockNumber: block.l1.blockNumber,
|
|
376
|
+
...block.data.header.globalVariables.toInspect(),
|
|
377
|
+
...block.data.getStats()
|
|
378
|
+
});
|
|
586
379
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (latestBlockNumber > 0 && !latestBlockHeader) {
|
|
597
|
-
throw new Error(`Failed to retrieve latest block header for block ${latestBlockNumber}`);
|
|
598
|
-
}
|
|
599
|
-
if (provenBlockNumber > 0 && !provenBlockHeader) {
|
|
600
|
-
throw new Error(`Failed to retrieve proven block header for block ${provenBlockNumber} (latest block is ${latestBlockNumber})`);
|
|
601
|
-
}
|
|
602
|
-
const latestBlockHeaderHash = await latestBlockHeader?.hash();
|
|
603
|
-
const provenBlockHeaderHash = await provenBlockHeader?.hash();
|
|
604
|
-
const finalizedBlockHeaderHash = await provenBlockHeader?.hash();
|
|
605
|
-
return {
|
|
606
|
-
latest: { number: latestBlockNumber, hash: latestBlockHeaderHash?.toString() },
|
|
607
|
-
proven: { number: provenBlockNumber, hash: provenBlockHeaderHash?.toString() },
|
|
608
|
-
finalized: { number: provenBlockNumber, hash: finalizedBlockHeaderHash?.toString() },
|
|
609
|
-
};
|
|
380
|
+
const [processDuration] = await elapsed(()=>this.store.addBlocks(retrievedBlocks));
|
|
381
|
+
this.instrumentation.processNewBlocks(processDuration / retrievedBlocks.length, retrievedBlocks.map((b)=>b.data));
|
|
382
|
+
for (const block of retrievedBlocks){
|
|
383
|
+
this.log.info(`Downloaded L2 block ${block.data.number}`, {
|
|
384
|
+
blockHash: block.data.hash(),
|
|
385
|
+
blockNumber: block.data.number,
|
|
386
|
+
txCount: block.data.body.txEffects.length,
|
|
387
|
+
globalVariables: block.data.header.globalVariables.toInspect()
|
|
388
|
+
});
|
|
610
389
|
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
(
|
|
390
|
+
}while (searchEndBlock < currentL1BlockNumber)
|
|
391
|
+
// Important that we update AFTER inserting the blocks.
|
|
392
|
+
await updateProvenBlock();
|
|
393
|
+
return {
|
|
394
|
+
provenBlockNumber
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Stops the archiver.
|
|
399
|
+
* @returns A promise signalling completion of the stop process.
|
|
400
|
+
*/ async stop() {
|
|
401
|
+
this.log.debug('Stopping...');
|
|
402
|
+
await this.runningPromise?.stop();
|
|
403
|
+
this.log.info('Stopped.');
|
|
404
|
+
return Promise.resolve();
|
|
405
|
+
}
|
|
406
|
+
getL1Constants() {
|
|
407
|
+
return Promise.resolve(this.l1constants);
|
|
408
|
+
}
|
|
409
|
+
getRollupAddress() {
|
|
410
|
+
return Promise.resolve(this.l1Addresses.rollupAddress);
|
|
411
|
+
}
|
|
412
|
+
getRegistryAddress() {
|
|
413
|
+
return Promise.resolve(this.l1Addresses.registryAddress);
|
|
414
|
+
}
|
|
415
|
+
getL1BlockNumber() {
|
|
416
|
+
const l1BlockNumber = this.l1BlockNumber;
|
|
417
|
+
if (!l1BlockNumber) {
|
|
418
|
+
throw new Error('L1 block number not yet available. Complete an initial sync first.');
|
|
419
|
+
}
|
|
420
|
+
return l1BlockNumber;
|
|
421
|
+
}
|
|
422
|
+
getL1Timestamp() {
|
|
423
|
+
const l1Timestamp = this.l1Timestamp;
|
|
424
|
+
if (!l1Timestamp) {
|
|
425
|
+
throw new Error('L1 timestamp not yet available. Complete an initial sync first.');
|
|
426
|
+
}
|
|
427
|
+
return l1Timestamp;
|
|
428
|
+
}
|
|
429
|
+
getL2SlotNumber() {
|
|
430
|
+
return Promise.resolve(getSlotAtTimestamp(this.getL1Timestamp(), this.l1constants));
|
|
431
|
+
}
|
|
432
|
+
getL2EpochNumber() {
|
|
433
|
+
return Promise.resolve(getEpochNumberAtTimestamp(this.getL1Timestamp(), this.l1constants));
|
|
434
|
+
}
|
|
435
|
+
async getBlocksForEpoch(epochNumber) {
|
|
436
|
+
const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
|
|
437
|
+
const blocks = [];
|
|
438
|
+
// Walk the list of blocks backwards and filter by slots matching the requested epoch.
|
|
439
|
+
// We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
|
|
440
|
+
let block = await this.getBlock(await this.store.getSynchedL2BlockNumber());
|
|
441
|
+
const slot = (b)=>b.header.globalVariables.slotNumber.toBigInt();
|
|
442
|
+
while(block && slot(block) >= start){
|
|
443
|
+
if (slot(block) <= end) {
|
|
444
|
+
blocks.push(block);
|
|
445
|
+
}
|
|
446
|
+
block = await this.getBlock(block.number - 1);
|
|
447
|
+
}
|
|
448
|
+
return blocks.reverse();
|
|
449
|
+
}
|
|
450
|
+
async isEpochComplete(epochNumber) {
|
|
451
|
+
// The epoch is complete if the current L2 block is the last one in the epoch (or later)
|
|
452
|
+
const header = await this.getBlockHeader('latest');
|
|
453
|
+
const slot = header?.globalVariables.slotNumber.toBigInt();
|
|
454
|
+
const [_startSlot, endSlot] = getSlotRangeForEpoch(epochNumber, this.l1constants);
|
|
455
|
+
if (slot && slot >= endSlot) {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
// If we haven't run an initial sync, just return false.
|
|
459
|
+
const l1Timestamp = this.l1Timestamp;
|
|
460
|
+
if (l1Timestamp === undefined) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
// If not, the epoch may also be complete if the L2 slot has passed without a block
|
|
464
|
+
// We compute this based on the end timestamp for the given epoch and the timestamp of the last L1 block
|
|
465
|
+
const [_startTimestamp, endTimestamp] = getTimestampRangeForEpoch(epochNumber, this.l1constants);
|
|
466
|
+
// For this computation, we throw in a few extra seconds just for good measure,
|
|
467
|
+
// since we know the next L1 block won't be mined within this range. Remember that
|
|
468
|
+
// l1timestamp is the timestamp of the last l1 block we've seen, so this relies on
|
|
469
|
+
// the fact that L1 won't mine two blocks within this time of each other.
|
|
470
|
+
// TODO(palla/reorg): Is the above a safe assumption?
|
|
471
|
+
const leeway = 1n;
|
|
472
|
+
return l1Timestamp + leeway >= endTimestamp;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Gets up to `limit` amount of L2 blocks starting from `from`.
|
|
476
|
+
* @param from - Number of the first block to return (inclusive).
|
|
477
|
+
* @param limit - The number of blocks to return.
|
|
478
|
+
* @param proven - If true, only return blocks that have been proven.
|
|
479
|
+
* @returns The requested L2 blocks.
|
|
480
|
+
*/ async getBlocks(from, limit, proven) {
|
|
481
|
+
const limitWithProven = proven ? Math.min(limit, Math.max(await this.store.getProvenL2BlockNumber() - from + 1, 0)) : limit;
|
|
482
|
+
return limitWithProven === 0 ? [] : (await this.store.getBlocks(from, limitWithProven)).map((b)=>b.data);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Gets an l2 block.
|
|
486
|
+
* @param number - The block number to return.
|
|
487
|
+
* @returns The requested L2 block.
|
|
488
|
+
*/ async getBlock(number) {
|
|
489
|
+
// If the number provided is -ve, then return the latest block.
|
|
490
|
+
if (number < 0) {
|
|
491
|
+
number = await this.store.getSynchedL2BlockNumber();
|
|
492
|
+
}
|
|
493
|
+
if (number == 0) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
const blocks = await this.store.getBlocks(number, 1);
|
|
497
|
+
return blocks.length === 0 ? undefined : blocks[0].data;
|
|
498
|
+
}
|
|
499
|
+
async getBlockHeader(number) {
|
|
500
|
+
if (number === 'latest') {
|
|
501
|
+
number = await this.store.getSynchedL2BlockNumber();
|
|
502
|
+
}
|
|
503
|
+
if (number === 0) {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
const headers = await this.store.getBlockHeaders(number, 1);
|
|
507
|
+
return headers.length === 0 ? undefined : headers[0];
|
|
508
|
+
}
|
|
509
|
+
getTxEffect(txHash) {
|
|
510
|
+
return this.store.getTxEffect(txHash);
|
|
511
|
+
}
|
|
512
|
+
getSettledTxReceipt(txHash) {
|
|
513
|
+
return this.store.getSettledTxReceipt(txHash);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Gets the public function data for a contract.
|
|
517
|
+
* @param address - The contract address containing the function to fetch.
|
|
518
|
+
* @param selector - The function selector of the function to fetch.
|
|
519
|
+
* @returns The public function data (if found).
|
|
520
|
+
*/ async getPublicFunction(address, selector) {
|
|
521
|
+
const instance = await this.getContract(address);
|
|
522
|
+
if (!instance) {
|
|
523
|
+
throw new Error(`Contract ${address.toString()} not found`);
|
|
524
|
+
}
|
|
525
|
+
const contractClass = await this.getContractClass(instance.currentContractClassId);
|
|
526
|
+
if (!contractClass) {
|
|
527
|
+
throw new Error(`Contract class ${instance.currentContractClassId.toString()} for ${address.toString()} not found`);
|
|
528
|
+
}
|
|
529
|
+
return contractClass.publicFunctions.find((f)=>f.selector.equals(selector));
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Retrieves all private logs from up to `limit` blocks, starting from the block number `from`.
|
|
533
|
+
* @param from - The block number from which to begin retrieving logs.
|
|
534
|
+
* @param limit - The maximum number of blocks to retrieve logs from.
|
|
535
|
+
* @returns An array of private logs from the specified range of blocks.
|
|
536
|
+
*/ getPrivateLogs(from, limit) {
|
|
537
|
+
return this.store.getPrivateLogs(from, limit);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
|
|
541
|
+
* @param tags - The tags to filter the logs by.
|
|
542
|
+
* @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
|
|
543
|
+
* that tag.
|
|
544
|
+
*/ getLogsByTags(tags) {
|
|
545
|
+
return this.store.getLogsByTags(tags);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Returns the provided nullifier indexes scoped to the block
|
|
549
|
+
* they were first included in, or undefined if they're not present in the tree
|
|
550
|
+
* @param blockNumber Max block number to search for the nullifiers
|
|
551
|
+
* @param nullifiers Nullifiers to get
|
|
552
|
+
* @returns The block scoped indexes of the provided nullifiers, or undefined if the nullifier doesn't exist in the tree
|
|
553
|
+
*/ findNullifiersIndexesWithBlock(blockNumber, nullifiers) {
|
|
554
|
+
return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Gets public logs based on the provided filter.
|
|
558
|
+
* @param filter - The filter to apply to the logs.
|
|
559
|
+
* @returns The requested logs.
|
|
560
|
+
*/ getPublicLogs(filter) {
|
|
561
|
+
return this.store.getPublicLogs(filter);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Gets contract class logs based on the provided filter.
|
|
565
|
+
* @param filter - The filter to apply to the logs.
|
|
566
|
+
* @returns The requested logs.
|
|
567
|
+
*/ getContractClassLogs(filter) {
|
|
568
|
+
return this.store.getContractClassLogs(filter);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Gets the number of the latest L2 block processed by the block source implementation.
|
|
572
|
+
* @returns The number of the latest L2 block processed by the block source implementation.
|
|
573
|
+
*/ getBlockNumber() {
|
|
574
|
+
return this.store.getSynchedL2BlockNumber();
|
|
575
|
+
}
|
|
576
|
+
getProvenBlockNumber() {
|
|
577
|
+
return this.store.getProvenL2BlockNumber();
|
|
578
|
+
}
|
|
579
|
+
getProvenL2EpochNumber() {
|
|
580
|
+
return this.store.getProvenL2EpochNumber();
|
|
581
|
+
}
|
|
582
|
+
/** Forcefully updates the last proven block number. Use for testing. */ setProvenBlockNumber(blockNumber) {
|
|
583
|
+
return this.store.setProvenL2BlockNumber(blockNumber);
|
|
584
|
+
}
|
|
585
|
+
getContractClass(id) {
|
|
586
|
+
return this.store.getContractClass(id);
|
|
587
|
+
}
|
|
588
|
+
getBytecodeCommitment(id) {
|
|
589
|
+
return this.store.getBytecodeCommitment(id);
|
|
590
|
+
}
|
|
591
|
+
getContract(address) {
|
|
592
|
+
return this.store.getContractInstance(address);
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Gets L1 to L2 message (to be) included in a given block.
|
|
596
|
+
* @param blockNumber - L2 block number to get messages for.
|
|
597
|
+
* @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found).
|
|
598
|
+
*/ getL1ToL2Messages(blockNumber) {
|
|
599
|
+
return this.store.getL1ToL2Messages(blockNumber);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Gets the L1 to L2 message index in the L1 to L2 message tree.
|
|
603
|
+
* @param l1ToL2Message - The L1 to L2 message.
|
|
604
|
+
* @returns The index of the L1 to L2 message in the L1 to L2 message tree (undefined if not found).
|
|
605
|
+
*/ getL1ToL2MessageIndex(l1ToL2Message) {
|
|
606
|
+
return this.store.getL1ToL2MessageIndex(l1ToL2Message);
|
|
607
|
+
}
|
|
608
|
+
getContractClassIds() {
|
|
609
|
+
return this.store.getContractClassIds();
|
|
610
|
+
}
|
|
611
|
+
// TODO(#10007): Remove this method
|
|
612
|
+
async addContractClass(contractClass) {
|
|
613
|
+
await this.store.addContractClasses([
|
|
614
|
+
contractClass
|
|
615
|
+
], [
|
|
616
|
+
await computePublicBytecodeCommitment(contractClass.packedBytecode)
|
|
617
|
+
], 0);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
registerContractFunctionSignatures(address, signatures) {
|
|
621
|
+
return this.store.registerContractFunctionSignatures(address, signatures);
|
|
622
|
+
}
|
|
623
|
+
getContractFunctionName(address, selector) {
|
|
624
|
+
return this.store.getContractFunctionName(address, selector);
|
|
625
|
+
}
|
|
626
|
+
async getL2Tips() {
|
|
627
|
+
const [latestBlockNumber, provenBlockNumber] = await Promise.all([
|
|
628
|
+
this.getBlockNumber(),
|
|
629
|
+
this.getProvenBlockNumber()
|
|
630
|
+
]);
|
|
631
|
+
const [latestBlockHeader, provenBlockHeader] = await Promise.all([
|
|
632
|
+
latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
|
|
633
|
+
provenBlockNumber > 0 ? this.getBlockHeader(provenBlockNumber) : undefined
|
|
634
|
+
]);
|
|
635
|
+
if (latestBlockNumber > 0 && !latestBlockHeader) {
|
|
636
|
+
throw new Error(`Failed to retrieve latest block header for block ${latestBlockNumber}`);
|
|
637
|
+
}
|
|
638
|
+
if (provenBlockNumber > 0 && !provenBlockHeader) {
|
|
639
|
+
throw new Error(`Failed to retrieve proven block header for block ${provenBlockNumber} (latest block is ${latestBlockNumber})`);
|
|
640
|
+
}
|
|
641
|
+
const latestBlockHeaderHash = await latestBlockHeader?.hash();
|
|
642
|
+
const provenBlockHeaderHash = await provenBlockHeader?.hash();
|
|
643
|
+
const finalizedBlockHeaderHash = await provenBlockHeader?.hash();
|
|
644
|
+
return {
|
|
645
|
+
latest: {
|
|
646
|
+
number: latestBlockNumber,
|
|
647
|
+
hash: latestBlockHeaderHash?.toString()
|
|
648
|
+
},
|
|
649
|
+
proven: {
|
|
650
|
+
number: provenBlockNumber,
|
|
651
|
+
hash: provenBlockHeaderHash?.toString()
|
|
652
|
+
},
|
|
653
|
+
finalized: {
|
|
654
|
+
number: provenBlockNumber,
|
|
655
|
+
hash: finalizedBlockHeaderHash?.toString()
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
_ts_decorate([
|
|
661
|
+
trackSpan('Archiver.sync', (initialRun)=>({
|
|
662
|
+
[Attributes.INITIAL_SYNC]: initialRun
|
|
663
|
+
}))
|
|
664
|
+
], Archiver.prototype, "sync", null);
|
|
665
|
+
var Operation = /*#__PURE__*/ function(Operation) {
|
|
623
666
|
Operation[Operation["Store"] = 0] = "Store";
|
|
624
667
|
Operation[Operation["Delete"] = 1] = "Delete";
|
|
625
|
-
|
|
668
|
+
return Operation;
|
|
669
|
+
}(Operation || {});
|
|
626
670
|
/**
|
|
627
671
|
* A helper class that we use to deal with some of the logic needed when adding blocks.
|
|
628
672
|
*
|
|
629
673
|
* I would have preferred to not have this type. But it is useful for handling the logic that any
|
|
630
674
|
* store would need to include otherwise while exposing fewer functions and logic directly to the archiver.
|
|
631
|
-
*/
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
675
|
+
*/ class ArchiverStoreHelper {
|
|
676
|
+
store;
|
|
677
|
+
#log;
|
|
678
|
+
constructor(store){
|
|
635
679
|
this.store = store;
|
|
636
|
-
|
|
680
|
+
this.#log = createLogger('archiver:block-helper');
|
|
637
681
|
}
|
|
638
682
|
// TODO(#10007): Remove this method
|
|
639
683
|
addContractClasses(contractClasses, bytecodeCommitments, blockNum) {
|
|
640
684
|
return this.store.addContractClasses(contractClasses, bytecodeCommitments, blockNum);
|
|
641
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
|
|
688
|
+
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
689
|
+
*/ async #updateRegisteredContractClasses(allLogs, blockNum, operation) {
|
|
690
|
+
const contractClassRegisteredEvents = allLogs.filter((log)=>ContractClassRegisteredEvent.isContractClassRegisteredEvent(log)).map((log)=>ContractClassRegisteredEvent.fromLog(log));
|
|
691
|
+
const contractClasses = await Promise.all(contractClassRegisteredEvents.map((e)=>e.toContractClassPublic()));
|
|
692
|
+
if (contractClasses.length > 0) {
|
|
693
|
+
contractClasses.forEach((c)=>this.#log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
|
|
694
|
+
if (operation == 0) {
|
|
695
|
+
// TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive
|
|
696
|
+
const commitments = await Promise.all(contractClasses.map((c)=>computePublicBytecodeCommitment(c.packedBytecode)));
|
|
697
|
+
return await this.store.addContractClasses(contractClasses, commitments, blockNum);
|
|
698
|
+
} else if (operation == 1) {
|
|
699
|
+
return await this.store.deleteContractClasses(contractClasses, blockNum);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
|
|
706
|
+
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
707
|
+
*/ async #updateDeployedContractInstances(allLogs, blockNum, operation) {
|
|
708
|
+
const contractInstances = allLogs.filter((log)=>ContractInstanceDeployedEvent.isContractInstanceDeployedEvent(log)).map((log)=>ContractInstanceDeployedEvent.fromLog(log)).map((e)=>e.toContractInstance());
|
|
709
|
+
if (contractInstances.length > 0) {
|
|
710
|
+
contractInstances.forEach((c)=>this.#log.verbose(`${Operation[operation]} contract instance at ${c.address.toString()}`));
|
|
711
|
+
if (operation == 0) {
|
|
712
|
+
return await this.store.addContractInstances(contractInstances, blockNum);
|
|
713
|
+
} else if (operation == 1) {
|
|
714
|
+
return await this.store.deleteContractInstances(contractInstances, blockNum);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
|
|
721
|
+
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
722
|
+
*/ async #updateUpdatedContractInstances(allLogs, blockNum, operation) {
|
|
723
|
+
const contractUpdates = allLogs.filter((log)=>ContractInstanceUpdatedEvent.isContractInstanceUpdatedEvent(log)).map((log)=>ContractInstanceUpdatedEvent.fromLog(log)).map((e)=>e.toContractInstanceUpdate());
|
|
724
|
+
if (contractUpdates.length > 0) {
|
|
725
|
+
contractUpdates.forEach((c)=>this.#log.verbose(`${Operation[operation]} contract instance update at ${c.address.toString()}`));
|
|
726
|
+
if (operation == 0) {
|
|
727
|
+
return await this.store.addContractInstanceUpdates(contractUpdates, blockNum);
|
|
728
|
+
} else if (operation == 1) {
|
|
729
|
+
return await this.store.deleteContractInstanceUpdates(contractUpdates, blockNum);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Stores the functions that was broadcasted individually
|
|
736
|
+
*
|
|
737
|
+
* @dev Beware that there is not a delete variant of this, since they are added to contract classes
|
|
738
|
+
* and will be deleted as part of the class if needed.
|
|
739
|
+
*
|
|
740
|
+
* @param allLogs - The logs from the block
|
|
741
|
+
* @param _blockNum - The block number
|
|
742
|
+
* @returns
|
|
743
|
+
*/ async #storeBroadcastedIndividualFunctions(allLogs, _blockNum) {
|
|
744
|
+
// Filter out private and unconstrained function broadcast events
|
|
745
|
+
const privateFnEvents = allLogs.filter((log)=>PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log)).map((log)=>PrivateFunctionBroadcastedEvent.fromLog(log));
|
|
746
|
+
const unconstrainedFnEvents = allLogs.filter((log)=>UnconstrainedFunctionBroadcastedEvent.isUnconstrainedFunctionBroadcastedEvent(log)).map((log)=>UnconstrainedFunctionBroadcastedEvent.fromLog(log));
|
|
747
|
+
// Group all events by contract class id
|
|
748
|
+
for (const [classIdString, classEvents] of Object.entries(groupBy([
|
|
749
|
+
...privateFnEvents,
|
|
750
|
+
...unconstrainedFnEvents
|
|
751
|
+
], (e)=>e.contractClassId.toString()))){
|
|
752
|
+
const contractClassId = Fr.fromHexString(classIdString);
|
|
753
|
+
const contractClass = await this.getContractClass(contractClassId);
|
|
754
|
+
if (!contractClass) {
|
|
755
|
+
this.#log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
// Split private and unconstrained functions, and filter out invalid ones
|
|
759
|
+
const allFns = classEvents.map((e)=>e.toFunctionWithMembershipProof());
|
|
760
|
+
const privateFns = allFns.filter((fn)=>'unconstrainedFunctionsArtifactTreeRoot' in fn);
|
|
761
|
+
const unconstrainedFns = allFns.filter((fn)=>'privateFunctionsArtifactTreeRoot' in fn);
|
|
762
|
+
const privateFunctionsWithValidity = await Promise.all(privateFns.map(async (fn)=>({
|
|
763
|
+
fn,
|
|
764
|
+
valid: await isValidPrivateFunctionMembershipProof(fn, contractClass)
|
|
765
|
+
})));
|
|
766
|
+
const validPrivateFns = privateFunctionsWithValidity.filter(({ valid })=>valid).map(({ fn })=>fn);
|
|
767
|
+
const unconstrainedFunctionsWithValidity = await Promise.all(unconstrainedFns.map(async (fn)=>({
|
|
768
|
+
fn,
|
|
769
|
+
valid: await isValidUnconstrainedFunctionMembershipProof(fn, contractClass)
|
|
770
|
+
})));
|
|
771
|
+
const validUnconstrainedFns = unconstrainedFunctionsWithValidity.filter(({ valid })=>valid).map(({ fn })=>fn);
|
|
772
|
+
const validFnCount = validPrivateFns.length + validUnconstrainedFns.length;
|
|
773
|
+
if (validFnCount !== allFns.length) {
|
|
774
|
+
this.#log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
|
|
775
|
+
}
|
|
776
|
+
// Store the functions in the contract class in a single operation
|
|
777
|
+
if (validFnCount > 0) {
|
|
778
|
+
this.#log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
|
|
779
|
+
}
|
|
780
|
+
return await this.store.addFunctions(contractClassId, validPrivateFns, validUnconstrainedFns);
|
|
781
|
+
}
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
642
784
|
async addBlocks(blocks) {
|
|
643
785
|
const opResults = await Promise.all([
|
|
644
|
-
this.store.addLogs(blocks.map(block
|
|
786
|
+
this.store.addLogs(blocks.map((block)=>block.data)),
|
|
645
787
|
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
646
|
-
...blocks.map(async (block)
|
|
647
|
-
const contractClassLogs = block.data.body.txEffects
|
|
648
|
-
.flatMap(txEffect => (txEffect ? [txEffect.contractClassLogs] : []))
|
|
649
|
-
.flatMap(txLog => txLog.unrollLogs());
|
|
788
|
+
...blocks.map(async (block)=>{
|
|
789
|
+
const contractClassLogs = block.data.body.txEffects.flatMap((txEffect)=>txEffect.contractClassLogs);
|
|
650
790
|
// ContractInstanceDeployed event logs are broadcast in privateLogs.
|
|
651
|
-
const privateLogs = block.data.body.txEffects.flatMap(txEffect
|
|
791
|
+
const privateLogs = block.data.body.txEffects.flatMap((txEffect)=>txEffect.privateLogs);
|
|
792
|
+
const publicLogs = block.data.body.txEffects.flatMap((txEffect)=>txEffect.publicLogs);
|
|
652
793
|
return (await Promise.all([
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
794
|
+
this.#updateRegisteredContractClasses(contractClassLogs, block.data.number, 0),
|
|
795
|
+
this.#updateDeployedContractInstances(privateLogs, block.data.number, 0),
|
|
796
|
+
this.#updateUpdatedContractInstances(publicLogs, block.data.number, 0),
|
|
797
|
+
this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.data.number)
|
|
656
798
|
])).every(Boolean);
|
|
657
799
|
}),
|
|
658
|
-
this.store.addNullifiers(blocks.map(block
|
|
659
|
-
this.store.addBlocks(blocks)
|
|
800
|
+
this.store.addNullifiers(blocks.map((block)=>block.data)),
|
|
801
|
+
this.store.addBlocks(blocks)
|
|
660
802
|
]);
|
|
661
803
|
return opResults.every(Boolean);
|
|
662
804
|
}
|
|
@@ -669,19 +811,19 @@ class ArchiverStoreHelper {
|
|
|
669
811
|
const blocks = await this.getBlocks(from - blocksToUnwind + 1, blocksToUnwind);
|
|
670
812
|
const opResults = await Promise.all([
|
|
671
813
|
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
672
|
-
...blocks.map(async (block)
|
|
673
|
-
const contractClassLogs = block.data.body.txEffects
|
|
674
|
-
.flatMap(txEffect => (txEffect ? [txEffect.contractClassLogs] : []))
|
|
675
|
-
.flatMap(txLog => txLog.unrollLogs());
|
|
814
|
+
...blocks.map(async (block)=>{
|
|
815
|
+
const contractClassLogs = block.data.body.txEffects.flatMap((txEffect)=>txEffect.contractClassLogs);
|
|
676
816
|
// ContractInstanceDeployed event logs are broadcast in privateLogs.
|
|
677
|
-
const privateLogs = block.data.body.txEffects.flatMap(txEffect
|
|
817
|
+
const privateLogs = block.data.body.txEffects.flatMap((txEffect)=>txEffect.privateLogs);
|
|
818
|
+
const publicLogs = block.data.body.txEffects.flatMap((txEffect)=>txEffect.publicLogs);
|
|
678
819
|
return (await Promise.all([
|
|
679
|
-
|
|
680
|
-
|
|
820
|
+
this.#updateRegisteredContractClasses(contractClassLogs, block.data.number, 1),
|
|
821
|
+
this.#updateDeployedContractInstances(privateLogs, block.data.number, 1),
|
|
822
|
+
this.#updateUpdatedContractInstances(publicLogs, block.data.number, 1)
|
|
681
823
|
])).every(Boolean);
|
|
682
824
|
}),
|
|
683
|
-
this.store.deleteLogs(blocks.map(b
|
|
684
|
-
this.store.unwindBlocks(from, blocksToUnwind)
|
|
825
|
+
this.store.deleteLogs(blocks.map((b)=>b.data)),
|
|
826
|
+
this.store.unwindBlocks(from, blocksToUnwind)
|
|
685
827
|
]);
|
|
686
828
|
return opResults.every(Boolean);
|
|
687
829
|
}
|
|
@@ -770,96 +912,3 @@ class ArchiverStoreHelper {
|
|
|
770
912
|
return this.store.estimateSize();
|
|
771
913
|
}
|
|
772
914
|
}
|
|
773
|
-
_ArchiverStoreHelper_log = new WeakMap(), _ArchiverStoreHelper_instances = new WeakSet(), _ArchiverStoreHelper_updateRegisteredContractClasses =
|
|
774
|
-
/**
|
|
775
|
-
* Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
|
|
776
|
-
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
777
|
-
*/
|
|
778
|
-
async function _ArchiverStoreHelper_updateRegisteredContractClasses(allLogs, blockNum, operation) {
|
|
779
|
-
const contractClassRegisteredEvents = allLogs
|
|
780
|
-
.filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log.data))
|
|
781
|
-
.map(log => ContractClassRegisteredEvent.fromLog(log.data));
|
|
782
|
-
const contractClasses = await Promise.all(contractClassRegisteredEvents.map(e => e.toContractClassPublic()));
|
|
783
|
-
if (contractClasses.length > 0) {
|
|
784
|
-
contractClasses.forEach(c => __classPrivateFieldGet(this, _ArchiverStoreHelper_log, "f").verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
|
|
785
|
-
if (operation == Operation.Store) {
|
|
786
|
-
// TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive
|
|
787
|
-
const commitments = await Promise.all(contractClasses.map(c => computePublicBytecodeCommitment(c.packedBytecode)));
|
|
788
|
-
return await this.store.addContractClasses(contractClasses, commitments, blockNum);
|
|
789
|
-
}
|
|
790
|
-
else if (operation == Operation.Delete) {
|
|
791
|
-
return await this.store.deleteContractClasses(contractClasses, blockNum);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
return true;
|
|
795
|
-
}, _ArchiverStoreHelper_updateDeployedContractInstances =
|
|
796
|
-
/**
|
|
797
|
-
* Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
|
|
798
|
-
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
799
|
-
*/
|
|
800
|
-
async function _ArchiverStoreHelper_updateDeployedContractInstances(allLogs, blockNum, operation) {
|
|
801
|
-
const contractInstances = allLogs
|
|
802
|
-
.filter(log => ContractInstanceDeployedEvent.isContractInstanceDeployedEvent(log))
|
|
803
|
-
.map(log => ContractInstanceDeployedEvent.fromLog(log))
|
|
804
|
-
.map(e => e.toContractInstance());
|
|
805
|
-
if (contractInstances.length > 0) {
|
|
806
|
-
contractInstances.forEach(c => __classPrivateFieldGet(this, _ArchiverStoreHelper_log, "f").verbose(`${Operation[operation]} contract instance at ${c.address.toString()}`));
|
|
807
|
-
if (operation == Operation.Store) {
|
|
808
|
-
return await this.store.addContractInstances(contractInstances, blockNum);
|
|
809
|
-
}
|
|
810
|
-
else if (operation == Operation.Delete) {
|
|
811
|
-
return await this.store.deleteContractInstances(contractInstances, blockNum);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
return true;
|
|
815
|
-
}, _ArchiverStoreHelper_storeBroadcastedIndividualFunctions =
|
|
816
|
-
/**
|
|
817
|
-
* Stores the functions that was broadcasted individually
|
|
818
|
-
*
|
|
819
|
-
* @dev Beware that there is not a delete variant of this, since they are added to contract classes
|
|
820
|
-
* and will be deleted as part of the class if needed.
|
|
821
|
-
*
|
|
822
|
-
* @param allLogs - The logs from the block
|
|
823
|
-
* @param _blockNum - The block number
|
|
824
|
-
* @returns
|
|
825
|
-
*/
|
|
826
|
-
async function _ArchiverStoreHelper_storeBroadcastedIndividualFunctions(allLogs, _blockNum) {
|
|
827
|
-
// Filter out private and unconstrained function broadcast events
|
|
828
|
-
const privateFnEvents = allLogs
|
|
829
|
-
.filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log.data))
|
|
830
|
-
.map(log => PrivateFunctionBroadcastedEvent.fromLog(log.data));
|
|
831
|
-
const unconstrainedFnEvents = allLogs
|
|
832
|
-
.filter(log => UnconstrainedFunctionBroadcastedEvent.isUnconstrainedFunctionBroadcastedEvent(log.data))
|
|
833
|
-
.map(log => UnconstrainedFunctionBroadcastedEvent.fromLog(log.data));
|
|
834
|
-
// Group all events by contract class id
|
|
835
|
-
for (const [classIdString, classEvents] of Object.entries(groupBy([...privateFnEvents, ...unconstrainedFnEvents], e => e.contractClassId.toString()))) {
|
|
836
|
-
const contractClassId = Fr.fromHexString(classIdString);
|
|
837
|
-
const contractClass = await this.getContractClass(contractClassId);
|
|
838
|
-
if (!contractClass) {
|
|
839
|
-
__classPrivateFieldGet(this, _ArchiverStoreHelper_log, "f").warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
|
|
840
|
-
continue;
|
|
841
|
-
}
|
|
842
|
-
// Split private and unconstrained functions, and filter out invalid ones
|
|
843
|
-
const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
|
|
844
|
-
const privateFns = allFns.filter((fn) => 'unconstrainedFunctionsArtifactTreeRoot' in fn);
|
|
845
|
-
const unconstrainedFns = allFns.filter((fn) => 'privateFunctionsArtifactTreeRoot' in fn);
|
|
846
|
-
const privateFunctionsWithValidity = await Promise.all(privateFns.map(async (fn) => ({ fn, valid: await isValidPrivateFunctionMembershipProof(fn, contractClass) })));
|
|
847
|
-
const validPrivateFns = privateFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
|
|
848
|
-
const unconstrainedFunctionsWithValidity = await Promise.all(unconstrainedFns.map(async (fn) => ({
|
|
849
|
-
fn,
|
|
850
|
-
valid: await isValidUnconstrainedFunctionMembershipProof(fn, contractClass),
|
|
851
|
-
})));
|
|
852
|
-
const validUnconstrainedFns = unconstrainedFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
|
|
853
|
-
const validFnCount = validPrivateFns.length + validUnconstrainedFns.length;
|
|
854
|
-
if (validFnCount !== allFns.length) {
|
|
855
|
-
__classPrivateFieldGet(this, _ArchiverStoreHelper_log, "f").warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
|
|
856
|
-
}
|
|
857
|
-
// Store the functions in the contract class in a single operation
|
|
858
|
-
if (validFnCount > 0) {
|
|
859
|
-
__classPrivateFieldGet(this, _ArchiverStoreHelper_log, "f").verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
|
|
860
|
-
}
|
|
861
|
-
return await this.store.addFunctions(contractClassId, validPrivateFns, validUnconstrainedFns);
|
|
862
|
-
}
|
|
863
|
-
return true;
|
|
864
|
-
};
|
|
865
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXJjaGl2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXJjaGl2ZXIvYXJjaGl2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFDQSxPQUFPLEVBbUJMLHlCQUF5QixFQUN6QixrQkFBa0IsRUFDbEIsb0JBQW9CLEVBQ3BCLHlCQUF5QixHQUMxQixNQUFNLHNCQUFzQixDQUFDO0FBQzlCLE9BQU8sRUFVTCwrQkFBK0IsRUFDL0IscUNBQXFDLEVBQ3JDLDJDQUEyQyxHQUM1QyxNQUFNLG9CQUFvQixDQUFDO0FBQzVCLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBR3RELE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUM5QyxPQUFPLEVBQWUsWUFBWSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDbEUsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNqRCxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDbEQsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUMxRCxPQUFPLEVBQ0wsNEJBQTRCLEVBQzVCLCtCQUErQixFQUMvQixxQ0FBcUMsR0FDdEMsTUFBTSw0Q0FBNEMsQ0FBQztBQUNwRCxPQUFPLEVBQUUsNkJBQTZCLEVBQUUsTUFBTSw2Q0FBNkMsQ0FBQztBQUM1RixPQUFPLEVBQUUsVUFBVSxFQUFxRCxTQUFTLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUVuSCxPQUFPLE9BQU8sTUFBTSxnQkFBZ0IsQ0FBQztBQUNyQyxPQUFPLEVBS0wsa0JBQWtCLEVBQ2xCLFdBQVcsRUFDWCxJQUFJLEdBQ0wsTUFBTSxNQUFNLENBQUM7QUFJZCxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsc0JBQXNCLEVBQUUsTUFB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
|