@aztec-labs/validator-client 6.0.0-nightly.20260829

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 (62) hide show
  1. package/README.md +327 -0
  2. package/dest/checkpoint_builder.d.ts +92 -0
  3. package/dest/checkpoint_builder.d.ts.map +1 -0
  4. package/dest/checkpoint_builder.js +272 -0
  5. package/dest/config.d.ts +17 -0
  6. package/dest/config.d.ts.map +1 -0
  7. package/dest/config.js +102 -0
  8. package/dest/duties/validation_service.d.ts +65 -0
  9. package/dest/duties/validation_service.d.ts.map +1 -0
  10. package/dest/duties/validation_service.js +128 -0
  11. package/dest/factory.d.ts +41 -0
  12. package/dest/factory.d.ts.map +1 -0
  13. package/dest/factory.js +19 -0
  14. package/dest/index.d.ts +7 -0
  15. package/dest/index.d.ts.map +1 -0
  16. package/dest/index.js +6 -0
  17. package/dest/key_store/ha_key_store.d.ts +99 -0
  18. package/dest/key_store/ha_key_store.d.ts.map +1 -0
  19. package/dest/key_store/ha_key_store.js +208 -0
  20. package/dest/key_store/index.d.ts +6 -0
  21. package/dest/key_store/index.d.ts.map +1 -0
  22. package/dest/key_store/index.js +5 -0
  23. package/dest/key_store/interface.d.ts +104 -0
  24. package/dest/key_store/interface.d.ts.map +1 -0
  25. package/dest/key_store/interface.js +4 -0
  26. package/dest/key_store/local_key_store.d.ts +63 -0
  27. package/dest/key_store/local_key_store.d.ts.map +1 -0
  28. package/dest/key_store/local_key_store.js +83 -0
  29. package/dest/key_store/node_keystore_adapter.d.ts +151 -0
  30. package/dest/key_store/node_keystore_adapter.d.ts.map +1 -0
  31. package/dest/key_store/node_keystore_adapter.js +330 -0
  32. package/dest/key_store/web3signer_key_store.d.ts +74 -0
  33. package/dest/key_store/web3signer_key_store.d.ts.map +1 -0
  34. package/dest/key_store/web3signer_key_store.js +147 -0
  35. package/dest/metrics.d.ts +31 -0
  36. package/dest/metrics.d.ts.map +1 -0
  37. package/dest/metrics.js +101 -0
  38. package/dest/proposal_handler.d.ts +188 -0
  39. package/dest/proposal_handler.d.ts.map +1 -0
  40. package/dest/proposal_handler.js +1438 -0
  41. package/dest/streaming_inbox_checks.d.ts +103 -0
  42. package/dest/streaming_inbox_checks.d.ts.map +1 -0
  43. package/dest/streaming_inbox_checks.js +112 -0
  44. package/dest/validator.d.ts +137 -0
  45. package/dest/validator.d.ts.map +1 -0
  46. package/dest/validator.js +771 -0
  47. package/package.json +110 -0
  48. package/src/checkpoint_builder.ts +449 -0
  49. package/src/config.ts +130 -0
  50. package/src/duties/validation_service.ts +224 -0
  51. package/src/factory.ts +95 -0
  52. package/src/index.ts +6 -0
  53. package/src/key_store/ha_key_store.ts +268 -0
  54. package/src/key_store/index.ts +5 -0
  55. package/src/key_store/interface.ts +120 -0
  56. package/src/key_store/local_key_store.ts +104 -0
  57. package/src/key_store/node_keystore_adapter.ts +397 -0
  58. package/src/key_store/web3signer_key_store.ts +188 -0
  59. package/src/metrics.ts +150 -0
  60. package/src/proposal_handler.ts +1598 -0
  61. package/src/streaming_inbox_checks.ts +198 -0
  62. package/src/validator.ts +1125 -0
