@aztec/stdlib 5.0.0-rc.2 → 5.0.0
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/block/attestation_info.d.ts +1 -1
- package/dest/block/attestation_info.d.ts.map +1 -1
- package/dest/block/attestation_info.js +7 -4
- package/dest/block/l2_block_source.d.ts +34 -3
- package/dest/block/l2_block_source.d.ts.map +1 -1
- package/dest/block/l2_block_source.js +15 -0
- package/dest/block/l2_block_stream/event_driven_l2_block_stream.d.ts +39 -0
- package/dest/block/l2_block_stream/event_driven_l2_block_stream.d.ts.map +1 -0
- package/dest/block/l2_block_stream/event_driven_l2_block_stream.js +165 -0
- package/dest/block/l2_block_stream/index.d.ts +2 -1
- package/dest/block/l2_block_stream/index.d.ts.map +1 -1
- package/dest/block/l2_block_stream/index.js +1 -0
- package/dest/block/l2_block_stream/interfaces.d.ts +16 -2
- package/dest/block/l2_block_stream/interfaces.d.ts.map +1 -1
- package/dest/block/l2_block_stream/interfaces.js +25 -1
- package/dest/block/l2_block_stream/l2_block_stream.d.ts +21 -35
- package/dest/block/l2_block_stream/l2_block_stream.d.ts.map +1 -1
- package/dest/block/l2_block_stream/l2_block_stream.js +5 -30
- package/dest/block/proposal/attestations_and_signers.d.ts +19 -1
- package/dest/block/proposal/attestations_and_signers.d.ts.map +1 -1
- package/dest/block/proposal/attestations_and_signers.js +47 -7
- package/dest/block/validate_block_result.d.ts +16 -1
- package/dest/block/validate_block_result.d.ts.map +1 -1
- package/dest/block/validate_block_result.js +18 -3
- package/dest/config/network-consensus-config.d.ts +1 -1
- package/dest/config/network-consensus-config.d.ts.map +1 -1
- package/dest/config/network-consensus-config.js +2 -10
- package/dest/contract/contract_address.d.ts +5 -4
- package/dest/contract/contract_address.d.ts.map +1 -1
- package/dest/contract/contract_instance.d.ts +16 -2
- package/dest/contract/contract_instance.d.ts.map +1 -1
- package/dest/contract/contract_instance.js +45 -0
- package/dest/contract/interfaces/contract_instance.d.ts +71 -10
- package/dest/contract/interfaces/contract_instance.d.ts.map +1 -1
- package/dest/contract/interfaces/contract_instance.js +7 -2
- package/dest/contract/partial_address.d.ts +9 -3
- package/dest/contract/partial_address.d.ts.map +1 -1
- package/dest/contract/partial_address.js +8 -2
- package/dest/database-version/version_manager.d.ts +18 -4
- package/dest/database-version/version_manager.d.ts.map +1 -1
- package/dest/database-version/version_manager.js +76 -6
- package/dest/ha-signing/config.d.ts +19 -5
- package/dest/ha-signing/config.d.ts.map +1 -1
- package/dest/ha-signing/config.js +9 -3
- package/dest/ha-signing/local_config.d.ts +10 -2
- package/dest/ha-signing/local_config.d.ts.map +1 -1
- package/dest/ha-signing/local_config.js +8 -2
- package/dest/hash/hash.d.ts +1 -2
- package/dest/hash/hash.d.ts.map +1 -1
- package/dest/hash/hash.js +0 -7
- package/dest/interfaces/archiver.d.ts +8 -1
- package/dest/interfaces/archiver.d.ts.map +1 -1
- package/dest/interfaces/archiver.js +2 -1
- package/dest/interfaces/aztec-node-admin.d.ts +5 -2
- package/dest/interfaces/aztec-node-admin.d.ts.map +1 -1
- package/dest/interfaces/configs.d.ts +4 -1
- package/dest/interfaces/configs.d.ts.map +1 -1
- package/dest/interfaces/configs.js +1 -0
- package/dest/interfaces/private_kernel_prover.d.ts +9 -11
- package/dest/interfaces/private_kernel_prover.d.ts.map +1 -1
- package/dest/interfaces/proving-job.d.ts +87 -74
- package/dest/interfaces/proving-job.d.ts.map +1 -1
- package/dest/interfaces/proving-job.js +13 -4
- package/dest/interfaces/server_circuit_prover.d.ts +3 -3
- package/dest/interfaces/server_circuit_prover.d.ts.map +1 -1
- package/dest/interfaces/validator.d.ts +7 -3
- package/dest/interfaces/validator.d.ts.map +1 -1
- package/dest/interfaces/world_state.d.ts +14 -1
- package/dest/interfaces/world_state.d.ts.map +1 -1
- package/dest/kernel/hints/find_private_kernel_reset_dimensions.d.ts +8 -2
- package/dest/kernel/hints/find_private_kernel_reset_dimensions.d.ts.map +1 -1
- package/dest/kernel/hints/find_private_kernel_reset_dimensions.js +12 -74
- package/dest/kernel/index.d.ts +2 -2
- package/dest/kernel/index.d.ts.map +1 -1
- package/dest/kernel/index.js +1 -1
- package/dest/kernel/private_circuit_public_inputs.d.ts +13 -3
- package/dest/kernel/private_circuit_public_inputs.d.ts.map +1 -1
- package/dest/kernel/private_circuit_public_inputs.js +14 -7
- package/dest/kernel/private_context_inputs.d.ts +4 -3
- package/dest/kernel/private_context_inputs.d.ts.map +1 -1
- package/dest/kernel/private_context_inputs.js +7 -3
- package/dest/kernel/private_kernel_circuit_public_inputs.d.ts +7 -1
- package/dest/kernel/private_kernel_circuit_public_inputs.d.ts.map +1 -1
- package/dest/kernel/private_kernel_circuit_public_inputs.js +7 -0
- package/dest/kernel/private_kernel_init_2_circuit_private_inputs.d.ts +2 -3
- package/dest/kernel/private_kernel_init_2_circuit_private_inputs.d.ts.map +1 -1
- package/dest/kernel/private_kernel_init_2_circuit_private_inputs.js +3 -5
- package/dest/kernel/private_kernel_init_3_circuit_private_inputs.d.ts +2 -3
- package/dest/kernel/private_kernel_init_3_circuit_private_inputs.d.ts.map +1 -1
- package/dest/kernel/private_kernel_init_3_circuit_private_inputs.js +3 -5
- package/dest/kernel/private_kernel_init_circuit_private_inputs.d.ts +1 -9
- package/dest/kernel/private_kernel_init_circuit_private_inputs.d.ts.map +1 -1
- package/dest/kernel/private_kernel_init_circuit_private_inputs.js +2 -6
- package/dest/kernel/private_kernel_reset_dimensions.d.ts +7 -8
- package/dest/kernel/private_kernel_reset_dimensions.d.ts.map +1 -1
- package/dest/kernel/private_kernel_reset_tail_circuit_private_inputs.d.ts +29 -0
- package/dest/kernel/private_kernel_reset_tail_circuit_private_inputs.d.ts.map +1 -0
- package/dest/kernel/private_kernel_reset_tail_circuit_private_inputs.js +36 -0
- package/dest/keys/derivation.d.ts +41 -6
- package/dest/keys/derivation.d.ts.map +1 -1
- package/dest/keys/derivation.js +49 -26
- package/dest/logs/app_tagging_secret.d.ts +16 -8
- package/dest/logs/app_tagging_secret.d.ts.map +1 -1
- package/dest/logs/app_tagging_secret.js +24 -27
- package/dest/logs/index.d.ts +1 -3
- package/dest/logs/index.d.ts.map +1 -1
- package/dest/logs/index.js +0 -2
- package/dest/logs/shared_secret_derivation.d.ts +19 -6
- package/dest/logs/shared_secret_derivation.d.ts.map +1 -1
- package/dest/logs/shared_secret_derivation.js +26 -11
- package/dest/messaging/l1_to_l2_message.d.ts +23 -2
- package/dest/messaging/l1_to_l2_message.d.ts.map +1 -1
- package/dest/messaging/l1_to_l2_message.js +36 -11
- package/dest/p2p/attestation_utils.d.ts +1 -1
- package/dest/p2p/attestation_utils.d.ts.map +1 -1
- package/dest/p2p/attestation_utils.js +8 -1
- package/dest/p2p/checkpoint_attestation.d.ts +9 -1
- package/dest/p2p/checkpoint_attestation.d.ts.map +1 -1
- package/dest/p2p/checkpoint_attestation.js +10 -0
- package/dest/p2p/signature_utils.d.ts +10 -1
- package/dest/p2p/signature_utils.d.ts.map +1 -1
- package/dest/p2p/signature_utils.js +9 -1
- package/dest/rollup/avm_proof_data.d.ts +3 -3
- package/dest/rollup/avm_proof_data.d.ts.map +1 -1
- package/dest/stats/stats.d.ts +2 -2
- package/dest/stats/stats.d.ts.map +1 -1
- package/dest/tests/factories.d.ts +1 -1
- package/dest/tests/factories.d.ts.map +1 -1
- package/dest/tests/factories.js +4 -3
- package/dest/tx/private_tx_constant_data.d.ts +18 -2
- package/dest/tx/private_tx_constant_data.d.ts.map +1 -1
- package/dest/tx/private_tx_constant_data.js +13 -4
- package/dest/tx/simulated_tx.d.ts +10 -6
- package/dest/tx/simulated_tx.d.ts.map +1 -1
- package/dest/world-state/genesis_data.d.ts +9 -1
- package/dest/world-state/genesis_data.d.ts.map +1 -1
- package/dest/world-state/genesis_data.js +1 -0
- package/package.json +8 -8
- package/src/block/attestation_info.ts +7 -2
- package/src/block/l2_block_source.ts +54 -2
- package/src/block/l2_block_stream/event_driven_l2_block_stream.ts +207 -0
- package/src/block/l2_block_stream/index.ts +1 -0
- package/src/block/l2_block_stream/interfaces.ts +35 -1
- package/src/block/l2_block_stream/l2_block_stream.ts +35 -62
- package/src/block/proposal/attestations_and_signers.ts +53 -7
- package/src/block/validate_block_result.ts +48 -2
- package/src/config/network-consensus-config.ts +2 -13
- package/src/contract/contract_address.ts +4 -3
- package/src/contract/contract_instance.ts +62 -1
- package/src/contract/interfaces/contract_instance.ts +26 -8
- package/src/contract/partial_address.ts +8 -2
- package/src/database-version/version_manager.ts +96 -6
- package/src/ha-signing/config.ts +23 -5
- package/src/ha-signing/local_config.ts +20 -1
- package/src/hash/hash.ts +0 -8
- package/src/interfaces/archiver.ts +8 -0
- package/src/interfaces/configs.ts +3 -0
- package/src/interfaces/private_kernel_prover.ts +10 -12
- package/src/interfaces/proving-job.ts +15 -3
- package/src/interfaces/server_circuit_prover.ts +2 -2
- package/src/interfaces/world_state.ts +14 -0
- package/src/kernel/hints/find_private_kernel_reset_dimensions.ts +22 -112
- package/src/kernel/index.ts +1 -1
- package/src/kernel/private_circuit_public_inputs.ts +12 -1
- package/src/kernel/private_context_inputs.ts +10 -3
- package/src/kernel/private_kernel_circuit_public_inputs.ts +9 -0
- package/src/kernel/private_kernel_init_2_circuit_private_inputs.ts +0 -3
- package/src/kernel/private_kernel_init_3_circuit_private_inputs.ts +0 -3
- package/src/kernel/private_kernel_init_circuit_private_inputs.ts +0 -6
- package/src/kernel/private_kernel_reset_dimensions.ts +15 -5
- package/src/kernel/private_kernel_reset_tail_circuit_private_inputs.ts +69 -0
- package/src/keys/derivation.ts +69 -24
- package/src/logs/app_tagging_secret.ts +34 -27
- package/src/logs/index.ts +0 -2
- package/src/logs/shared_secret_derivation.ts +31 -15
- package/src/messaging/l1_to_l2_message.ts +51 -13
- package/src/p2p/attestation_utils.ts +8 -1
- package/src/p2p/checkpoint_attestation.ts +12 -0
- package/src/p2p/signature_utils.ts +9 -0
- package/src/rollup/avm_proof_data.ts +2 -2
- package/src/stats/stats.ts +2 -2
- package/src/tests/factories.ts +3 -6
- package/src/tx/private_tx_constant_data.ts +25 -2
- package/src/world-state/genesis_data.ts +10 -0
- package/dest/kernel/private_kernel_tail_circuit_private_inputs.d.ts +0 -47
- package/dest/kernel/private_kernel_tail_circuit_private_inputs.d.ts.map +0 -1
- package/dest/kernel/private_kernel_tail_circuit_private_inputs.js +0 -38
- package/dest/logs/message_context.d.ts +0 -17
- package/dest/logs/message_context.d.ts.map +0 -1
- package/dest/logs/message_context.js +0 -9
- package/dest/logs/pending_tagged_log.d.ts +0 -11
- package/dest/logs/pending_tagged_log.d.ts.map +0 -1
- package/dest/logs/pending_tagged_log.js +0 -4
- package/src/kernel/private_kernel_tail_circuit_private_inputs.ts +0 -58
- package/src/logs/message_context.ts +0 -18
- package/src/logs/pending_tagged_log.ts +0 -12
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
3
|
+
|
|
4
|
+
import type { BlockData } from '../block_data.js';
|
|
5
|
+
import type { L2Block } from '../l2_block.js';
|
|
6
|
+
import {
|
|
7
|
+
type ArchiverEmitter,
|
|
8
|
+
type BlockQuery,
|
|
9
|
+
type BlocksQuery,
|
|
10
|
+
type L2BlockSource,
|
|
11
|
+
type L2BlockSourceEventEmitter,
|
|
12
|
+
L2BlockSourceEvents,
|
|
13
|
+
type L2BlockSourceUpdatedEvent,
|
|
14
|
+
type L2Tips,
|
|
15
|
+
} from '../l2_block_source.js';
|
|
16
|
+
import { type L2BlockStreamEventHandler, type L2BlockStreamLocalDataProvider, localTipsMatch } from './interfaces.js';
|
|
17
|
+
import { L2BlockStream, type L2BlockStreamOptions, type L2BlockStreamSource } from './l2_block_stream.js';
|
|
18
|
+
|
|
19
|
+
/** Derives the metadata-only {@link BlockData} view of a hydrated {@link L2Block}. */
|
|
20
|
+
async function l2BlockToBlockData(block: L2Block): Promise<BlockData> {
|
|
21
|
+
return {
|
|
22
|
+
header: block.header,
|
|
23
|
+
archive: block.archive,
|
|
24
|
+
blockHash: await block.hash(),
|
|
25
|
+
checkpointNumber: block.checkpointNumber,
|
|
26
|
+
indexWithinCheckpoint: block.indexWithinCheckpoint,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Returns the event emitter of a source that exposes one, or undefined for plain (e.g. RPC-backed) sources. */
|
|
31
|
+
function getEmitter(source: L2BlockSource | L2BlockSourceEventEmitter): ArchiverEmitter | undefined {
|
|
32
|
+
return 'events' in source ? source.events : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Fast-path context for a single sync pass: blocks to serve by number, plus the tips to report as the source's. */
|
|
36
|
+
type ActiveUpdate = { byNumber: Map<number, L2Block>; toTips: L2Tips };
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Wraps a block source so a single sync pass can be served from blocks delivered by an aggregate update event,
|
|
40
|
+
* avoiding round-trips to archiver storage. The fast path is only armed (via {@link activate}) for a pass that is
|
|
41
|
+
* confirmed caught up to the event's pre-pass tips: in that case the event's `blocksAdded` contiguously cover the
|
|
42
|
+
* pass's download range and `toTips` is the exact post-pass tip, so `getL2Tips` can report it without querying the
|
|
43
|
+
* source. When the fast path is not armed, every read delegates to the source, so a stale or partial cache never
|
|
44
|
+
* changes the sync outcome.
|
|
45
|
+
*/
|
|
46
|
+
class HotBlockSourceAdapter implements L2BlockStreamSource {
|
|
47
|
+
/** Set for the duration of one fast-path pass; undefined when reads must delegate to the source. */
|
|
48
|
+
private active: ActiveUpdate | undefined;
|
|
49
|
+
|
|
50
|
+
constructor(
|
|
51
|
+
private readonly source: L2BlockStreamSource,
|
|
52
|
+
private readonly log: Logger,
|
|
53
|
+
) {}
|
|
54
|
+
|
|
55
|
+
/** Arms the fast path for the current pass: serve these blocks and report `toTips` as the source tips. */
|
|
56
|
+
public activate(blocks: readonly L2Block[], toTips: L2Tips): void {
|
|
57
|
+
const byNumber = new Map<number, L2Block>();
|
|
58
|
+
for (const block of blocks) {
|
|
59
|
+
byNumber.set(block.number, block);
|
|
60
|
+
}
|
|
61
|
+
this.active = { byNumber, toTips };
|
|
62
|
+
this.log.trace(`Armed hot-block fast path`, { blocks: byNumber.size, toTips });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Disarms the fast path so subsequent reads delegate to the source again. */
|
|
66
|
+
public deactivate(): void {
|
|
67
|
+
this.active = undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public getL2Tips(): Promise<L2Tips> {
|
|
71
|
+
return this.active ? Promise.resolve(this.active.toTips) : this.source.getL2Tips();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public getBlocks(query: BlocksQuery): Promise<L2Block[]> {
|
|
75
|
+
const served = this.active ? this.tryServeBlocksFromCache(query) : undefined;
|
|
76
|
+
return served ? Promise.resolve(served) : this.source.getBlocks(query);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public getBlockData(query: BlockQuery): Promise<BlockData | undefined> {
|
|
80
|
+
if (this.active && 'number' in query) {
|
|
81
|
+
const block = this.active.byNumber.get(query.number);
|
|
82
|
+
if (block) {
|
|
83
|
+
return l2BlockToBlockData(block);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return this.source.getBlockData(query);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Serves a block range from the cache only when it is fully covered by contiguous cached blocks. */
|
|
90
|
+
private tryServeBlocksFromCache(query: BlocksQuery): L2Block[] | undefined {
|
|
91
|
+
// Only the by-range form is cacheable, and only for the full (not checkpointed-only) chain: the cache may hold
|
|
92
|
+
// uncheckpointed blocks that an onlyCheckpointed query must not receive.
|
|
93
|
+
if (!this.active || !('from' in query) || query.onlyCheckpointed) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const from = query.from;
|
|
97
|
+
const to = from + query.limit - 1;
|
|
98
|
+
const blocks: L2Block[] = [];
|
|
99
|
+
for (let n = from; n <= to; n++) {
|
|
100
|
+
const block = this.active.byNumber.get(n);
|
|
101
|
+
if (!block) {
|
|
102
|
+
// A gap inside the requested range: bail out and let the source serve the whole range.
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
blocks.push(block);
|
|
106
|
+
}
|
|
107
|
+
return blocks;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Event-driven wrapper around {@link L2BlockStream}. Subscribes to the source's aggregate `l2BlockSourceUpdated`
|
|
113
|
+
* event (when the source exposes one) to trigger an immediate reconciliation, while keeping the periodic poll as
|
|
114
|
+
* the correctness fallback. Subsystems keep consuming the same {@link L2BlockStreamEvent}s; the archiver aggregate
|
|
115
|
+
* event is handled entirely here.
|
|
116
|
+
*
|
|
117
|
+
* The event is passed through to the sync pass via {@link RunningPromise.trigger}. If, at the time the pass runs,
|
|
118
|
+
* the stream's local tips match the event's `fromTips` (it is caught up to where the event began), the event's
|
|
119
|
+
* hydrated blocks are served back through a hot-block cache and the event's `toTips` is reported as the source tips
|
|
120
|
+
* — so the triggered sync re-reads neither block bodies nor tips from the archiver. Otherwise the pass delegates
|
|
121
|
+
* entirely to the source, and the periodic poll guarantees eventual catch-up.
|
|
122
|
+
*/
|
|
123
|
+
export class EventDrivenL2BlockStream {
|
|
124
|
+
private readonly adapter: HotBlockSourceAdapter;
|
|
125
|
+
private readonly blockStream: L2BlockStream;
|
|
126
|
+
private readonly runningPromise: RunningPromise<L2BlockSourceUpdatedEvent>;
|
|
127
|
+
private readonly emitter: ArchiverEmitter | undefined;
|
|
128
|
+
private started = false;
|
|
129
|
+
|
|
130
|
+
private readonly onSourceUpdated = (event: L2BlockSourceUpdatedEvent) => {
|
|
131
|
+
// Fire-and-forget: trigger coalesces with any in-flight or periodic pass (see RunningPromise.trigger), so a
|
|
132
|
+
// burst of events does not run passes concurrently. Errors are swallowed by the inner stream's own handler.
|
|
133
|
+
void this.runningPromise
|
|
134
|
+
.trigger(event)
|
|
135
|
+
.catch(err => this.log.error(`Error in event-triggered block stream sync`, err));
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
constructor(
|
|
139
|
+
source: L2BlockSource | L2BlockSourceEventEmitter,
|
|
140
|
+
private readonly localData: L2BlockStreamLocalDataProvider,
|
|
141
|
+
handler: L2BlockStreamEventHandler,
|
|
142
|
+
private readonly log = createLogger('types:event_driven_block_stream'),
|
|
143
|
+
opts: L2BlockStreamOptions = {},
|
|
144
|
+
) {
|
|
145
|
+
this.adapter = new HotBlockSourceAdapter(source, log);
|
|
146
|
+
// The inner stream's own RunningPromise is never started; this wrapper owns the polling loop and drives the
|
|
147
|
+
// stream through `sync()` (which runs `work()` directly when the inner loop is stopped).
|
|
148
|
+
this.blockStream = new L2BlockStream(this.adapter, localData, handler, log, opts);
|
|
149
|
+
this.runningPromise = new RunningPromise<L2BlockSourceUpdatedEvent>(
|
|
150
|
+
this.runPass.bind(this),
|
|
151
|
+
log,
|
|
152
|
+
opts.pollIntervalMS ?? 1000,
|
|
153
|
+
);
|
|
154
|
+
this.emitter = getEmitter(source);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public start(): void {
|
|
158
|
+
if (this.started) {
|
|
159
|
+
this.log.warn(`Attempted to start an already-started event-driven block stream`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.started = true;
|
|
163
|
+
this.emitter?.on(L2BlockSourceEvents.L2BlockSourceUpdated, this.onSourceUpdated);
|
|
164
|
+
this.runningPromise.start();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public async stop(): Promise<void> {
|
|
168
|
+
this.started = false;
|
|
169
|
+
this.emitter?.off(L2BlockSourceEvents.L2BlockSourceUpdated, this.onSourceUpdated);
|
|
170
|
+
await this.runningPromise.stop();
|
|
171
|
+
await this.blockStream.stop();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public isRunning(): boolean {
|
|
175
|
+
return this.runningPromise.isRunning();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Runs a synchronization pass now, bypassing the poll interval, and resolves once that pass completes. Concurrent
|
|
180
|
+
* callers and periodic ticks coalesce onto a single pass; a caller that coalesces onto an already in-flight pass
|
|
181
|
+
* can resolve against a pass that began just before it, so this guarantees freshness only up to that coalescing
|
|
182
|
+
* window. The periodic poll and per-pass reorg handling make the gap a latency effect, never a correctness one.
|
|
183
|
+
*/
|
|
184
|
+
public sync(): Promise<void> {
|
|
185
|
+
return this.runningPromise.trigger();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Runs a single pass over the underlying block stream. When triggered by an aggregate event and the stream is
|
|
190
|
+
* caught up to the event's pre-pass tips, the event's blocks and tips serve the pass directly; the fast path is
|
|
191
|
+
* always disarmed afterwards so a subsequent poll-driven pass reads from the source.
|
|
192
|
+
*/
|
|
193
|
+
private async runPass(event?: L2BlockSourceUpdatedEvent): Promise<void> {
|
|
194
|
+
if (event) {
|
|
195
|
+
const localTips = await this.localData.getL2Tips();
|
|
196
|
+
if (localTipsMatch(localTips, event.fromTips)) {
|
|
197
|
+
this.adapter.activate(event.blocksAdded, event.toTips);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
await this.blockStream.sync();
|
|
203
|
+
} finally {
|
|
204
|
+
this.adapter.deactivate();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
|
|
3
3
|
import type { L2Block } from '../l2_block.js';
|
|
4
|
-
import type { CheckpointId, L2BlockId, L2TipId, LocalL2Tips } from '../l2_block_source.js';
|
|
4
|
+
import type { CheckpointId, L2BlockId, L2TipId, L2Tips, LocalL2Tips } from '../l2_block_source.js';
|
|
5
5
|
|
|
6
6
|
/** Provides the current chain tips. Implemented by world-state, l2-tips-store, and AztecNode. */
|
|
7
7
|
export interface L2TipsProvider {
|
|
@@ -35,6 +35,40 @@ export interface L2BlockStreamLocalDataProvider {
|
|
|
35
35
|
getL2BlockHash(number: number): Promise<string | undefined>;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Returns whether a local block id differs from a source block id. Compares block number and, when the local hash is
|
|
40
|
+
* known, block hash. The hash comparison is skipped when the local hash is undefined: world-state legitimately
|
|
41
|
+
* reports `undefined` hashes for tips ahead of its synced range, and comparing against an undefined hash would treat
|
|
42
|
+
* such a tip as different on every poll. An `undefined` local block (no local tip yet) always counts as differing.
|
|
43
|
+
*/
|
|
44
|
+
export function localBlockIdDiffers(localBlock: LocalL2BlockId | undefined, sourceBlock: L2BlockId): boolean {
|
|
45
|
+
if (localBlock === undefined) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (sourceBlock.number !== localBlock.number) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (localBlock.hash === undefined) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return sourceBlock.hash !== localBlock.hash;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns whether the local chain tips agree with the given source tips on every tier the local provider exposes.
|
|
59
|
+
* Each tier is compared at the block level via {@link localBlockIdDiffers} (so an unresolved local hash matches on
|
|
60
|
+
* number alone); checkpoint ids are not compared, mirroring the stream's own tier reconciliation. The optional
|
|
61
|
+
* `checkpointed` tier is only compared when present (it is absent when the stream ignores checkpoints).
|
|
62
|
+
*/
|
|
63
|
+
export function localTipsMatch(local: LocalChainTips, source: L2Tips): boolean {
|
|
64
|
+
return (
|
|
65
|
+
!localBlockIdDiffers(local.proposed, source.proposed) &&
|
|
66
|
+
(local.checkpointed === undefined || !localBlockIdDiffers(local.checkpointed.block, source.checkpointed.block)) &&
|
|
67
|
+
!localBlockIdDiffers(local.proven.block, source.proven.block) &&
|
|
68
|
+
!localBlockIdDiffers(local.finalized.block, source.finalized.block)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
/** Interface to a handler of events emitted. */
|
|
39
73
|
export interface L2BlockStreamEventHandler {
|
|
40
74
|
handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
|
|
@@ -4,22 +4,34 @@ import { createLogger } from '@aztec/foundation/log';
|
|
|
4
4
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
5
5
|
|
|
6
6
|
import type { L2Block } from '../l2_block.js';
|
|
7
|
+
import { type L2BlockId, type L2BlockSource, type LocalL2Tips, makeL2BlockId } from '../l2_block_source.js';
|
|
7
8
|
import {
|
|
8
|
-
type
|
|
9
|
-
type
|
|
10
|
-
type
|
|
11
|
-
|
|
12
|
-
makeL2BlockId,
|
|
13
|
-
} from '../l2_block_source.js';
|
|
14
|
-
import type {
|
|
15
|
-
L2BlockStreamEvent,
|
|
16
|
-
L2BlockStreamEventHandler,
|
|
17
|
-
L2BlockStreamLocalDataProvider,
|
|
18
|
-
LocalL2BlockId,
|
|
9
|
+
type L2BlockStreamEvent,
|
|
10
|
+
type L2BlockStreamEventHandler,
|
|
11
|
+
type L2BlockStreamLocalDataProvider,
|
|
12
|
+
localBlockIdDiffers,
|
|
19
13
|
} from './interfaces.js';
|
|
20
14
|
|
|
21
15
|
/** Subset of the block source the stream depends on. Checkpoint payloads are no longer fetched here. */
|
|
22
|
-
type L2BlockStreamSource = Pick<L2BlockSource, 'getBlocks' | 'getBlockData' | 'getL2Tips'>;
|
|
16
|
+
export type L2BlockStreamSource = Pick<L2BlockSource, 'getBlocks' | 'getBlockData' | 'getL2Tips'>;
|
|
17
|
+
|
|
18
|
+
/** Options accepted by {@link L2BlockStream} and {@link EventDrivenL2BlockStream}. */
|
|
19
|
+
export type L2BlockStreamOptions = {
|
|
20
|
+
pollIntervalMS?: number;
|
|
21
|
+
batchSize?: number;
|
|
22
|
+
startingBlock?: number;
|
|
23
|
+
/** Instead of downloading all blocks, only fetch the smallest subset that results in reliable reorg detection. */
|
|
24
|
+
skipFinalized?: boolean;
|
|
25
|
+
/** When true, checkpoint events will not be emitted. Blocks are still fetched but only blocks-added events are emitted. */
|
|
26
|
+
ignoreCheckpoints?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* When true, the block download loop is skipped entirely: `getBlocks` is never called and `blocks-added` is
|
|
29
|
+
* never emitted. Only the tip events (`chain-proposed`/`chain-checkpointed`/`chain-proven`/`chain-finalized`)
|
|
30
|
+
* and `chain-pruned` are emitted, driven by the `getL2Tips` snapshot. For consumers that track tips but never
|
|
31
|
+
* consume block payloads.
|
|
32
|
+
*/
|
|
33
|
+
tipsOnly?: boolean;
|
|
34
|
+
};
|
|
23
35
|
|
|
24
36
|
/** Creates a stream of events for new blocks, chain tips updates, and reorgs, out of polling an archiver or a node. */
|
|
25
37
|
export class L2BlockStream {
|
|
@@ -32,22 +44,7 @@ export class L2BlockStream {
|
|
|
32
44
|
private localData: L2BlockStreamLocalDataProvider,
|
|
33
45
|
private handler: L2BlockStreamEventHandler,
|
|
34
46
|
private readonly log = createLogger('types:block_stream'),
|
|
35
|
-
private opts: {
|
|
36
|
-
pollIntervalMS?: number;
|
|
37
|
-
batchSize?: number;
|
|
38
|
-
startingBlock?: number;
|
|
39
|
-
/** Instead of downloading all blocks, only fetch the smallest subset that results in reliable reorg detection. */
|
|
40
|
-
skipFinalized?: boolean;
|
|
41
|
-
/** When true, checkpoint events will not be emitted. Blocks are still fetched but only blocks-added events are emitted. */
|
|
42
|
-
ignoreCheckpoints?: boolean;
|
|
43
|
-
/**
|
|
44
|
-
* When true, the block download loop is skipped entirely: `getBlocks` is never called and `blocks-added` is
|
|
45
|
-
* never emitted. Only the tip events (`chain-proposed`/`chain-checkpointed`/`chain-proven`/`chain-finalized`)
|
|
46
|
-
* and `chain-pruned` are emitted, driven by the `getL2Tips` snapshot. For consumers that track tips but never
|
|
47
|
-
* consume block payloads.
|
|
48
|
-
*/
|
|
49
|
-
tipsOnly?: boolean;
|
|
50
|
-
} = {},
|
|
47
|
+
private opts: L2BlockStreamOptions = {},
|
|
51
48
|
) {
|
|
52
49
|
if (opts.tipsOnly && (opts.startingBlock !== undefined || opts.batchSize !== undefined || opts.skipFinalized)) {
|
|
53
50
|
throw new Error(
|
|
@@ -181,19 +178,25 @@ export class L2BlockStream {
|
|
|
181
178
|
// End-of-pass reconciliation: chain-proposed fires against the pre-pass baseline (a post-prune re-read would
|
|
182
179
|
// equal the source tip and suppress it), then the tiers highest-to-lowest so the finalized <= proven <=
|
|
183
180
|
// checkpointed <= proposed invariant holds mid-pass.
|
|
184
|
-
if (
|
|
181
|
+
if (localBlockIdDiffers(prePassProposed, sourceTips.proposed)) {
|
|
185
182
|
await this.emitEvent({ type: 'chain-proposed', block: sourceTips.proposed });
|
|
186
183
|
}
|
|
187
184
|
|
|
188
185
|
const reconcileTips = pruned ? await this.localData.getL2Tips() : localTips;
|
|
189
|
-
if (
|
|
186
|
+
if (
|
|
187
|
+
!this.opts.ignoreCheckpoints &&
|
|
188
|
+
localBlockIdDiffers(reconcileTips.checkpointed?.block, sourceTips.checkpointed.block)
|
|
189
|
+
) {
|
|
190
190
|
await this.emitEvent({
|
|
191
191
|
type: 'chain-checkpointed',
|
|
192
192
|
block: sourceTips.checkpointed.block,
|
|
193
193
|
checkpoint: sourceTips.checkpointed.checkpoint,
|
|
194
194
|
});
|
|
195
195
|
}
|
|
196
|
-
if (
|
|
196
|
+
if (
|
|
197
|
+
reconcileTips.proven !== undefined &&
|
|
198
|
+
localBlockIdDiffers(reconcileTips.proven.block, sourceTips.proven.block)
|
|
199
|
+
) {
|
|
197
200
|
await this.emitEvent({
|
|
198
201
|
type: 'chain-proven',
|
|
199
202
|
block: sourceTips.proven.block,
|
|
@@ -202,7 +205,7 @@ export class L2BlockStream {
|
|
|
202
205
|
}
|
|
203
206
|
if (
|
|
204
207
|
reconcileTips.finalized !== undefined &&
|
|
205
|
-
|
|
208
|
+
localBlockIdDiffers(reconcileTips.finalized.block, sourceTips.finalized.block)
|
|
206
209
|
) {
|
|
207
210
|
await this.emitEvent({
|
|
208
211
|
type: 'chain-finalized',
|
|
@@ -286,36 +289,6 @@ export class L2BlockStream {
|
|
|
286
289
|
return true;
|
|
287
290
|
}
|
|
288
291
|
|
|
289
|
-
/**
|
|
290
|
-
* Returns whether the source tip differs from the local one and therefore warrants a tier event. Compares block
|
|
291
|
-
* number and, when both hashes are known, block hash. The hash comparison is skipped when the local hash is
|
|
292
|
-
* undefined or missing: world-state legitimately reports `undefined` hashes for tips ahead of its synced range,
|
|
293
|
-
* and comparing against an undefined hash would re-emit the event on every poll.
|
|
294
|
-
*/
|
|
295
|
-
private tipDiffers(localBlock: LocalL2BlockId | undefined, sourceTip: L2TipId): boolean {
|
|
296
|
-
return this.blockTipDiffers(localBlock, sourceTip.block);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Block-only variant of {@link tipDiffers} for the proposed tip (an {@link L2BlockId}, with no checkpoint). Compares
|
|
301
|
-
* block number and, when the local hash is known, block hash. The hash comparison is skipped when the local hash is
|
|
302
|
-
* undefined: world-state reports `undefined` for its proposed hash, and a strict comparison would re-emit
|
|
303
|
-
* `chain-proposed` on every poll for it. ({@link L2TipsStoreBase} consumers always carry a hash, so the leniency is
|
|
304
|
-
* inert for them.)
|
|
305
|
-
*/
|
|
306
|
-
private blockTipDiffers(localBlock: LocalL2BlockId | undefined, sourceBlock: L2BlockId): boolean {
|
|
307
|
-
if (localBlock === undefined) {
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
if (sourceBlock.number !== localBlock.number) {
|
|
311
|
-
return true;
|
|
312
|
-
}
|
|
313
|
-
if (localBlock.hash === undefined) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
return sourceBlock.hash !== localBlock.hash;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
292
|
/**
|
|
320
293
|
* Returns whether the source and local agree on the block hash at a given height.
|
|
321
294
|
* @param blockNumber - The block number to test.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ViemCommitteeAttestations } from '@aztec/ethereum/contracts';
|
|
2
|
-
import { hexToBuffer } from '@aztec/foundation/string';
|
|
2
|
+
import { bufferToHex, hexToBuffer } from '@aztec/foundation/string';
|
|
3
3
|
|
|
4
4
|
import { encodeAbiParameters, parseAbiParameters } from 'viem';
|
|
5
5
|
import { z } from 'zod';
|
|
@@ -76,8 +76,10 @@ export class CommitteeAttestationsAndSigners implements Signable {
|
|
|
76
76
|
let totalDataSize = 0;
|
|
77
77
|
for (const attestation of viemAttestations) {
|
|
78
78
|
const signature = attestation.signature;
|
|
79
|
-
//
|
|
80
|
-
|
|
79
|
+
// A slot is empty (a non-signing member, packed as its address) only when r, s and v are all zero
|
|
80
|
+
// — matching Signature.isEmpty() and getSigners(), so the bitmap popcount and the signers list can
|
|
81
|
+
// never disagree (which would revert propose() with SignersSizeMismatch).
|
|
82
|
+
const isEmpty = signature.v === 0 && BigInt(signature.r) === 0n && BigInt(signature.s) === 0n;
|
|
81
83
|
|
|
82
84
|
if (!isEmpty) {
|
|
83
85
|
totalDataSize += 65; // v (1) + r (32) + s (32)
|
|
@@ -93,8 +95,8 @@ export class CommitteeAttestationsAndSigners implements Signable {
|
|
|
93
95
|
for (const [i, attestation] of viemAttestations.entries()) {
|
|
94
96
|
const signature = attestation.signature;
|
|
95
97
|
|
|
96
|
-
//
|
|
97
|
-
const isEmpty = signature.v === 0;
|
|
98
|
+
// Empty iff r, s and v are all zero (see the size-tally loop above).
|
|
99
|
+
const isEmpty = signature.v === 0 && BigInt(signature.r) === 0n && BigInt(signature.s) === 0n;
|
|
98
100
|
|
|
99
101
|
if (!isEmpty) {
|
|
100
102
|
// Set bit in bitmap (bit 7-0 in each byte, left to right)
|
|
@@ -102,8 +104,10 @@ export class CommitteeAttestationsAndSigners implements Signable {
|
|
|
102
104
|
const bitIndex = 7 - (i % 8);
|
|
103
105
|
signatureIndices[byteIndex] = (signatureIndices[byteIndex] ?? 0) | (1 << bitIndex);
|
|
104
106
|
|
|
105
|
-
// Pack signature: v + r + s
|
|
106
|
-
|
|
107
|
+
// Pack signature: v + r + s. Canonicalize a yParity recovery byte (v = 0/1) to 27/28 — it
|
|
108
|
+
// recovers to the same signer, but L1 ECDSA.recover only accepts 27/28. Any other value is left
|
|
109
|
+
// as-is so a genuinely malformed signature still fails on L1 rather than being silently rewritten.
|
|
110
|
+
signaturesOrAddresses[dataIndex] = signature.v === 0 || signature.v === 1 ? signature.v + 27 : signature.v;
|
|
107
111
|
dataIndex++;
|
|
108
112
|
|
|
109
113
|
// Pack r (32 bytes)
|
|
@@ -153,3 +157,45 @@ export class MaliciousCommitteeAttestationsAndSigners extends CommitteeAttestati
|
|
|
153
157
|
return this.signers;
|
|
154
158
|
}
|
|
155
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Malicious extension of CommitteeAttestationsAndSigners that rewrites every non-proposer signature slot's
|
|
163
|
+
* recovery byte to yParity form (v ∈ {0, 1}) in the packed output, after the honest `packAttestations` has
|
|
164
|
+
* already canonicalized it to v ∈ {27, 28}. Models a malicious selected proposer that hand-crafts
|
|
165
|
+
* `propose()` calldata L1 accepts but no honest node can byte-replay: each rewritten signature still
|
|
166
|
+
* recovers to the same member (r, s and the recovery parity are preserved), the bitmap bits stay set, and
|
|
167
|
+
* `getSigners()` stays consistent, so `propose()` does not revert `SignersSizeMismatch` -- yet the
|
|
168
|
+
* checkpoint can never be proven (`ECDSA.recover` rejects v ∉ {27, 28}). The proposer's own slot is left
|
|
169
|
+
* canonical so L1 `verifyProposer` (which recovers that slot) still accepts the checkpoint. For testing only.
|
|
170
|
+
*/
|
|
171
|
+
export class MaliciousYParityCommitteeAttestationsAndSigners extends CommitteeAttestationsAndSigners {
|
|
172
|
+
constructor(
|
|
173
|
+
attestations: CommitteeAttestation[],
|
|
174
|
+
/** Committee index of the proposer's own slot, left canonical so `propose()` passes `verifyProposer`. */
|
|
175
|
+
private proposerIndex: number,
|
|
176
|
+
signatureContext: CoordinationSignatureContext,
|
|
177
|
+
) {
|
|
178
|
+
super(attestations, signatureContext);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
override getPackedAttestations(): ViemCommitteeAttestations {
|
|
182
|
+
const packed = super.getPackedAttestations();
|
|
183
|
+
const data = hexToBuffer(packed.signaturesOrAddresses);
|
|
184
|
+
|
|
185
|
+
// Walk the packed byte-vector and rewrite every non-proposer signed slot's v-byte to yParity form. A
|
|
186
|
+
// signed slot occupies 65 bytes (v, r, s); an empty slot occupies 20 bytes (address only).
|
|
187
|
+
let offset = 0;
|
|
188
|
+
for (let i = 0; i < this.attestations.length; i++) {
|
|
189
|
+
const isSigned = !this.attestations[i].signature.isEmpty();
|
|
190
|
+
if (isSigned && i !== this.proposerIndex) {
|
|
191
|
+
// `packAttestations` canonicalized v to 27/28; rewrite back to the equivalent yParity byte (0/1),
|
|
192
|
+
// preserving the recovery parity so the signature still recovers to the same member.
|
|
193
|
+
const v = data[offset];
|
|
194
|
+
data[offset] = v >= 27 ? v - 27 : v;
|
|
195
|
+
}
|
|
196
|
+
offset += isSigned ? 65 : 20;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { signatureIndices: packed.signatureIndices, signaturesOrAddresses: bufferToHex(data) };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { ViemCommitteeAttestations } from '@aztec/ethereum/contracts';
|
|
1
2
|
import { EpochNumber, EpochNumberSchema } from '@aztec/foundation/branded-types';
|
|
2
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
4
|
import { type ZodFor, schemas } from '@aztec/foundation/schemas';
|
|
4
5
|
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
6
|
+
import { bufferToHex, hexToBuffer } from '@aztec/foundation/string';
|
|
5
7
|
|
|
6
8
|
import { z } from 'zod';
|
|
7
9
|
|
|
@@ -30,6 +32,14 @@ export type ValidateCheckpointNegativeResult =
|
|
|
30
32
|
attestors: EthAddress[];
|
|
31
33
|
/** Committee attestations for this checkpoint as they were posted to L1 */
|
|
32
34
|
attestations: CommitteeAttestation[];
|
|
35
|
+
/**
|
|
36
|
+
* The exact packed `CommitteeAttestations` tuple as posted to L1 calldata, carried verbatim so the
|
|
37
|
+
* invalidation evidence is byte-faithful to the stored `attestationsHash`. A repack via
|
|
38
|
+
* `packAttestations` is not a round-trip inverse of `fromPacked`, so it would diverge from the hash
|
|
39
|
+
* and revert `invalidateBadAttestation`/`invalidateInsufficientAttestations`. Always populated on the
|
|
40
|
+
* calldata validation path, the only production source of invalidation evidence.
|
|
41
|
+
*/
|
|
42
|
+
verbatimAttestations: ViemCommitteeAttestations;
|
|
33
43
|
/** Reason for the checkpoint being invalid: not enough attestations were posted */
|
|
34
44
|
reason: 'insufficient-attestations';
|
|
35
45
|
}
|
|
@@ -47,6 +57,12 @@ export type ValidateCheckpointNegativeResult =
|
|
|
47
57
|
attestors: EthAddress[];
|
|
48
58
|
/** Committee attestations for this checkpoint as they were posted to L1 */
|
|
49
59
|
attestations: CommitteeAttestation[];
|
|
60
|
+
/**
|
|
61
|
+
* The exact packed `CommitteeAttestations` tuple as posted to L1 calldata, carried verbatim so the
|
|
62
|
+
* invalidation evidence is byte-faithful to the stored `attestationsHash`. See the same field on the
|
|
63
|
+
* insufficient-attestations variant for why a repack cannot be used.
|
|
64
|
+
*/
|
|
65
|
+
verbatimAttestations: ViemCommitteeAttestations;
|
|
50
66
|
/** Reason for the checkpoint being invalid: an invalid attestation was posted */
|
|
51
67
|
reason: 'invalid-attestation';
|
|
52
68
|
/** Index in the attestations array of the invalid attestation posted */
|
|
@@ -56,6 +72,12 @@ export type ValidateCheckpointNegativeResult =
|
|
|
56
72
|
/** Result type for validating checkpoint attestations */
|
|
57
73
|
export type ValidateCheckpointResult = { valid: true } | ValidateCheckpointNegativeResult;
|
|
58
74
|
|
|
75
|
+
/** Zod schema for the raw packed `CommitteeAttestations` viem tuple (two 0x-prefixed hex strings). */
|
|
76
|
+
const ViemCommitteeAttestationsSchema: ZodFor<ViemCommitteeAttestations> = z.object({
|
|
77
|
+
signatureIndices: schemas.HexStringWith0x,
|
|
78
|
+
signaturesOrAddresses: schemas.HexStringWith0x,
|
|
79
|
+
});
|
|
80
|
+
|
|
59
81
|
export const ValidateCheckpointResultSchema: ZodFor<ValidateCheckpointResult> = z.union([
|
|
60
82
|
z.object({ valid: z.literal(true) }),
|
|
61
83
|
z.object({
|
|
@@ -66,6 +88,7 @@ export const ValidateCheckpointResultSchema: ZodFor<ValidateCheckpointResult> =
|
|
|
66
88
|
seed: schemas.BigInt,
|
|
67
89
|
attestors: z.array(schemas.EthAddress),
|
|
68
90
|
attestations: z.array(CommitteeAttestation.schema),
|
|
91
|
+
verbatimAttestations: ViemCommitteeAttestationsSchema,
|
|
69
92
|
reason: z.literal('insufficient-attestations'),
|
|
70
93
|
}),
|
|
71
94
|
z.object({
|
|
@@ -76,6 +99,7 @@ export const ValidateCheckpointResultSchema: ZodFor<ValidateCheckpointResult> =
|
|
|
76
99
|
seed: schemas.BigInt,
|
|
77
100
|
attestors: z.array(schemas.EthAddress),
|
|
78
101
|
attestations: z.array(CommitteeAttestation.schema),
|
|
102
|
+
verbatimAttestations: ViemCommitteeAttestationsSchema,
|
|
79
103
|
reason: z.literal('invalid-attestation'),
|
|
80
104
|
invalidIndex: z.number(),
|
|
81
105
|
}),
|
|
@@ -87,6 +111,8 @@ export function serializeValidateCheckpointResult(result: ValidateCheckpointResu
|
|
|
87
111
|
}
|
|
88
112
|
|
|
89
113
|
const checkpointBuffer = serializeCheckpointInfo(result.checkpoint);
|
|
114
|
+
const signatureIndices = hexToBuffer(result.verbatimAttestations.signatureIndices);
|
|
115
|
+
const signaturesOrAddresses = hexToBuffer(result.verbatimAttestations.signaturesOrAddresses);
|
|
90
116
|
return serializeToBuffer(
|
|
91
117
|
result.valid,
|
|
92
118
|
result.reason,
|
|
@@ -101,6 +127,10 @@ export function serializeValidateCheckpointResult(result: ValidateCheckpointResu
|
|
|
101
127
|
result.attestations.length,
|
|
102
128
|
result.attestations,
|
|
103
129
|
result.reason === 'invalid-attestation' ? result.invalidIndex : 0,
|
|
130
|
+
signatureIndices.length,
|
|
131
|
+
signatureIndices,
|
|
132
|
+
signaturesOrAddresses.length,
|
|
133
|
+
signaturesOrAddresses,
|
|
104
134
|
);
|
|
105
135
|
}
|
|
106
136
|
|
|
@@ -118,10 +148,26 @@ export function deserializeValidateCheckpointResult(bufferOrReader: Buffer | Buf
|
|
|
118
148
|
const attestors = reader.readVector(EthAddress, MAX_COMMITTEE_SIZE);
|
|
119
149
|
const attestations = reader.readVector(CommitteeAttestation, MAX_COMMITTEE_SIZE);
|
|
120
150
|
const invalidIndex = reader.readNumber();
|
|
151
|
+
const verbatimAttestations: ViemCommitteeAttestations = {
|
|
152
|
+
signatureIndices: bufferToHex(reader.readBuffer()),
|
|
153
|
+
signaturesOrAddresses: bufferToHex(reader.readBuffer()),
|
|
154
|
+
};
|
|
155
|
+
|
|
121
156
|
if (reason === 'insufficient-attestations') {
|
|
122
|
-
return { valid, reason, checkpoint, committee, epoch, seed, attestors, attestations };
|
|
157
|
+
return { valid, reason, checkpoint, committee, epoch, seed, attestors, attestations, verbatimAttestations };
|
|
123
158
|
} else if (reason === 'invalid-attestation') {
|
|
124
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
valid,
|
|
161
|
+
reason,
|
|
162
|
+
checkpoint,
|
|
163
|
+
committee,
|
|
164
|
+
epoch,
|
|
165
|
+
seed,
|
|
166
|
+
attestors,
|
|
167
|
+
invalidIndex,
|
|
168
|
+
attestations,
|
|
169
|
+
verbatimAttestations,
|
|
170
|
+
};
|
|
125
171
|
} else {
|
|
126
172
|
const _: never = reason;
|
|
127
173
|
throw new Error(`Unknown reason: ${reason}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type L1ContractsConfig, l1ContractsConfigMappings } from '@aztec/ethereum/config';
|
|
1
|
+
import { type L1ContractsConfig, l1ContractsConfigMappings, validateSlotDurations } from '@aztec/ethereum/config';
|
|
2
2
|
import { type EnvVar, pickConfigMappings } from '@aztec/foundation/config';
|
|
3
3
|
|
|
4
4
|
import type { SequencerConfig } from '../interfaces/configs.js';
|
|
@@ -156,21 +156,10 @@ export function validateNetworkConsensusConfig(config: NetworkConsensusConfig):
|
|
|
156
156
|
return errors;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
errors.push(`ethereumSlotDuration must be positive (got ${config.ethereumSlotDuration})`);
|
|
161
|
-
}
|
|
159
|
+
errors.push(...validateSlotDurations(config));
|
|
162
160
|
if (config.blockDurationMs <= 0) {
|
|
163
161
|
errors.push(`blockDurationMs must be positive (got ${config.blockDurationMs})`);
|
|
164
162
|
}
|
|
165
|
-
if (config.aztecSlotDuration <= 0) {
|
|
166
|
-
errors.push(`aztecSlotDuration must be positive (got ${config.aztecSlotDuration})`);
|
|
167
|
-
}
|
|
168
|
-
if (config.ethereumSlotDuration > 0 && config.aztecSlotDuration % config.ethereumSlotDuration !== 0) {
|
|
169
|
-
errors.push(
|
|
170
|
-
`aztecSlotDuration (${config.aztecSlotDuration}s) must be a multiple of ethereumSlotDuration ` +
|
|
171
|
-
`(${config.ethereumSlotDuration}s)`,
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
163
|
if (config.blockDurationMs / 1000 > config.aztecSlotDuration) {
|
|
175
164
|
errors.push(
|
|
176
165
|
`blockDurationMs (${config.blockDurationMs}ms) exceeds aztecSlotDuration (${config.aztecSlotDuration}s)`,
|
|
@@ -6,7 +6,8 @@ import { type FunctionAbi, FunctionSelector, encodeArguments } from '../abi/inde
|
|
|
6
6
|
import type { AztecAddress } from '../aztec-address/index.js';
|
|
7
7
|
import { computeVarArgsHash } from '../hash/hash.js';
|
|
8
8
|
import { computeAddress } from '../keys/index.js';
|
|
9
|
-
import type { ContractInstance } from './interfaces/contract_instance.js';
|
|
9
|
+
import type { ContractInstance, ContractInstancePreimage } from './interfaces/contract_instance.js';
|
|
10
|
+
import type { PartialAddress } from './partial_address.js';
|
|
10
11
|
|
|
11
12
|
// TODO(@spalladino): Review all generator indices in this file
|
|
12
13
|
|
|
@@ -21,7 +22,7 @@ import type { ContractInstance } from './interfaces/contract_instance.js';
|
|
|
21
22
|
*/
|
|
22
23
|
export async function computeContractAddressFromInstance(
|
|
23
24
|
instance:
|
|
24
|
-
|
|
|
25
|
+
| ContractInstancePreimage
|
|
25
26
|
| ({ originalContractClassId: Fr; saltedInitializationHash: Fr } & Pick<ContractInstance, 'publicKeys'>),
|
|
26
27
|
): Promise<AztecAddress> {
|
|
27
28
|
const partialAddress = await computePartialAddress(instance);
|
|
@@ -36,7 +37,7 @@ export async function computePartialAddress(
|
|
|
36
37
|
instance:
|
|
37
38
|
| Pick<ContractInstance, 'originalContractClassId' | 'initializationHash' | 'salt' | 'deployer' | 'immutablesHash'>
|
|
38
39
|
| { originalContractClassId: Fr; saltedInitializationHash: Fr },
|
|
39
|
-
): Promise<
|
|
40
|
+
): Promise<PartialAddress> {
|
|
40
41
|
const saltedInitializationHash =
|
|
41
42
|
'saltedInitializationHash' in instance
|
|
42
43
|
? instance.saltedInitializationHash
|