@aztec-labs/aztec-node 6.1.0-nightly.20261102 → 6.1.0-nightly.20261104
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/aztec-node/next_block/next_block_fee_cache.d.ts +38 -5
- package/dest/aztec-node/next_block/next_block_fee_cache.d.ts.map +1 -1
- package/dest/aztec-node/next_block/next_block_fee_cache.js +93 -19
- package/dest/factory.d.ts +1 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +14 -3
- package/package.json +26 -26
- package/src/aztec-node/next_block/next_block_fee_cache.ts +120 -23
- package/src/factory.ts +14 -3
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec-labs/epoch-cache';
|
|
2
2
|
import { type RollupContract } from '@aztec-labs/ethereum/contracts';
|
|
3
|
+
import { type SlotNumber } from '@aztec-labs/foundation/branded-types';
|
|
3
4
|
import { type Logger } from '@aztec-labs/foundation/log';
|
|
4
5
|
import { type DateProvider } from '@aztec-labs/foundation/timer';
|
|
5
|
-
import type { L2BlockSource, L2Frontier } from '@aztec-labs/stdlib/block';
|
|
6
|
+
import type { L2BlockId, L2BlockSource, L2Frontier } from '@aztec-labs/stdlib/block';
|
|
7
|
+
import type { GasFees } from '@aztec-labs/stdlib/gas';
|
|
6
8
|
import type { CoordinationSignatureContext } from '@aztec-labs/stdlib/p2p';
|
|
7
9
|
import type { CheckpointGlobalVariables, GlobalVariableBuilder } from '@aztec-labs/stdlib/tx';
|
|
10
|
+
import type { CheckpointOpeningFeeSource } from '@aztec-labs/validator-client';
|
|
8
11
|
import { type BoundaryFeeKey } from './next_block_planner.js';
|
|
9
12
|
/** Default interval for the background refresh, well below L1's block time. */
|
|
10
13
|
export declare const DEFAULT_REFRESH_INTERVAL_MS = 1000;
|
|
@@ -51,7 +54,7 @@ export interface NextBlockFeeCacheDeps {
|
|
|
51
54
|
* Before {@link start} (or after {@link stop}) requests still price inline through the same refresh path, which
|
|
52
55
|
* is what tests and TXE-like environments rely on.
|
|
53
56
|
*/
|
|
54
|
-
export declare class NextBlockFeeCache {
|
|
57
|
+
export declare class NextBlockFeeCache implements CheckpointOpeningFeeSource {
|
|
55
58
|
private readonly blockSource;
|
|
56
59
|
private readonly globalVariableBuilder;
|
|
57
60
|
private readonly rollupContract;
|
|
@@ -83,17 +86,47 @@ export declare class NextBlockFeeCache {
|
|
|
83
86
|
getBoundaryGlobals(key: BoundaryFeeKey, frontier: L2Frontier, opts?: {
|
|
84
87
|
maxWaitMs?: number;
|
|
85
88
|
}): Promise<CheckpointGlobalVariables | undefined>;
|
|
89
|
+
/**
|
|
90
|
+
* The gas fees the first block of a checkpoint at `slot` built on `parent` must carry, for checking a block proposal
|
|
91
|
+
* against. Keyed on the proposal's slot and parent rather than on the slot this node's clock would plan: the frontier
|
|
92
|
+
* must end at `parent`, and the boundary is planned for `slot` from it. A matching record is served from memory; on
|
|
93
|
+
* a miss the request joins the refresh in flight and then runs a pass planned on `slot`, so it gets the exact price
|
|
94
|
+
* and not a fallback.
|
|
95
|
+
*
|
|
96
|
+
* Undefined when this node's view does not hold the data to produce the price the proposer computes yet: the
|
|
97
|
+
* frontier does not end at `parent` (the parent's checkpoint proposal has not been processed, or the chain moved
|
|
98
|
+
* on), or the checkpointed tip has not reached the parent's parent, whose fee state the pipelined plan reads from
|
|
99
|
+
* L1 (see {@link pricesAsProposer}). The caller waits for a later archiver sync rather than accepting the pinned-tips
|
|
100
|
+
* approximation. Throws when the L1 read fails.
|
|
101
|
+
*
|
|
102
|
+
* Only a record priced at the frontier's L1 block, or at a later one, is served. The L1 fee oracle can change without
|
|
103
|
+
* moving the frontier (anyone may call its update), and the background loop only catches up within a refresh
|
|
104
|
+
* interval, so an older record could carry a price the proposer has already moved past.
|
|
105
|
+
*/
|
|
106
|
+
getCheckpointOpeningGasFees(slot: SlotNumber, parent: L2BlockId): Promise<GasFees | undefined>;
|
|
86
107
|
/**
|
|
87
108
|
* Runs one refresh pass, or joins the one already running. Single-flight is what keeps a burst of requests
|
|
88
109
|
* during a transition down to a single L1 round trip, shared with the background loop.
|
|
89
110
|
* @param frontier - Snapshot to plan from; read fresh from the archiver when omitted, as the loop does.
|
|
111
|
+
* @param slot - Slot to plan the boundary for in place of this node's clock slot.
|
|
90
112
|
*/
|
|
91
|
-
refresh(frontier?: L2Frontier): Promise<void>;
|
|
113
|
+
refresh(frontier?: L2Frontier, slot?: SlotNumber): Promise<void>;
|
|
114
|
+
private findOrPrice;
|
|
92
115
|
private runRefresh;
|
|
93
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* The freshest record matching `key`, or undefined when none is recent enough to trust. With `minL1SyncPoint`, a
|
|
118
|
+
* record also has to be priced at that L1 block or a later one.
|
|
119
|
+
*/
|
|
94
120
|
private findRecord;
|
|
95
121
|
/** Awaits `promise` for at most `maxWaitMs`, swallowing both a timeout and the promise's own failure. */
|
|
96
122
|
private waitFor;
|
|
123
|
+
/**
|
|
124
|
+
* Whether {@link buildOverridesPlan} prices `newCheckpoint` with the plan its proposer uses, rather than with the
|
|
125
|
+
* pinned-tips fallback. The sequencer never builds on a proposed parent more than one checkpoint ahead of its
|
|
126
|
+
* checkpointed tip, so a frontier in that state trails L1: the parent's parent, whose fee header the pipelined plan
|
|
127
|
+
* reads, is not checkpointed in it yet, and the fallback price is not one a proposer would have used.
|
|
128
|
+
*/
|
|
129
|
+
private pricesAsProposer;
|
|
97
130
|
/**
|
|
98
131
|
* Builds the chain-state overrides plan passed to `buildCheckpointGlobalVariables`, mirroring the sequencer
|
|
99
132
|
* (which always pins tips to neutralize prunes). When pipelining, the plan carries the proposed parent's
|
|
@@ -107,4 +140,4 @@ export declare class NextBlockFeeCache {
|
|
|
107
140
|
*/
|
|
108
141
|
private buildOverridesPlan;
|
|
109
142
|
}
|
|
110
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
143
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV4dF9ibG9ja19mZWVfY2FjaGUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9henRlYy1ub2RlL25leHRfYmxvY2svbmV4dF9ibG9ja19mZWVfY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNuRSxPQUFPLEVBQ0wsS0FBSyxjQUFjLEVBR3BCLE1BQU0sZ0NBQWdDLENBQUM7QUFDeEMsT0FBTyxFQUFvQixLQUFLLFVBQVUsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBRXpGLE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSw0QkFBNEIsQ0FBQztBQUV2RSxPQUFPLEVBQUUsS0FBSyxZQUFZLEVBQWtCLE1BQU0sOEJBQThCLENBQUM7QUFFakYsT0FBTyxLQUFLLEVBQWUsU0FBUyxFQUFFLGFBQWEsRUFBRSxVQUFVLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUVsRyxPQUFPLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN0RCxPQUFPLEtBQUssRUFBRSw0QkFBNEIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzNFLE9BQU8sS0FBSyxFQUFFLHlCQUF5QixFQUFFLHFCQUFxQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDOUYsT0FBTyxLQUFLLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUUvRSxPQUFPLEVBQ0wsS0FBSyxjQUFjLEVBTXBCLE1BQU0seUJBQXlCLENBQUM7QUFFakMsK0VBQStFO0FBQy9FLGVBQU8sTUFBTSwyQkFBMkIsT0FBTyxDQUFDO0FBRWhEOzs7O0dBSUc7QUFDSCxlQUFPLE1BQU0saUJBQWlCLEtBQUssQ0FBQztBQWFwQyxrRUFBa0U7QUFDbEUsTUFBTSxXQUFXLHFCQUFxQjtJQUNwQyxXQUFXLEVBQUUsYUFBYSxDQUFDO0lBQzNCLHFCQUFxQixFQUFFLHFCQUFxQixDQUFDO0lBQzdDOzs7OztPQUtHO0lBQ0gsY0FBYyxDQUFDLEVBQUUsY0FBYyxDQUFDO0lBQ2hDLFVBQVUsRUFBRSxtQkFBbUIsQ0FBQztJQUNoQyxnQkFBZ0IsRUFBRSw0QkFBNEIsQ0FBQztJQUMvQyxZQUFZLEVBQUUsWUFBWSxDQUFDO0lBQzNCLEdBQUcsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUNkO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBb0JHO0FBQ0gscUJBQWEsaUJBQWtCLFlBQVcsMEJBQTBCO0lBQ2xFLE9BQU8sQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFnQjtJQUM1QyxPQUFPLENBQUMsUUFBUSxDQUFDLHFCQUFxQixDQUF3QjtJQUM5RCxPQUFPLENBQUMsUUFBUSxDQUFDLGNBQWMsQ0FBNkI7SUFDNUQsT0FBTyxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQXNCO0lBQ2pELE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQStCO0lBQ2hFLE9BQU8sQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFlO0lBQzVDLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBRTdCLE9BQU8sQ0FBQyxPQUFPLENBQWdDO0lBQy9DLE9BQU8sQ0FBQyxRQUFRLENBQWdDO0lBQ2hELE9BQU8sQ0FBQyxpQkFBaUIsQ0FBK0I7SUFDeEQsT0FBTyxDQUFDLFdBQVcsQ0FBNkI7SUFDaEQsT0FBTyxDQUFDLGVBQWUsQ0FBNEI7SUFFbkQsWUFBWSxJQUFJLEVBQUUscUJBQXFCLEVBUXRDO0lBRUQ7Ozs7O09BS0c7SUFDSSxLQUFLLENBQUMsaUJBQWlCLFNBQThCLEdBQUcsSUFBSSxDQU1sRTtJQUVZLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBTWpDO0lBRUQ7Ozs7OztPQU1HO0lBQ0ksa0JBQWtCLENBQ3ZCLEdBQUcsRUFBRSxjQUFjLEVBQ25CLFFBQVEsRUFBRSxVQUFVLEVBQ3BCLElBQUksQ0FBQyxFQUFFO1FBQUUsU0FBUyxDQUFDLEVBQUUsTUFBTSxDQUFBO0tBQUUsR0FDNUIsT0FBTyxDQUFDLHlCQUF5QixHQUFHLFNBQVMsQ0FBQyxDQUVoRDtJQUVEOzs7Ozs7Ozs7Ozs7Ozs7O09BZ0JHO0lBQ1UsMkJBQTJCLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsU0FBUyxHQUFHLE9BQU8sQ0FBQyxPQUFPLEdBQUcsU0FBUyxDQUFDLENBeUIxRztJQUVEOzs7OztPQUtHO0lBQ0ksT0FBTyxDQUFDLFFBQVEsQ0FBQyxFQUFFLFVBQVUsRUFBRSxJQUFJLENBQUMsRUFBRSxVQUFVLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQVl0RTtZQU9hLFdBQVc7WUFxQlgsVUFBVTtJQXVDeEI7OztPQUdHO0lBQ0gsT0FBTyxDQUFDLFVBQVU7SUFZbEIseUdBQXlHO0lBQ3pHLE9BQU8sQ0FBQyxPQUFPO0lBTWY7Ozs7O09BS0c7SUFDSCxPQUFPLENBQUMsZ0JBQWdCO0lBWXhCOzs7Ozs7Ozs7O09BVUc7SUFDSCxPQUFPLENBQUMsa0JBQWtCO0NBOEQzQiJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next_block_fee_cache.d.ts","sourceRoot":"","sources":["../../../src/aztec-node/next_block/next_block_fee_cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"next_block_fee_cache.d.ts","sourceRoot":"","sources":["../../../src/aztec-node/next_block/next_block_fee_cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -81,30 +81,61 @@ import { boundaryFeeKeyEquals, computeBoundaryFeeKey, getClockSlot, planNextBloc
|
|
|
81
81
|
* A matching record under the staleness cutoff is served straight away. Otherwise the caller joins the single
|
|
82
82
|
* shared refresh: a simulation waits for it and surfaces its failure, while a quote passes `maxWaitMs` and
|
|
83
83
|
* falls back to whatever the cache already holds rather than turning an L1 outage into a multi-second RPC.
|
|
84
|
-
*/
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
*/ getBoundaryGlobals(key, frontier, opts) {
|
|
85
|
+
return this.findOrPrice(key, frontier, opts?.maxWaitMs);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The gas fees the first block of a checkpoint at `slot` built on `parent` must carry, for checking a block proposal
|
|
89
|
+
* against. Keyed on the proposal's slot and parent rather than on the slot this node's clock would plan: the frontier
|
|
90
|
+
* must end at `parent`, and the boundary is planned for `slot` from it. A matching record is served from memory; on
|
|
91
|
+
* a miss the request joins the refresh in flight and then runs a pass planned on `slot`, so it gets the exact price
|
|
92
|
+
* and not a fallback.
|
|
93
|
+
*
|
|
94
|
+
* Undefined when this node's view does not hold the data to produce the price the proposer computes yet: the
|
|
95
|
+
* frontier does not end at `parent` (the parent's checkpoint proposal has not been processed, or the chain moved
|
|
96
|
+
* on), or the checkpointed tip has not reached the parent's parent, whose fee state the pipelined plan reads from
|
|
97
|
+
* L1 (see {@link pricesAsProposer}). The caller waits for a later archiver sync rather than accepting the pinned-tips
|
|
98
|
+
* approximation. Throws when the L1 read fails.
|
|
99
|
+
*
|
|
100
|
+
* Only a record priced at the frontier's L1 block, or at a later one, is served. The L1 fee oracle can change without
|
|
101
|
+
* moving the frontier (anyone may call its update), and the background loop only catches up within a refresh
|
|
102
|
+
* interval, so an older record could carry a price the proposer has already moved past.
|
|
103
|
+
*/ async getCheckpointOpeningGasFees(slot, parent) {
|
|
104
|
+
const frontier = await this.blockSource.getL2Frontier();
|
|
105
|
+
const tip = frontier.tips.proposed;
|
|
106
|
+
if (tip.number !== parent.number || tip.hash !== parent.hash) {
|
|
107
|
+
this.log.debug(`Cannot price the checkpoint opened at slot ${slot}: the proposed tip is not its parent`, {
|
|
108
|
+
slot,
|
|
109
|
+
parent,
|
|
110
|
+
tip
|
|
111
|
+
});
|
|
112
|
+
return undefined;
|
|
96
113
|
}
|
|
97
|
-
|
|
114
|
+
const plan = planNextBlock(frontier, slot);
|
|
115
|
+
const newCheckpoint = plan.newCheckpoint;
|
|
116
|
+
if (!newCheckpoint || newCheckpoint.targetSlot !== slot || !this.pricesAsProposer(frontier, newCheckpoint)) {
|
|
117
|
+
this.log.debug(`Cannot price the checkpoint opened at slot ${slot} as its proposer does from this frontier`, {
|
|
118
|
+
slot,
|
|
119
|
+
parent,
|
|
120
|
+
targetSlot: newCheckpoint?.targetSlot,
|
|
121
|
+
proposedCheckpointNumber: newCheckpoint?.proposedCheckpointData?.checkpointNumber,
|
|
122
|
+
checkpointedCheckpointNumber: newCheckpoint?.checkpointedCheckpointNumber
|
|
123
|
+
});
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const key = computeBoundaryFeeKey(plan, frontier.pendingChainValidationStatus);
|
|
127
|
+
return key && (await this.findOrPrice(key, frontier, undefined, slot, frontier.l1SyncPoint))?.gasFees;
|
|
98
128
|
}
|
|
99
129
|
/**
|
|
100
130
|
* Runs one refresh pass, or joins the one already running. Single-flight is what keeps a burst of requests
|
|
101
131
|
* during a transition down to a single L1 round trip, shared with the background loop.
|
|
102
132
|
* @param frontier - Snapshot to plan from; read fresh from the archiver when omitted, as the loop does.
|
|
103
|
-
|
|
133
|
+
* @param slot - Slot to plan the boundary for in place of this node's clock slot.
|
|
134
|
+
*/ refresh(frontier, slot) {
|
|
104
135
|
if (this.inFlightRefresh) {
|
|
105
136
|
return this.inFlightRefresh;
|
|
106
137
|
}
|
|
107
|
-
const refresh = this.runRefresh(frontier).finally(()=>{
|
|
138
|
+
const refresh = this.runRefresh(frontier, slot).finally(()=>{
|
|
108
139
|
this.inFlightRefresh = undefined;
|
|
109
140
|
});
|
|
110
141
|
// Every caller awaits the returned promise and reports its failure, but the stored copy may only be awaited by
|
|
@@ -113,9 +144,27 @@ import { boundaryFeeKeyEquals, computeBoundaryFeeKey, getClockSlot, planNextBloc
|
|
|
113
144
|
this.inFlightRefresh = refresh;
|
|
114
145
|
return refresh;
|
|
115
146
|
}
|
|
116
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Serves the record matching `key`, or joins the shared refresh to produce it, as {@link getBoundaryGlobals}
|
|
149
|
+
* describes. `slot` plans a refresh this reader starts for that slot rather than for the clock slot.
|
|
150
|
+
* `minL1SyncPoint`, when set, skips records priced before that L1 block (see {@link pricedAtOrAfter}).
|
|
151
|
+
*/ async findOrPrice(key, frontier, maxWaitMs, slot, minL1SyncPoint) {
|
|
152
|
+
// An uncapped reader gets more than one attempt because the refresh it joins may be one that started from an
|
|
153
|
+
// older frontier and therefore priced a different boundary; the next pass is its own. Still never concurrent.
|
|
154
|
+
const attempts = maxWaitMs === undefined ? MAX_REFRESH_ATTEMPTS : 1;
|
|
155
|
+
for(let attempt = 0; attempt < attempts; attempt++){
|
|
156
|
+
const hit = this.findRecord(key, minL1SyncPoint);
|
|
157
|
+
if (hit) {
|
|
158
|
+
return hit.globals;
|
|
159
|
+
}
|
|
160
|
+
const refresh = this.refresh(frontier, slot);
|
|
161
|
+
await (maxWaitMs === undefined ? refresh : this.waitFor(refresh, maxWaitMs));
|
|
162
|
+
}
|
|
163
|
+
return this.findRecord(key, minL1SyncPoint)?.globals;
|
|
164
|
+
}
|
|
165
|
+
async runRefresh(frontier, slot) {
|
|
117
166
|
const snapshot = frontier ?? await this.blockSource.getL2Frontier();
|
|
118
|
-
const plan = planNextBlock(snapshot, getClockSlot(this.epochCache));
|
|
167
|
+
const plan = planNextBlock(snapshot, slot ?? getClockSlot(this.epochCache));
|
|
119
168
|
const key = computeBoundaryFeeKey(plan, snapshot.pendingChainValidationStatus);
|
|
120
169
|
if (!key || !plan.newCheckpoint) {
|
|
121
170
|
// Mid-checkpoint: the fee is frozen in the in-progress checkpoint's header, so there is nothing to price.
|
|
@@ -152,18 +201,34 @@ import { boundaryFeeKeyEquals, computeBoundaryFeeKey, getClockSlot, planNextBloc
|
|
|
152
201
|
}
|
|
153
202
|
this.current = record;
|
|
154
203
|
}
|
|
155
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* The freshest record matching `key`, or undefined when none is recent enough to trust. With `minL1SyncPoint`, a
|
|
206
|
+
* record also has to be priced at that L1 block or a later one.
|
|
207
|
+
*/ findRecord(key, minL1SyncPoint) {
|
|
156
208
|
const maxAgeMs = MAX_AGE_INTERVALS * this.refreshIntervalMs;
|
|
157
209
|
const now = this.dateProvider.now();
|
|
158
210
|
return [
|
|
159
211
|
this.current,
|
|
160
212
|
this.previous
|
|
161
|
-
].find((record)=>record !== undefined && boundaryFeeKeyEquals(record.key, key) && now - record.refreshedAtMs < maxAgeMs);
|
|
213
|
+
].find((record)=>record !== undefined && boundaryFeeKeyEquals(record.key, key) && now - record.refreshedAtMs < maxAgeMs && (minL1SyncPoint === undefined || pricedAtOrAfter(record.l1SyncPoint, minL1SyncPoint)));
|
|
162
214
|
}
|
|
163
215
|
/** Awaits `promise` for at most `maxWaitMs`, swallowing both a timeout and the promise's own failure. */ waitFor(promise, maxWaitMs) {
|
|
164
216
|
return executeTimeout(()=>promise, maxWaitMs).catch((err)=>this.log.debug(`Refreshing the next-block boundary fee failed or timed out`, err));
|
|
165
217
|
}
|
|
166
218
|
/**
|
|
219
|
+
* Whether {@link buildOverridesPlan} prices `newCheckpoint` with the plan its proposer uses, rather than with the
|
|
220
|
+
* pinned-tips fallback. The sequencer never builds on a proposed parent more than one checkpoint ahead of its
|
|
221
|
+
* checkpointed tip, so a frontier in that state trails L1: the parent's parent, whose fee header the pipelined plan
|
|
222
|
+
* reads, is not checkpointed in it yet, and the fallback price is not one a proposer would have used.
|
|
223
|
+
*/ pricesAsProposer(frontier, newCheckpoint) {
|
|
224
|
+
const { proposedCheckpointData, checkpointedCheckpointNumber } = newCheckpoint;
|
|
225
|
+
if (proposedCheckpointData) {
|
|
226
|
+
return this.rollupContract !== undefined && proposedCheckpointData.checkpointNumber === checkpointedCheckpointNumber + 1;
|
|
227
|
+
}
|
|
228
|
+
// On a valid chain with no proposed parent the pinned-tips plan is the one the proposer builds.
|
|
229
|
+
return this.rollupContract !== undefined || frontier.pendingChainValidationStatus.valid;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
167
232
|
* Builds the chain-state overrides plan passed to `buildCheckpointGlobalVariables`, mirroring the sequencer
|
|
168
233
|
* (which always pins tips to neutralize prunes). When pipelining, the plan carries the proposed parent's
|
|
169
234
|
* archive, temp-checkpoint-log cell, and locally-derived fee header; when the pending chain is invalid, it
|
|
@@ -223,3 +288,12 @@ import { boundaryFeeKeyEquals, computeBoundaryFeeKey, getClockSlot, planNextBloc
|
|
|
223
288
|
function l1SyncPointEquals(a, b) {
|
|
224
289
|
return a === undefined || b === undefined ? a === b : a.blockHash.equals(b.blockHash);
|
|
225
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Whether a record priced at `pricedAt` reflects L1 at least as recent as `min`. At the same height the hashes must
|
|
293
|
+
* match, since a reorg that replaces that block can carry different fee state.
|
|
294
|
+
*/ function pricedAtOrAfter(pricedAt, min) {
|
|
295
|
+
if (pricedAt === undefined || pricedAt.blockNumber < min.blockNumber) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return pricedAt.blockNumber > min.blockNumber || pricedAt.blockHash.equals(min.blockHash);
|
|
299
|
+
}
|
package/dest/factory.d.ts
CHANGED
|
@@ -42,4 +42,4 @@ export interface CreateAztecNodeOptions {
|
|
|
42
42
|
* @returns A fully synced Aztec Node for use in development/testing.
|
|
43
43
|
*/
|
|
44
44
|
export declare function createAztecNodeService(inputConfig: AztecNodeConfig, deps?: CreateAztecNodeDeps, options?: CreateAztecNodeOptions): Promise<AztecNodeService>;
|
|
45
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
45
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBYUEsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFnQixNQUFNLDRCQUE0QixDQUFDO0FBQ3ZFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUk1RCxPQUFPLEVBQUUsS0FBSyxhQUFhLEVBQW1CLE1BQU0saUJBQWlCLENBQUM7QUFDdEUsT0FBTyxFQUFtQixLQUFLLGNBQWMsRUFBb0IsTUFBTSx5QkFBeUIsQ0FBQztBQUVqRyxPQUFPLEVBQ0wsS0FBSyw4QkFBOEIsRUFJbkMsS0FBSyxrQkFBa0IsRUFDeEIsTUFBTSw4QkFBOEIsQ0FBQztBQWtCdEMsT0FBTyxLQUFLLEVBQUUsV0FBVyxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbEUsT0FBTyxFQUFFLEtBQUssZUFBZSxFQUFzQixNQUFNLDhCQUE4QixDQUFDO0FBQ3hGLE9BQU8sRUFDTCxLQUFLLHNCQUFzQixFQVE1QixNQUFNLDhCQUE4QixDQUFDO0FBQ3RDLE9BQU8sS0FBSyxFQUFFLDBCQUEwQixFQUFFLE1BQU0sdUNBQXVDLENBQUM7QUFJeEYsT0FBTyxFQUFFLEtBQUssZUFBZSxFQUE4QixNQUFNLHdCQUF3QixDQUFDO0FBRTFGLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRzFELG9HQUFvRztBQUNwRyxNQUFNLFdBQVcsbUJBQW1CO0lBQ2xDLFNBQVMsQ0FBQyxFQUFFLGVBQWUsQ0FBQztJQUM1QixNQUFNLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDaEIsU0FBUyxDQUFDLEVBQUUsa0JBQWtCLENBQUM7SUFDL0IsWUFBWSxDQUFDLEVBQUUsWUFBWSxDQUFDO0lBQzVCLGFBQWEsQ0FBQyxFQUFFLGFBQWEsQ0FBQztJQUM5QixjQUFjLENBQUMsRUFBRSxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUM7SUFDekMsb0JBQW9CLENBQUMsRUFBRSwwQkFBMEIsQ0FBQztJQUNsRDs7O09BR0c7SUFDSCw4QkFBOEIsQ0FBQyxFQUFFLDhCQUE4QixDQUFDO0lBQ2hFOzs7O09BSUc7SUFDSCxzQkFBc0IsQ0FBQyxFQUFFLHNCQUFzQixDQUFDO0NBQ2pEO0FBRUQsNkVBQTZFO0FBQzdFLE1BQU0sV0FBVyxzQkFBc0I7SUFDckMsT0FBTyxDQUFDLEVBQUUsV0FBVyxDQUFDO0lBQ3RCLGtCQUFrQixDQUFDLEVBQUUsT0FBTyxDQUFDO0lBQzdCLG1CQUFtQixDQUFDLEVBQUUsT0FBTyxDQUFDO0NBQy9CO0FBRUQ7Ozs7R0FJRztBQUNILHdCQUFzQixzQkFBc0IsQ0FDMUMsV0FBVyxFQUFFLGVBQWUsRUFDNUIsSUFBSSxHQUFFLG1CQUF3QixFQUM5QixPQUFPLEdBQUUsc0JBQTJCLEdBQ25DLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQyxDQWluQjNCIn0=
|
package/dest/factory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAI5D,OAAO,EAAE,KAAK,aAAa,EAAmB,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAmB,KAAK,cAAc,EAAoB,MAAM,yBAAyB,CAAC;AAEjG,OAAO,EACL,KAAK,8BAA8B,EAInC,KAAK,kBAAkB,EACxB,MAAM,8BAA8B,CAAC;AAkBtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,8BAA8B,CAAC;AACxF,OAAO,EACL,KAAK,sBAAsB,EAQ5B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAIxF,OAAO,EAAE,KAAK,eAAe,EAA8B,MAAM,wBAAwB,CAAC;AAE1F,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D,oGAAoG;AACpG,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACzC,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD;;;OAGG;IACH,8BAA8B,CAAC,EAAE,8BAA8B,CAAC;IAChE;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;CACjD;AAED,6EAA6E;AAC7E,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,eAAe,EAC5B,IAAI,GAAE,mBAAwB,EAC9B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAI5D,OAAO,EAAE,KAAK,aAAa,EAAmB,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAmB,KAAK,cAAc,EAAoB,MAAM,yBAAyB,CAAC;AAEjG,OAAO,EACL,KAAK,8BAA8B,EAInC,KAAK,kBAAkB,EACxB,MAAM,8BAA8B,CAAC;AAkBtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,8BAA8B,CAAC;AACxF,OAAO,EACL,KAAK,sBAAsB,EAQ5B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAIxF,OAAO,EAAE,KAAK,eAAe,EAA8B,MAAM,wBAAwB,CAAC;AAE1F,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D,oGAAoG;AACpG,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACzC,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD;;;OAGG;IACH,8BAA8B,CAAC,EAAE,8BAA8B,CAAC;IAChE;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;CACjD;AAED,6EAA6E;AAC7E,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,eAAe,EAC5B,IAAI,GAAE,mBAAwB,EAC9B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,gBAAgB,CAAC,CAinB3B"}
|
package/dest/factory.js
CHANGED
|
@@ -32,7 +32,7 @@ import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBu
|
|
|
32
32
|
import { createWorldState, createWorldStateSynchronizer } from '@aztec-labs/world-state';
|
|
33
33
|
import { createPublicClient } from 'viem';
|
|
34
34
|
import { createKeyStoreForValidator } from './aztec-node/config.js';
|
|
35
|
-
import { AdmissionMinFeesProvider, NextBlockPredictor } from './aztec-node/next_block/index.js';
|
|
35
|
+
import { AdmissionMinFeesProvider, NextBlockFeeCache, NextBlockPredictor } from './aztec-node/next_block/index.js';
|
|
36
36
|
import { AztecNodeService } from './aztec-node/server.js';
|
|
37
37
|
import { createSentinel } from './sentinel/factory.js';
|
|
38
38
|
/**
|
|
@@ -200,7 +200,10 @@ import { createSentinel } from './sentinel/factory.js';
|
|
|
200
200
|
const feeProvider = new FeeProviderImpl(dateProvider, publicClient, globalVariableBuilderConfig, archiver);
|
|
201
201
|
await feeProvider.start();
|
|
202
202
|
started.push(feeProvider);
|
|
203
|
-
|
|
203
|
+
// One fee cache serves both the RPC-side predictor and the proposal handler, which checks a checkpoint's first
|
|
204
|
+
// block against the same L1 price.
|
|
205
|
+
const nextBlockLog = log.createChild('next-block-predictor');
|
|
206
|
+
const nextBlockFeeCache = new NextBlockFeeCache({
|
|
204
207
|
blockSource: archiver,
|
|
205
208
|
globalVariableBuilder,
|
|
206
209
|
rollupContract,
|
|
@@ -210,7 +213,13 @@ import { createSentinel } from './sentinel/factory.js';
|
|
|
210
213
|
rollupAddress: config.rollupAddress
|
|
211
214
|
},
|
|
212
215
|
dateProvider,
|
|
213
|
-
log:
|
|
216
|
+
log: nextBlockLog.createChild('fee-cache')
|
|
217
|
+
});
|
|
218
|
+
const nextBlockPredictor = new NextBlockPredictor({
|
|
219
|
+
blockSource: archiver,
|
|
220
|
+
feeCache: nextBlockFeeCache,
|
|
221
|
+
epochCache,
|
|
222
|
+
log: nextBlockLog
|
|
214
223
|
});
|
|
215
224
|
nextBlockPredictor.start();
|
|
216
225
|
started.push(nextBlockPredictor);
|
|
@@ -263,6 +272,7 @@ import { createSentinel } from './sentinel/factory.js';
|
|
|
263
272
|
keyStoreManager,
|
|
264
273
|
blobClient,
|
|
265
274
|
reexecutionTracker,
|
|
275
|
+
checkpointFeeSource: nextBlockFeeCache,
|
|
266
276
|
slashingProtectionDb: deps.slashingProtectionDb,
|
|
267
277
|
blockProposalObservers: deps.blockProposalObservers
|
|
268
278
|
});
|
|
@@ -299,6 +309,7 @@ import { createSentinel } from './sentinel/factory.js';
|
|
|
299
309
|
dateProvider,
|
|
300
310
|
telemetry,
|
|
301
311
|
reexecutionTracker,
|
|
312
|
+
checkpointFeeSource: nextBlockFeeCache,
|
|
302
313
|
blockProposalObservers: deps.blockProposalObservers
|
|
303
314
|
});
|
|
304
315
|
proposalHandler.register(p2pClient, reexecute, archiver);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec-labs/aztec-node",
|
|
3
|
-
"version": "6.1.0-nightly.
|
|
3
|
+
"version": "6.1.0-nightly.20261104",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -66,37 +66,37 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@aztec-foundation/bb.js": "6.0.0-rc.7",
|
|
69
|
-
"@aztec-labs/archiver": "6.1.0-nightly.
|
|
70
|
-
"@aztec-labs/bb-prover": "6.1.0-nightly.
|
|
71
|
-
"@aztec-labs/blob-client": "6.1.0-nightly.
|
|
72
|
-
"@aztec-labs/blob-lib": "6.1.0-nightly.
|
|
73
|
-
"@aztec-labs/constants": "6.1.0-nightly.
|
|
74
|
-
"@aztec-labs/epoch-cache": "6.1.0-nightly.
|
|
75
|
-
"@aztec-labs/ethereum": "6.1.0-nightly.
|
|
76
|
-
"@aztec-labs/foundation": "6.1.0-nightly.
|
|
77
|
-
"@aztec-labs/kv-store": "6.1.0-nightly.
|
|
78
|
-
"@aztec-labs/node-keystore": "6.1.0-nightly.
|
|
79
|
-
"@aztec-labs/node-lib": "6.1.0-nightly.
|
|
80
|
-
"@aztec-labs/p2p": "6.1.0-nightly.
|
|
81
|
-
"@aztec-labs/protocol-contracts": "6.1.0-nightly.
|
|
82
|
-
"@aztec-labs/prover-client": "6.1.0-nightly.
|
|
83
|
-
"@aztec-labs/prover-node": "6.1.0-nightly.
|
|
84
|
-
"@aztec-labs/sequencer-client": "6.1.0-nightly.
|
|
85
|
-
"@aztec-labs/simulator": "6.1.0-nightly.
|
|
86
|
-
"@aztec-labs/slasher": "6.1.0-nightly.
|
|
87
|
-
"@aztec-labs/standard-contracts": "6.1.0-nightly.
|
|
88
|
-
"@aztec-labs/stdlib": "6.1.0-nightly.
|
|
89
|
-
"@aztec-labs/telemetry-client": "6.1.0-nightly.
|
|
90
|
-
"@aztec-labs/validator-client": "6.1.0-nightly.
|
|
91
|
-
"@aztec-labs/validator-ha-signer": "6.1.0-nightly.
|
|
92
|
-
"@aztec-labs/world-state": "6.1.0-nightly.
|
|
69
|
+
"@aztec-labs/archiver": "6.1.0-nightly.20261104",
|
|
70
|
+
"@aztec-labs/bb-prover": "6.1.0-nightly.20261104",
|
|
71
|
+
"@aztec-labs/blob-client": "6.1.0-nightly.20261104",
|
|
72
|
+
"@aztec-labs/blob-lib": "6.1.0-nightly.20261104",
|
|
73
|
+
"@aztec-labs/constants": "6.1.0-nightly.20261104",
|
|
74
|
+
"@aztec-labs/epoch-cache": "6.1.0-nightly.20261104",
|
|
75
|
+
"@aztec-labs/ethereum": "6.1.0-nightly.20261104",
|
|
76
|
+
"@aztec-labs/foundation": "6.1.0-nightly.20261104",
|
|
77
|
+
"@aztec-labs/kv-store": "6.1.0-nightly.20261104",
|
|
78
|
+
"@aztec-labs/node-keystore": "6.1.0-nightly.20261104",
|
|
79
|
+
"@aztec-labs/node-lib": "6.1.0-nightly.20261104",
|
|
80
|
+
"@aztec-labs/p2p": "6.1.0-nightly.20261104",
|
|
81
|
+
"@aztec-labs/protocol-contracts": "6.1.0-nightly.20261104",
|
|
82
|
+
"@aztec-labs/prover-client": "6.1.0-nightly.20261104",
|
|
83
|
+
"@aztec-labs/prover-node": "6.1.0-nightly.20261104",
|
|
84
|
+
"@aztec-labs/sequencer-client": "6.1.0-nightly.20261104",
|
|
85
|
+
"@aztec-labs/simulator": "6.1.0-nightly.20261104",
|
|
86
|
+
"@aztec-labs/slasher": "6.1.0-nightly.20261104",
|
|
87
|
+
"@aztec-labs/standard-contracts": "6.1.0-nightly.20261104",
|
|
88
|
+
"@aztec-labs/stdlib": "6.1.0-nightly.20261104",
|
|
89
|
+
"@aztec-labs/telemetry-client": "6.1.0-nightly.20261104",
|
|
90
|
+
"@aztec-labs/validator-client": "6.1.0-nightly.20261104",
|
|
91
|
+
"@aztec-labs/validator-ha-signer": "6.1.0-nightly.20261104",
|
|
92
|
+
"@aztec-labs/world-state": "6.1.0-nightly.20261104",
|
|
93
93
|
"koa": "^2.16.1",
|
|
94
94
|
"koa-router": "^13.1.1",
|
|
95
95
|
"tslib": "^2.4.0",
|
|
96
96
|
"viem": "2.57.1"
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
|
-
"@aztec-labs/noir-protocol-circuits-types": "6.1.0-nightly.
|
|
99
|
+
"@aztec-labs/noir-protocol-circuits-types": "6.1.0-nightly.20261104",
|
|
100
100
|
"@jest/globals": "^30.0.0",
|
|
101
101
|
"@types/jest": "^30.0.0",
|
|
102
102
|
"@types/node": "^22.15.17",
|
|
@@ -4,16 +4,18 @@ import {
|
|
|
4
4
|
SimulationOverridesBuilder,
|
|
5
5
|
type SimulationOverridesPlan,
|
|
6
6
|
} from '@aztec-labs/ethereum/contracts';
|
|
7
|
-
import { CheckpointNumber } from '@aztec-labs/foundation/branded-types';
|
|
7
|
+
import { CheckpointNumber, type SlotNumber } from '@aztec-labs/foundation/branded-types';
|
|
8
8
|
import { EthAddress } from '@aztec-labs/foundation/eth-address';
|
|
9
9
|
import { type Logger, createLogger } from '@aztec-labs/foundation/log';
|
|
10
10
|
import { RunningPromise } from '@aztec-labs/foundation/promise';
|
|
11
11
|
import { type DateProvider, executeTimeout } from '@aztec-labs/foundation/timer';
|
|
12
12
|
import { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
|
|
13
|
-
import type { L1SyncPoint, L2BlockSource, L2Frontier } from '@aztec-labs/stdlib/block';
|
|
13
|
+
import type { L1SyncPoint, L2BlockId, L2BlockSource, L2Frontier } from '@aztec-labs/stdlib/block';
|
|
14
14
|
import { buildCheckpointSimulationOverridesPlan } from '@aztec-labs/stdlib/checkpoint';
|
|
15
|
+
import type { GasFees } from '@aztec-labs/stdlib/gas';
|
|
15
16
|
import type { CoordinationSignatureContext } from '@aztec-labs/stdlib/p2p';
|
|
16
17
|
import type { CheckpointGlobalVariables, GlobalVariableBuilder } from '@aztec-labs/stdlib/tx';
|
|
18
|
+
import type { CheckpointOpeningFeeSource } from '@aztec-labs/validator-client';
|
|
17
19
|
|
|
18
20
|
import {
|
|
19
21
|
type BoundaryFeeKey,
|
|
@@ -83,7 +85,7 @@ export interface NextBlockFeeCacheDeps {
|
|
|
83
85
|
* Before {@link start} (or after {@link stop}) requests still price inline through the same refresh path, which
|
|
84
86
|
* is what tests and TXE-like environments rely on.
|
|
85
87
|
*/
|
|
86
|
-
export class NextBlockFeeCache {
|
|
88
|
+
export class NextBlockFeeCache implements CheckpointOpeningFeeSource {
|
|
87
89
|
private readonly blockSource: L2BlockSource;
|
|
88
90
|
private readonly globalVariableBuilder: GlobalVariableBuilder;
|
|
89
91
|
private readonly rollupContract: RollupContract | undefined;
|
|
@@ -137,36 +139,69 @@ export class NextBlockFeeCache {
|
|
|
137
139
|
* shared refresh: a simulation waits for it and surfaces its failure, while a quote passes `maxWaitMs` and
|
|
138
140
|
* falls back to whatever the cache already holds rather than turning an L1 outage into a multi-second RPC.
|
|
139
141
|
*/
|
|
140
|
-
public
|
|
142
|
+
public getBoundaryGlobals(
|
|
141
143
|
key: BoundaryFeeKey,
|
|
142
144
|
frontier: L2Frontier,
|
|
143
145
|
opts?: { maxWaitMs?: number },
|
|
144
146
|
): Promise<CheckpointGlobalVariables | undefined> {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
147
|
+
return this.findOrPrice(key, frontier, opts?.maxWaitMs);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The gas fees the first block of a checkpoint at `slot` built on `parent` must carry, for checking a block proposal
|
|
152
|
+
* against. Keyed on the proposal's slot and parent rather than on the slot this node's clock would plan: the frontier
|
|
153
|
+
* must end at `parent`, and the boundary is planned for `slot` from it. A matching record is served from memory; on
|
|
154
|
+
* a miss the request joins the refresh in flight and then runs a pass planned on `slot`, so it gets the exact price
|
|
155
|
+
* and not a fallback.
|
|
156
|
+
*
|
|
157
|
+
* Undefined when this node's view does not hold the data to produce the price the proposer computes yet: the
|
|
158
|
+
* frontier does not end at `parent` (the parent's checkpoint proposal has not been processed, or the chain moved
|
|
159
|
+
* on), or the checkpointed tip has not reached the parent's parent, whose fee state the pipelined plan reads from
|
|
160
|
+
* L1 (see {@link pricesAsProposer}). The caller waits for a later archiver sync rather than accepting the pinned-tips
|
|
161
|
+
* approximation. Throws when the L1 read fails.
|
|
162
|
+
*
|
|
163
|
+
* Only a record priced at the frontier's L1 block, or at a later one, is served. The L1 fee oracle can change without
|
|
164
|
+
* moving the frontier (anyone may call its update), and the background loop only catches up within a refresh
|
|
165
|
+
* interval, so an older record could carry a price the proposer has already moved past.
|
|
166
|
+
*/
|
|
167
|
+
public async getCheckpointOpeningGasFees(slot: SlotNumber, parent: L2BlockId): Promise<GasFees | undefined> {
|
|
168
|
+
const frontier = await this.blockSource.getL2Frontier();
|
|
169
|
+
const tip = frontier.tips.proposed;
|
|
170
|
+
if (tip.number !== parent.number || tip.hash !== parent.hash) {
|
|
171
|
+
this.log.debug(`Cannot price the checkpoint opened at slot ${slot}: the proposed tip is not its parent`, {
|
|
172
|
+
slot,
|
|
173
|
+
parent,
|
|
174
|
+
tip,
|
|
175
|
+
});
|
|
176
|
+
return undefined;
|
|
156
177
|
}
|
|
157
|
-
|
|
178
|
+
const plan = planNextBlock(frontier, slot);
|
|
179
|
+
const newCheckpoint = plan.newCheckpoint;
|
|
180
|
+
if (!newCheckpoint || newCheckpoint.targetSlot !== slot || !this.pricesAsProposer(frontier, newCheckpoint)) {
|
|
181
|
+
this.log.debug(`Cannot price the checkpoint opened at slot ${slot} as its proposer does from this frontier`, {
|
|
182
|
+
slot,
|
|
183
|
+
parent,
|
|
184
|
+
targetSlot: newCheckpoint?.targetSlot,
|
|
185
|
+
proposedCheckpointNumber: newCheckpoint?.proposedCheckpointData?.checkpointNumber,
|
|
186
|
+
checkpointedCheckpointNumber: newCheckpoint?.checkpointedCheckpointNumber,
|
|
187
|
+
});
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
const key = computeBoundaryFeeKey(plan, frontier.pendingChainValidationStatus);
|
|
191
|
+
return key && (await this.findOrPrice(key, frontier, undefined, slot, frontier.l1SyncPoint))?.gasFees;
|
|
158
192
|
}
|
|
159
193
|
|
|
160
194
|
/**
|
|
161
195
|
* Runs one refresh pass, or joins the one already running. Single-flight is what keeps a burst of requests
|
|
162
196
|
* during a transition down to a single L1 round trip, shared with the background loop.
|
|
163
197
|
* @param frontier - Snapshot to plan from; read fresh from the archiver when omitted, as the loop does.
|
|
198
|
+
* @param slot - Slot to plan the boundary for in place of this node's clock slot.
|
|
164
199
|
*/
|
|
165
|
-
public refresh(frontier?: L2Frontier): Promise<void> {
|
|
200
|
+
public refresh(frontier?: L2Frontier, slot?: SlotNumber): Promise<void> {
|
|
166
201
|
if (this.inFlightRefresh) {
|
|
167
202
|
return this.inFlightRefresh;
|
|
168
203
|
}
|
|
169
|
-
const refresh = this.runRefresh(frontier).finally(() => {
|
|
204
|
+
const refresh = this.runRefresh(frontier, slot).finally(() => {
|
|
170
205
|
this.inFlightRefresh = undefined;
|
|
171
206
|
});
|
|
172
207
|
// Every caller awaits the returned promise and reports its failure, but the stored copy may only be awaited by
|
|
@@ -176,9 +211,35 @@ export class NextBlockFeeCache {
|
|
|
176
211
|
return refresh;
|
|
177
212
|
}
|
|
178
213
|
|
|
179
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Serves the record matching `key`, or joins the shared refresh to produce it, as {@link getBoundaryGlobals}
|
|
216
|
+
* describes. `slot` plans a refresh this reader starts for that slot rather than for the clock slot.
|
|
217
|
+
* `minL1SyncPoint`, when set, skips records priced before that L1 block (see {@link pricedAtOrAfter}).
|
|
218
|
+
*/
|
|
219
|
+
private async findOrPrice(
|
|
220
|
+
key: BoundaryFeeKey,
|
|
221
|
+
frontier: L2Frontier,
|
|
222
|
+
maxWaitMs: number | undefined,
|
|
223
|
+
slot?: SlotNumber,
|
|
224
|
+
minL1SyncPoint?: L1SyncPoint,
|
|
225
|
+
): Promise<CheckpointGlobalVariables | undefined> {
|
|
226
|
+
// An uncapped reader gets more than one attempt because the refresh it joins may be one that started from an
|
|
227
|
+
// older frontier and therefore priced a different boundary; the next pass is its own. Still never concurrent.
|
|
228
|
+
const attempts = maxWaitMs === undefined ? MAX_REFRESH_ATTEMPTS : 1;
|
|
229
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
230
|
+
const hit = this.findRecord(key, minL1SyncPoint);
|
|
231
|
+
if (hit) {
|
|
232
|
+
return hit.globals;
|
|
233
|
+
}
|
|
234
|
+
const refresh = this.refresh(frontier, slot);
|
|
235
|
+
await (maxWaitMs === undefined ? refresh : this.waitFor(refresh, maxWaitMs));
|
|
236
|
+
}
|
|
237
|
+
return this.findRecord(key, minL1SyncPoint)?.globals;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private async runRefresh(frontier?: L2Frontier, slot?: SlotNumber): Promise<void> {
|
|
180
241
|
const snapshot = frontier ?? (await this.blockSource.getL2Frontier());
|
|
181
|
-
const plan = planNextBlock(snapshot, getClockSlot(this.epochCache));
|
|
242
|
+
const plan = planNextBlock(snapshot, slot ?? getClockSlot(this.epochCache));
|
|
182
243
|
const key = computeBoundaryFeeKey(plan, snapshot.pendingChainValidationStatus);
|
|
183
244
|
if (!key || !plan.newCheckpoint) {
|
|
184
245
|
// Mid-checkpoint: the fee is frozen in the in-progress checkpoint's header, so there is nothing to price.
|
|
@@ -215,12 +276,19 @@ export class NextBlockFeeCache {
|
|
|
215
276
|
this.current = record;
|
|
216
277
|
}
|
|
217
278
|
|
|
218
|
-
/**
|
|
219
|
-
|
|
279
|
+
/**
|
|
280
|
+
* The freshest record matching `key`, or undefined when none is recent enough to trust. With `minL1SyncPoint`, a
|
|
281
|
+
* record also has to be priced at that L1 block or a later one.
|
|
282
|
+
*/
|
|
283
|
+
private findRecord(key: BoundaryFeeKey, minL1SyncPoint?: L1SyncPoint): BoundaryFeeRecord | undefined {
|
|
220
284
|
const maxAgeMs = MAX_AGE_INTERVALS * this.refreshIntervalMs;
|
|
221
285
|
const now = this.dateProvider.now();
|
|
222
286
|
return [this.current, this.previous].find(
|
|
223
|
-
record =>
|
|
287
|
+
record =>
|
|
288
|
+
record !== undefined &&
|
|
289
|
+
boundaryFeeKeyEquals(record.key, key) &&
|
|
290
|
+
now - record.refreshedAtMs < maxAgeMs &&
|
|
291
|
+
(minL1SyncPoint === undefined || pricedAtOrAfter(record.l1SyncPoint, minL1SyncPoint)),
|
|
224
292
|
);
|
|
225
293
|
}
|
|
226
294
|
|
|
@@ -231,6 +299,24 @@ export class NextBlockFeeCache {
|
|
|
231
299
|
);
|
|
232
300
|
}
|
|
233
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Whether {@link buildOverridesPlan} prices `newCheckpoint` with the plan its proposer uses, rather than with the
|
|
304
|
+
* pinned-tips fallback. The sequencer never builds on a proposed parent more than one checkpoint ahead of its
|
|
305
|
+
* checkpointed tip, so a frontier in that state trails L1: the parent's parent, whose fee header the pipelined plan
|
|
306
|
+
* reads, is not checkpointed in it yet, and the fallback price is not one a proposer would have used.
|
|
307
|
+
*/
|
|
308
|
+
private pricesAsProposer(frontier: L2Frontier, newCheckpoint: NewCheckpointPlan): boolean {
|
|
309
|
+
const { proposedCheckpointData, checkpointedCheckpointNumber } = newCheckpoint;
|
|
310
|
+
if (proposedCheckpointData) {
|
|
311
|
+
return (
|
|
312
|
+
this.rollupContract !== undefined &&
|
|
313
|
+
proposedCheckpointData.checkpointNumber === checkpointedCheckpointNumber + 1
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
// On a valid chain with no proposed parent the pinned-tips plan is the one the proposer builds.
|
|
317
|
+
return this.rollupContract !== undefined || frontier.pendingChainValidationStatus.valid;
|
|
318
|
+
}
|
|
319
|
+
|
|
234
320
|
/**
|
|
235
321
|
* Builds the chain-state overrides plan passed to `buildCheckpointGlobalVariables`, mirroring the sequencer
|
|
236
322
|
* (which always pins tips to neutralize prunes). When pipelining, the plan carries the proposed parent's
|
|
@@ -309,3 +395,14 @@ export class NextBlockFeeCache {
|
|
|
309
395
|
function l1SyncPointEquals(a: L1SyncPoint | undefined, b: L1SyncPoint | undefined): boolean {
|
|
310
396
|
return a === undefined || b === undefined ? a === b : a.blockHash.equals(b.blockHash);
|
|
311
397
|
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Whether a record priced at `pricedAt` reflects L1 at least as recent as `min`. At the same height the hashes must
|
|
401
|
+
* match, since a reorg that replaces that block can carry different fee state.
|
|
402
|
+
*/
|
|
403
|
+
function pricedAtOrAfter(pricedAt: L1SyncPoint | undefined, min: L1SyncPoint): boolean {
|
|
404
|
+
if (pricedAt === undefined || pricedAt.blockNumber < min.blockNumber) {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
return pricedAt.blockNumber > min.blockNumber || pricedAt.blockHash.equals(min.blockHash);
|
|
408
|
+
}
|
package/src/factory.ts
CHANGED
|
@@ -60,7 +60,7 @@ import { createWorldState, createWorldStateSynchronizer } from '@aztec-labs/worl
|
|
|
60
60
|
import { createPublicClient } from 'viem';
|
|
61
61
|
|
|
62
62
|
import { type AztecNodeConfig, createKeyStoreForValidator } from './aztec-node/config.js';
|
|
63
|
-
import { AdmissionMinFeesProvider, NextBlockPredictor } from './aztec-node/next_block/index.js';
|
|
63
|
+
import { AdmissionMinFeesProvider, NextBlockFeeCache, NextBlockPredictor } from './aztec-node/next_block/index.js';
|
|
64
64
|
import { AztecNodeService } from './aztec-node/server.js';
|
|
65
65
|
import { createSentinel } from './sentinel/factory.js';
|
|
66
66
|
|
|
@@ -290,14 +290,23 @@ export async function createAztecNodeService(
|
|
|
290
290
|
await feeProvider.start();
|
|
291
291
|
started.push(feeProvider);
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
// One fee cache serves both the RPC-side predictor and the proposal handler, which checks a checkpoint's first
|
|
294
|
+
// block against the same L1 price.
|
|
295
|
+
const nextBlockLog = log.createChild('next-block-predictor');
|
|
296
|
+
const nextBlockFeeCache = new NextBlockFeeCache({
|
|
294
297
|
blockSource: archiver,
|
|
295
298
|
globalVariableBuilder,
|
|
296
299
|
rollupContract,
|
|
297
300
|
epochCache,
|
|
298
301
|
signatureContext: { chainId: ethereumChain.chainInfo.id, rollupAddress: config.rollupAddress },
|
|
299
302
|
dateProvider,
|
|
300
|
-
log:
|
|
303
|
+
log: nextBlockLog.createChild('fee-cache'),
|
|
304
|
+
});
|
|
305
|
+
const nextBlockPredictor = new NextBlockPredictor({
|
|
306
|
+
blockSource: archiver,
|
|
307
|
+
feeCache: nextBlockFeeCache,
|
|
308
|
+
epochCache,
|
|
309
|
+
log: nextBlockLog,
|
|
301
310
|
});
|
|
302
311
|
nextBlockPredictor.start();
|
|
303
312
|
started.push(nextBlockPredictor);
|
|
@@ -383,6 +392,7 @@ export async function createAztecNodeService(
|
|
|
383
392
|
keyStoreManager,
|
|
384
393
|
blobClient,
|
|
385
394
|
reexecutionTracker,
|
|
395
|
+
checkpointFeeSource: nextBlockFeeCache,
|
|
386
396
|
slashingProtectionDb: deps.slashingProtectionDb,
|
|
387
397
|
blockProposalObservers: deps.blockProposalObservers,
|
|
388
398
|
});
|
|
@@ -423,6 +433,7 @@ export async function createAztecNodeService(
|
|
|
423
433
|
dateProvider,
|
|
424
434
|
telemetry,
|
|
425
435
|
reexecutionTracker,
|
|
436
|
+
checkpointFeeSource: nextBlockFeeCache,
|
|
426
437
|
blockProposalObservers: deps.blockProposalObservers,
|
|
427
438
|
});
|
|
428
439
|
proposalHandler.register(p2pClient, reexecute, archiver);
|