@aztec/aztec-node 5.3.0-nightly.20260909 → 5.3.0-nightly.20260911
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/index.d.ts +4 -0
- package/dest/aztec-node/next_block/index.d.ts.map +1 -0
- package/dest/aztec-node/next_block/index.js +3 -0
- package/dest/aztec-node/next_block/next_block_fee_cache.d.ts +110 -0
- package/dest/aztec-node/next_block/next_block_fee_cache.d.ts.map +1 -0
- package/dest/aztec-node/next_block/next_block_fee_cache.js +201 -0
- package/dest/aztec-node/next_block/next_block_planner.d.ts +78 -0
- package/dest/aztec-node/next_block/next_block_planner.d.ts.map +1 -0
- package/dest/aztec-node/next_block/next_block_planner.js +109 -0
- package/dest/aztec-node/next_block/next_block_predictor.d.ts +91 -0
- package/dest/aztec-node/next_block/next_block_predictor.d.ts.map +1 -0
- package/dest/aztec-node/next_block/next_block_predictor.js +146 -0
- package/dest/aztec-node/next_block/test_helpers.d.ts +40 -0
- package/dest/aztec-node/next_block/test_helpers.d.ts.map +1 -0
- package/dest/aztec-node/next_block/test_helpers.js +100 -0
- package/dest/aztec-node/node_public_calls_simulator.d.ts +10 -48
- package/dest/aztec-node/node_public_calls_simulator.d.ts.map +1 -1
- package/dest/aztec-node/node_public_calls_simulator.js +56 -164
- package/dest/aztec-node/server.d.ts +18 -2
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +34 -12
- package/dest/factory.d.ts +1 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +17 -1
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/package.json +28 -28
- package/src/aztec-node/next_block/index.ts +3 -0
- package/src/aztec-node/next_block/next_block_fee_cache.ts +279 -0
- package/src/aztec-node/next_block/next_block_planner.ts +198 -0
- package/src/aztec-node/next_block/next_block_predictor.ts +158 -0
- package/src/aztec-node/next_block/test_helpers.ts +115 -0
- package/src/aztec-node/node_public_calls_simulator.ts +78 -218
- package/src/aztec-node/server.ts +32 -9
- package/src/factory.ts +15 -1
- package/src/index.ts +1 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * from './next_block_fee_cache.js';
|
|
2
|
+
export * from './next_block_planner.js';
|
|
3
|
+
export * from './next_block_predictor.js';
|
|
4
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9henRlYy1ub2RlL25leHRfYmxvY2svaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxjQUFjLHlCQUF5QixDQUFDO0FBQ3hDLGNBQWMsMkJBQTJCLENBQUMifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/aztec-node/next_block/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
+
import { type RollupContract } from '@aztec/ethereum/contracts';
|
|
3
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
4
|
+
import { type DateProvider } from '@aztec/foundation/timer';
|
|
5
|
+
import type { L2BlockSource, L2Frontier } from '@aztec/stdlib/block';
|
|
6
|
+
import type { CoordinationSignatureContext } from '@aztec/stdlib/p2p';
|
|
7
|
+
import type { CheckpointGlobalVariables, GlobalVariableBuilder } from '@aztec/stdlib/tx';
|
|
8
|
+
import { type BoundaryFeeKey } from './next_block_planner.js';
|
|
9
|
+
/** Default interval for the background refresh, well below L1's block time. */
|
|
10
|
+
export declare const DEFAULT_REFRESH_INTERVAL_MS = 1000;
|
|
11
|
+
/**
|
|
12
|
+
* A record older than this many refresh intervals is treated as missing: the refresh has been failing long
|
|
13
|
+
* enough that serving its value could underquote a fee that has since stepped. Ten intervals because one
|
|
14
|
+
* failed pass is a hiccup, and a ten-second-old boundary fee is almost always still right.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_AGE_INTERVALS = 10;
|
|
17
|
+
/** Dependencies required to build a {@link NextBlockFeeCache}. */
|
|
18
|
+
export interface NextBlockFeeCacheDeps {
|
|
19
|
+
blockSource: L2BlockSource;
|
|
20
|
+
globalVariableBuilder: GlobalVariableBuilder;
|
|
21
|
+
/**
|
|
22
|
+
* Rollup contract used to build the fee-relevant L1 state overrides when opening a new checkpoint.
|
|
23
|
+
* Only needed when a proposed parent checkpoint exists (pipelining) or the pending chain is invalid;
|
|
24
|
+
* may be omitted in environments that never reach those states (e.g. TXE). When omitted, those paths
|
|
25
|
+
* degrade to a pinned-tips plan (non-pipelined fees) instead.
|
|
26
|
+
*/
|
|
27
|
+
rollupContract?: RollupContract;
|
|
28
|
+
epochCache: EpochCacheInterface;
|
|
29
|
+
signatureContext: CoordinationSignatureContext;
|
|
30
|
+
dateProvider: DateProvider;
|
|
31
|
+
log?: Logger;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Caches the checkpoint globals, and so the mana min fee, that a block opening a fresh checkpoint would carry.
|
|
35
|
+
* This is the only value on the RPC path that has to be read from L1.
|
|
36
|
+
*
|
|
37
|
+
* A background loop prices the upcoming boundary on every pass, so requests are normally answered from memory.
|
|
38
|
+
* Records are looked up by {@link BoundaryFeeKey}: the target slot, the checkpointed tip, the block the plan
|
|
39
|
+
* builds on, and the proposed parent's fee-relevant fields or the pending chain's validity. The L1 block a record
|
|
40
|
+
* was priced at is stored with it but is not part of the key: the rollup transactions that move the fee also move
|
|
41
|
+
* the frontier and hence the key, so a miss means the chain moved (a slot rollover, a checkpoint landing or being
|
|
42
|
+
* proposed, a validity flip), not merely that L1 produced a block. Governance updates such as the mana target are
|
|
43
|
+
* the exception; the loop re-prices a matching record whenever the L1 anchor moves, so those lag by at most one
|
|
44
|
+
* refresh interval.
|
|
45
|
+
*
|
|
46
|
+
* On a miss a request does not call L1 itself. It joins the single refresh already in flight, or starts the one
|
|
47
|
+
* the loop would have started, so a burst of requests during a transition costs one L1 round trip. Simulations
|
|
48
|
+
* wait for as long as the refresh takes and surface its failure; fee quotes cap their wait and fall back to the
|
|
49
|
+
* last record they can trust.
|
|
50
|
+
*
|
|
51
|
+
* Before {@link start} (or after {@link stop}) requests still price inline through the same refresh path, which
|
|
52
|
+
* is what tests and TXE-like environments rely on.
|
|
53
|
+
*/
|
|
54
|
+
export declare class NextBlockFeeCache {
|
|
55
|
+
private readonly blockSource;
|
|
56
|
+
private readonly globalVariableBuilder;
|
|
57
|
+
private readonly rollupContract;
|
|
58
|
+
private readonly epochCache;
|
|
59
|
+
private readonly signatureContext;
|
|
60
|
+
private readonly dateProvider;
|
|
61
|
+
private readonly log;
|
|
62
|
+
private current;
|
|
63
|
+
private previous;
|
|
64
|
+
private refreshIntervalMs;
|
|
65
|
+
private refreshLoop;
|
|
66
|
+
private inFlightRefresh;
|
|
67
|
+
constructor(deps: NextBlockFeeCacheDeps);
|
|
68
|
+
/**
|
|
69
|
+
* Starts the background refresh. The loop's first pass begins immediately and a request that arrives before it
|
|
70
|
+
* completes joins it rather than pricing on its own, so nothing waits on priming here: a node whose archiver or
|
|
71
|
+
* L1 client is not ready yet still starts, and the loop fills the cache once they are. A second call while
|
|
72
|
+
* running is a no-op, so it cannot orphan a loop that {@link stop} could then never reach.
|
|
73
|
+
*/
|
|
74
|
+
start(pollingIntervalMs?: number): void;
|
|
75
|
+
stop(): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* The checkpoint globals for a boundary keyed by `key`, or undefined when they could not be produced in time.
|
|
78
|
+
*
|
|
79
|
+
* A matching record under the staleness cutoff is served straight away. Otherwise the caller joins the single
|
|
80
|
+
* shared refresh: a simulation waits for it and surfaces its failure, while a quote passes `maxWaitMs` and
|
|
81
|
+
* falls back to whatever the cache already holds rather than turning an L1 outage into a multi-second RPC.
|
|
82
|
+
*/
|
|
83
|
+
getBoundaryGlobals(key: BoundaryFeeKey, frontier: L2Frontier, opts?: {
|
|
84
|
+
maxWaitMs?: number;
|
|
85
|
+
}): Promise<CheckpointGlobalVariables | undefined>;
|
|
86
|
+
/**
|
|
87
|
+
* Runs one refresh pass, or joins the one already running. Single-flight is what keeps a burst of requests
|
|
88
|
+
* during a transition down to a single L1 round trip, shared with the background loop.
|
|
89
|
+
* @param frontier - Snapshot to plan from; read fresh from the archiver when omitted, as the loop does.
|
|
90
|
+
*/
|
|
91
|
+
refresh(frontier?: L2Frontier): Promise<void>;
|
|
92
|
+
private runRefresh;
|
|
93
|
+
/** The freshest record matching `key`, or undefined when none is recent enough to trust. */
|
|
94
|
+
private findRecord;
|
|
95
|
+
/** Awaits `promise` for at most `maxWaitMs`, swallowing both a timeout and the promise's own failure. */
|
|
96
|
+
private waitFor;
|
|
97
|
+
/**
|
|
98
|
+
* Builds the chain-state overrides plan passed to `buildCheckpointGlobalVariables`, mirroring the sequencer
|
|
99
|
+
* (which always pins tips to neutralize prunes). When pipelining, the plan carries the proposed parent's
|
|
100
|
+
* archive, temp-checkpoint-log cell, and locally-derived fee header; when the pending chain is invalid, it
|
|
101
|
+
* pins the tips to the last valid checkpoint instead.
|
|
102
|
+
*
|
|
103
|
+
* Both of those need a rollup contract for the L1 fee reads. Environments that omit it (e.g. TXE, which never
|
|
104
|
+
* has a proposed checkpoint and whose pending chain is always valid) fall back to pinning both pending and
|
|
105
|
+
* proven tips to the checkpointed tip, which neutralizes prunes in fee computation at the cost of
|
|
106
|
+
* non-pipelined fees.
|
|
107
|
+
*/
|
|
108
|
+
private buildOverridesPlan;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV4dF9ibG9ja19mZWVfY2FjaGUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9henRlYy1ub2RlL25leHRfYmxvY2svbmV4dF9ibG9ja19mZWVfY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM5RCxPQUFPLEVBQ0wsS0FBSyxjQUFjLEVBR3BCLE1BQU0sMkJBQTJCLENBQUM7QUFHbkMsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBRWxFLE9BQU8sRUFBRSxLQUFLLFlBQVksRUFBa0IsTUFBTSx5QkFBeUIsQ0FBQztBQUU1RSxPQUFPLEtBQUssRUFBZSxhQUFhLEVBQUUsVUFBVSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFbEYsT0FBTyxLQUFLLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUN0RSxPQUFPLEtBQUssRUFBRSx5QkFBeUIsRUFBRSxxQkFBcUIsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRXpGLE9BQU8sRUFDTCxLQUFLLGNBQWMsRUFNcEIsTUFBTSx5QkFBeUIsQ0FBQztBQUVqQywrRUFBK0U7QUFDL0UsZUFBTyxNQUFNLDJCQUEyQixPQUFPLENBQUM7QUFFaEQ7Ozs7R0FJRztBQUNILGVBQU8sTUFBTSxpQkFBaUIsS0FBSyxDQUFDO0FBYXBDLGtFQUFrRTtBQUNsRSxNQUFNLFdBQVcscUJBQXFCO0lBQ3BDLFdBQVcsRUFBRSxhQUFhLENBQUM7SUFDM0IscUJBQXFCLEVBQUUscUJBQXFCLENBQUM7SUFDN0M7Ozs7O09BS0c7SUFDSCxjQUFjLENBQUMsRUFBRSxjQUFjLENBQUM7SUFDaEMsVUFBVSxFQUFFLG1CQUFtQixDQUFDO0lBQ2hDLGdCQUFnQixFQUFFLDRCQUE0QixDQUFDO0lBQy9DLFlBQVksRUFBRSxZQUFZLENBQUM7SUFDM0IsR0FBRyxDQUFDLEVBQUUsTUFBTSxDQUFDO0NBQ2Q7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FvQkc7QUFDSCxxQkFBYSxpQkFBaUI7SUFDNUIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxXQUFXLENBQWdCO0lBQzVDLE9BQU8sQ0FBQyxRQUFRLENBQUMscUJBQXFCLENBQXdCO0lBQzlELE9BQU8sQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUE2QjtJQUM1RCxPQUFPLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBc0I7SUFDakQsT0FBTyxDQUFDLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBK0I7SUFDaEUsT0FBTyxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQWU7SUFDNUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQVM7SUFFN0IsT0FBTyxDQUFDLE9BQU8sQ0FBZ0M7SUFDL0MsT0FBTyxDQUFDLFFBQVEsQ0FBZ0M7SUFDaEQsT0FBTyxDQUFDLGlCQUFpQixDQUErQjtJQUN4RCxPQUFPLENBQUMsV0FBVyxDQUE2QjtJQUNoRCxPQUFPLENBQUMsZUFBZSxDQUE0QjtJQUVuRCxZQUFZLElBQUksRUFBRSxxQkFBcUIsRUFRdEM7SUFFRDs7Ozs7T0FLRztJQUNJLEtBQUssQ0FBQyxpQkFBaUIsU0FBOEIsR0FBRyxJQUFJLENBTWxFO0lBRVksSUFBSSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FNakM7SUFFRDs7Ozs7O09BTUc7SUFDVSxrQkFBa0IsQ0FDN0IsR0FBRyxFQUFFLGNBQWMsRUFDbkIsUUFBUSxFQUFFLFVBQVUsRUFDcEIsSUFBSSxDQUFDLEVBQUU7UUFBRSxTQUFTLENBQUMsRUFBRSxNQUFNLENBQUE7S0FBRSxHQUM1QixPQUFPLENBQUMseUJBQXlCLEdBQUcsU0FBUyxDQUFDLENBY2hEO0lBRUQ7Ozs7T0FJRztJQUNJLE9BQU8sQ0FBQyxRQUFRLENBQUMsRUFBRSxVQUFVLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQVluRDtZQUVhLFVBQVU7SUF1Q3hCLDRGQUE0RjtJQUM1RixPQUFPLENBQUMsVUFBVTtJQVFsQix5R0FBeUc7SUFDekcsT0FBTyxDQUFDLE9BQU87SUFNZjs7Ozs7Ozs7OztPQVVHO0lBQ0gsT0FBTyxDQUFDLGtCQUFrQjtDQThCM0IifQ==
|
|
@@ -0,0 +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,oBAAoB,CAAC;AAC9D,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAElE,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,yBAAyB,CAAC;AAE5E,OAAO,KAAK,EAAe,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAElF,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzF,OAAO,EACL,KAAK,cAAc,EAMpB,MAAM,yBAAyB,CAAC;AAEjC,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAapC,kEAAkE;AAClE,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,aAAa,CAAC;IAC3B,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C;;;;;OAKG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,UAAU,EAAE,mBAAmB,CAAC;IAChC,gBAAgB,EAAE,4BAA4B,CAAC;IAC/C,YAAY,EAAE,YAAY,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAwB;IAC9D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA+B;IAChE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,eAAe,CAA4B;IAEnD,YAAY,IAAI,EAAE,qBAAqB,EAQtC;IAED;;;;;OAKG;IACI,KAAK,CAAC,iBAAiB,SAA8B,GAAG,IAAI,CAMlE;IAEY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAMjC;IAED;;;;;;OAMG;IACU,kBAAkB,CAC7B,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,UAAU,EACpB,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5B,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAchD;IAED;;;;OAIG;IACI,OAAO,CAAC,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAYnD;YAEa,UAAU;IAuCxB,4FAA4F;IAC5F,OAAO,CAAC,UAAU;IAQlB,yGAAyG;IACzG,OAAO,CAAC,OAAO;IAMf;;;;;;;;;;OAUG;IACH,OAAO,CAAC,kBAAkB;CA8B3B"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { SimulationOverridesBuilder } from '@aztec/ethereum/contracts';
|
|
2
|
+
import { CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
5
|
+
import { RunningPromise } from '@aztec/foundation/promise';
|
|
6
|
+
import { executeTimeout } from '@aztec/foundation/timer';
|
|
7
|
+
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
8
|
+
import { buildCheckpointSimulationOverridesPlan } from '@aztec/stdlib/checkpoint';
|
|
9
|
+
import { boundaryFeeKeyEquals, computeBoundaryFeeKey, getClockSlot, planNextBlock } from './next_block_planner.js';
|
|
10
|
+
/** Default interval for the background refresh, well below L1's block time. */ export const DEFAULT_REFRESH_INTERVAL_MS = 1000;
|
|
11
|
+
/**
|
|
12
|
+
* A record older than this many refresh intervals is treated as missing: the refresh has been failing long
|
|
13
|
+
* enough that serving its value could underquote a fee that has since stepped. Ten intervals because one
|
|
14
|
+
* failed pass is a hiccup, and a ten-second-old boundary fee is almost always still right.
|
|
15
|
+
*/ export const MAX_AGE_INTERVALS = 10;
|
|
16
|
+
/** Refresh passes an uncapped reader will wait through before giving up on its boundary. */ const MAX_REFRESH_ATTEMPTS = 2;
|
|
17
|
+
/**
|
|
18
|
+
* Caches the checkpoint globals, and so the mana min fee, that a block opening a fresh checkpoint would carry.
|
|
19
|
+
* This is the only value on the RPC path that has to be read from L1.
|
|
20
|
+
*
|
|
21
|
+
* A background loop prices the upcoming boundary on every pass, so requests are normally answered from memory.
|
|
22
|
+
* Records are looked up by {@link BoundaryFeeKey}: the target slot, the checkpointed tip, the block the plan
|
|
23
|
+
* builds on, and the proposed parent's fee-relevant fields or the pending chain's validity. The L1 block a record
|
|
24
|
+
* was priced at is stored with it but is not part of the key: the rollup transactions that move the fee also move
|
|
25
|
+
* the frontier and hence the key, so a miss means the chain moved (a slot rollover, a checkpoint landing or being
|
|
26
|
+
* proposed, a validity flip), not merely that L1 produced a block. Governance updates such as the mana target are
|
|
27
|
+
* the exception; the loop re-prices a matching record whenever the L1 anchor moves, so those lag by at most one
|
|
28
|
+
* refresh interval.
|
|
29
|
+
*
|
|
30
|
+
* On a miss a request does not call L1 itself. It joins the single refresh already in flight, or starts the one
|
|
31
|
+
* the loop would have started, so a burst of requests during a transition costs one L1 round trip. Simulations
|
|
32
|
+
* wait for as long as the refresh takes and surface its failure; fee quotes cap their wait and fall back to the
|
|
33
|
+
* last record they can trust.
|
|
34
|
+
*
|
|
35
|
+
* Before {@link start} (or after {@link stop}) requests still price inline through the same refresh path, which
|
|
36
|
+
* is what tests and TXE-like environments rely on.
|
|
37
|
+
*/ export class NextBlockFeeCache {
|
|
38
|
+
blockSource;
|
|
39
|
+
globalVariableBuilder;
|
|
40
|
+
rollupContract;
|
|
41
|
+
epochCache;
|
|
42
|
+
signatureContext;
|
|
43
|
+
dateProvider;
|
|
44
|
+
log;
|
|
45
|
+
current;
|
|
46
|
+
previous;
|
|
47
|
+
refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS;
|
|
48
|
+
refreshLoop;
|
|
49
|
+
inFlightRefresh;
|
|
50
|
+
constructor(deps){
|
|
51
|
+
this.blockSource = deps.blockSource;
|
|
52
|
+
this.globalVariableBuilder = deps.globalVariableBuilder;
|
|
53
|
+
this.rollupContract = deps.rollupContract;
|
|
54
|
+
this.epochCache = deps.epochCache;
|
|
55
|
+
this.signatureContext = deps.signatureContext;
|
|
56
|
+
this.dateProvider = deps.dateProvider;
|
|
57
|
+
this.log = deps.log ?? createLogger('node:next-block-fee-cache');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Starts the background refresh. The loop's first pass begins immediately and a request that arrives before it
|
|
61
|
+
* completes joins it rather than pricing on its own, so nothing waits on priming here: a node whose archiver or
|
|
62
|
+
* L1 client is not ready yet still starts, and the loop fills the cache once they are. A second call while
|
|
63
|
+
* running is a no-op, so it cannot orphan a loop that {@link stop} could then never reach.
|
|
64
|
+
*/ start(pollingIntervalMs = DEFAULT_REFRESH_INTERVAL_MS) {
|
|
65
|
+
if (this.refreshLoop) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.refreshIntervalMs = pollingIntervalMs;
|
|
69
|
+
this.refreshLoop = new RunningPromise(()=>this.refresh(), this.log, pollingIntervalMs).start();
|
|
70
|
+
}
|
|
71
|
+
async stop() {
|
|
72
|
+
const loop = this.refreshLoop;
|
|
73
|
+
this.refreshLoop = undefined;
|
|
74
|
+
await loop?.stop();
|
|
75
|
+
// A refresh started by a request rather than by the loop may still be running; let it drain.
|
|
76
|
+
await this.inFlightRefresh?.catch(()=>{});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The checkpoint globals for a boundary keyed by `key`, or undefined when they could not be produced in time.
|
|
80
|
+
*
|
|
81
|
+
* A matching record under the staleness cutoff is served straight away. Otherwise the caller joins the single
|
|
82
|
+
* shared refresh: a simulation waits for it and surfaces its failure, while a quote passes `maxWaitMs` and
|
|
83
|
+
* falls back to whatever the cache already holds rather than turning an L1 outage into a multi-second RPC.
|
|
84
|
+
*/ async getBoundaryGlobals(key, frontier, opts) {
|
|
85
|
+
const maxWaitMs = opts?.maxWaitMs;
|
|
86
|
+
// An uncapped reader gets more than one attempt because the refresh it joins may be one that started from an
|
|
87
|
+
// older frontier and therefore priced a different boundary; the next pass is its own. Still never concurrent.
|
|
88
|
+
const attempts = maxWaitMs === undefined ? MAX_REFRESH_ATTEMPTS : 1;
|
|
89
|
+
for(let attempt = 0; attempt < attempts; attempt++){
|
|
90
|
+
const hit = this.findRecord(key);
|
|
91
|
+
if (hit) {
|
|
92
|
+
return hit.globals;
|
|
93
|
+
}
|
|
94
|
+
const refresh = this.refresh(frontier);
|
|
95
|
+
await (maxWaitMs === undefined ? refresh : this.waitFor(refresh, maxWaitMs));
|
|
96
|
+
}
|
|
97
|
+
return this.findRecord(key)?.globals;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Runs one refresh pass, or joins the one already running. Single-flight is what keeps a burst of requests
|
|
101
|
+
* during a transition down to a single L1 round trip, shared with the background loop.
|
|
102
|
+
* @param frontier - Snapshot to plan from; read fresh from the archiver when omitted, as the loop does.
|
|
103
|
+
*/ refresh(frontier) {
|
|
104
|
+
if (this.inFlightRefresh) {
|
|
105
|
+
return this.inFlightRefresh;
|
|
106
|
+
}
|
|
107
|
+
const refresh = this.runRefresh(frontier).finally(()=>{
|
|
108
|
+
this.inFlightRefresh = undefined;
|
|
109
|
+
});
|
|
110
|
+
// Every caller awaits the returned promise and reports its failure, but the stored copy may only be awaited by
|
|
111
|
+
// stop() after it has settled. Mark it handled now so a failure can never surface as an unhandled rejection.
|
|
112
|
+
refresh.catch(()=>{});
|
|
113
|
+
this.inFlightRefresh = refresh;
|
|
114
|
+
return refresh;
|
|
115
|
+
}
|
|
116
|
+
async runRefresh(frontier) {
|
|
117
|
+
const snapshot = frontier ?? await this.blockSource.getL2Frontier();
|
|
118
|
+
const plan = planNextBlock(snapshot, getClockSlot(this.epochCache));
|
|
119
|
+
const key = computeBoundaryFeeKey(plan, snapshot.pendingChainValidationStatus);
|
|
120
|
+
if (!key || !plan.newCheckpoint) {
|
|
121
|
+
// Mid-checkpoint: the fee is frozen in the in-progress checkpoint's header, so there is nothing to price.
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const current = this.current;
|
|
125
|
+
const sameKey = current !== undefined && boundaryFeeKeyEquals(current.key, key);
|
|
126
|
+
if (sameKey && l1SyncPointEquals(current.l1SyncPoint, snapshot.l1SyncPoint)) {
|
|
127
|
+
// This pass confirmed against a fresh snapshot that none of the fee's inputs moved, so the record is as
|
|
128
|
+
// good as one just priced. Re-stamping it is what makes the staleness cutoff mean "how long we have been
|
|
129
|
+
// unable to confirm", rather than expiring a value that is still exactly right.
|
|
130
|
+
this.current = {
|
|
131
|
+
...current,
|
|
132
|
+
refreshedAtMs: this.dateProvider.now()
|
|
133
|
+
};
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const overrides = await this.buildOverridesPlan(snapshot, plan.newCheckpoint);
|
|
137
|
+
// Pinned to the L1 block the frontier was read at, so the fee describes the same L1 state the plan derives
|
|
138
|
+
// from. Undefined before the archiver's first sync pass, where the read falls back to L1's head.
|
|
139
|
+
const globals = await this.globalVariableBuilder.buildCheckpointGlobalVariables(EthAddress.ZERO, AztecAddress.ZERO, plan.newCheckpoint.targetSlot, overrides, {
|
|
140
|
+
blockNumber: snapshot.l1SyncPoint?.blockNumber
|
|
141
|
+
});
|
|
142
|
+
const record = {
|
|
143
|
+
key,
|
|
144
|
+
l1SyncPoint: snapshot.l1SyncPoint,
|
|
145
|
+
globals,
|
|
146
|
+
refreshedAtMs: this.dateProvider.now()
|
|
147
|
+
};
|
|
148
|
+
if (!sameKey) {
|
|
149
|
+
// Keep the boundary we just left addressable: a request that planned from the previous frontier is still
|
|
150
|
+
// served while the new one settles.
|
|
151
|
+
this.previous = current;
|
|
152
|
+
}
|
|
153
|
+
this.current = record;
|
|
154
|
+
}
|
|
155
|
+
/** The freshest record matching `key`, or undefined when none is recent enough to trust. */ findRecord(key) {
|
|
156
|
+
const maxAgeMs = MAX_AGE_INTERVALS * this.refreshIntervalMs;
|
|
157
|
+
const now = this.dateProvider.now();
|
|
158
|
+
return [
|
|
159
|
+
this.current,
|
|
160
|
+
this.previous
|
|
161
|
+
].find((record)=>record !== undefined && boundaryFeeKeyEquals(record.key, key) && now - record.refreshedAtMs < maxAgeMs);
|
|
162
|
+
}
|
|
163
|
+
/** Awaits `promise` for at most `maxWaitMs`, swallowing both a timeout and the promise's own failure. */ waitFor(promise, maxWaitMs) {
|
|
164
|
+
return executeTimeout(()=>promise, maxWaitMs).catch((err)=>this.log.debug(`Refreshing the next-block boundary fee failed or timed out`, err));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Builds the chain-state overrides plan passed to `buildCheckpointGlobalVariables`, mirroring the sequencer
|
|
168
|
+
* (which always pins tips to neutralize prunes). When pipelining, the plan carries the proposed parent's
|
|
169
|
+
* archive, temp-checkpoint-log cell, and locally-derived fee header; when the pending chain is invalid, it
|
|
170
|
+
* pins the tips to the last valid checkpoint instead.
|
|
171
|
+
*
|
|
172
|
+
* Both of those need a rollup contract for the L1 fee reads. Environments that omit it (e.g. TXE, which never
|
|
173
|
+
* has a proposed checkpoint and whose pending chain is always valid) fall back to pinning both pending and
|
|
174
|
+
* proven tips to the checkpointed tip, which neutralizes prunes in fee computation at the cost of
|
|
175
|
+
* non-pipelined fees.
|
|
176
|
+
*/ buildOverridesPlan(frontier, newCheckpoint) {
|
|
177
|
+
const { targetCheckpoint, proposedCheckpointData, checkpointedCheckpointNumber } = newCheckpoint;
|
|
178
|
+
const rollup = this.rollupContract;
|
|
179
|
+
if (!rollup) {
|
|
180
|
+
return Promise.resolve(new SimulationOverridesBuilder().withChainTips({
|
|
181
|
+
pending: checkpointedCheckpointNumber,
|
|
182
|
+
proven: checkpointedCheckpointNumber
|
|
183
|
+
}).build());
|
|
184
|
+
}
|
|
185
|
+
// The helper treats pipelining and invalidation as mutually exclusive; a proposed parent takes precedence.
|
|
186
|
+
const validationStatus = frontier.pendingChainValidationStatus;
|
|
187
|
+
const invalidateToPendingCheckpointNumber = !proposedCheckpointData && !validationStatus.valid ? CheckpointNumber(validationStatus.checkpoint.checkpointNumber - 1) : undefined;
|
|
188
|
+
return buildCheckpointSimulationOverridesPlan({
|
|
189
|
+
checkpointNumber: targetCheckpoint,
|
|
190
|
+
proposedCheckpointData,
|
|
191
|
+
invalidateToPendingCheckpointNumber,
|
|
192
|
+
checkpointedCheckpointNumber,
|
|
193
|
+
rollup,
|
|
194
|
+
signatureContext: this.signatureContext,
|
|
195
|
+
log: this.log
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function l1SyncPointEquals(a, b) {
|
|
200
|
+
return a === undefined || b === undefined ? a === b : a.blockHash.equals(b.blockHash);
|
|
201
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
+
import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { type L2Frontier, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
4
|
+
import type { ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
|
|
5
|
+
/** Slot, target checkpoint, and parent data for a next block that opens a fresh checkpoint. */
|
|
6
|
+
export type NewCheckpointPlan = {
|
|
7
|
+
/** Slot the next block will land in. */
|
|
8
|
+
targetSlot: SlotNumber;
|
|
9
|
+
/** Checkpoint whose L1-to-L2 messages the simulation fork needs. */
|
|
10
|
+
targetCheckpoint: CheckpointNumber;
|
|
11
|
+
/** The proposed (not yet L1-confirmed) parent checkpoint, when pipelining. */
|
|
12
|
+
proposedCheckpointData: ProposedCheckpointData | undefined;
|
|
13
|
+
/** Checkpointed tip at the time the plan's snapshot was taken. */
|
|
14
|
+
checkpointedCheckpointNumber: CheckpointNumber;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* How the next block sits on the chain, derived from a single atomic archiver snapshot. Everything here comes
|
|
18
|
+
* from archiver reads only; turning it into globals is what may hit L1. `newCheckpoint` is set only when the
|
|
19
|
+
* next block opens a fresh checkpoint rather than continuing the in-progress one.
|
|
20
|
+
*/
|
|
21
|
+
export type NextBlockPlan = {
|
|
22
|
+
latestBlockNumber: BlockNumber;
|
|
23
|
+
/** Hash of the latest proposed block, so the world-state fork can be checked against the plan's chain. */
|
|
24
|
+
latestBlockHash: string;
|
|
25
|
+
newCheckpoint?: NewCheckpointPlan;
|
|
26
|
+
};
|
|
27
|
+
/** Identity of the parent the boundary fee is computed on top of. */
|
|
28
|
+
export type BoundaryFeeParent = {
|
|
29
|
+
kind: 'proposed';
|
|
30
|
+
headerHash: string;
|
|
31
|
+
archiveRoot: string;
|
|
32
|
+
checkpointOutHash: string;
|
|
33
|
+
totalManaUsed: bigint;
|
|
34
|
+
feeAssetPriceModifier: bigint;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'checkpointed';
|
|
37
|
+
pendingChainValid: boolean;
|
|
38
|
+
firstInvalidCheckpoint?: CheckpointNumber;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Every input the min fee of a checkpoint-opening block derives from, so two equal keys prove a cached fee is
|
|
42
|
+
* still what a fresh build would produce: the target slot, the checkpointed tip, the block the plan sits on,
|
|
43
|
+
* and either the proposed parent's fee-relevant fields or the pending-chain validity that selects the
|
|
44
|
+
* invalidation override.
|
|
45
|
+
*
|
|
46
|
+
* The L1 block the fee was read at is deliberately not part of the key. The min fee for a fixed slot and parent
|
|
47
|
+
* depends only on rollup storage, and the rollup transactions that move it (checkpoints landing, invalidations,
|
|
48
|
+
* prunes) all move the frontier and therefore this key, so a new L1 block on its own is a hit. The exception is a
|
|
49
|
+
* governance parameter update such as the mana target, which changes the fee without touching the frontier;
|
|
50
|
+
* the cache re-prices a matching record whenever the L1 anchor moves, so that lags by at most one refresh.
|
|
51
|
+
*/
|
|
52
|
+
export type BoundaryFeeKey = {
|
|
53
|
+
targetSlot: SlotNumber;
|
|
54
|
+
checkpointedCheckpointNumber: CheckpointNumber;
|
|
55
|
+
latestBlockHash: string;
|
|
56
|
+
parent: BoundaryFeeParent;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The slot this node's clock says the next block would be built in, the first of the three terms
|
|
60
|
+
* {@link planNextBlock} maximizes over. Pure arithmetic over the epoch cache's in-memory view.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getClockSlot(epochCache: EpochCacheInterface): SlotNumber;
|
|
63
|
+
/**
|
|
64
|
+
* Works out how the next block sits on the chain from one atomic frontier snapshot: whether it continues the
|
|
65
|
+
* in-progress checkpoint or opens a fresh one, which slot it lands in, and which checkpoint's L1-to-L2
|
|
66
|
+
* messages a fork would need. Pure: the caller supplies both the snapshot and the clock slot, so the fee a
|
|
67
|
+
* wallet is quoted and the fee a simulation charges can never derive from different decisions.
|
|
68
|
+
*/
|
|
69
|
+
export declare function planNextBlock(frontier: L2Frontier, clockSlot: SlotNumber): NextBlockPlan;
|
|
70
|
+
/**
|
|
71
|
+
* Keys the fee of a plan that opens a new checkpoint. Undefined mid-checkpoint, where the fee is copied from
|
|
72
|
+
* the in-progress checkpoint's header and nothing needs pricing.
|
|
73
|
+
* @param pendingChainValidationStatus - From the same frontier snapshot the plan was built from.
|
|
74
|
+
*/
|
|
75
|
+
export declare function computeBoundaryFeeKey(plan: NextBlockPlan, pendingChainValidationStatus: ValidateCheckpointResult): BoundaryFeeKey | undefined;
|
|
76
|
+
/** Whether two boundary fee keys describe the same fee. */
|
|
77
|
+
export declare function boundaryFeeKeyEquals(a: BoundaryFeeKey, b: BoundaryFeeKey): boolean;
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV4dF9ibG9ja19wbGFubmVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYXp0ZWMtbm9kZS9uZXh0X2Jsb2NrL25leHRfYmxvY2tfcGxhbm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzlELE9BQU8sRUFBRSxXQUFXLEVBQUUsZ0JBQWdCLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFNUYsT0FBTyxFQUFFLEtBQUssVUFBVSxFQUFFLEtBQUssd0JBQXdCLEVBQTBCLE1BQU0scUJBQXFCLENBQUM7QUFDN0csT0FBTyxLQUFLLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUV2RSwrRkFBK0Y7QUFDL0YsTUFBTSxNQUFNLGlCQUFpQixHQUFHO0lBQzlCLHdDQUF3QztJQUN4QyxVQUFVLEVBQUUsVUFBVSxDQUFDO0lBQ3ZCLG9FQUFvRTtJQUNwRSxnQkFBZ0IsRUFBRSxnQkFBZ0IsQ0FBQztJQUNuQyw4RUFBOEU7SUFDOUUsc0JBQXNCLEVBQUUsc0JBQXNCLEdBQUcsU0FBUyxDQUFDO0lBQzNELGtFQUFrRTtJQUNsRSw0QkFBNEIsRUFBRSxnQkFBZ0IsQ0FBQztDQUNoRCxDQUFDO0FBRUY7Ozs7R0FJRztBQUNILE1BQU0sTUFBTSxhQUFhLEdBQUc7SUFDMUIsaUJBQWlCLEVBQUUsV0FBVyxDQUFDO0lBQy9CLDBHQUEwRztJQUMxRyxlQUFlLEVBQUUsTUFBTSxDQUFDO0lBQ3hCLGFBQWEsQ0FBQyxFQUFFLGlCQUFpQixDQUFDO0NBQ25DLENBQUM7QUFFRixxRUFBcUU7QUFDckUsTUFBTSxNQUFNLGlCQUFpQixHQUN6QjtJQUNFLElBQUksRUFBRSxVQUFVLENBQUM7SUFDakIsVUFBVSxFQUFFLE1BQU0sQ0FBQztJQUNuQixXQUFXLEVBQUUsTUFBTSxDQUFDO0lBQ3BCLGlCQUFpQixFQUFFLE1BQU0sQ0FBQztJQUMxQixhQUFhLEVBQUUsTUFBTSxDQUFDO0lBQ3RCLHFCQUFxQixFQUFFLE1BQU0sQ0FBQztDQUMvQixHQUNEO0lBQUUsSUFBSSxFQUFFLGNBQWMsQ0FBQztJQUFDLGlCQUFpQixFQUFFLE9BQU8sQ0FBQztJQUFDLHNCQUFzQixDQUFDLEVBQUUsZ0JBQWdCLENBQUE7Q0FBRSxDQUFDO0FBRXBHOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxNQUFNLGNBQWMsR0FBRztJQUMzQixVQUFVLEVBQUUsVUFBVSxDQUFDO0lBQ3ZCLDRCQUE0QixFQUFFLGdCQUFnQixDQUFDO0lBQy9DLGVBQWUsRUFBRSxNQUFNLENBQUM7SUFDeEIsTUFBTSxFQUFFLGlCQUFpQixDQUFDO0NBQzNCLENBQUM7QUE2QkY7OztHQUdHO0FBQ0gsd0JBQWdCLFlBQVksQ0FBQyxVQUFVLEVBQUUsbUJBQW1CLEdBQUcsVUFBVSxDQUV4RTtBQUVEOzs7OztHQUtHO0FBQ0gsd0JBQWdCLGFBQWEsQ0FBQyxRQUFRLEVBQUUsVUFBVSxFQUFFLFNBQVMsRUFBRSxVQUFVLEdBQUcsYUFBYSxDQWtDeEY7QUFFRDs7OztHQUlHO0FBQ0gsd0JBQWdCLHFCQUFxQixDQUNuQyxJQUFJLEVBQUUsYUFBYSxFQUNuQiw0QkFBNEIsRUFBRSx3QkFBd0IsR0FDckQsY0FBYyxHQUFHLFNBQVMsQ0FzQjVCO0FBRUQsMkRBQTJEO0FBQzNELHdCQUFnQixvQkFBb0IsQ0FBQyxDQUFDLEVBQUUsY0FBYyxFQUFFLENBQUMsRUFBRSxjQUFjLEdBQUcsT0FBTyxDQU9sRiJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next_block_planner.d.ts","sourceRoot":"","sources":["../../../src/aztec-node/next_block/next_block_planner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAE5F,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,wBAAwB,EAA0B,MAAM,qBAAqB,CAAC;AAC7G,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE,+FAA+F;AAC/F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,wCAAwC;IACxC,UAAU,EAAE,UAAU,CAAC;IACvB,oEAAoE;IACpE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,8EAA8E;IAC9E,sBAAsB,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAC3D,kEAAkE;IAClE,4BAA4B,EAAE,gBAAgB,CAAC;CAChD,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,iBAAiB,EAAE,WAAW,CAAC;IAC/B,0GAA0G;IAC1G,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,CAAC;CAC/B,GACD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAC;IAAC,sBAAsB,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAEpG;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,4BAA4B,EAAE,gBAAgB,CAAC;IAC/C,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AA6BF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,mBAAmB,GAAG,UAAU,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,aAAa,CAkCxF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,aAAa,EACnB,4BAA4B,EAAE,wBAAwB,GACrD,cAAc,GAAG,SAAS,CAsB5B;AAED,2DAA2D;AAC3D,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,cAAc,GAAG,OAAO,CAOlF"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { PROPOSER_PIPELINING_SLOT_OFFSET } from '@aztec/epoch-cache';
|
|
2
|
+
import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { compactArray } from '@aztec/foundation/collection';
|
|
4
|
+
import { getCheckpointedTipSlot } from '@aztec/stdlib/block';
|
|
5
|
+
/**
|
|
6
|
+
* Slot the next block will land in, the largest of three terms:
|
|
7
|
+
*
|
|
8
|
+
* - The sequencer's exact formula, `getEpochAndSlotInNextL1Slot().slot + PROPOSER_PIPELINING_SLOT_OFFSET`.
|
|
9
|
+
* - `proposedCheckpointSlot + 1`, an RPC-side approximation of the next build: when a proposed checkpoint
|
|
10
|
+
* is gossiped before its L1 slot starts, the next build (once its wall clock arrives) will target
|
|
11
|
+
* `parentSlot + 1`. The sequencer never advances its own target past wall clock — it just declines to
|
|
12
|
+
* build — so this is a prediction of inclusion globals, not literal sequencer behavior. The parent slot
|
|
13
|
+
* comes from the proposed checkpoint header so the slot and the overrides plan cannot derive from
|
|
14
|
+
* different snapshots.
|
|
15
|
+
* - `checkpointedTipSlot + 1`, a floor: the next block can never land in a slot already taken by a
|
|
16
|
+
* checkpointed checkpoint. The slot comes from the frontier's checkpointed checkpoint header, so it
|
|
17
|
+
* describes the same instant as the tips and the proposed checkpoint. This only binds when this node's
|
|
18
|
+
* clock is behind the chain, in which case the first term would otherwise price the next block in a slot
|
|
19
|
+
* L1 has already moved past — and the L1 gas oracle can step between the two, so wallet quotes and
|
|
20
|
+
* simulations would disagree on the fee.
|
|
21
|
+
*/ function computeTargetSlot(clockSlot, proposedCheckpointData, checkpointedTipSlot) {
|
|
22
|
+
const slotAfterProposedCheckpoint = proposedCheckpointData ? proposedCheckpointData.header.slotNumber + 1 : undefined;
|
|
23
|
+
const slotAfterCheckpointedTip = checkpointedTipSlot !== undefined ? checkpointedTipSlot + 1 : undefined;
|
|
24
|
+
return SlotNumber(Math.max(...compactArray([
|
|
25
|
+
clockSlot,
|
|
26
|
+
slotAfterProposedCheckpoint,
|
|
27
|
+
slotAfterCheckpointedTip
|
|
28
|
+
])));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The slot this node's clock says the next block would be built in, the first of the three terms
|
|
32
|
+
* {@link planNextBlock} maximizes over. Pure arithmetic over the epoch cache's in-memory view.
|
|
33
|
+
*/ export function getClockSlot(epochCache) {
|
|
34
|
+
return SlotNumber(epochCache.getEpochAndSlotInNextL1Slot().slot + PROPOSER_PIPELINING_SLOT_OFFSET);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Works out how the next block sits on the chain from one atomic frontier snapshot: whether it continues the
|
|
38
|
+
* in-progress checkpoint or opens a fresh one, which slot it lands in, and which checkpoint's L1-to-L2
|
|
39
|
+
* messages a fork would need. Pure: the caller supplies both the snapshot and the clock slot, so the fee a
|
|
40
|
+
* wallet is quoted and the fee a simulation charges can never derive from different decisions.
|
|
41
|
+
*/ export function planNextBlock(frontier, clockSlot) {
|
|
42
|
+
const { tips, proposedCheckpoint: proposedCheckpointData } = frontier;
|
|
43
|
+
const latestBlockNumber = tips.proposed.number;
|
|
44
|
+
const latestBlockHash = tips.proposed.hash;
|
|
45
|
+
// Terminating block of the proposed-checkpoint frontier: the leading proposed (not-yet-L1-confirmed)
|
|
46
|
+
// checkpoint's last block is `startBlock + blockCount - 1`; with no proposed checkpoint the frontier
|
|
47
|
+
// coincides with the checkpointed tip.
|
|
48
|
+
const proposedCheckpointLastBlock = proposedCheckpointData ? BlockNumber.add(proposedCheckpointData.startBlock, proposedCheckpointData.blockCount - 1) : tips.checkpointed.block.number;
|
|
49
|
+
// The next block continues the in-progress checkpoint when the latest proposed block is ahead of the
|
|
50
|
+
// proposed-checkpoint terminating block; it opens a new checkpoint when they coincide.
|
|
51
|
+
if (proposedCheckpointLastBlock !== latestBlockNumber) {
|
|
52
|
+
return {
|
|
53
|
+
latestBlockNumber,
|
|
54
|
+
latestBlockHash
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const checkpointedCheckpointNumber = tips.checkpointed.checkpoint.number;
|
|
58
|
+
// The new checkpoint sits on top of the proposed one when pipelining, otherwise on the checkpointed tip.
|
|
59
|
+
const parentCheckpointNumber = proposedCheckpointData?.checkpointNumber ?? checkpointedCheckpointNumber;
|
|
60
|
+
// Undefined before the first checkpoint lands: no slot is taken yet, so there is no floor.
|
|
61
|
+
const checkpointedTipSlot = frontier.checkpointedCheckpoint ? getCheckpointedTipSlot(frontier) : undefined;
|
|
62
|
+
return {
|
|
63
|
+
latestBlockNumber,
|
|
64
|
+
latestBlockHash,
|
|
65
|
+
newCheckpoint: {
|
|
66
|
+
targetSlot: computeTargetSlot(clockSlot, proposedCheckpointData, checkpointedTipSlot),
|
|
67
|
+
targetCheckpoint: CheckpointNumber(parentCheckpointNumber + 1),
|
|
68
|
+
proposedCheckpointData,
|
|
69
|
+
checkpointedCheckpointNumber
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Keys the fee of a plan that opens a new checkpoint. Undefined mid-checkpoint, where the fee is copied from
|
|
75
|
+
* the in-progress checkpoint's header and nothing needs pricing.
|
|
76
|
+
* @param pendingChainValidationStatus - From the same frontier snapshot the plan was built from.
|
|
77
|
+
*/ export function computeBoundaryFeeKey(plan, pendingChainValidationStatus) {
|
|
78
|
+
if (!plan.newCheckpoint) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const { targetSlot, proposedCheckpointData, checkpointedCheckpointNumber } = plan.newCheckpoint;
|
|
82
|
+
const parent = proposedCheckpointData ? {
|
|
83
|
+
kind: 'proposed',
|
|
84
|
+
headerHash: proposedCheckpointData.header.hash().toString(),
|
|
85
|
+
archiveRoot: proposedCheckpointData.archive.root.toString(),
|
|
86
|
+
checkpointOutHash: proposedCheckpointData.checkpointOutHash.toString(),
|
|
87
|
+
totalManaUsed: proposedCheckpointData.totalManaUsed,
|
|
88
|
+
feeAssetPriceModifier: proposedCheckpointData.feeAssetPriceModifier
|
|
89
|
+
} : {
|
|
90
|
+
kind: 'checkpointed',
|
|
91
|
+
pendingChainValid: pendingChainValidationStatus.valid,
|
|
92
|
+
firstInvalidCheckpoint: pendingChainValidationStatus.valid ? undefined : pendingChainValidationStatus.checkpoint.checkpointNumber
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
targetSlot,
|
|
96
|
+
checkpointedCheckpointNumber,
|
|
97
|
+
latestBlockHash: plan.latestBlockHash,
|
|
98
|
+
parent
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Whether two boundary fee keys describe the same fee. */ export function boundaryFeeKeyEquals(a, b) {
|
|
102
|
+
return a.targetSlot === b.targetSlot && a.checkpointedCheckpointNumber === b.checkpointedCheckpointNumber && a.latestBlockHash === b.latestBlockHash && boundaryFeeParentEquals(a.parent, b.parent);
|
|
103
|
+
}
|
|
104
|
+
function boundaryFeeParentEquals(a, b) {
|
|
105
|
+
if (a.kind === 'proposed') {
|
|
106
|
+
return b.kind === 'proposed' && a.headerHash === b.headerHash && a.archiveRoot === b.archiveRoot && a.checkpointOutHash === b.checkpointOutHash && a.totalManaUsed === b.totalManaUsed && a.feeAssetPriceModifier === b.feeAssetPriceModifier;
|
|
107
|
+
}
|
|
108
|
+
return b.kind === 'checkpointed' && a.pendingChainValid === b.pendingChainValid && a.firstInvalidCheckpoint === b.firstInvalidCheckpoint;
|
|
109
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
3
|
+
import type { L1SyncPoint, L2BlockSource, L2Frontier } from '@aztec/stdlib/block';
|
|
4
|
+
import type { GasFees } from '@aztec/stdlib/gas';
|
|
5
|
+
import { GlobalVariables } from '@aztec/stdlib/tx';
|
|
6
|
+
import { NextBlockFeeCache, type NextBlockFeeCacheDeps } from './next_block_fee_cache.js';
|
|
7
|
+
import { type NextBlockPlan } from './next_block_planner.js';
|
|
8
|
+
/**
|
|
9
|
+
* How long a fee quote waits for a boundary refresh before answering with what it already has. Long enough for
|
|
10
|
+
* a slow but alive L1 RPC; short enough that an L1 outage does not turn every quote into a multi-second RPC.
|
|
11
|
+
*/
|
|
12
|
+
export declare const QUOTE_MAX_WAIT_MS = 5000;
|
|
13
|
+
/** The next block as this node predicts it: how it sits on the chain, plus the globals it would carry. */
|
|
14
|
+
export type NextBlockPrediction = {
|
|
15
|
+
plan: NextBlockPlan;
|
|
16
|
+
/** Snapshot the plan was derived from, so callers can pin their own reads to the same instant. */
|
|
17
|
+
frontier: L2Frontier;
|
|
18
|
+
globals: GlobalVariables;
|
|
19
|
+
};
|
|
20
|
+
/** Dependencies required to build a {@link NextBlockPredictor}. */
|
|
21
|
+
export interface NextBlockPredictorDeps {
|
|
22
|
+
blockSource: L2BlockSource;
|
|
23
|
+
feeCache: NextBlockFeeCache;
|
|
24
|
+
epochCache: EpochCacheInterface;
|
|
25
|
+
log?: Logger;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Answers "what would the next block look like" for everything on the RPC side: the public simulation forks and
|
|
29
|
+
* executes against this prediction, and the fee quote reports the fee it carries.
|
|
30
|
+
*
|
|
31
|
+
* The plan is re-derived from a fresh archiver snapshot on every call — it is a handful of in-memory reads, and
|
|
32
|
+
* a stale plan would fork the wrong block, target the wrong checkpoint's L1-to-L2 messages, or miss that an
|
|
33
|
+
* in-progress checkpoint froze a different fee. Only the L1-derived part, the checkpoint globals of a block that
|
|
34
|
+
* opens a fresh checkpoint, is cached (see {@link NextBlockFeeCache}).
|
|
35
|
+
*
|
|
36
|
+
* Deliberately not used by the sequencer: its slot policy is stricter (it declines to build rather than
|
|
37
|
+
* predicting inclusion) and a stale fee would make L1 reject its checkpoint.
|
|
38
|
+
*/
|
|
39
|
+
export declare class NextBlockPredictor {
|
|
40
|
+
private readonly blockSource;
|
|
41
|
+
private readonly feeCache;
|
|
42
|
+
private readonly epochCache;
|
|
43
|
+
private readonly log;
|
|
44
|
+
constructor(deps: NextBlockPredictorDeps);
|
|
45
|
+
/**
|
|
46
|
+
* Builds a predictor together with the fee cache it reads from. It takes the cache's dependencies rather than
|
|
47
|
+
* {@link NextBlockPredictorDeps} because the cache is constructed here; everything the predictor itself needs is
|
|
48
|
+
* a subset of them.
|
|
49
|
+
*/
|
|
50
|
+
static create(deps: NextBlockFeeCacheDeps): NextBlockPredictor;
|
|
51
|
+
start(pollingIntervalMs?: number): void;
|
|
52
|
+
stop(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Plans the next block and builds the globals it would carry, in one of two ways, mirroring how the sequencer
|
|
55
|
+
* builds the next block:
|
|
56
|
+
*
|
|
57
|
+
* - **Continuing an in-progress checkpoint**: every block in a checkpoint shares the same
|
|
58
|
+
* `CheckpointGlobalVariables`, so the latest proposed block's globals are copied verbatim — including the
|
|
59
|
+
* proposer's real coinbase and fee recipient — with only the block number bumped. No L1 involved.
|
|
60
|
+
* - **Opening a new checkpoint**: the globals are priced for the slot the next block will land in, under the
|
|
61
|
+
* same overrides plan the sequencer applies, so the simulated mana min fee matches what the sequencer will
|
|
62
|
+
* write into the block header. Coinbase and fee recipient stay zero: the future proposer's payout addresses
|
|
63
|
+
* are unknowable.
|
|
64
|
+
*
|
|
65
|
+
* Waits without a bound for a boundary fee it does not have cached, and surfaces the L1 failure if that read
|
|
66
|
+
* fails, as a simulation did before this cache existed.
|
|
67
|
+
*/
|
|
68
|
+
predict(): Promise<NextBlockPrediction>;
|
|
69
|
+
/**
|
|
70
|
+
* The mana min fee the next block will charge, for wallets to pad and pay. Mid-checkpoint this is the fee the
|
|
71
|
+
* in-progress checkpoint froze into its first block, which no forward-looking L1 projection can see. At a
|
|
72
|
+
* boundary it is the cached L1 price, waited on for at most {@link QUOTE_MAX_WAIT_MS}; undefined when the node
|
|
73
|
+
* has nothing usable, in which case the caller falls back to the fee provider's projections alone.
|
|
74
|
+
*
|
|
75
|
+
* Returns the L1 block the answer describes so the caller can tag its own fee reads with the same anchor.
|
|
76
|
+
*/
|
|
77
|
+
quoteMinFees(): Promise<{
|
|
78
|
+
fees: GasFees;
|
|
79
|
+
l1SyncPoint: L1SyncPoint | undefined;
|
|
80
|
+
} | undefined>;
|
|
81
|
+
private planFromFrontier;
|
|
82
|
+
/**
|
|
83
|
+
* The header comes from the same snapshot as the tips, so it cannot describe a different block than the
|
|
84
|
+
* proposed tip. A missing header at a non-genesis proposed tip is an invariant violation and throws rather
|
|
85
|
+
* than falling through to the new-checkpoint path: a fork at the proposed tip already contains the ongoing
|
|
86
|
+
* checkpoint's L1-to-L2 messages, so a caller inserting the next checkpoint's messages would append them a
|
|
87
|
+
* second time.
|
|
88
|
+
*/
|
|
89
|
+
private copyGlobalsFromLatestBlock;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV4dF9ibG9ja19wcmVkaWN0b3IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9henRlYy1ub2RlL25leHRfYmxvY2svbmV4dF9ibG9ja19wcmVkaWN0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUU5RCxPQUFPLEVBQUUsS0FBSyxNQUFNLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFDbEUsT0FBTyxLQUFLLEVBQUUsV0FBVyxFQUFFLGFBQWEsRUFBRSxVQUFVLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNsRixPQUFPLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUNqRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFbkQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLEtBQUsscUJBQXFCLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUMxRixPQUFPLEVBQUUsS0FBSyxhQUFhLEVBQXNELE1BQU0seUJBQXlCLENBQUM7QUFFakg7OztHQUdHO0FBQ0gsZUFBTyxNQUFNLGlCQUFpQixPQUFPLENBQUM7QUFFdEMsMEdBQTBHO0FBQzFHLE1BQU0sTUFBTSxtQkFBbUIsR0FBRztJQUNoQyxJQUFJLEVBQUUsYUFBYSxDQUFDO0lBQ3BCLGtHQUFrRztJQUNsRyxRQUFRLEVBQUUsVUFBVSxDQUFDO0lBQ3JCLE9BQU8sRUFBRSxlQUFlLENBQUM7Q0FDMUIsQ0FBQztBQUVGLG1FQUFtRTtBQUNuRSxNQUFNLFdBQVcsc0JBQXNCO0lBQ3JDLFdBQVcsRUFBRSxhQUFhLENBQUM7SUFDM0IsUUFBUSxFQUFFLGlCQUFpQixDQUFDO0lBQzVCLFVBQVUsRUFBRSxtQkFBbUIsQ0FBQztJQUNoQyxHQUFHLENBQUMsRUFBRSxNQUFNLENBQUM7Q0FDZDtBQUVEOzs7Ozs7Ozs7OztHQVdHO0FBQ0gscUJBQWEsa0JBQWtCO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFnQjtJQUM1QyxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBb0I7SUFDN0MsT0FBTyxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQXNCO0lBQ2pELE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBRTdCLFlBQVksSUFBSSxFQUFFLHNCQUFzQixFQUt2QztJQUVEOzs7O09BSUc7SUFDSCxPQUFjLE1BQU0sQ0FBQyxJQUFJLEVBQUUscUJBQXFCLEdBQUcsa0JBQWtCLENBUXBFO0lBRU0sS0FBSyxDQUFDLGlCQUFpQixDQUFDLEVBQUUsTUFBTSxHQUFHLElBQUksQ0FFN0M7SUFFTSxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUUzQjtJQUVEOzs7Ozs7Ozs7Ozs7OztPQWNHO0lBQ1UsT0FBTyxJQUFJLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQyxDQVluRDtJQUVEOzs7Ozs7O09BT0c7SUFDVSxZQUFZLElBQUksT0FBTyxDQUFDO1FBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQztRQUFDLFdBQVcsRUFBRSxXQUFXLEdBQUcsU0FBUyxDQUFBO0tBQUUsR0FBRyxTQUFTLENBQUMsQ0FleEc7WUFHYSxnQkFBZ0I7SUFPOUI7Ozs7OztPQU1HO0lBQ0gsT0FBTyxDQUFDLDBCQUEwQjtDQVFuQyJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next_block_predictor.d.ts","sourceRoot":"","sources":["../../../src/aztec-node/next_block/next_block_predictor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,KAAK,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC1F,OAAO,EAAE,KAAK,aAAa,EAAsD,MAAM,yBAAyB,CAAC;AAEjH;;;GAGG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,0GAA0G;AAC1G,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,aAAa,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,UAAU,CAAC;IACrB,OAAO,EAAE,eAAe,CAAC;CAC1B,CAAC;AAEF,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,aAAa,CAAC;IAC3B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,EAAE,mBAAmB,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,YAAY,IAAI,EAAE,sBAAsB,EAKvC;IAED;;;;OAIG;IACH,OAAc,MAAM,CAAC,IAAI,EAAE,qBAAqB,GAAG,kBAAkB,CAQpE;IAEM,KAAK,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAE7C;IAEM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAED;;;;;;;;;;;;;;OAcG;IACU,OAAO,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAYnD;IAED;;;;;;;OAOG;IACU,YAAY,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,WAAW,GAAG,SAAS,CAAA;KAAE,GAAG,SAAS,CAAC,CAexG;YAGa,gBAAgB;IAO9B;;;;;;OAMG;IACH,OAAO,CAAC,0BAA0B;CAQnC"}
|