@aztec/world-state 0.0.1-commit.3469e52 → 0.0.1-commit.350e0a4d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/instrumentation/instrumentation.d.ts +4 -3
- package/dest/instrumentation/instrumentation.d.ts.map +1 -1
- package/dest/instrumentation/instrumentation.js +13 -11
- package/dest/native/fork_checkpoint.d.ts +7 -1
- package/dest/native/fork_checkpoint.d.ts.map +1 -1
- package/dest/native/fork_checkpoint.js +15 -3
- package/dest/native/ipc_world_state_instance.d.ts +89 -0
- package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
- package/dest/native/ipc_world_state_instance.js +662 -0
- package/dest/native/merkle_trees_facade.d.ts +12 -8
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +63 -115
- package/dest/native/message.d.ts +14 -221
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/message.js +0 -42
- package/dest/native/native_world_state.d.ts +22 -10
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +118 -59
- package/dest/native/native_world_state_instance.d.ts +60 -41
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +5 -202
- package/dest/native/world_state_operation.d.ts +7 -0
- package/dest/native/world_state_operation.d.ts.map +1 -0
- package/dest/native/world_state_operation.js +5 -0
- package/dest/synchronizer/config.d.ts +3 -5
- package/dest/synchronizer/config.d.ts.map +1 -1
- package/dest/synchronizer/config.js +15 -18
- package/dest/synchronizer/errors.d.ts +8 -1
- package/dest/synchronizer/errors.d.ts.map +1 -1
- package/dest/synchronizer/errors.js +8 -1
- package/dest/synchronizer/factory.d.ts +6 -5
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +7 -6
- package/dest/synchronizer/index.d.ts +2 -1
- package/dest/synchronizer/index.d.ts.map +1 -1
- package/dest/synchronizer/index.js +1 -0
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +13 -8
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +134 -49
- package/dest/test/utils.d.ts +6 -6
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +9 -4
- package/dest/testing.d.ts +4 -3
- package/dest/testing.d.ts.map +1 -1
- package/dest/testing.js +14 -7
- package/dest/world-state-db/merkle_tree_db.d.ts +3 -12
- package/dest/world-state-db/merkle_tree_db.d.ts.map +1 -1
- package/package.json +14 -11
- package/src/instrumentation/instrumentation.ts +15 -19
- package/src/native/fork_checkpoint.ts +19 -3
- package/src/native/ipc_world_state_instance.ts +834 -0
- package/src/native/merkle_trees_facade.ts +89 -109
- package/src/native/message.ts +13 -286
- package/src/native/native_world_state.ts +140 -93
- package/src/native/native_world_state_instance.ts +98 -278
- package/src/native/world_state_operation.ts +33 -0
- package/src/synchronizer/config.ts +16 -23
- package/src/synchronizer/errors.ts +8 -0
- package/src/synchronizer/factory.ts +18 -11
- package/src/synchronizer/index.ts +1 -0
- package/src/synchronizer/server_world_state_synchronizer.ts +156 -51
- package/src/test/utils.ts +14 -5
- package/src/testing.ts +12 -10
- package/src/world-state-db/merkle_tree_db.ts +2 -12
- package/dest/native/world_state_ops_queue.d.ts +0 -19
- package/dest/native/world_state_ops_queue.d.ts.map +0 -1
- package/dest/native/world_state_ops_queue.js +0 -146
- package/src/native/world_state_ops_queue.ts +0 -190
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
1
|
+
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
3
2
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
5
4
|
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
6
5
|
import { elapsed } from '@aztec/foundation/timer';
|
|
7
6
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
type
|
|
7
|
+
type BlockHash,
|
|
8
|
+
EventDrivenL2BlockStream,
|
|
9
|
+
type L2Block,
|
|
11
10
|
type L2BlockSource,
|
|
12
|
-
L2BlockStream,
|
|
13
11
|
type L2BlockStreamEvent,
|
|
14
12
|
type L2BlockStreamEventHandler,
|
|
15
13
|
type L2BlockStreamLocalDataProvider,
|
|
16
|
-
type
|
|
14
|
+
type LocalChainTips,
|
|
17
15
|
} from '@aztec/stdlib/block';
|
|
18
16
|
import {
|
|
19
17
|
WorldStateRunningState,
|
|
@@ -50,7 +48,7 @@ export class ServerWorldStateSynchronizer
|
|
|
50
48
|
private currentState: WorldStateRunningState = WorldStateRunningState.IDLE;
|
|
51
49
|
|
|
52
50
|
private syncPromise = promiseWithResolvers<void>();
|
|
53
|
-
protected blockStream:
|
|
51
|
+
protected blockStream: EventDrivenL2BlockStream | undefined;
|
|
54
52
|
|
|
55
53
|
// WorldState doesn't track the proven block number, it only tracks the latest tips of the pending chain and the finalized chain
|
|
56
54
|
// store the proven block number here, in the synchronizer, so that we don't end up spamming the logs with 'chain-proved' events
|
|
@@ -64,7 +62,7 @@ export class ServerWorldStateSynchronizer
|
|
|
64
62
|
private readonly log: Logger = createLogger('world_state'),
|
|
65
63
|
) {
|
|
66
64
|
this.merkleTreeCommitted = this.merkleTreeDb.getCommitted();
|
|
67
|
-
this.historyToKeep = config.
|
|
65
|
+
this.historyToKeep = config.worldStateCheckpointHistory < 1 ? undefined : config.worldStateCheckpointHistory;
|
|
68
66
|
this.log.info(
|
|
69
67
|
`Created world state synchroniser with block history of ${
|
|
70
68
|
this.historyToKeep === undefined ? 'infinity' : this.historyToKeep
|
|
@@ -80,6 +78,46 @@ export class ServerWorldStateSynchronizer
|
|
|
80
78
|
return this.merkleTreeDb.getSnapshot(blockNumber);
|
|
81
79
|
}
|
|
82
80
|
|
|
81
|
+
public async getVerifiedSnapshot(blockNumber: BlockNumber, blockHash: BlockHash): Promise<MerkleTreeReadOperations> {
|
|
82
|
+
const snapshot = this.merkleTreeDb.getSnapshot(blockNumber);
|
|
83
|
+
// Block 0's snapshot is the pre-genesis archive view (size 0), so archive leaf 0 is not visible from it;
|
|
84
|
+
// verify against the initial header hash instead. For later blocks, read archive leaf `blockNumber` from the
|
|
85
|
+
// snapshot's own view so the exact handle we return is validated against the requested fork.
|
|
86
|
+
const actualHash =
|
|
87
|
+
blockNumber === BlockNumber.ZERO
|
|
88
|
+
? (await this.merkleTreeCommitted.getInitialHeader().hash()).toString()
|
|
89
|
+
: (await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber)))?.toString();
|
|
90
|
+
|
|
91
|
+
if (actualHash === undefined) {
|
|
92
|
+
// A missing archive leaf means either the block's history has been pruned away (permanent: the block predates
|
|
93
|
+
// the oldest historical block kept by world state) or a reorg flipped the fork between the sync and this read
|
|
94
|
+
// (transient). Only the latter is worth retrying, so it alone surfaces as WorldStateSynchronizerError.
|
|
95
|
+
const { oldestHistoricalBlock } = await this.merkleTreeDb.getStatusSummary();
|
|
96
|
+
if (blockNumber < oldestHistoricalBlock) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Unable to find leaf for block ${blockNumber} in the archive tree: world state history has been pruned to block ${oldestHistoricalBlock}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
throw new WorldStateSynchronizerError(`Unable to read block hash at block ${blockNumber} to verify snapshot`, {
|
|
102
|
+
cause: { reason: 'block_not_available', targetBlockNumber: blockNumber },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (actualHash !== blockHash.toString()) {
|
|
106
|
+
throw new WorldStateSynchronizerError(
|
|
107
|
+
`Block hash mismatch at block ${blockNumber} (expected ${blockHash} but got ${actualHash})`,
|
|
108
|
+
{
|
|
109
|
+
cause: {
|
|
110
|
+
reason: 'block_hash_mismatch',
|
|
111
|
+
targetBlockNumber: blockNumber,
|
|
112
|
+
expectedHash: blockHash.toString(),
|
|
113
|
+
actualHash,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return snapshot;
|
|
119
|
+
}
|
|
120
|
+
|
|
83
121
|
public fork(blockNumber?: BlockNumber, opts?: { closeDelayMs?: number }): Promise<MerkleTreeWriteOperations> {
|
|
84
122
|
return this.merkleTreeDb.fork(blockNumber, opts);
|
|
85
123
|
}
|
|
@@ -101,11 +139,7 @@ export class ServerWorldStateSynchronizer
|
|
|
101
139
|
}
|
|
102
140
|
|
|
103
141
|
// Get the current latest block number
|
|
104
|
-
this.latestBlockNumberAtStart = BlockNumber(
|
|
105
|
-
await (this.config.worldStateProvenBlocksOnly
|
|
106
|
-
? this.l2BlockSource.getProvenBlockNumber()
|
|
107
|
-
: this.l2BlockSource.getBlockNumber()),
|
|
108
|
-
);
|
|
142
|
+
this.latestBlockNumberAtStart = BlockNumber(await this.l2BlockSource.getBlockNumber());
|
|
109
143
|
|
|
110
144
|
const blockToDownloadFrom = (await this.getLatestBlockNumber()) + 1;
|
|
111
145
|
|
|
@@ -126,10 +160,9 @@ export class ServerWorldStateSynchronizer
|
|
|
126
160
|
return this.syncPromise.promise;
|
|
127
161
|
}
|
|
128
162
|
|
|
129
|
-
protected createBlockStream():
|
|
163
|
+
protected createBlockStream(): EventDrivenL2BlockStream {
|
|
130
164
|
const logger = createLogger('world-state:block_stream');
|
|
131
|
-
return new
|
|
132
|
-
proven: this.config.worldStateProvenBlocksOnly,
|
|
165
|
+
return new EventDrivenL2BlockStream(this.l2BlockSource, this, this, logger, {
|
|
133
166
|
pollIntervalMS: this.config.worldStateBlockCheckIntervalMS,
|
|
134
167
|
batchSize: this.config.worldStateBlockRequestBatchSize,
|
|
135
168
|
ignoreCheckpoints: true,
|
|
@@ -182,13 +215,10 @@ export class ServerWorldStateSynchronizer
|
|
|
182
215
|
/**
|
|
183
216
|
* Forces an immediate sync.
|
|
184
217
|
* @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it.
|
|
185
|
-
* @param
|
|
218
|
+
* @param blockHash - If provided, verifies the block at targetBlockNumber matches this hash. On mismatch, triggers a resync (reorg detection).
|
|
186
219
|
* @returns A promise that resolves with the block number the world state was synced to
|
|
187
220
|
*/
|
|
188
|
-
public async syncImmediate(
|
|
189
|
-
targetBlockNumber?: BlockNumber,
|
|
190
|
-
skipThrowIfTargetNotReached?: boolean,
|
|
191
|
-
): Promise<BlockNumber> {
|
|
221
|
+
public async syncImmediate(targetBlockNumber?: BlockNumber, blockHash?: BlockHash): Promise<BlockNumber> {
|
|
192
222
|
if (this.currentState !== WorldStateRunningState.RUNNING) {
|
|
193
223
|
throw new Error(`World State is not running. Unable to perform sync.`);
|
|
194
224
|
}
|
|
@@ -200,7 +230,19 @@ export class ServerWorldStateSynchronizer
|
|
|
200
230
|
// If we have been given a block number to sync to and we have reached that number then return
|
|
201
231
|
const currentBlockNumber = await this.getLatestBlockNumber();
|
|
202
232
|
if (targetBlockNumber !== undefined && targetBlockNumber <= currentBlockNumber) {
|
|
203
|
-
|
|
233
|
+
if (blockHash === undefined) {
|
|
234
|
+
return currentBlockNumber;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// If a block hash was provided, verify we're on the expected fork
|
|
238
|
+
const currentHash = await this.getL2BlockHash(targetBlockNumber);
|
|
239
|
+
if (currentHash === blockHash.toString()) {
|
|
240
|
+
return currentBlockNumber;
|
|
241
|
+
}
|
|
242
|
+
// Hash mismatch: a reorg may have occurred, fall through to trigger sync
|
|
243
|
+
this.log.debug(
|
|
244
|
+
`World state block hash mismatch at ${targetBlockNumber} (expected ${blockHash}, got ${currentHash}). Triggering resync.`,
|
|
245
|
+
);
|
|
204
246
|
}
|
|
205
247
|
this.log.debug(`World State at ${currentBlockNumber} told to sync to ${targetBlockNumber ?? 'latest'}`);
|
|
206
248
|
|
|
@@ -218,7 +260,7 @@ export class ServerWorldStateSynchronizer
|
|
|
218
260
|
|
|
219
261
|
// If we have been given a block number to sync to and we have not reached that number then fail
|
|
220
262
|
const updatedBlockNumber = await this.getLatestBlockNumber();
|
|
221
|
-
if (
|
|
263
|
+
if (targetBlockNumber !== undefined && targetBlockNumber > updatedBlockNumber) {
|
|
222
264
|
throw new WorldStateSynchronizerError(
|
|
223
265
|
`Unable to sync to block number ${targetBlockNumber} (last synced is ${updatedBlockNumber})`,
|
|
224
266
|
{
|
|
@@ -232,6 +274,24 @@ export class ServerWorldStateSynchronizer
|
|
|
232
274
|
);
|
|
233
275
|
}
|
|
234
276
|
|
|
277
|
+
// If a block hash was provided, verify we're on the expected fork after syncing, throw otherwise
|
|
278
|
+
if (blockHash !== undefined && targetBlockNumber !== undefined) {
|
|
279
|
+
const updatedHash = await this.getL2BlockHash(targetBlockNumber);
|
|
280
|
+
if (updatedHash !== blockHash.toString()) {
|
|
281
|
+
throw new WorldStateSynchronizerError(
|
|
282
|
+
`Block hash mismatch at block ${targetBlockNumber} (expected ${blockHash} but got ${updatedHash})`,
|
|
283
|
+
{
|
|
284
|
+
cause: {
|
|
285
|
+
reason: 'block_hash_mismatch',
|
|
286
|
+
targetBlockNumber,
|
|
287
|
+
expectedHash: blockHash.toString(),
|
|
288
|
+
actualHash: updatedHash,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
235
295
|
return updatedBlockNumber;
|
|
236
296
|
}
|
|
237
297
|
|
|
@@ -243,8 +303,12 @@ export class ServerWorldStateSynchronizer
|
|
|
243
303
|
return this.merkleTreeCommitted.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(number)).then(leaf => leaf?.toString());
|
|
244
304
|
}
|
|
245
305
|
|
|
246
|
-
/**
|
|
247
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Returns the proposed, proven, and finalized block tips of the chain. World state drives its block stream with
|
|
308
|
+
* `ignoreCheckpoints`, so it does not track checkpointed blocks or checkpoints and omits `checkpointed` from the tips
|
|
309
|
+
* it reports.
|
|
310
|
+
*/
|
|
311
|
+
public async getL2Tips(): Promise<LocalChainTips> {
|
|
248
312
|
const status = await this.merkleTreeDb.getStatusSummary();
|
|
249
313
|
const unfinalizedBlockHashPromise = this.getL2BlockHash(status.unfinalizedBlockNumber);
|
|
250
314
|
const finalizedBlockHashPromise = this.getL2BlockHash(status.finalizedBlockNumber);
|
|
@@ -258,25 +322,13 @@ export class ServerWorldStateSynchronizer
|
|
|
258
322
|
finalizedBlockHashPromise,
|
|
259
323
|
provenBlockHashPromise,
|
|
260
324
|
]);
|
|
261
|
-
const latestBlockId: L2BlockId = { number: status.unfinalizedBlockNumber, hash: unfinalizedBlockHash! };
|
|
262
|
-
|
|
263
|
-
// World state doesn't track checkpointed blocks or checkpoints themselves.
|
|
264
|
-
// but we use a block stream so we need to provide 'local' L2Tips.
|
|
265
|
-
// We configure the block stream to ignore checkpoints and set checkpoint values to genesis here.
|
|
266
|
-
const genesisCheckpointHeaderHash = GENESIS_CHECKPOINT_HEADER_HASH.toString();
|
|
267
325
|
return {
|
|
268
|
-
proposed:
|
|
269
|
-
checkpointed: {
|
|
270
|
-
block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() },
|
|
271
|
-
checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
|
|
272
|
-
},
|
|
326
|
+
proposed: { number: status.unfinalizedBlockNumber, hash: unfinalizedBlockHash },
|
|
273
327
|
finalized: {
|
|
274
|
-
block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash
|
|
275
|
-
checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
|
|
328
|
+
block: { number: status.finalizedBlockNumber, hash: finalizedBlockHash },
|
|
276
329
|
},
|
|
277
330
|
proven: {
|
|
278
|
-
block: { number: provenBlockNumber, hash: provenBlockHash
|
|
279
|
-
checkpoint: { number: INITIAL_L2_CHECKPOINT_NUM, hash: genesisCheckpointHeaderHash },
|
|
331
|
+
block: { number: provenBlockNumber, hash: provenBlockHash },
|
|
280
332
|
},
|
|
281
333
|
};
|
|
282
334
|
}
|
|
@@ -296,6 +348,15 @@ export class ServerWorldStateSynchronizer
|
|
|
296
348
|
case 'chain-finalized':
|
|
297
349
|
await this.handleChainFinalized(event.block.number);
|
|
298
350
|
break;
|
|
351
|
+
// World state runs in block mode with ignoreCheckpoints: it tracks tips via blocks-added/pruned/proven/finalized
|
|
352
|
+
// and ignores the thin tip events (it never anchors on them).
|
|
353
|
+
case 'chain-proposed':
|
|
354
|
+
case 'chain-checkpointed':
|
|
355
|
+
break;
|
|
356
|
+
default: {
|
|
357
|
+
const _: never = event;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
299
360
|
}
|
|
300
361
|
}
|
|
301
362
|
|
|
@@ -304,8 +365,8 @@ export class ServerWorldStateSynchronizer
|
|
|
304
365
|
* @param l2Blocks - The L2 blocks to handle.
|
|
305
366
|
* @returns Whether the block handled was produced by this same node.
|
|
306
367
|
*/
|
|
307
|
-
private async handleL2Blocks(l2Blocks:
|
|
308
|
-
this.log.
|
|
368
|
+
private async handleL2Blocks(l2Blocks: L2Block[]) {
|
|
369
|
+
this.log.debug(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`);
|
|
309
370
|
|
|
310
371
|
// Fetch the L1->L2 messages for the first block in a checkpoint.
|
|
311
372
|
const messagesForBlocks = new Map<BlockNumber, Fr[]>();
|
|
@@ -345,11 +406,13 @@ export class ServerWorldStateSynchronizer
|
|
|
345
406
|
* @param l1ToL2Messages - The L1 to L2 messages for the block.
|
|
346
407
|
* @returns Whether the block handled was produced by this same node.
|
|
347
408
|
*/
|
|
348
|
-
private async handleL2Block(l2Block:
|
|
349
|
-
this.log.
|
|
409
|
+
private async handleL2Block(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull> {
|
|
410
|
+
this.log.debug(`Pushing L2 block ${l2Block.number} to merkle tree db `, {
|
|
350
411
|
blockNumber: l2Block.number,
|
|
351
412
|
blockHash: await l2Block.hash().then(h => h.toString()),
|
|
352
413
|
l1ToL2Messages: l1ToL2Messages.map(msg => msg.toString()),
|
|
414
|
+
blockHeader: l2Block.header.toInspect(),
|
|
415
|
+
blockStats: l2Block.getStats(),
|
|
353
416
|
});
|
|
354
417
|
const result = await this.merkleTreeDb.handleL2BlockAndMessages(l2Block, l1ToL2Messages);
|
|
355
418
|
|
|
@@ -363,16 +426,56 @@ export class ServerWorldStateSynchronizer
|
|
|
363
426
|
|
|
364
427
|
private async handleChainFinalized(blockNumber: BlockNumber) {
|
|
365
428
|
this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
|
|
429
|
+
// If the finalized block number is older than the oldest available block in world state,
|
|
430
|
+
// skip entirely. The finalized block number can jump backwards (e.g. when the finalization
|
|
431
|
+
// heuristic changes) and try to read block data that has already been pruned. When this
|
|
432
|
+
// happens, there is nothing useful to do — the native world state is already finalized
|
|
433
|
+
// past this point and pruning has already happened.
|
|
434
|
+
const currentSummary = await this.merkleTreeDb.getStatusSummary();
|
|
435
|
+
if (blockNumber < currentSummary.oldestHistoricalBlock || blockNumber < 1) {
|
|
436
|
+
this.log.trace(
|
|
437
|
+
`Finalized block ${blockNumber} is older than the oldest available block ${currentSummary.oldestHistoricalBlock}. Skipping.`,
|
|
438
|
+
);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
366
441
|
const summary = await this.merkleTreeDb.setFinalized(blockNumber);
|
|
367
442
|
if (this.historyToKeep === undefined) {
|
|
368
443
|
return;
|
|
369
444
|
}
|
|
370
|
-
const
|
|
371
|
-
if (
|
|
445
|
+
const finalisedBlockData = await this.l2BlockSource.getBlockData({ number: summary.finalizedBlockNumber });
|
|
446
|
+
if (finalisedBlockData === undefined) {
|
|
447
|
+
this.log.warn(
|
|
448
|
+
`Failed to retrieve checkpointed block for finalized block number: ${summary.finalizedBlockNumber}`,
|
|
449
|
+
);
|
|
372
450
|
return;
|
|
373
451
|
}
|
|
374
|
-
|
|
375
|
-
const
|
|
452
|
+
// Compute the required historic checkpoint number
|
|
453
|
+
const newHistoricCheckpointNumber = finalisedBlockData.checkpointNumber - this.historyToKeep + 1;
|
|
454
|
+
if (newHistoricCheckpointNumber <= 1) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
// Retrieve the historic checkpoint
|
|
458
|
+
const historicCheckpoint = await this.l2BlockSource.getCheckpoint({
|
|
459
|
+
number: CheckpointNumber(newHistoricCheckpointNumber),
|
|
460
|
+
});
|
|
461
|
+
if (!historicCheckpoint) {
|
|
462
|
+
this.log.warn(`Failed to retrieve checkpoint number ${newHistoricCheckpointNumber} from Archiver`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (historicCheckpoint.checkpoint.blocks.length === 0 || historicCheckpoint.checkpoint.blocks[0] === undefined) {
|
|
466
|
+
this.log.warn(`Retrieved checkpoint number ${newHistoricCheckpointNumber} has no blocks!`);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
// Find the block at the start of the checkpoint and remove blocks up to this one
|
|
470
|
+
const newHistoricBlock = historicCheckpoint.checkpoint.blocks[0];
|
|
471
|
+
if (newHistoricBlock.number <= currentSummary.oldestHistoricalBlock) {
|
|
472
|
+
this.log.debug(
|
|
473
|
+
`Historic block ${newHistoricBlock.number} is not newer than oldest available ${currentSummary.oldestHistoricalBlock}. Skipping prune.`,
|
|
474
|
+
);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
this.log.verbose(`Pruning historic blocks to ${newHistoricBlock.number}`);
|
|
478
|
+
const status = await this.merkleTreeDb.removeHistoricalBlocks(BlockNumber(newHistoricBlock.number));
|
|
376
479
|
this.log.debug(`World state summary `, status.summary);
|
|
377
480
|
}
|
|
378
481
|
|
|
@@ -383,9 +486,11 @@ export class ServerWorldStateSynchronizer
|
|
|
383
486
|
}
|
|
384
487
|
|
|
385
488
|
private async handleChainPruned(blockNumber: BlockNumber) {
|
|
386
|
-
this.log.
|
|
489
|
+
this.log.info(`Chain pruned to block ${blockNumber}`);
|
|
387
490
|
const status = await this.merkleTreeDb.unwindBlocks(blockNumber);
|
|
388
|
-
this.provenBlockNumber
|
|
491
|
+
if (this.provenBlockNumber !== undefined && this.provenBlockNumber > blockNumber) {
|
|
492
|
+
this.provenBlockNumber = undefined;
|
|
493
|
+
}
|
|
389
494
|
this.instrumentation.updateWorldStateMetrics(status);
|
|
390
495
|
}
|
|
391
496
|
|
package/src/test/utils.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { asyncMap } from '@aztec/foundation/async-map';
|
|
|
8
8
|
import { BlockNumber, type CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
|
|
9
9
|
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
10
10
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
11
|
-
import {
|
|
11
|
+
import { L2Block } from '@aztec/stdlib/block';
|
|
12
12
|
import type {
|
|
13
13
|
IndexedTreeId,
|
|
14
14
|
MerkleTreeReadOperations,
|
|
@@ -20,7 +20,7 @@ import { BlockHeader } from '@aztec/stdlib/tx';
|
|
|
20
20
|
|
|
21
21
|
import type { NativeWorldStateService } from '../native/native_world_state.js';
|
|
22
22
|
|
|
23
|
-
export async function updateBlockState(block:
|
|
23
|
+
export async function updateBlockState(block: L2Block, l1ToL2Messages: Fr[], fork: MerkleTreeWriteOperations) {
|
|
24
24
|
const insertData = async (
|
|
25
25
|
treeId: IndexedTreeId,
|
|
26
26
|
data: Buffer[][],
|
|
@@ -60,7 +60,16 @@ export async function updateBlockState(block: L2BlockNew, l1ToL2Messages: Fr[],
|
|
|
60
60
|
await Promise.all([publicDataInsert, nullifierInsert, noteHashInsert, messageInsert]);
|
|
61
61
|
|
|
62
62
|
const state = await fork.getStateReference();
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
// Capture the archive root *before* appending this block so the header's lastArchive chains off the previous block.
|
|
65
|
+
// sync_block now verifies lastArchive against the committed archive root, so a block carrying an unchained value
|
|
66
|
+
// (e.g. the random lastArchive from L2Block.random) would be rejected as a divergence from the canonical chain.
|
|
67
|
+
const previousArchive = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
68
|
+
block.header = BlockHeader.from({
|
|
69
|
+
...block.header,
|
|
70
|
+
state,
|
|
71
|
+
lastArchive: new AppendOnlyTreeSnapshot(Fr.fromBuffer(previousArchive.root), Number(previousArchive.size)),
|
|
72
|
+
});
|
|
64
73
|
await fork.updateArchive(block.header);
|
|
65
74
|
|
|
66
75
|
const archiveState = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
@@ -76,7 +85,7 @@ export async function mockBlock(
|
|
|
76
85
|
numL1ToL2Messages: number = NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
|
|
77
86
|
isFirstBlockInCheckpoint: boolean = true,
|
|
78
87
|
) {
|
|
79
|
-
const block = await
|
|
88
|
+
const block = await L2Block.random(blockNum, {
|
|
80
89
|
indexWithinCheckpoint: isFirstBlockInCheckpoint ? IndexWithinCheckpoint(0) : IndexWithinCheckpoint(1),
|
|
81
90
|
txsPerBlock: size,
|
|
82
91
|
txOptions: { maxEffects },
|
|
@@ -92,7 +101,7 @@ export async function mockBlock(
|
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
export async function mockEmptyBlock(blockNum: BlockNumber, fork: MerkleTreeWriteOperations) {
|
|
95
|
-
const l2Block =
|
|
104
|
+
const l2Block = L2Block.empty();
|
|
96
105
|
const l1ToL2Messages = Array(16).fill(0).map(Fr.zero);
|
|
97
106
|
|
|
98
107
|
l2Block.header.globalVariables.blockNumber = blockNum;
|
package/src/testing.ts
CHANGED
|
@@ -3,22 +3,22 @@ import { Fr } from '@aztec/foundation/curves/bn254';
|
|
|
3
3
|
import { computeFeePayerBalanceLeafSlot } from '@aztec/protocol-contracts/fee-juice';
|
|
4
4
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
5
5
|
import { MerkleTreeId, PublicDataTreeLeaf } from '@aztec/stdlib/trees';
|
|
6
|
+
import type { GenesisData } from '@aztec/stdlib/world-state';
|
|
6
7
|
|
|
7
8
|
import { NativeWorldStateService } from './native/index.js';
|
|
8
9
|
|
|
9
|
-
async function generateGenesisValues(
|
|
10
|
-
if (!prefilledPublicData.length) {
|
|
10
|
+
async function generateGenesisValues(genesis: GenesisData) {
|
|
11
|
+
if (!genesis.prefilledPublicData.length && genesis.genesisTimestamp === 0n) {
|
|
11
12
|
return {
|
|
12
13
|
genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT),
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
17
|
+
// Compute the genesis values on a throwaway world state. The archive root derives only from the
|
|
18
|
+
// prefilled public data and the genesis timestamp, so the fsync-off ephemeral store (no version
|
|
19
|
+
// manager, no crash-recoverability) produces an identical root while skipping the fsync overhead
|
|
20
|
+
// that `tmp` pays. close() removes the tmpdir.
|
|
21
|
+
const ws = await NativeWorldStateService.ephemeral(genesis);
|
|
22
22
|
const genesisArchiveRoot = new Fr((await ws.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
23
23
|
await ws.close();
|
|
24
24
|
|
|
@@ -33,6 +33,7 @@ export async function getGenesisValues(
|
|
|
33
33
|
initialAccounts: AztecAddress[],
|
|
34
34
|
initialAccountFeeJuice = defaultInitialAccountFeeJuice,
|
|
35
35
|
genesisPublicData: PublicDataTreeLeaf[] = [],
|
|
36
|
+
genesisTimestamp: bigint = 0n,
|
|
36
37
|
) {
|
|
37
38
|
// Top up the accounts with fee juice.
|
|
38
39
|
let prefilledPublicData = await Promise.all(
|
|
@@ -46,11 +47,12 @@ export async function getGenesisValues(
|
|
|
46
47
|
|
|
47
48
|
prefilledPublicData.sort((a, b) => (b.slot.lt(a.slot) ? 1 : -1));
|
|
48
49
|
|
|
49
|
-
const
|
|
50
|
+
const genesis: GenesisData = { prefilledPublicData, genesisTimestamp };
|
|
51
|
+
const { genesisArchiveRoot } = await generateGenesisValues(genesis);
|
|
50
52
|
|
|
51
53
|
return {
|
|
52
54
|
genesisArchiveRoot,
|
|
53
|
-
|
|
55
|
+
genesis,
|
|
54
56
|
fundingNeeded: BigInt(initialAccounts.length) * initialAccountFeeJuice.toBigInt(),
|
|
55
57
|
};
|
|
56
58
|
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { MAX_NULLIFIERS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX } from '@aztec/constants';
|
|
2
2
|
import type { BlockNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
-
import type {
|
|
5
|
-
import type { L2BlockNew } from '@aztec/stdlib/block';
|
|
4
|
+
import type { L2Block } from '@aztec/stdlib/block';
|
|
6
5
|
import type {
|
|
7
6
|
ForkMerkleTreeOperations,
|
|
8
7
|
MerkleTreeReadOperations,
|
|
9
8
|
ReadonlyWorldStateAccess,
|
|
10
9
|
} from '@aztec/stdlib/interfaces/server';
|
|
11
|
-
import type { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
12
10
|
|
|
13
11
|
import type { WorldStateStatusFull, WorldStateStatusSummary } from '../native/message.js';
|
|
14
12
|
|
|
@@ -31,21 +29,13 @@ export const INITIAL_NULLIFIER_TREE_SIZE = 2 * MAX_NULLIFIERS_PER_TX;
|
|
|
31
29
|
|
|
32
30
|
export const INITIAL_PUBLIC_DATA_TREE_SIZE = 2 * MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX;
|
|
33
31
|
|
|
34
|
-
export type TreeSnapshots = {
|
|
35
|
-
[MerkleTreeId.NULLIFIER_TREE]: IndexedTreeSnapshot;
|
|
36
|
-
[MerkleTreeId.NOTE_HASH_TREE]: TreeSnapshot<Fr>;
|
|
37
|
-
[MerkleTreeId.PUBLIC_DATA_TREE]: IndexedTreeSnapshot;
|
|
38
|
-
[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]: TreeSnapshot<Fr>;
|
|
39
|
-
[MerkleTreeId.ARCHIVE]: TreeSnapshot<Fr>;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
32
|
export interface MerkleTreeAdminDatabase extends ForkMerkleTreeOperations, ReadonlyWorldStateAccess {
|
|
43
33
|
/**
|
|
44
34
|
* Handles a single L2 block (i.e. Inserts the new note hashes into the merkle tree).
|
|
45
35
|
* @param block - The L2 block to handle.
|
|
46
36
|
* @param l1ToL2Messages - The L1 to L2 messages for the block.
|
|
47
37
|
*/
|
|
48
|
-
handleL2BlockAndMessages(block:
|
|
38
|
+
handleL2BlockAndMessages(block: L2Block, l1ToL2Messages: Fr[]): Promise<WorldStateStatusFull>;
|
|
49
39
|
|
|
50
40
|
/**
|
|
51
41
|
* Gets a handle that allows reading the latest committed state
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { WorldStateMessageType } from './message.js';
|
|
2
|
-
export declare const MUTATING_MSG_TYPES: Set<WorldStateMessageType>;
|
|
3
|
-
export declare class WorldStateOpsQueue {
|
|
4
|
-
private requests;
|
|
5
|
-
private inFlightMutatingCount;
|
|
6
|
-
private inFlightCount;
|
|
7
|
-
private stopPromise?;
|
|
8
|
-
private stopResolve?;
|
|
9
|
-
private requestId;
|
|
10
|
-
private ops;
|
|
11
|
-
execute(request: () => Promise<any>, messageType: WorldStateMessageType, committedOnly: boolean): Promise<any>;
|
|
12
|
-
private executeMutating;
|
|
13
|
-
private executeNonMutatingUncommitted;
|
|
14
|
-
private executeNonMutatingCommitted;
|
|
15
|
-
private checkAndEnqueue;
|
|
16
|
-
private sendEnqueuedRequest;
|
|
17
|
-
stop(): Promise<void>;
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ybGRfc3RhdGVfb3BzX3F1ZXVlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbmF0aXZlL3dvcmxkX3N0YXRlX29wc19xdWV1ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUF5QnJELGVBQU8sTUFBTSxrQkFBa0IsNEJBZ0I3QixDQUFDO0FBR0gscUJBQWEsa0JBQWtCO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQXNCO0lBQ3RDLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBSztJQUNsQyxPQUFPLENBQUMsYUFBYSxDQUFLO0lBQzFCLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBZ0I7SUFDcEMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQUFhO0lBQ2pDLE9BQU8sQ0FBQyxTQUFTLENBQUs7SUFDdEIsT0FBTyxDQUFDLEdBQUcsQ0FBd0M7SUFJNUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRSxXQUFXLEVBQUUscUJBQXFCLEVBQUUsYUFBYSxFQUFFLE9BQU8sZ0JBc0JyRztJQUdELE9BQU8sQ0FBQyxlQUFlO0lBV3ZCLE9BQU8sQ0FBQyw2QkFBNkI7SUFZckMsT0FBTyxDQUFDLDJCQUEyQjtJQVVuQyxPQUFPLENBQUMsZUFBZTtJQXlDdkIsT0FBTyxDQUFDLG1CQUFtQjtJQWdCcEIsSUFBSSxrQkFnQlY7Q0FDRiJ9
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"world_state_ops_queue.d.ts","sourceRoot":"","sources":["../../src/native/world_state_ops_queue.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAyBrD,eAAO,MAAM,kBAAkB,4BAgB7B,CAAC;AAGH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAgB;IACpC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,GAAG,CAAwC;IAI5C,OAAO,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,qBAAqB,EAAE,aAAa,EAAE,OAAO,gBAsBrG;IAGD,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,6BAA6B;IAYrC,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,eAAe;IAyCvB,OAAO,CAAC,mBAAmB;IAgBpB,IAAI,kBAgBV;CACF"}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
2
|
-
import { WorldStateMessageType } from './message.js';
|
|
3
|
-
// These are the set of message types that implement mutating operations
|
|
4
|
-
// Messages of these types require exclusive access to their given forks
|
|
5
|
-
export const MUTATING_MSG_TYPES = new Set([
|
|
6
|
-
WorldStateMessageType.APPEND_LEAVES,
|
|
7
|
-
WorldStateMessageType.BATCH_INSERT,
|
|
8
|
-
WorldStateMessageType.SEQUENTIAL_INSERT,
|
|
9
|
-
WorldStateMessageType.UPDATE_ARCHIVE,
|
|
10
|
-
WorldStateMessageType.COMMIT,
|
|
11
|
-
WorldStateMessageType.ROLLBACK,
|
|
12
|
-
WorldStateMessageType.SYNC_BLOCK,
|
|
13
|
-
WorldStateMessageType.CREATE_FORK,
|
|
14
|
-
WorldStateMessageType.DELETE_FORK,
|
|
15
|
-
WorldStateMessageType.FINALIZE_BLOCKS,
|
|
16
|
-
WorldStateMessageType.UNWIND_BLOCKS,
|
|
17
|
-
WorldStateMessageType.REMOVE_HISTORICAL_BLOCKS,
|
|
18
|
-
WorldStateMessageType.CREATE_CHECKPOINT,
|
|
19
|
-
WorldStateMessageType.COMMIT_CHECKPOINT,
|
|
20
|
-
WorldStateMessageType.REVERT_CHECKPOINT
|
|
21
|
-
]);
|
|
22
|
-
// This class implements the per-fork operation queue
|
|
23
|
-
export class WorldStateOpsQueue {
|
|
24
|
-
requests = [];
|
|
25
|
-
inFlightMutatingCount = 0;
|
|
26
|
-
inFlightCount = 0;
|
|
27
|
-
stopPromise;
|
|
28
|
-
stopResolve;
|
|
29
|
-
requestId = 0;
|
|
30
|
-
ops = new Map();
|
|
31
|
-
// The primary public api, this is where an operation is queued
|
|
32
|
-
// We return a promise that will ultimately be resolved/rejected with the response/error generated by the 'request' argument
|
|
33
|
-
execute(request, messageType, committedOnly) {
|
|
34
|
-
if (this.stopResolve !== undefined) {
|
|
35
|
-
throw new Error('Unable to send request to world state, queue already stopped');
|
|
36
|
-
}
|
|
37
|
-
const op = {
|
|
38
|
-
requestId: this.requestId++,
|
|
39
|
-
mutating: MUTATING_MSG_TYPES.has(messageType),
|
|
40
|
-
request,
|
|
41
|
-
promise: promiseWithResolvers()
|
|
42
|
-
};
|
|
43
|
-
this.ops.set(op.requestId, op);
|
|
44
|
-
// Perform the appropriate action based upon the queueing rules
|
|
45
|
-
if (op.mutating) {
|
|
46
|
-
this.executeMutating(op);
|
|
47
|
-
} else if (committedOnly === false) {
|
|
48
|
-
this.executeNonMutatingUncommitted(op);
|
|
49
|
-
} else {
|
|
50
|
-
this.executeNonMutatingCommitted(op);
|
|
51
|
-
}
|
|
52
|
-
return op.promise.promise;
|
|
53
|
-
}
|
|
54
|
-
// Mutating requests need exclusive access
|
|
55
|
-
executeMutating(op) {
|
|
56
|
-
// If nothing is in flight then we send the request immediately
|
|
57
|
-
// Otherwise add to the queue
|
|
58
|
-
if (this.inFlightCount === 0) {
|
|
59
|
-
this.sendEnqueuedRequest(op);
|
|
60
|
-
} else {
|
|
61
|
-
this.requests.push(op);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// Non mutating requests including uncommitted state
|
|
65
|
-
executeNonMutatingUncommitted(op) {
|
|
66
|
-
// If there are no mutating requests in flight and there is nothing queued
|
|
67
|
-
// then send the request immediately
|
|
68
|
-
// If a mutating request is in flight then we must wait
|
|
69
|
-
// If a mutating request is not in flight but something is queued then it must be a mutating request
|
|
70
|
-
if (this.inFlightMutatingCount == 0 && this.requests.length == 0) {
|
|
71
|
-
this.sendEnqueuedRequest(op);
|
|
72
|
-
} else {
|
|
73
|
-
this.requests.push(op);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
executeNonMutatingCommitted(op) {
|
|
77
|
-
// This is a non-mutating request for committed data
|
|
78
|
-
// It can always be sent
|
|
79
|
-
op.request().then(op.promise.resolve, op.promise.reject).finally(()=>{
|
|
80
|
-
this.ops.delete(op.requestId);
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
checkAndEnqueue(completedOp) {
|
|
84
|
-
// As request has completed
|
|
85
|
-
// First we decrements the relevant in flight counters
|
|
86
|
-
if (completedOp.mutating) {
|
|
87
|
-
--this.inFlightMutatingCount;
|
|
88
|
-
}
|
|
89
|
-
--this.inFlightCount;
|
|
90
|
-
// If there are still requests in flight then do nothing further
|
|
91
|
-
if (this.inFlightCount != 0) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
// No requests in flight, send next queued requests
|
|
95
|
-
// We loop and send:
|
|
96
|
-
// 1 mutating request if it is next in the queue
|
|
97
|
-
// As many non-mutating requests as we encounter until
|
|
98
|
-
// we exhaust the queue or we reach a mutating request
|
|
99
|
-
while(this.requests.length > 0){
|
|
100
|
-
const next = this.requests[0];
|
|
101
|
-
if (next.mutating) {
|
|
102
|
-
if (this.inFlightCount == 0) {
|
|
103
|
-
// send the mutating request
|
|
104
|
-
this.requests.shift();
|
|
105
|
-
this.sendEnqueuedRequest(next);
|
|
106
|
-
}
|
|
107
|
-
break;
|
|
108
|
-
} else {
|
|
109
|
-
// not mutating, send and go round again
|
|
110
|
-
this.requests.shift();
|
|
111
|
-
this.sendEnqueuedRequest(next);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// If the queue is empty, there is nothing in flight and we have been told to stop, then resolve the stop promise
|
|
115
|
-
if (this.inFlightCount == 0 && this.stopResolve !== undefined) {
|
|
116
|
-
this.stopResolve();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
sendEnqueuedRequest(op) {
|
|
120
|
-
// Here we increment the in flight counts before sending
|
|
121
|
-
++this.inFlightCount;
|
|
122
|
-
if (op.mutating) {
|
|
123
|
-
++this.inFlightMutatingCount;
|
|
124
|
-
}
|
|
125
|
-
// Make the request and pass the response/error through to the stored promise
|
|
126
|
-
op.request().then(op.promise.resolve, op.promise.reject).finally(()=>{
|
|
127
|
-
this.checkAndEnqueue(op);
|
|
128
|
-
this.ops.delete(op.requestId);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
stop() {
|
|
132
|
-
// If there is already a stop promise then return it
|
|
133
|
-
if (this.stopPromise) {
|
|
134
|
-
return this.stopPromise;
|
|
135
|
-
}
|
|
136
|
-
// Otherwise create a new one and capture the resolve method
|
|
137
|
-
this.stopPromise = new Promise((resolve)=>{
|
|
138
|
-
this.stopResolve = resolve;
|
|
139
|
-
});
|
|
140
|
-
// If no outstanding requests then immediately resolve the promise
|
|
141
|
-
if (this.requests.length == 0 && this.inFlightCount == 0 && this.stopResolve !== undefined) {
|
|
142
|
-
this.stopResolve();
|
|
143
|
-
}
|
|
144
|
-
return this.stopPromise;
|
|
145
|
-
}
|
|
146
|
-
}
|