@aztec/aztec-node 0.0.1-commit.3fd054f6 → 0.0.1-commit.431c48d

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.
Files changed (75) hide show
  1. package/dest/aztec-node/block_response_helpers.d.ts +25 -0
  2. package/dest/aztec-node/block_response_helpers.d.ts.map +1 -0
  3. package/dest/aztec-node/block_response_helpers.js +112 -0
  4. package/dest/aztec-node/config.d.ts +16 -4
  5. package/dest/aztec-node/config.d.ts.map +1 -1
  6. package/dest/aztec-node/config.js +15 -5
  7. package/dest/aztec-node/node_public_calls_simulator.d.ts +90 -0
  8. package/dest/aztec-node/node_public_calls_simulator.d.ts.map +1 -0
  9. package/dest/aztec-node/node_public_calls_simulator.js +346 -0
  10. package/dest/aztec-node/public_data_overrides.d.ts +13 -0
  11. package/dest/aztec-node/public_data_overrides.d.ts.map +1 -0
  12. package/dest/aztec-node/public_data_overrides.js +21 -0
  13. package/dest/aztec-node/register_node_rpc_handlers.d.ts +11 -0
  14. package/dest/aztec-node/register_node_rpc_handlers.d.ts.map +1 -0
  15. package/dest/aztec-node/register_node_rpc_handlers.js +48 -0
  16. package/dest/aztec-node/server.d.ts +126 -133
  17. package/dest/aztec-node/server.d.ts.map +1 -1
  18. package/dest/aztec-node/server.js +400 -846
  19. package/dest/bin/index.js +31 -15
  20. package/dest/factory.d.ts +33 -0
  21. package/dest/factory.d.ts.map +1 -0
  22. package/dest/factory.js +523 -0
  23. package/dest/index.d.ts +3 -1
  24. package/dest/index.d.ts.map +1 -1
  25. package/dest/index.js +2 -0
  26. package/dest/modules/block_parameter.d.ts +25 -0
  27. package/dest/modules/block_parameter.d.ts.map +1 -0
  28. package/dest/modules/block_parameter.js +100 -0
  29. package/dest/modules/node_block_provider.d.ts +21 -0
  30. package/dest/modules/node_block_provider.d.ts.map +1 -0
  31. package/dest/modules/node_block_provider.js +114 -0
  32. package/dest/modules/node_tx_receipt.d.ts +24 -0
  33. package/dest/modules/node_tx_receipt.d.ts.map +1 -0
  34. package/dest/modules/node_tx_receipt.js +70 -0
  35. package/dest/modules/node_world_state_queries.d.ts +68 -0
  36. package/dest/modules/node_world_state_queries.d.ts.map +1 -0
  37. package/dest/modules/node_world_state_queries.js +272 -0
  38. package/dest/modules/unseen_block_hold_off.d.ts +52 -0
  39. package/dest/modules/unseen_block_hold_off.d.ts.map +1 -0
  40. package/dest/modules/unseen_block_hold_off.js +150 -0
  41. package/dest/sentinel/config.d.ts +3 -2
  42. package/dest/sentinel/config.d.ts.map +1 -1
  43. package/dest/sentinel/config.js +15 -5
  44. package/dest/sentinel/factory.d.ts +5 -3
  45. package/dest/sentinel/factory.d.ts.map +1 -1
  46. package/dest/sentinel/factory.js +12 -5
  47. package/dest/sentinel/sentinel.d.ts +145 -21
  48. package/dest/sentinel/sentinel.d.ts.map +1 -1
  49. package/dest/sentinel/sentinel.js +226 -104
  50. package/dest/sentinel/store.d.ts +8 -8
  51. package/dest/sentinel/store.d.ts.map +1 -1
  52. package/dest/sentinel/store.js +25 -17
  53. package/dest/test/index.d.ts +3 -3
  54. package/dest/test/index.d.ts.map +1 -1
  55. package/package.json +28 -26
  56. package/src/aztec-node/block_response_helpers.ts +161 -0
  57. package/src/aztec-node/config.ts +30 -7
  58. package/src/aztec-node/node_public_calls_simulator.ts +383 -0
  59. package/src/aztec-node/public_data_overrides.ts +35 -0
  60. package/src/aztec-node/register_node_rpc_handlers.ts +41 -0
  61. package/src/aztec-node/server.ts +530 -1091
  62. package/src/bin/index.ts +39 -16
  63. package/src/factory.ts +688 -0
  64. package/src/index.ts +2 -0
  65. package/src/modules/block_parameter.ts +93 -0
  66. package/src/modules/node_block_provider.ts +153 -0
  67. package/src/modules/node_tx_receipt.ts +115 -0
  68. package/src/modules/node_world_state_queries.ts +376 -0
  69. package/src/modules/unseen_block_hold_off.ts +199 -0
  70. package/src/sentinel/README.md +103 -0
  71. package/src/sentinel/config.ts +18 -6
  72. package/src/sentinel/factory.ts +21 -6
  73. package/src/sentinel/sentinel.ts +276 -129
  74. package/src/sentinel/store.ts +26 -18
  75. package/src/test/index.ts +2 -2