@@ -0,0 +1,198 @@
1
+ import type { Fr } from '@aztec-labs/foundation/curves/bn254';
2
+ import type { InboxBucket, InboxBucketRef, L1ToL2MessageSource } from '@aztec-labs/stdlib/messaging';
3
+
4
+ /**
5
+ * Reason a streaming-Inbox block proposal fails the per-block acceptance checks. Follows the
6
+ * handler's existing `{ isValid, reason }` string style.
7
+ */
8
+ export type StreamingBlockCheckReason =
9
+ | 'bucket_unknown'
10
+ | 'bucket_hash_mismatch'
11
+ | 'parent_bucket_unresolved'
12
+ | 'bucket_moves_backwards'
13
+ | 'bucket_too_new'
14
+ | 'bundle_over_block_cap'
15
+ | 'checkpoint_over_msg_cap';
16
+
17
+ /** The subset of the archiver's Inbox-bucket queries the per-block streaming checks need. */
18
+ export type StreamingInboxBucketSource = Pick<
19
+ L1ToL2MessageSource,
20
+ 'getInboxBucket' | 'getInboxBucketByTotalMsgCount' | 'getL1ToL2MessagesBetweenBuckets'
21
+ >;
22
+
23
+ /** Inputs to the per-block streaming Inbox metadata checks. */
24
+ export type StreamingBlockMetadataCheckInput = {
25
+ /** Archiver Inbox-bucket queries (resolved against this node's own Inbox view). */
26
+ messageSource: Pick<StreamingInboxBucketSource, 'getInboxBucket' | 'getInboxBucketByTotalMsgCount'>;
27
+ /** The proposal's bucket reference: `bucketSeq` is the lookup hint, `inboxRollingHash` the expected commitment. */
28
+ bucketRef: InboxBucketRef | undefined;
29
+ /** Cumulative Inbox message count consumed through the parent block (its L1-to-L2 tree leaf count; 0 at genesis). */
30
+ parentTotalMsgCount: bigint;
31
+ /** Cumulative Inbox message count consumed as of the parent checkpoint; the per-checkpoint cap origin. */
32
+ checkpointStartTotalMsgCount: bigint;
33
+ /** Validation-time wall clock in seconds; the lag-eligibility anchor. */
34
+ nowSeconds: bigint;
35
+ /**
36
+ * Minimum bucket age in seconds for a bucket to be lag-eligible: one configured Ethereum slot, the same value the
37
+ * sequencer's selection uses.
38
+ *
39
+ * Age in seconds is a proxy for L1 reorg depth, and only an exact one when no L1 slot is missed: with missed slots a
40
+ * bucket can be a full Ethereum slot old and still sit in the latest L1 block. A block-depth rule would be
41
+ * stronger — require at least one later L1 block to have synced — and is implementable without new L1 state, since
42
+ * the archiver records each bucket's `l1BlockNumber`.
43
+ */
44
+ minBucketAgeSeconds: number;
45
+ /** Maximum number of messages this block may consume (`MAX_L1_TO_L2_MSGS_PER_BLOCK`). */
46
+ perBlockCap: number;
47
+ /** Maximum number of messages the checkpoint may consume in total (`MAX_L1_TO_L2_MSGS_PER_CHECKPOINT`). */
48
+ perCheckpointCap: number;
49
+ };
50
+
51
+ /** Inputs to the full per-block streaming Inbox acceptance checks, metadata plus bundle derivation. */
52
+ export type StreamingBlockCheckInput = Omit<StreamingBlockMetadataCheckInput, 'messageSource'> & {
53
+ messageSource: StreamingInboxBucketSource;
54
+ };
55
+
56
+ /** The buckets a passing block proposal consumes between; the input to bundle derivation. */
57
+ export type StreamingBlockBucketRange = {
58
+ /** The bucket the block consumes through, resolved from this node's own Inbox view. */
59
+ bucket: InboxBucket;
60
+ /** The bucket the parent block consumed through. Equal to `bucket` when the block consumes nothing. */
61
+ parentBucket: InboxBucket;
62
+ };
63
+
64
+ /** Result of the per-block streaming Inbox metadata checks. */
65
+ export type StreamingBlockMetadataCheckResult =
66
+ | ({
67
+ /** Every metadata check passed; the block's bundle can be derived from the returned bucket range. */
68
+ accepted: true;
69
+ } & StreamingBlockBucketRange)
70
+ | {
71
+ /** A check failed; `reason` mirrors the L1 acceptance condition that would have rejected the proposal. */
72
+ accepted: false;
73
+ reason: StreamingBlockCheckReason;
74
+ };
75
+
76
+ /** Result of the per-block streaming Inbox acceptance checks. */
77
+ export type StreamingBlockCheckResult =
78
+ | {
79
+ /** All checks passed; `bundle` is the message-leaf bundle this block consumes, for re-execution. */
80
+ accepted: true;
81
+ bundle: Fr[];
82
+ }
83
+ | {
84
+ /** A check failed; `reason` mirrors the L1 acceptance condition that would have rejected the proposal. */
85
+ accepted: false;
86
+ reason: StreamingBlockCheckReason;
87
+ };
88
+
89
+ /**
90
+ * Runs the metadata half of the per-block acceptance checks a validator applies to a streaming block proposal:
91
+ * every check that is a bounded number of point lookups against the local Inbox view, with no message data read.
92
+ * Mirrors the L1 acceptance conditions:
93
+ *
94
+ * 1. **Exists**: the referenced bucket resolves in this node's own Inbox view, and its consensus rolling hash matches
95
+ * the reference. An unknown bucket is an immediate reject here (there is no bounded wait yet); a hash
96
+ * mismatch means the wire reference disagrees with the local bucket. The reference is trusted only as a `bucketSeq`
97
+ * lookup hint — timestamp and message counts are read from the locally resolved bucket, never from the wire.
98
+ * 2. **Moves forward**: the bucket's cumulative total is at least the parent block's, so consumption never rewinds.
99
+ * Equal totals mean the block consumes nothing (empty bundle).
100
+ * 3. **Not too new**: the bucket is at least `minBucketAgeSeconds` old at validation time
101
+ * (`timestamp <= now - minBucketAgeSeconds`, inclusive — a bucket exactly that old is eligible, matching L1's
102
+ * strict `>` "too new" test).
103
+ * 4. **Caps**: the per-block message count and the running per-checkpoint total fit their respective caps.
104
+ * 5. **Parent boundary**: the parent block's cumulative total sits on a bucket boundary, so the consumed range is
105
+ * well defined.
106
+ *
107
+ * Because this phase is cheap and needs nothing off the network, a caller can run it before committing to any
108
+ * expensive work on a proposal — notably before collecting the proposal's transactions over P2P.
109
+ *
110
+ * The reject branch is a single function so a future bounded wait can wrap `bucket_unknown`.
111
+ */
112
+ export async function checkStreamingBlockProposalMetadata(
113
+ input: StreamingBlockMetadataCheckInput,
114
+ ): Promise<StreamingBlockMetadataCheckResult> {
115
+ const {
116
+ messageSource,
117
+ bucketRef,
118
+ parentTotalMsgCount,
119
+ checkpointStartTotalMsgCount,
120
+ nowSeconds,
121
+ minBucketAgeSeconds,
122
+ perBlockCap,
123
+ perCheckpointCap,
124
+ } = input;
125
+
126
+ // A streaming proposal must carry a bucket reference to derive its bundle from.
127
+ if (bucketRef === undefined) {
128
+ return { accepted: false, reason: 'bucket_unknown' };
129
+ }
130
+
131
+ // Check 1: exists in our own Inbox view, and the resolved rolling hash matches the reference.
132
+ const bucket = await messageSource.getInboxBucket(bucketRef.bucketSeq);
133
+ if (bucket === undefined) {
134
+ return { accepted: false, reason: 'bucket_unknown' };
135
+ }
136
+ if (!bucket.inboxRollingHash.equals(bucketRef.inboxRollingHash)) {
137
+ return { accepted: false, reason: 'bucket_hash_mismatch' };
138
+ }
139
+
140
+ // Check 2: consumption moves forward relative to the parent block.
141
+ if (bucket.totalMsgCount < parentTotalMsgCount) {
142
+ return { accepted: false, reason: 'bucket_moves_backwards' };
143
+ }
144
+
145
+ // Check 3: the bucket is at least `minBucketAgeSeconds` old at validation time.
146
+ if (bucket.timestamp > nowSeconds - BigInt(minBucketAgeSeconds)) {
147
+ return { accepted: false, reason: 'bucket_too_new' };
148
+ }
149
+
150
+ // Check 4a: the per-block message count fits the per-block cap.
151
+ const blockCount = bucket.totalMsgCount - parentTotalMsgCount;
152
+ if (blockCount > BigInt(perBlockCap)) {
153
+ return { accepted: false, reason: 'bundle_over_block_cap' };
154
+ }
155
+
156
+ // Check 4b: the running per-checkpoint total fits the per-checkpoint cap.
157
+ const checkpointCount = bucket.totalMsgCount - checkpointStartTotalMsgCount;
158
+ if (checkpointCount > BigInt(perCheckpointCap)) {
159
+ return { accepted: false, reason: 'checkpoint_over_msg_cap' };
160
+ }
161
+
162
+ // Check 5: the parent bucket is the one whose cumulative total equals the parent block's leaf count (messages are
163
+ // indexed compactly, with no padding); a parent whose count does not sit on a bucket boundary is unresolvable.
164
+ const parentBucket = await messageSource.getInboxBucketByTotalMsgCount(parentTotalMsgCount);
165
+ if (parentBucket === undefined) {
166
+ return { accepted: false, reason: 'parent_bucket_unresolved' };
167
+ }
168
+
169
+ return { accepted: true, bucket, parentBucket };
170
+ }
171
+
172
+ /**
173
+ * Derives the message-leaf bundle a streaming block proposal consumes (for re-execution): the leaves between the
174
+ * parent bucket and the proposed one, bounded by the per-block cap that
175
+ * {@link checkStreamingBlockProposalMetadata} already enforced.
176
+ */
177
+ export function getStreamingBlockBundle(
178
+ messageSource: Pick<StreamingInboxBucketSource, 'getL1ToL2MessagesBetweenBuckets'>,
179
+ range: StreamingBlockBucketRange,
180
+ ): Promise<Fr[]> {
181
+ const { bucket, parentBucket } = range;
182
+ return parentBucket.seq === bucket.seq
183
+ ? Promise.resolve([])
184
+ : messageSource.getL1ToL2MessagesBetweenBuckets(parentBucket.seq, bucket.seq);
185
+ }
186
+
187
+ /**
188
+ * Runs the per-block acceptance checks a validator applies to a streaming block proposal, and derives the
189
+ * message-leaf bundle the block consumes. Composes {@link checkStreamingBlockProposalMetadata} with
190
+ * {@link getStreamingBlockBundle} for callers that have no use for running the two phases separately.
191
+ */
192
+ export async function checkStreamingBlockProposal(input: StreamingBlockCheckInput): Promise<StreamingBlockCheckResult> {
193
+ const metadata = await checkStreamingBlockProposalMetadata(input);
194
+ if (!metadata.accepted) {
195
+ return metadata;
196
+ }
197
+ return { accepted: true, bundle: await getStreamingBlockBundle(input.messageSource, metadata) };
198
+ }