@@ -0,0 +1,272 @@
1
+ import { BlockNumber } from '@aztec/foundation/branded-types';
2
+ import { chunkBy } from '@aztec/foundation/collection';
3
+ import { Fr } from '@aztec/foundation/curves/bn254';
4
+ import { createLogger } from '@aztec/foundation/log';
5
+ import { sleep } from '@aztec/foundation/sleep';
6
+ import { MembershipWitness } from '@aztec/foundation/trees';
7
+ import { inspectBlockParameter } from '@aztec/stdlib/block';
8
+ import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
9
+ import { InboxLeaf } from '@aztec/stdlib/messaging';
10
+ import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
11
+ import { WorldStateSynchronizerError } from '@aztec/world-state';
12
+ import { normalizeBlockParameter } from './block_parameter.js';
13
+ /** Attempts at resolving a query and syncing world state to it before giving up (see {@link NodeWorldStateQueries.getWorldState}). */ const WORLD_STATE_SYNC_ATTEMPTS = 3;
14
+ /** Delay between world-state sync attempts. */ const WORLD_STATE_SYNC_RETRY_DELAY_MS = 100;
15
+ /**
16
+ * Serves the node's Merkle-tree and membership-witness queries against committed world-state at a
17
+ * requested block. Extracted from `AztecNodeService` so the block-resolution and reorg-aware sync logic
18
+ * can be unit-tested without standing up the whole node, and to keep `server.ts` smaller.
19
+ */ export class NodeWorldStateQueries {
20
+ worldStateSynchronizer;
21
+ blockSource;
22
+ l1ToL2MessageSource;
23
+ holdOff;
24
+ log;
25
+ constructor(deps){
26
+ this.worldStateSynchronizer = deps.worldStateSynchronizer;
27
+ this.blockSource = deps.blockSource;
28
+ this.l1ToL2MessageSource = deps.l1ToL2MessageSource;
29
+ this.holdOff = deps.holdOff;
30
+ this.log = deps.log ?? createLogger('node:world-state-queries');
31
+ }
32
+ async findLeavesIndexes(referenceBlock, treeId, leafValues) {
33
+ const committedDb = await this.getWorldState(referenceBlock);
34
+ const maybeIndices = await committedDb.findLeafIndices(treeId, leafValues.map((x)=>x.toBuffer()));
35
+ // Filter out undefined values to query block numbers only for found leaves
36
+ const definedIndices = maybeIndices.filter((x)=>x !== undefined);
37
+ // Now we find the block numbers for the defined indices
38
+ const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, definedIndices);
39
+ // Build a map from leaf index to block number
40
+ const indexToBlockNumber = new Map();
41
+ for(let i = 0; i < definedIndices.length; i++){
42
+ const blockNumber = blockNumbers[i];
43
+ if (blockNumber === undefined) {
44
+ throw new Error(`Block number is undefined for leaf index ${definedIndices[i]} in tree ${MerkleTreeId[treeId]}`);
45
+ }
46
+ indexToBlockNumber.set(definedIndices[i], blockNumber);
47
+ }
48
+ // Get unique block numbers in order to optimize num calls to getLeafValue function.
49
+ const uniqueBlockNumbers = [
50
+ ...new Set(indexToBlockNumber.values())
51
+ ];
52
+ // Now we obtain the block hashes from the archive tree (block number = leaf index in archive tree).
53
+ const blockHashes = await Promise.all(uniqueBlockNumbers.map((blockNumber)=>{
54
+ return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
55
+ }));
56
+ // Build a map from block number to block hash
57
+ const blockNumberToHash = new Map();
58
+ for(let i = 0; i < uniqueBlockNumbers.length; i++){
59
+ const blockHash = blockHashes[i];
60
+ if (blockHash === undefined) {
61
+ throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
62
+ }
63
+ blockNumberToHash.set(uniqueBlockNumbers[i], blockHash);
64
+ }
65
+ // Create DataInBlock objects by combining indices, blockNumbers and blockHashes and return them.
66
+ return maybeIndices.map((index)=>{
67
+ if (index === undefined) {
68
+ return undefined;
69
+ }
70
+ const blockNumber = indexToBlockNumber.get(index);
71
+ if (blockNumber === undefined) {
72
+ throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
73
+ }
74
+ const l2BlockHash = blockNumberToHash.get(blockNumber);
75
+ if (l2BlockHash === undefined) {
76
+ throw new Error(`Block hash not found for block number ${blockNumber}`);
77
+ }
78
+ return {
79
+ l2BlockNumber: blockNumber,
80
+ l2BlockHash,
81
+ data: index
82
+ };
83
+ });
84
+ }
85
+ async getBlockHashMembershipWitness(referenceBlock, blockHash) {
86
+ // The Noir circuit checks the archive membership proof against `anchor_block_header.last_archive.root`,
87
+ // which is the archive tree root BEFORE the anchor block was added (i.e. the state after block N-1).
88
+ // So we need the world state at block N-1, not block N, to produce a sibling path matching that root.
89
+ const { blockNumber: referenceBlockNumber } = await this.#resolveBlockNumberAndHash(normalizeBlockParameter(referenceBlock));
90
+ if (referenceBlockNumber === BlockNumber.ZERO) {
91
+ // Block 0 (the initial block) has an empty archive, so no membership witness can exist.
92
+ return undefined;
93
+ }
94
+ const committedDb = await this.getWorldState(BlockNumber(referenceBlockNumber - 1));
95
+ const [pathAndIndex] = await committedDb.findSiblingPaths(MerkleTreeId.ARCHIVE, [
96
+ blockHash
97
+ ]);
98
+ return pathAndIndex === undefined ? undefined : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
99
+ }
100
+ async getNoteHashMembershipWitness(referenceBlock, noteHash) {
101
+ const committedDb = await this.getWorldState(referenceBlock);
102
+ const [pathAndIndex] = await committedDb.findSiblingPaths(MerkleTreeId.NOTE_HASH_TREE, [
103
+ noteHash
104
+ ]);
105
+ return pathAndIndex === undefined ? undefined : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
106
+ }
107
+ async getL1ToL2MessageMembershipWitness(referenceBlock, l1ToL2Message) {
108
+ const db = await this.getWorldState(referenceBlock);
109
+ const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [
110
+ l1ToL2Message
111
+ ]);
112
+ if (!witness) {
113
+ return undefined;
114
+ }
115
+ // REFACTOR: Return a MembershipWitness object
116
+ return [
117
+ witness.index,
118
+ witness.path
119
+ ];
120
+ }
121
+ async getL1ToL2MessageCheckpoint(l1ToL2Message) {
122
+ const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
123
+ return messageIndex !== undefined ? InboxLeaf.checkpointNumberFromIndex(messageIndex) : undefined;
124
+ }
125
+ /**
126
+ * Returns all the L2 to L1 messages in an epoch (empty array if the epoch is not found). The public
127
+ * `AztecNodeService.getL2ToL1Messages` that delegates here is deprecated in favor of
128
+ * {@link getL2ToL1MembershipWitness}.
129
+ * @param epoch - The epoch at which to get the data.
130
+ */ async getL2ToL1Messages(epoch) {
131
+ const blocks = await this.blockSource.getBlocks({
132
+ epoch,
133
+ onlyCheckpointed: true
134
+ });
135
+ const blocksInCheckpoints = chunkBy(blocks, (block)=>block.header.globalVariables.slotNumber);
136
+ return blocksInCheckpoints.map((slotBlocks)=>slotBlocks.map((block)=>block.body.txEffects.map((txEffect)=>txEffect.l2ToL1Msgs)));
137
+ }
138
+ /**
139
+ * Returns the L2-to-L1 membership witness for a message in `txHash`. Passthrough to the
140
+ * archiver's locally-cached resolver — see {@link Archiver.getL2ToL1MembershipWitness}.
141
+ */ getL2ToL1MembershipWitness(txHash, message, messageIndexInTx) {
142
+ return this.blockSource.getL2ToL1MembershipWitness(txHash, message, messageIndexInTx);
143
+ }
144
+ async getNullifierMembershipWitness(referenceBlock, nullifier) {
145
+ const db = await this.getWorldState(referenceBlock);
146
+ const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [
147
+ nullifier.toBuffer()
148
+ ]);
149
+ if (!witness) {
150
+ return undefined;
151
+ }
152
+ const { index, path } = witness;
153
+ const leafPreimage = await db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
154
+ if (!leafPreimage) {
155
+ return undefined;
156
+ }
157
+ return new NullifierMembershipWitness(index, leafPreimage, path);
158
+ }
159
+ async getLowNullifierMembershipWitness(referenceBlock, nullifier) {
160
+ const committedDb = await this.getWorldState(referenceBlock);
161
+ const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
162
+ if (!findResult) {
163
+ return undefined;
164
+ }
165
+ const { index, alreadyPresent } = findResult;
166
+ if (alreadyPresent) {
167
+ throw new Error(`Cannot prove nullifier non-inclusion: nullifier ${nullifier.toBigInt()} already exists in the tree`);
168
+ }
169
+ const preimageData = await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
170
+ const siblingPath = await committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, BigInt(index));
171
+ return new NullifierMembershipWitness(BigInt(index), preimageData, siblingPath);
172
+ }
173
+ async getPublicDataWitness(referenceBlock, leafSlot) {
174
+ const committedDb = await this.getWorldState(referenceBlock);
175
+ const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
176
+ if (!lowLeafResult) {
177
+ return undefined;
178
+ } else {
179
+ const preimage = await committedDb.getLeafPreimage(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
180
+ const path = await committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
181
+ return new PublicDataWitness(lowLeafResult.index, preimage, path);
182
+ }
183
+ }
184
+ async getPublicStorageAt(referenceBlock, contract, slot) {
185
+ const committedDb = await this.getWorldState(referenceBlock);
186
+ const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
187
+ const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
188
+ if (!lowLeafResult || !lowLeafResult.alreadyPresent) {
189
+ return Fr.ZERO;
190
+ }
191
+ const preimage = await committedDb.getLeafPreimage(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
192
+ return preimage.leaf.value;
193
+ }
194
+ /**
195
+ * Returns an instance of MerkleTreeOperations having first ensured the world state is synced to the requested
196
+ * block on the correct fork. Every query variant is resolved to a concrete (block number, block hash), which is
197
+ * threaded through both the sync and the snapshot read so a reorg that replaced the block at that height is
198
+ * detected rather than served silently. Transient sync failures — a prune landing between resolution and sync, or
199
+ * a fork flip caught at either the sync or the snapshot stage — are retried a few times, re-resolving the query
200
+ * against the updated chain each time; terminal failures — a query that resolves to no block, or a block whose
201
+ * history world state has pruned away — are thrown immediately.
202
+ * @param block - The block parameter (block number, block hash, or tag) at which to get the data.
203
+ * @returns An instance of a committed MerkleTreeOperations
204
+ */ async getWorldState(block) {
205
+ const query = normalizeBlockParameter(block);
206
+ // User requests 'latest on the current fork', so the committed db is returned unverified
207
+ if ('tag' in query && query.tag === 'proposed') {
208
+ this.log.debug(`Using committed db for latest block`);
209
+ await this.worldStateSynchronizer.syncImmediate();
210
+ return this.worldStateSynchronizer.getCommitted();
211
+ }
212
+ // Resolve the query against the block source BEFORE syncing, so the sync can be driven to a concrete
213
+ // (number, hash). Resolving after the sync races the block source: the resolved tip can advance past what world
214
+ // state synced while the sync is in flight. Resolving here rather than inside the retry loop also keeps a
215
+ // retry from re-entering the hold-off, which would multiply the wait a client experiences by the attempt count.
216
+ let resolved = await this.#resolveBlockNumberAndHash(query);
217
+ for(let attempt = 1;; attempt++){
218
+ try {
219
+ return await this.#resolveWorldState(resolved);
220
+ } catch (err) {
221
+ if (attempt >= WORLD_STATE_SYNC_ATTEMPTS || !(err instanceof WorldStateSynchronizerError)) {
222
+ throw err;
223
+ }
224
+ this.log.verbose(`Retrying world state query after sync failure: ${err.message}`, {
225
+ attempt,
226
+ block: inspectBlockParameter(block)
227
+ });
228
+ await sleep(WORLD_STATE_SYNC_RETRY_DELAY_MS);
229
+ resolved = await this.#resolveBlockNumberAndHash(query, {
230
+ holdOff: false
231
+ });
232
+ }
233
+ }
234
+ }
235
+ /**
236
+ * Syncs world state to the resolved fork and returns the fork-verified snapshot at that block. Passing the hash
237
+ * makes the sync reorg-aware — it barriers until the archive-tree commit for that block has landed and verifies
238
+ * it matches the requested fork, throwing otherwise.
239
+ */ async #resolveWorldState({ blockNumber, blockHash }) {
240
+ const blockSyncedTo = await this.worldStateSynchronizer.syncImmediate(blockNumber, blockHash);
241
+ // The fork could flip between it returning and the snapshot being read, so getVerifiedSnapshot pins the
242
+ // returned view to the resolved fork (re-resolved by the retry loop on mismatch).
243
+ this.log.debug(`Using verified snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
244
+ return await this.worldStateSynchronizer.getVerifiedSnapshot(blockNumber, blockHash);
245
+ }
246
+ /**
247
+ * Resolves any {@link BlockParameter} variant to its concrete `(blockNumber, blockHash)`, holding the query
248
+ * briefly (unless the caller opts out) when it references a block the node is about to see.
249
+ */ async #resolveBlockNumberAndHash(query, opts = {}) {
250
+ const blockData = await this.holdOff.getBlockData(query, opts);
251
+ if (blockData === undefined) {
252
+ this.#throwOnUndefinedBlockData(query);
253
+ }
254
+ return {
255
+ blockNumber: blockData.header.getBlockNumber(),
256
+ blockHash: blockData.blockHash
257
+ };
258
+ }
259
+ /**
260
+ * Hash and archive misses report an unknown block (likely a reorg); tag and number misses report a transient
261
+ * condition — the block may have been pruned, or may simply not have arrived yet — and are distinguished to the
262
+ * caller as a {@link WorldStateSynchronizerError}.
263
+ */ #throwOnUndefinedBlockData(query) {
264
+ if ('hash' in query) {
265
+ throw new Error(`Block hash ${query.hash.toString()} not found when resolving query. If the node API has been queried with anchor block hash possibly a reorg has occurred.`);
266
+ }
267
+ if ('archive' in query) {
268
+ throw new Error(`Block with archive ${query.archive.toString()} not found when resolving query.`);
269
+ }
270
+ throw new WorldStateSynchronizerError(`Block not found for ${inspectBlockParameter(query)} when resolving query.`);
271
+ }
272
+ }
@@ -0,0 +1,52 @@
1
+ import { type Logger } from '@aztec/foundation/log';
2
+ import { type BlockData, type L2Block, type L2BlockSource, type L2BlockSourceEventEmitter, type NormalizedBlockParameter } from '@aztec/stdlib/block';
3
+ /** Max requests held simultaneously. Beyond this, misses fail fast as if the hold-off were disabled. */
4
+ export declare const MAX_CONCURRENT_HOLDS = 100;
5
+ /** Wait budgets for {@link UnseenBlockHoldOff}, in milliseconds. Zero (or negative) disables a budget. */
6
+ export type UnseenBlockHoldOffConfig = {
7
+ /** Budget for a query anchored on the block number right after the node's proposed tip. */
8
+ byNumberWaitMs: number;
9
+ /** Budget for a query anchored on a block hash or archive root the node does not know. */
10
+ byHashWaitMs: number;
11
+ };
12
+ /** Options for a single {@link UnseenBlockHoldOff} query. */
13
+ export type UnseenBlockHoldOffOptions = {
14
+ /** Set to false to resolve without ever waiting (used by callers that already spent their budget). */
15
+ holdOff?: boolean;
16
+ };
17
+ /**
18
+ * Resolves RPC block anchors against the block source, briefly holding a request whose anchor the node is about to
19
+ * see instead of failing it immediately.
20
+ *
21
+ * Behind a load balancer a client can sync to block N+1 through one node and then anchor follow-up queries against
22
+ * another node that is still at block N. Failing those queries aborts a whole client flow over a skew that resolves
23
+ * in under a block time, so a miss on an anchor that plausibly lies just ahead of the tip is retried for a bounded
24
+ * budget. Everything else — a tag, a number far past the tip, a budget of zero, or too many requests already held —
25
+ * resolves exactly as the bare block source would.
26
+ *
27
+ * Held requests do not poll at a rate of their own choosing: they all sleep on one wake-up that the block source
28
+ * triggers when it reports it moved, so the source is read when there is something new to read and, failing that, at
29
+ * most once per {@link MAX_SLEEP_MS}. A source that reports no updates cannot wake them, so it never holds anything
30
+ * off.
31
+ */
32
+ export declare class UnseenBlockHoldOff {
33
+ #private;
34
+ private readonly blockSource;
35
+ private readonly config;
36
+ private activeHolds;
37
+ private readonly log;
38
+ /** Shared by every held request; interrupted on a source update. Undefined for a source that reports none. */
39
+ private readonly wakeup;
40
+ constructor(blockSource: L2BlockSource | L2BlockSourceEventEmitter, config: UnseenBlockHoldOffConfig, log?: Logger);
41
+ /** Number of requests currently held waiting for their anchor block. Exposed for tests and diagnostics. */
42
+ get holds(): number;
43
+ /**
44
+ * Resolves `query` to block metadata, holding off briefly when it references a block the node is about to see.
45
+ * Returns undefined on a miss, so callers keep whatever behavior they had before (throwing or returning
46
+ * undefined) — the hold-off only delays that outcome.
47
+ */
48
+ getBlockData(query: NormalizedBlockParameter, opts?: UnseenBlockHoldOffOptions): Promise<BlockData | undefined>;
49
+ /** Resolves `query` to a full block with its transactions, holding off as {@link getBlockData} does. */
50
+ getBlock(query: NormalizedBlockParameter, opts?: UnseenBlockHoldOffOptions): Promise<L2Block | undefined>;
51
+ }
52
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidW5zZWVuX2Jsb2NrX2hvbGRfb2ZmLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kdWxlcy91bnNlZW5fYmxvY2tfaG9sZF9vZmYudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBR2xFLE9BQU8sRUFDTCxLQUFLLFNBQVMsRUFFZCxLQUFLLE9BQU8sRUFDWixLQUFLLGFBQWEsRUFDbEIsS0FBSyx5QkFBeUIsRUFFOUIsS0FBSyx3QkFBd0IsRUFHOUIsTUFBTSxxQkFBcUIsQ0FBQztBQVM3Qix3R0FBd0c7QUFDeEcsZUFBTyxNQUFNLG9CQUFvQixNQUFNLENBQUM7QUFFeEMsMEdBQTBHO0FBQzFHLE1BQU0sTUFBTSx3QkFBd0IsR0FBRztJQUNyQywyRkFBMkY7SUFDM0YsY0FBYyxFQUFFLE1BQU0sQ0FBQztJQUN2QiwwRkFBMEY7SUFDMUYsWUFBWSxFQUFFLE1BQU0sQ0FBQztDQUN0QixDQUFDO0FBRUYsNkRBQTZEO0FBQzdELE1BQU0sTUFBTSx5QkFBeUIsR0FBRztJQUN0QyxzR0FBc0c7SUFDdEcsT0FBTyxDQUFDLEVBQUUsT0FBTyxDQUFDO0NBQ25CLENBQUM7QUFFRjs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHFCQUFhLGtCQUFrQjs7SUFPM0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxXQUFXO0lBQzVCLE9BQU8sQ0FBQyxRQUFRLENBQUMsTUFBTTtJQVB6QixPQUFPLENBQUMsV0FBVyxDQUFLO0lBQ3hCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBQzdCLDhHQUE4RztJQUM5RyxPQUFPLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBaUM7SUFFeEQsWUFDbUIsV0FBVyxFQUFFLGFBQWEsR0FBRyx5QkFBeUIsRUFDdEQsTUFBTSxFQUFFLHdCQUF3QixFQUNqRCxHQUFHLENBQUMsRUFBRSxNQUFNLEVBV2I7SUFFRCwyR0FBMkc7SUFDM0csSUFBVyxLQUFLLElBQUksTUFBTSxDQUV6QjtJQUVEOzs7O09BSUc7SUFDSSxZQUFZLENBQ2pCLEtBQUssRUFBRSx3QkFBd0IsRUFDL0IsSUFBSSxHQUFFLHlCQUE4QixHQUNuQyxPQUFPLENBQUMsU0FBUyxHQUFHLFNBQVMsQ0FBQyxDQUVoQztJQUVELHdHQUF3RztJQUNqRyxRQUFRLENBQUMsS0FBSyxFQUFFLHdCQUF3QixFQUFFLElBQUksR0FBRSx5QkFBOEIsR0FBRyxPQUFPLENBQUMsT0FBTyxHQUFHLFNBQVMsQ0FBQyxDQUVuSDtDQXNHRiJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unseen_block_hold_off.d.ts","sourceRoot":"","sources":["../../src/modules/unseen_block_hold_off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAGlE,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,yBAAyB,EAE9B,KAAK,wBAAwB,EAG9B,MAAM,qBAAqB,CAAC;AAS7B,wGAAwG;AACxG,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,0GAA0G;AAC1G,MAAM,MAAM,wBAAwB,GAAG;IACrC,2FAA2F;IAC3F,cAAc,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,6DAA6D;AAC7D,MAAM,MAAM,yBAAyB,GAAG;IACtC,sGAAsG;IACtG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,kBAAkB;;IAO3B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,8GAA8G;IAC9G,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAExD,YACmB,WAAW,EAAE,aAAa,GAAG,yBAAyB,EACtD,MAAM,EAAE,wBAAwB,EACjD,GAAG,CAAC,EAAE,MAAM,EAWb;IAED,2GAA2G;IAC3G,IAAW,KAAK,IAAI,MAAM,CAEzB;IAED;;;;OAIG;IACI,YAAY,CACjB,KAAK,EAAE,wBAAwB,EAC/B,IAAI,GAAE,yBAA8B,GACnC,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAEhC;IAED,wGAAwG;IACjG,QAAQ,CAAC,KAAK,EAAE,wBAAwB,EAAE,IAAI,GAAE,yBAA8B,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAEnH;CAsGF"}
@@ -0,0 +1,150 @@
1
+ import { createLogger } from '@aztec/foundation/log';
2
+ import { InterruptibleSleep } from '@aztec/foundation/sleep';
3
+ import { Timer } from '@aztec/foundation/timer';
4
+ import { L2BlockSourceEvents, getBlockSourceEmitter, inspectBlockParameter } from '@aztec/stdlib/block';
5
+ /**
6
+ * Longest a held request sleeps before re-reading the block source anyway. Held requests are woken by the source
7
+ * reporting an update, so this only bounds how long one waits when it misses the update that added its block: the
8
+ * source can commit between a request's read and its sleep, and a request that is not sleeping yet is not woken.
9
+ */ const MAX_SLEEP_MS = 1000;
10
+ /** Max requests held simultaneously. Beyond this, misses fail fast as if the hold-off were disabled. */ export const MAX_CONCURRENT_HOLDS = 100;
11
+ /**
12
+ * Resolves RPC block anchors against the block source, briefly holding a request whose anchor the node is about to
13
+ * see instead of failing it immediately.
14
+ *
15
+ * Behind a load balancer a client can sync to block N+1 through one node and then anchor follow-up queries against
16
+ * another node that is still at block N. Failing those queries aborts a whole client flow over a skew that resolves
17
+ * in under a block time, so a miss on an anchor that plausibly lies just ahead of the tip is retried for a bounded
18
+ * budget. Everything else — a tag, a number far past the tip, a budget of zero, or too many requests already held —
19
+ * resolves exactly as the bare block source would.
20
+ *
21
+ * Held requests do not poll at a rate of their own choosing: they all sleep on one wake-up that the block source
22
+ * triggers when it reports it moved, so the source is read when there is something new to read and, failing that, at
23
+ * most once per {@link MAX_SLEEP_MS}. A source that reports no updates cannot wake them, so it never holds anything
24
+ * off.
25
+ */ export class UnseenBlockHoldOff {
26
+ blockSource;
27
+ config;
28
+ activeHolds;
29
+ log;
30
+ /** Shared by every held request; interrupted on a source update. Undefined for a source that reports none. */ wakeup;
31
+ constructor(blockSource, config, log){
32
+ this.blockSource = blockSource;
33
+ this.config = config;
34
+ this.activeHolds = 0;
35
+ this.log = log ?? createLogger('node:unseen-block-hold-off');
36
+ const emitter = getBlockSourceEmitter(blockSource);
37
+ if (emitter === undefined) {
38
+ this.log.verbose(`Block source reports no updates, queries for unseen blocks will not be held off`);
39
+ } else {
40
+ const wakeup = new InterruptibleSleep();
41
+ this.wakeup = wakeup;
42
+ emitter.on(L2BlockSourceEvents.L2BlockSourceUpdated, ()=>wakeup.interrupt());
43
+ }
44
+ }
45
+ /** Number of requests currently held waiting for their anchor block. Exposed for tests and diagnostics. */ get holds() {
46
+ return this.activeHolds;
47
+ }
48
+ /**
49
+ * Resolves `query` to block metadata, holding off briefly when it references a block the node is about to see.
50
+ * Returns undefined on a miss, so callers keep whatever behavior they had before (throwing or returning
51
+ * undefined) — the hold-off only delays that outcome.
52
+ */ getBlockData(query, opts = {}) {
53
+ return this.#readWithHoldOff(query, (q)=>this.blockSource.getBlockData(q), opts);
54
+ }
55
+ /** Resolves `query` to a full block with its transactions, holding off as {@link getBlockData} does. */ getBlock(query, opts = {}) {
56
+ return this.#readWithHoldOff(query, (q)=>this.blockSource.getBlock(q), opts);
57
+ }
58
+ /**
59
+ * Reads `query` through `read`, and on a miss waits for the block it names before reading once more. Subject to the
60
+ * concurrent-hold cap: once it is saturated a miss resolves without waiting, as it would with the hold-off
61
+ * disabled.
62
+ */ async #readWithHoldOff(query, read, opts) {
63
+ const value = await read(query);
64
+ if (value !== undefined || opts.holdOff === false) {
65
+ return value;
66
+ }
67
+ const arrived = await this.#waitForBlock(query, await this.#resolveWaitBudgetMs(query));
68
+ return arrived ? await read(query) : undefined;
69
+ }
70
+ /**
71
+ * Waits for the block `query` names to show up on the block source, for at most `waitMs`, and reports whether it
72
+ * did. Returns false without waiting when the source reports no updates, when the budget is empty, or when the
73
+ * concurrent-hold cap is saturated, so a miss fails as fast as it would with the hold-off disabled.
74
+ *
75
+ * Arrival is checked on block metadata rather than through the caller's read, so a held request costs a metadata
76
+ * read per wake-up whatever it asked for: a held `getBlock` would otherwise reconstruct a whole block with its
77
+ * transactions on every wake-up only to find it is not the one it is waiting for.
78
+ *
79
+ * A budget is approximate: the deadline is only re-checked after a sleep, so the actual wait can exceed it by the
80
+ * read's own latency.
81
+ */ async #waitForBlock(query, waitMs) {
82
+ const wakeup = this.wakeup;
83
+ if (wakeup === undefined || !Number.isFinite(waitMs) || waitMs <= 0) {
84
+ return false;
85
+ }
86
+ const blockParameter = inspectBlockParameter(query);
87
+ if (this.activeHolds >= MAX_CONCURRENT_HOLDS) {
88
+ this.log.verbose(`Not holding off query for unseen block, too many requests already held`, {
89
+ blockParameter,
90
+ holds: this.activeHolds
91
+ });
92
+ return false;
93
+ }
94
+ this.activeHolds++;
95
+ const timer = new Timer();
96
+ try {
97
+ this.log.verbose(`Holding off query for unseen block`, {
98
+ blockParameter,
99
+ waitMs,
100
+ holds: this.activeHolds
101
+ });
102
+ while(timer.ms() < waitMs){
103
+ await wakeup.sleep(Math.min(waitMs - timer.ms(), MAX_SLEEP_MS));
104
+ const data = await this.blockSource.getBlockData(query);
105
+ if (data !== undefined) {
106
+ this.log.verbose(`Unseen block arrived after ${timer.ms()}ms`, {
107
+ blockParameter,
108
+ blockNumber: data.header.getBlockNumber(),
109
+ elapsedMs: timer.ms()
110
+ });
111
+ return true;
112
+ }
113
+ }
114
+ this.log.verbose(`Gave up waiting for unseen block after ${timer.ms()}ms`, {
115
+ blockParameter,
116
+ waitMs,
117
+ elapsedMs: timer.ms()
118
+ });
119
+ return false;
120
+ } finally{
121
+ this.activeHolds--;
122
+ }
123
+ }
124
+ /**
125
+ * Budget for waiting on a missing anchor, decided once on entry rather than per wake-up. A tag always resolves
126
+ * against the current tip, so a tag miss is never a skew. A number is only worth waiting on when it is the very
127
+ * next block: further ahead the client is not merely one block in front, and at or below the tip the block was
128
+ * pruned or reorged away. A hash or archive root carries no height, so "one ahead" and "reorged away" are
129
+ * indistinguishable and both get the shorter hash budget.
130
+ *
131
+ * The genesis block hash is the one hash never worth waiting on: a client anchors on it before it has synced any
132
+ * block (as a PXE does for its first tagged-log queries), and the block is synthetic, so a source that does not
133
+ * answer for it now never will.
134
+ */ async #resolveWaitBudgetMs(query) {
135
+ if ('tag' in query) {
136
+ return 0;
137
+ }
138
+ if ('number' in query) {
139
+ const tip = await this.blockSource.getBlockNumber();
140
+ return query.number === tip + 1 ? this.config.byNumberWaitMs : 0;
141
+ }
142
+ if ('hash' in query && this.#isGenesisBlockHash(query.hash)) {
143
+ return 0;
144
+ }
145
+ return this.config.byHashWaitMs;
146
+ }
147
+ /** True when `hash` names the synthetic genesis block, which never arrives and so is never waited for. */ #isGenesisBlockHash(hash) {
148
+ return hash.equals(this.blockSource.getGenesisBlockHash());
149
+ }
150
+ }
@@ -1,8 +1,9 @@
1
1
  import { type ConfigMappingsType } from '@aztec/foundation/config';
2
2
  export type SentinelConfig = {
3
3
  sentinelHistoryLengthInEpochs: number;
4
- sentinelHistoricProvenPerformanceLengthInEpochs: number;
4
+ sentinelHistoricEpochPerformanceLengthInEpochs: number;
5
5
  sentinelEnabled: boolean;
6
+ sentinelEpochEndBufferSlots: number;
6
7
  };
7
8
  export declare const sentinelConfigMappings: ConfigMappingsType<SentinelConfig>;
8
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VudGluZWwvY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLGtCQUFrQixFQUEyQyxNQUFNLDBCQUEwQixDQUFDO0FBRTVHLE1BQU0sTUFBTSxjQUFjLEdBQUc7SUFDM0IsNkJBQTZCLEVBQUUsTUFBTSxDQUFDO0lBQ3RDLCtDQUErQyxFQUFFLE1BQU0sQ0FBQztJQUN4RCxlQUFlLEVBQUUsT0FBTyxDQUFDO0NBQzFCLENBQUM7QUFFRixlQUFPLE1BQU0sc0JBQXNCLEVBQUUsa0JBQWtCLENBQUMsY0FBYyxDQTRCckUsQ0FBQyJ9
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VudGluZWwvY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLGtCQUFrQixFQUEyQyxNQUFNLDBCQUEwQixDQUFDO0FBRTVHLE1BQU0sTUFBTSxjQUFjLEdBQUc7SUFDM0IsNkJBQTZCLEVBQUUsTUFBTSxDQUFDO0lBQ3RDLDhDQUE4QyxFQUFFLE1BQU0sQ0FBQztJQUN2RCxlQUFlLEVBQUUsT0FBTyxDQUFDO0lBQ3pCLDJCQUEyQixFQUFFLE1BQU0sQ0FBQztDQUNyQyxDQUFDO0FBRUYsZUFBTyxNQUFNLHNCQUFzQixFQUFFLGtCQUFrQixDQUFDLGNBQWMsQ0F1Q3JFLENBQUMifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/sentinel/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAA2C,MAAM,0BAA0B,CAAC;AAE5G,MAAM,MAAM,cAAc,GAAG;IAC3B,6BAA6B,EAAE,MAAM,CAAC;IACtC,+CAA+C,EAAE,MAAM,CAAC;IACxD,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,kBAAkB,CAAC,cAAc,CA4BrE,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/sentinel/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAA2C,MAAM,0BAA0B,CAAC;AAE5G,MAAM,MAAM,cAAc,GAAG;IAC3B,6BAA6B,EAAE,MAAM,CAAC;IACtC,8CAA8C,EAAE,MAAM,CAAC;IACvD,eAAe,EAAE,OAAO,CAAC;IACzB,2BAA2B,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,kBAAkB,CAAC,cAAc,CAuCrE,CAAC"}
@@ -6,8 +6,9 @@ export const sentinelConfigMappings = {
6
6
  ...numberConfigHelper(24)
7
7
  },
8
8
  /**
9
- * The number of L2 epochs kept of proven performance history for each validator.
10
- * This value must be large enough so that we have proven performance for every validator
9
+ * The number of L2 epochs kept of per-epoch performance history for each validator. End-of-epoch
10
+ * activity is recorded here and used to decide consecutive-epoch inactivity slashing.
11
+ * This value must be large enough so that we have epoch performance for every validator
11
12
  * for at least slashInactivityConsecutiveEpochThreshold. Assuming this value is 3,
12
13
  * and the committee size is 48, and we have 10k validators, then we pick 48 out of 10k each draw.
13
14
  * For any fixed element, per-draw prob = 48/10000 = 0.0048.
@@ -16,14 +17,23 @@ export const sentinelConfigMappings = {
16
17
  * - 90% chance: n = 1108
17
18
  * - 95% chance: n = 1310
18
19
  * - 99% chance: n = 1749
19
- */ sentinelHistoricProvenPerformanceLengthInEpochs: {
20
- description: 'The number of L2 epochs kept of proven performance history for each validator.',
21
- env: 'SENTINEL_HISTORIC_PROVEN_PERFORMANCE_LENGTH_IN_EPOCHS',
20
+ */ sentinelHistoricEpochPerformanceLengthInEpochs: {
21
+ description: 'The number of L2 epochs kept of per-epoch performance history for each validator.',
22
+ env: 'SENTINEL_HISTORIC_EPOCH_PERFORMANCE_LENGTH_IN_EPOCHS',
22
23
  ...numberConfigHelper(2000)
23
24
  },
24
25
  sentinelEnabled: {
25
26
  description: 'Whether the sentinel is enabled or not.',
26
27
  env: 'SENTINEL_ENABLED',
27
28
  ...booleanConfigHelper(false)
29
+ },
30
+ /**
31
+ * Number of L2 slots to wait after the end of an epoch before computing the epoch's performance.
32
+ * The buffer allows P2P attestations and the local archiver to settle. Higher values reduce the
33
+ * risk of misjudging late-arriving activity at the cost of delayed slashing.
34
+ */ sentinelEpochEndBufferSlots: {
35
+ description: 'Number of L2 slots after the end of an epoch before the sentinel evaluates it.',
36
+ env: 'SENTINEL_EPOCH_END_BUFFER_SLOTS',
37
+ ...numberConfigHelper(2)
28
38
  }
29
39
  };
@@ -1,9 +1,11 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
2
  import type { P2PClient } from '@aztec/p2p';
3
3
  import type { L2BlockSource } from '@aztec/stdlib/block';
4
- import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
4
+ import type { CheckpointReexecutionTracker } from '@aztec/stdlib/checkpoint';
5
+ import type { ChainConfig } from '@aztec/stdlib/config';
6
+ import type { SlasherConfig, ValidatorClientConfig } from '@aztec/stdlib/interfaces/server';
5
7
  import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
6
8
  import type { SentinelConfig } from './config.js';
7
9
  import { Sentinel } from './sentinel.js';
8
- export declare function createSentinel(epochCache: EpochCache, archiver: L2BlockSource, p2p: P2PClient, config: SentinelConfig & DataStoreConfig & SlasherConfig, logger?: import("@aztec/foundation/log").Logger): Promise<Sentinel | undefined>;
9
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlbnRpbmVsL2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFHckQsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQzVDLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRTlELE9BQU8sS0FBSyxFQUFFLGNBQWMsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsRCxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBR3pDLHdCQUFzQixjQUFjLENBQ2xDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFFBQVEsRUFBRSxhQUFhLEVBQ3ZCLEdBQUcsRUFBRSxTQUFTLEVBQ2QsTUFBTSxFQUFFLGNBQWMsR0FBRyxlQUFlLEdBQUcsYUFBYSxFQUN4RCxNQUFNLHlDQUFnQyxHQUNyQyxPQUFPLENBQUMsUUFBUSxHQUFHLFNBQVMsQ0FBQyxDQVkvQiJ9
10
+ export declare function createSentinel(epochCache: EpochCache, archiver: L2BlockSource, p2p: P2PClient, reexecutionTracker: CheckpointReexecutionTracker, config: SentinelConfig & DataStoreConfig & SlasherConfig & Pick<ChainConfig, 'l1ChainId' | 'rollupAddress'> & Pick<ValidatorClientConfig, 'disableValidator'>, logger?: import("@aztec/foundation/log").Logger): Promise<Sentinel | undefined>;
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlbnRpbmVsL2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFHckQsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQzVDLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxFQUFFLDRCQUE0QixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDN0UsT0FBTyxLQUFLLEVBQUUsV0FBVyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDeEQsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLHFCQUFxQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDNUYsT0FBTyxLQUFLLEVBQUUsZUFBZSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFOUQsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ2xELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFHekMsd0JBQXNCLGNBQWMsQ0FDbEMsVUFBVSxFQUFFLFVBQVUsRUFDdEIsUUFBUSxFQUFFLGFBQWEsRUFDdkIsR0FBRyxFQUFFLFNBQVMsRUFDZCxrQkFBa0IsRUFBRSw0QkFBNEIsRUFDaEQsTUFBTSxFQUFFLGNBQWMsR0FDcEIsZUFBZSxHQUNmLGFBQWEsR0FDYixJQUFJLENBQUMsV0FBVyxFQUFFLFdBQVcsR0FBRyxlQUFlLENBQUMsR0FDaEQsSUFBSSxDQUFDLHFCQUFxQixFQUFFLGtCQUFrQixDQUFDLEVBQ2pELE1BQU0seUNBQWdDLEdBQ3JDLE9BQU8sQ0FBQyxRQUFRLEdBQUcsU0FBUyxDQUFDLENBb0IvQiJ9
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/sentinel/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,wBAAsB,cAAc,CAClC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,cAAc,GAAG,eAAe,GAAG,aAAa,EACxD,MAAM,yCAAgC,GACrC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAY/B"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/sentinel/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAC5F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,wBAAsB,cAAc,CAClC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,SAAS,EACd,kBAAkB,EAAE,4BAA4B,EAChD,MAAM,EAAE,cAAc,GACpB,eAAe,GACf,aAAa,GACb,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,eAAe,CAAC,GAChD,IAAI,CAAC,qBAAqB,EAAE,kBAAkB,CAAC,EACjD,MAAM,yCAAgC,GACrC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAoB/B"}
@@ -2,16 +2,23 @@ import { createLogger } from '@aztec/foundation/log';
2
2
  import { createStore } from '@aztec/kv-store/lmdb-v2';
3
3
  import { Sentinel } from './sentinel.js';
4
4
  import { SentinelStore } from './store.js';
5
- export async function createSentinel(epochCache, archiver, p2p, config, logger = createLogger('node:sentinel')) {
6
- if (!config.sentinelEnabled) {
5
+ export async function createSentinel(epochCache, archiver, p2p, reexecutionTracker, config, logger = createLogger('node:sentinel')) {
6
+ const runsValidator = !config.disableValidator;
7
+ if (!runsValidator && !config.sentinelEnabled) {
8
+ logger.verbose('Sentinel is disabled');
7
9
  return undefined;
8
10
  }
11
+ if (runsValidator) {
12
+ logger.info('Enabling sentinel since this node runs a validator');
13
+ } else {
14
+ logger.info('Enabling sentinel from SENTINEL_ENABLED configuration');
15
+ }
9
16
  const kvStore = await createStore('sentinel', SentinelStore.SCHEMA_VERSION, config, logger.getBindings());
10
17
  const storeHistoryLength = config.sentinelHistoryLengthInEpochs * epochCache.getL1Constants().epochDuration;
11
- const storeHistoricProvenPerformanceLength = config.sentinelHistoricProvenPerformanceLengthInEpochs;
18
+ const storeHistoricEpochPerformanceLength = config.sentinelHistoricEpochPerformanceLengthInEpochs;
12
19
  const sentinelStore = new SentinelStore(kvStore, {
13
20
  historyLength: storeHistoryLength,
14
- historicProvenPerformanceLength: storeHistoricProvenPerformanceLength
21
+ historicEpochPerformanceLength: storeHistoricEpochPerformanceLength
15
22
  });
16
- return new Sentinel(epochCache, archiver, p2p, sentinelStore, config, logger);
23
+ return new Sentinel(epochCache, archiver, p2p, sentinelStore, reexecutionTracker, config, logger);
17
24
  }