@aztec/sequencer-client 0.0.1-commit.993d240 → 0.0.1-commit.9a89641
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/README.md +40 -41
- package/dest/client/sequencer-client.d.ts +16 -3
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +18 -14
- package/dest/config.d.ts +9 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +29 -15
- package/dest/global_variable_builder/fee_predictor.d.ts +1 -1
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -1
- package/dest/global_variable_builder/fee_predictor.js +11 -1
- package/dest/global_variable_builder/fee_provider.d.ts +1 -1
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -1
- package/dest/global_variable_builder/fee_provider.js +27 -5
- package/dest/global_variable_builder/global_builder.d.ts +3 -16
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +2 -25
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/publisher/config.d.ts +5 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +11 -1
- package/dest/publisher/l1_to_l2_messaging.d.ts +21 -0
- package/dest/publisher/l1_to_l2_messaging.d.ts.map +1 -0
- package/dest/publisher/l1_to_l2_messaging.js +70 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +48 -8
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +68 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +2 -2
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +4 -2
- package/dest/publisher/l1_tx_failed_store/index.d.ts +2 -2
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/index.js +1 -0
- package/dest/publisher/sequencer-publisher.d.ts +39 -10
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +403 -75
- package/dest/publisher/write_json.d.ts +11 -0
- package/dest/publisher/write_json.d.ts.map +1 -0
- package/dest/publisher/write_json.js +57 -0
- package/dest/sequencer/automine/automine_factory.d.ts +5 -3
- package/dest/sequencer/automine/automine_factory.d.ts.map +1 -1
- package/dest/sequencer/automine/automine_factory.js +2 -1
- package/dest/sequencer/automine/automine_sequencer.d.ts +43 -5
- package/dest/sequencer/automine/automine_sequencer.d.ts.map +1 -1
- package/dest/sequencer/automine/automine_sequencer.js +209 -19
- package/dest/sequencer/automine/index.d.ts +3 -0
- package/dest/sequencer/automine/index.d.ts.map +1 -0
- package/dest/sequencer/automine/index.js +2 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +28 -15
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +158 -117
- package/dest/sequencer/events.d.ts +16 -4
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/index.d.ts +1 -3
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -2
- package/dest/sequencer/missing_committee.d.ts +72 -0
- package/dest/sequencer/missing_committee.d.ts.map +1 -0
- package/dest/sequencer/missing_committee.js +139 -0
- package/dest/sequencer/requests_tracker.d.ts +22 -0
- package/dest/sequencer/requests_tracker.d.ts.map +1 -0
- package/dest/sequencer/requests_tracker.js +33 -0
- package/dest/sequencer/sequencer.d.ts +113 -28
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +352 -162
- package/dest/test/index.d.ts +3 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/utils.d.ts +15 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +18 -1
- package/package.json +29 -28
- package/src/client/sequencer-client.ts +20 -14
- package/src/config.ts +32 -16
- package/src/global_variable_builder/fee_predictor.ts +11 -1
- package/src/global_variable_builder/fee_provider.ts +27 -5
- package/src/global_variable_builder/global_builder.ts +2 -34
- package/src/index.ts +1 -10
- package/src/publisher/config.ts +22 -1
- package/src/publisher/l1_to_l2_messaging.ts +85 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +114 -7
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +4 -3
- package/src/publisher/l1_tx_failed_store/index.ts +1 -1
- package/src/publisher/sequencer-publisher.ts +430 -86
- package/src/publisher/write_json.ts +78 -0
- package/src/sequencer/README.md +16 -6
- package/src/sequencer/automine/README.md +18 -4
- package/src/sequencer/automine/automine_factory.ts +8 -1
- package/src/sequencer/automine/automine_sequencer.ts +229 -21
- package/src/sequencer/automine/index.ts +6 -0
- package/src/sequencer/checkpoint_proposal_job.ts +177 -132
- package/src/sequencer/events.ts +16 -3
- package/src/sequencer/index.ts +0 -2
- package/src/sequencer/missing_committee.ts +192 -0
- package/src/sequencer/requests_tracker.ts +43 -0
- package/src/sequencer/sequencer.ts +382 -176
- package/src/test/index.ts +2 -2
- package/src/test/utils.ts +33 -0
- package/dest/sequencer/chain_state_overrides.d.ts +0 -61
- package/dest/sequencer/chain_state_overrides.d.ts.map +0 -1
- package/dest/sequencer/chain_state_overrides.js +0 -98
- package/dest/sequencer/timetable.d.ts +0 -101
- package/dest/sequencer/timetable.d.ts.map +0 -1
- package/dest/sequencer/timetable.js +0 -231
- package/src/sequencer/chain_state_overrides.ts +0 -162
- package/src/sequencer/timetable.ts +0 -295
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type EpochCache, PROPOSER_PIPELINING_SLOT_OFFSET } from '@aztec/epoch-cache';
|
|
2
2
|
import type { SimulationOverridesPlan } from '@aztec/ethereum/contracts';
|
|
3
3
|
import {
|
|
4
4
|
BlockNumber,
|
|
@@ -14,12 +14,12 @@ import {
|
|
|
14
14
|
generateUnrecoverableSignature,
|
|
15
15
|
} from '@aztec/foundation/crypto/secp256k1-signer';
|
|
16
16
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
17
|
+
import { InterruptError, TimeoutError } from '@aztec/foundation/error';
|
|
17
18
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
18
19
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
19
20
|
import { filter } from '@aztec/foundation/iterator';
|
|
20
21
|
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
21
|
-
import {
|
|
22
|
-
import { sleep, sleepUntil } from '@aztec/foundation/sleep';
|
|
22
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
23
23
|
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
24
24
|
import { type TypedEventEmitter, isErrorClass, unfreeze } from '@aztec/foundation/types';
|
|
25
25
|
import type { P2P } from '@aztec/p2p';
|
|
@@ -31,16 +31,18 @@ import {
|
|
|
31
31
|
type L2BlockSink,
|
|
32
32
|
type L2BlockSource,
|
|
33
33
|
MaliciousCommitteeAttestationsAndSigners,
|
|
34
|
+
MaliciousYParityCommitteeAttestationsAndSigners,
|
|
34
35
|
type ProposedCheckpointSink,
|
|
35
36
|
type ValidateCheckpointResult,
|
|
36
37
|
} from '@aztec/stdlib/block';
|
|
37
38
|
import {
|
|
38
39
|
type Checkpoint,
|
|
39
40
|
type ProposedCheckpointData,
|
|
41
|
+
buildCheckpointSimulationOverridesPlan,
|
|
40
42
|
getPreviousCheckpointOutHashes,
|
|
41
43
|
validateCheckpoint,
|
|
42
44
|
} from '@aztec/stdlib/checkpoint';
|
|
43
|
-
import { computeQuorum,
|
|
45
|
+
import { computeQuorum, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
44
46
|
import { Gas } from '@aztec/stdlib/gas';
|
|
45
47
|
import {
|
|
46
48
|
type BlockBuilderOptions,
|
|
@@ -59,6 +61,7 @@ import type {
|
|
|
59
61
|
} from '@aztec/stdlib/p2p';
|
|
60
62
|
import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
|
|
61
63
|
import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
|
|
64
|
+
import type { ProposerTimetable } from '@aztec/stdlib/timetable';
|
|
62
65
|
import { type FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
63
66
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
64
67
|
import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
@@ -67,18 +70,18 @@ import { DutyAlreadySignedError, SlashingProtectionError } from '@aztec/validato
|
|
|
67
70
|
|
|
68
71
|
import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
69
72
|
import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
70
|
-
import { buildCheckpointSimulationOverridesPlan } from './chain_state_overrides.js';
|
|
71
73
|
import type { CheckpointProposalJobMetricsRecorder } from './checkpoint_proposal_job_metrics.js';
|
|
72
74
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
73
75
|
import { SequencerInterruptedError } from './errors.js';
|
|
74
76
|
import type { SequencerEvents } from './events.js';
|
|
75
77
|
import type { SequencerMetrics } from './metrics.js';
|
|
76
|
-
import type {
|
|
78
|
+
import type { RequestsTracker } from './requests_tracker.js';
|
|
77
79
|
import type { SequencerRollupConstants } from './types.js';
|
|
78
80
|
import { SequencerState } from './utils.js';
|
|
79
81
|
|
|
80
82
|
/** How much time to sleep while waiting for min transactions to accumulate for a block */
|
|
81
83
|
const TXS_POLLING_MS = 500;
|
|
84
|
+
const ARCHIVER_SYNC_POLLING_MS = 200;
|
|
82
85
|
|
|
83
86
|
/** Result from proposeCheckpoint when a checkpoint was successfully built and broadcast. */
|
|
84
87
|
type CheckpointProposalBroadcast = {
|
|
@@ -104,8 +107,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
104
107
|
protected readonly log: Logger;
|
|
105
108
|
private readonly checkpointEventLog: Logger;
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
private
|
|
110
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
111
|
+
private interrupted = false;
|
|
109
112
|
|
|
110
113
|
/**
|
|
111
114
|
* Chain state overrides built once per slot in proposeCheckpoint after the checkpoint is
|
|
@@ -120,7 +123,6 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
constructor(
|
|
123
|
-
private readonly slotNow: SlotNumber,
|
|
124
126
|
private readonly targetSlot: SlotNumber,
|
|
125
127
|
private readonly targetEpoch: EpochNumber,
|
|
126
128
|
private readonly checkpointNumber: CheckpointNumber,
|
|
@@ -142,32 +144,70 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
142
144
|
private readonly l1Constants: SequencerRollupConstants,
|
|
143
145
|
private readonly signatureContext: CoordinationSignatureContext,
|
|
144
146
|
protected config: ResolvedSequencerConfig,
|
|
145
|
-
protected timetable:
|
|
147
|
+
protected timetable: ProposerTimetable,
|
|
146
148
|
private readonly slasherClient: SlasherClientInterface | undefined,
|
|
147
149
|
private readonly epochCache: EpochCache,
|
|
148
150
|
private readonly dateProvider: DateProvider,
|
|
149
151
|
private readonly metrics: SequencerMetrics,
|
|
150
152
|
private readonly checkpointMetrics: CheckpointProposalJobMetricsRecorder,
|
|
151
153
|
protected readonly eventEmitter: TypedEventEmitter<SequencerEvents>,
|
|
152
|
-
|
|
154
|
+
// Shared with the owning sequencer, which drains it during shutdown; the fire-and-forget L1
|
|
155
|
+
// submission this job backgrounds is tracked here rather than in a job-local tracker.
|
|
156
|
+
protected readonly pendingRequests: RequestsTracker,
|
|
157
|
+
private readonly setStateFn: (state: SequencerState, slot: SlotNumber) => void,
|
|
153
158
|
public readonly tracer: Tracer,
|
|
154
159
|
bindings?: LoggerBindings,
|
|
155
160
|
private readonly proposedCheckpointData?: ProposedCheckpointData,
|
|
156
161
|
) {
|
|
157
162
|
this.log = createLogger('sequencer:checkpoint-proposal', {
|
|
158
163
|
...bindings,
|
|
159
|
-
instanceId: `slot-${this.
|
|
164
|
+
instanceId: `slot-${this.getBuildSlot()}`,
|
|
160
165
|
});
|
|
161
166
|
this.checkpointEventLog = createLogger('sequencer:checkpoint-events', {
|
|
162
167
|
...bindings,
|
|
163
|
-
instanceId: `slot-${this.
|
|
168
|
+
instanceId: `slot-${this.getBuildSlot()}`,
|
|
164
169
|
});
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
/**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
/**
|
|
173
|
+
* The wall-clock slot during which this job builds, i.e. the slot one before {@link targetSlot} under
|
|
174
|
+
* proposer pipelining. Also the slot of the parent checkpoint this job builds on top of.
|
|
175
|
+
*/
|
|
176
|
+
private getBuildSlot(): SlotNumber {
|
|
177
|
+
return SlotNumber(this.targetSlot - PROPOSER_PIPELINING_SLOT_OFFSET);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Sets the sequencer state for this job, reporting the target slot the checkpoint is being proposed for
|
|
182
|
+
* (not the wall-clock build slot). The slot is informational on the event payload/metrics; the job knows
|
|
183
|
+
* its own target slot, so callers only pass the state.
|
|
184
|
+
*/
|
|
185
|
+
private setState(state: SequencerState): void {
|
|
186
|
+
this.setStateFn(state, this.targetSlot);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Interrupts job-owned waits, including the publisher's send-at-slot sleep, so shutdown can finish. */
|
|
190
|
+
public interrupt(): void {
|
|
191
|
+
this.interrupted = true;
|
|
192
|
+
this.interruptibleSleep.interrupt(true);
|
|
193
|
+
this.publisher.interrupt();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private async awaitInterruptibleSleep(ms: number): Promise<void> {
|
|
197
|
+
if (this.interrupted) {
|
|
198
|
+
throw new SequencerInterruptedError();
|
|
199
|
+
}
|
|
200
|
+
if (ms <= 0) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
await this.interruptibleSleep.sleep(ms);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
if (err instanceof InterruptError) {
|
|
207
|
+
throw new SequencerInterruptedError();
|
|
208
|
+
}
|
|
209
|
+
throw err;
|
|
210
|
+
}
|
|
171
211
|
}
|
|
172
212
|
|
|
173
213
|
private logCheckpointEvent(eventName: string, message: string, fields: Record<string, unknown>): void {
|
|
@@ -214,7 +254,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
214
254
|
// signature verification to fail silently inside Multicall3. Delay submission to the
|
|
215
255
|
// start of `targetSlot` so the tx mines in the slot the vote was signed for.
|
|
216
256
|
if (!this.config.fishermanMode) {
|
|
217
|
-
this.
|
|
257
|
+
this.pendingRequests.trackRequest(this.publisher.sendRequestsAt(this.targetSlot), () => this.interrupt());
|
|
218
258
|
}
|
|
219
259
|
return undefined;
|
|
220
260
|
}
|
|
@@ -229,7 +269,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
229
269
|
}
|
|
230
270
|
|
|
231
271
|
// Background the attestation → signing → L1 pipeline so the work loop is unblocked
|
|
232
|
-
this.
|
|
272
|
+
this.pendingRequests.trackRequest(this.waitForAttestationsAndEnqueueSubmissionAsync(broadcast, votesPromises), () =>
|
|
273
|
+
this.interrupt(),
|
|
274
|
+
);
|
|
233
275
|
|
|
234
276
|
// Return the built checkpoint immediately — the work loop is now unblocked
|
|
235
277
|
return checkpoint;
|
|
@@ -237,14 +279,13 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
237
279
|
|
|
238
280
|
/**
|
|
239
281
|
* Background pipeline: collects attestations, signs them, enqueues the checkpoint, and submits to L1.
|
|
240
|
-
* Runs as a fire-and-forget task
|
|
282
|
+
* Runs as a fire-and-forget task tracked in the sequencer's shared tracker so the work loop is unblocked.
|
|
241
283
|
*/
|
|
242
284
|
private async waitForAttestationsAndEnqueueSubmissionAsync(
|
|
243
285
|
broadcast: CheckpointProposalBroadcast,
|
|
244
286
|
votesPromises: Promise<unknown>[],
|
|
245
287
|
): Promise<void> {
|
|
246
288
|
const { checkpoint } = broadcast;
|
|
247
|
-
const isPipelining = this.epochCache.isProposerPipeliningEnabled();
|
|
248
289
|
|
|
249
290
|
try {
|
|
250
291
|
// Wait for all votes actions, enqueued at the beginning, to resolve
|
|
@@ -253,15 +294,14 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
253
294
|
// Try to collect attestations from the committee
|
|
254
295
|
const signedAttestations = await this.getSignedCommitteeAttestations(broadcast);
|
|
255
296
|
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
if (signedAttestations && (
|
|
297
|
+
// Wait for the previous checkpoint to land on L1 before submitting, so we can check it
|
|
298
|
+
// matches the proposed checkpoint we used as parent, and has valid attestations.
|
|
299
|
+
if (signedAttestations && (await this.waitForValidParentCheckpointOnL1())) {
|
|
259
300
|
await this.enqueueCheckpointForSubmission({ checkpoint, ...signedAttestations });
|
|
260
301
|
}
|
|
261
302
|
|
|
262
303
|
// If we failed to collect attestations, at least check if we need to issue an invalidation
|
|
263
|
-
|
|
264
|
-
if (!signedAttestations && isPipelining && (await this.waitForSyncedL2SlotNumber(this.slotNow))) {
|
|
304
|
+
if (!signedAttestations && (await this.waitForSyncedL2SlotNumber(this.getBuildSlot()))) {
|
|
265
305
|
const validationStatus = await this.l2BlockSource.getPendingChainValidationStatus();
|
|
266
306
|
if (!validationStatus.valid) {
|
|
267
307
|
this.log.warn(
|
|
@@ -305,9 +345,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
305
345
|
reason: 'propose_action_not_successful',
|
|
306
346
|
});
|
|
307
347
|
this.eventEmitter.emit('checkpoint-publish-failed', { ...l1Response, slot: this.targetSlot });
|
|
308
|
-
|
|
309
|
-
this.metrics.recordPipelineDiscard();
|
|
310
|
-
}
|
|
348
|
+
this.metrics.recordPipelineDiscard();
|
|
311
349
|
}
|
|
312
350
|
} catch (err) {
|
|
313
351
|
if (err instanceof SequencerInterruptedError) {
|
|
@@ -324,9 +362,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
324
362
|
reason: err instanceof Error ? err.message : String(err),
|
|
325
363
|
});
|
|
326
364
|
this.eventEmitter.emit('checkpoint-publish-failed', { slot: this.targetSlot });
|
|
327
|
-
|
|
328
|
-
this.metrics.recordPipelineDiscard();
|
|
329
|
-
}
|
|
365
|
+
this.metrics.recordPipelineDiscard();
|
|
330
366
|
}
|
|
331
367
|
}
|
|
332
368
|
|
|
@@ -334,10 +370,18 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
334
370
|
private async enqueueCheckpointForSubmission(result: CheckpointProposalResult): Promise<void> {
|
|
335
371
|
const { checkpoint, attestations, attestationsSignature } = result;
|
|
336
372
|
|
|
337
|
-
this.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
373
|
+
this.setState(SequencerState.PUBLISHING_CHECKPOINT);
|
|
374
|
+
// Latest L1 block the propose can still land in for the target slot: the last Ethereum block inside
|
|
375
|
+
// the target slot (`target_slot_start + S - E`). This is one ethereum slot later than
|
|
376
|
+
// `attestation_deadline` (= last_ethereum_block_in_target_slot - E), which bounds when validators must
|
|
377
|
+
// have signed, not when the proposer must have sent. Using the attestation deadline here is too tight:
|
|
378
|
+
// attestations are collected up to (and, when not enforcing, past) it, so the propose tx would be
|
|
379
|
+
// enqueued already expired and time out before it can mine.
|
|
380
|
+
const lastL1BlockInTargetSlot =
|
|
381
|
+
Number(getTimestampForSlot(this.targetSlot, this.l1Constants)) +
|
|
382
|
+
this.l1Constants.slotDuration -
|
|
383
|
+
this.l1Constants.ethereumSlotDuration;
|
|
384
|
+
const txTimeoutAt = new Date(lastL1BlockInTargetSlot * 1000);
|
|
341
385
|
|
|
342
386
|
// If we have been configured to potentially skip publishing checkpoint then roll the dice here
|
|
343
387
|
if (
|
|
@@ -353,9 +397,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
353
397
|
}
|
|
354
398
|
}
|
|
355
399
|
|
|
356
|
-
await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
|
|
357
|
-
txTimeoutAt,
|
|
358
|
-
});
|
|
400
|
+
await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, { txTimeoutAt });
|
|
359
401
|
}
|
|
360
402
|
|
|
361
403
|
/**
|
|
@@ -370,16 +412,21 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
370
412
|
const timeoutSeconds = Math.max(0.1, (targetSlotEndMs + syncDelayTolerance - this.dateProvider.now()) / 1000);
|
|
371
413
|
|
|
372
414
|
try {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
timeoutSeconds
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
415
|
+
const timer = new Timer();
|
|
416
|
+
while (true) {
|
|
417
|
+
const syncedSlot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
418
|
+
if (syncedSlot !== undefined && syncedSlot >= waitForSlot) {
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
if (timeoutSeconds && timer.s() > timeoutSeconds) {
|
|
422
|
+
throw new TimeoutError(`Timeout awaiting archiver sync past slot ${waitForSlot}`);
|
|
423
|
+
}
|
|
424
|
+
await this.awaitInterruptibleSleep(ARCHIVER_SYNC_POLLING_MS);
|
|
425
|
+
}
|
|
426
|
+
} catch (err) {
|
|
427
|
+
if (err instanceof SequencerInterruptedError) {
|
|
428
|
+
throw err;
|
|
429
|
+
}
|
|
383
430
|
this.log.warn(
|
|
384
431
|
`Archiver did not sync L1 past slot ${waitForSlot} before slot ${this.targetSlot} expired, discarding pipelined work`,
|
|
385
432
|
{ checkpointNumber: this.checkpointNumber },
|
|
@@ -406,8 +453,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
406
453
|
|
|
407
454
|
const parentCheckpointNumber = CheckpointNumber(this.checkpointNumber - 1);
|
|
408
455
|
|
|
409
|
-
// Wait until archiver has synced L1 past the parent's slot (
|
|
410
|
-
if (!(await this.waitForSyncedL2SlotNumber(this.
|
|
456
|
+
// Wait until archiver has synced L1 past the parent's slot (the build slot, one before targetSlot)
|
|
457
|
+
if (!(await this.waitForSyncedL2SlotNumber(this.getBuildSlot()))) {
|
|
411
458
|
return false;
|
|
412
459
|
}
|
|
413
460
|
|
|
@@ -510,11 +557,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
510
557
|
private async proposeCheckpoint(): Promise<CheckpointProposalBroadcast | undefined> {
|
|
511
558
|
try {
|
|
512
559
|
const now = this.dateProvider.now();
|
|
513
|
-
if (this.
|
|
560
|
+
if (this.proposedCheckpointData) {
|
|
514
561
|
// Measure against the wall-clock slot whose build window we are currently using.
|
|
515
562
|
// In pipelining mode `targetSlot` is intentionally one slot ahead, which makes the
|
|
516
563
|
// target-slot boundary a full slot away from the actual build start time.
|
|
517
|
-
const slotBoundaryMs = Number(getTimestampForSlot(this.
|
|
564
|
+
const slotBoundaryMs = Number(getTimestampForSlot(this.getBuildSlot(), this.l1Constants)) * 1000;
|
|
518
565
|
this.checkpointMetrics.recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(now - slotBoundaryMs);
|
|
519
566
|
}
|
|
520
567
|
this.checkpointMetrics.startCheckpointTiming(now);
|
|
@@ -524,13 +571,12 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
524
571
|
const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(this.attestorAddress);
|
|
525
572
|
|
|
526
573
|
// Start the checkpoint
|
|
527
|
-
this.
|
|
574
|
+
this.setState(SequencerState.INITIALIZING_CHECKPOINT);
|
|
528
575
|
this.logCheckpointEvent('slot-started', `Starting checkpoint proposal for slot ${this.targetSlot}`, {
|
|
529
|
-
buildSlot: this.
|
|
576
|
+
buildSlot: this.getBuildSlot(),
|
|
530
577
|
submissionSlot: this.targetSlot,
|
|
531
578
|
slot: this.targetSlot,
|
|
532
579
|
checkpointNumber: this.checkpointNumber,
|
|
533
|
-
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
534
580
|
proposer: this.proposer?.toString(),
|
|
535
581
|
attestorAddress: this.attestorAddress.toString(),
|
|
536
582
|
publisherAddress: this.publisher.getSenderAddress().toString(),
|
|
@@ -547,10 +593,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
547
593
|
// pending/archive/fee-header to "as if the proposed parent had landed", so both the
|
|
548
594
|
// mana-min-fee simulation (in the globals builder) and the pre-broadcast
|
|
549
595
|
// validateBlockHeader see the chain tip the eventual L1 send will see.
|
|
550
|
-
const isPipelining = this.epochCache.isProposerPipeliningEnabled();
|
|
551
596
|
this.checkpointSimulationOverridesPlan = await buildCheckpointSimulationOverridesPlan({
|
|
552
597
|
checkpointNumber: this.checkpointNumber,
|
|
553
|
-
proposedCheckpointData:
|
|
598
|
+
proposedCheckpointData: this.proposedCheckpointData,
|
|
554
599
|
invalidateToPendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
|
|
555
600
|
checkpointedCheckpointNumber: this.checkpointedCheckpointNumber,
|
|
556
601
|
rollup: this.publisher.rollupContract,
|
|
@@ -579,7 +624,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
579
624
|
epoch: this.targetEpoch,
|
|
580
625
|
checkpointNumber: this.checkpointNumber,
|
|
581
626
|
l1Constants: this.epochCache.getL1Constants(),
|
|
582
|
-
pipeliningEnabled:
|
|
627
|
+
pipeliningEnabled: true,
|
|
583
628
|
proposedCheckpointData: this.proposedCheckpointData,
|
|
584
629
|
log: this.log,
|
|
585
630
|
});
|
|
@@ -679,7 +724,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
679
724
|
|
|
680
725
|
// Assemble and broadcast the checkpoint proposal, including the last block that was not
|
|
681
726
|
// broadcasted yet, and wait to collect the committee attestations.
|
|
682
|
-
this.
|
|
727
|
+
this.setState(SequencerState.ASSEMBLING_CHECKPOINT);
|
|
683
728
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
684
729
|
|
|
685
730
|
// Final validation: per-block limits are only checked if the operator set them explicitly.
|
|
@@ -700,7 +745,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
700
745
|
reason: 'invalid_checkpoint',
|
|
701
746
|
checkpoint: checkpoint.header.toInspect(),
|
|
702
747
|
});
|
|
703
|
-
this.log.error(`Built an invalid checkpoint at slot ${this.
|
|
748
|
+
this.log.error(`Built an invalid checkpoint at slot ${this.targetSlot} (skipping proposal)`, err, {
|
|
704
749
|
slot: this.targetSlot,
|
|
705
750
|
checkpointNumber: this.checkpointNumber,
|
|
706
751
|
blocksBuilt: blocksInCheckpoint.length,
|
|
@@ -719,7 +764,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
719
764
|
);
|
|
720
765
|
this.logCheckpointEvent('built', `Checkpoint built for slot ${this.targetSlot}`, {
|
|
721
766
|
slot: this.targetSlot,
|
|
722
|
-
buildSlot: this.
|
|
767
|
+
buildSlot: this.getBuildSlot(),
|
|
723
768
|
checkpointNumber: this.checkpointNumber,
|
|
724
769
|
proposer: this.proposer?.toString(),
|
|
725
770
|
attestorAddress: this.attestorAddress.toString(),
|
|
@@ -843,14 +888,14 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
843
888
|
break;
|
|
844
889
|
}
|
|
845
890
|
|
|
846
|
-
const
|
|
847
|
-
const timingInfo = this.timetable.
|
|
891
|
+
const nowSeconds = this.dateProvider.now() / 1000;
|
|
892
|
+
const timingInfo = this.timetable.selectNextSubslot(this.targetSlot, nowSeconds);
|
|
848
893
|
|
|
849
894
|
if (!timingInfo.canStart) {
|
|
850
895
|
this.log.debug(`Not enough time left in slot to start another block`, {
|
|
851
896
|
slot: this.targetSlot,
|
|
852
897
|
blocksBuilt,
|
|
853
|
-
|
|
898
|
+
nowSeconds,
|
|
854
899
|
});
|
|
855
900
|
break;
|
|
856
901
|
}
|
|
@@ -860,10 +905,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
860
905
|
blockTimestamp: timestamp,
|
|
861
906
|
// Create an empty block if we haven't already and this is the last one
|
|
862
907
|
forceCreate: timingInfo.isLastBlock && blocksBuilt === 0 && this.config.buildCheckpointIfEmpty,
|
|
863
|
-
|
|
864
|
-
buildDeadline: timingInfo.deadline
|
|
865
|
-
? new Date((this.getSlotStartBuildTimestamp() + timingInfo.deadline) * 1000)
|
|
866
|
-
: undefined,
|
|
908
|
+
buildDeadline: new Date(timingInfo.deadline * 1000),
|
|
867
909
|
blockNumber,
|
|
868
910
|
indexWithinCheckpoint,
|
|
869
911
|
txHashesAlreadyIncluded,
|
|
@@ -871,8 +913,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
871
913
|
|
|
872
914
|
// If we failed to build the block due to insufficient txs, we try again if there is still time left in the slot
|
|
873
915
|
if ('failure' in buildResult) {
|
|
874
|
-
// If this was the last subslot,
|
|
875
|
-
if (timingInfo.isLastBlock
|
|
916
|
+
// If this was the last subslot, we're done.
|
|
917
|
+
if (timingInfo.isLastBlock) {
|
|
876
918
|
break;
|
|
877
919
|
}
|
|
878
920
|
// Otherwise, if there is still time for more blocks, we wait until the next subslot and try again
|
|
@@ -968,14 +1010,17 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
968
1010
|
);
|
|
969
1011
|
}
|
|
970
1012
|
|
|
971
|
-
/**
|
|
1013
|
+
/**
|
|
1014
|
+
* Sleeps until it is time to produce the next block in the slot.
|
|
1015
|
+
* @param nextSubslotStart - Absolute wall-clock timestamp in seconds of the previous sub-slot deadline.
|
|
1016
|
+
*/
|
|
972
1017
|
@trackSpan('CheckpointProposalJob.waitUntilNextSubslot')
|
|
973
|
-
|
|
974
|
-
this.
|
|
975
|
-
this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s
|
|
1018
|
+
protected async waitUntilNextSubslot(nextSubslotStart: number) {
|
|
1019
|
+
this.setState(SequencerState.WAITING_UNTIL_NEXT_BLOCK);
|
|
1020
|
+
this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s`, {
|
|
976
1021
|
slot: this.targetSlot,
|
|
977
1022
|
});
|
|
978
|
-
await this.
|
|
1023
|
+
await this.awaitInterruptibleSleep(Math.max(0, nextSubslotStart * 1000 - this.dateProvider.now()));
|
|
979
1024
|
}
|
|
980
1025
|
|
|
981
1026
|
/** Builds a single block. Called from the main block building loop. */
|
|
@@ -1003,7 +1048,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1003
1048
|
|
|
1004
1049
|
try {
|
|
1005
1050
|
// Wait until we have enough txs to build the block
|
|
1006
|
-
const {
|
|
1051
|
+
const { canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
|
|
1007
1052
|
if (!canStartBuilding) {
|
|
1008
1053
|
this.logCheckpointEvent('block-build-failed', `Block build failed for slot ${this.targetSlot}`, {
|
|
1009
1054
|
reason: 'insufficient_txs',
|
|
@@ -1011,38 +1056,39 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1011
1056
|
slot: this.targetSlot,
|
|
1012
1057
|
checkpointNumber: this.checkpointNumber,
|
|
1013
1058
|
indexWithinCheckpoint,
|
|
1014
|
-
availableTxs,
|
|
1015
1059
|
minTxs,
|
|
1016
1060
|
});
|
|
1017
|
-
this.log.
|
|
1018
|
-
`Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (
|
|
1061
|
+
this.log.verbose(
|
|
1062
|
+
`Not enough age-eligible txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (needs ${minTxs} eligible)`,
|
|
1019
1063
|
{
|
|
1020
1064
|
reason: 'insufficient_txs',
|
|
1021
1065
|
blockNumber,
|
|
1022
1066
|
slot: this.targetSlot,
|
|
1023
1067
|
checkpointNumber: this.checkpointNumber,
|
|
1024
1068
|
indexWithinCheckpoint,
|
|
1025
|
-
availableTxs,
|
|
1026
1069
|
minTxs,
|
|
1027
1070
|
},
|
|
1028
1071
|
);
|
|
1029
|
-
this.eventEmitter.emit('block-tx-count-check-failed', { minTxs,
|
|
1072
|
+
this.eventEmitter.emit('block-tx-count-check-failed', { minTxs, slot: this.targetSlot });
|
|
1030
1073
|
this.metrics.recordBlockProposalFailed('insufficient_txs');
|
|
1031
1074
|
return { failure: 'insufficient-txs' };
|
|
1032
1075
|
}
|
|
1033
1076
|
|
|
1034
1077
|
// Create iterator to pending txs. We filter out txs already included in previous blocks in the checkpoint
|
|
1035
1078
|
// just in case p2p failed to sync the provisional block and didn't get to remove those txs from the mempool yet.
|
|
1079
|
+
// Block building only executes txs, so we skip loading their proofs unless these same tx objects get attached
|
|
1080
|
+
// to the broadcasted proposals via publishTxsWithProposals.
|
|
1036
1081
|
const pendingTxs = filter(
|
|
1037
|
-
this.p2pClient.iterateEligiblePendingTxs(),
|
|
1082
|
+
this.p2pClient.iterateEligiblePendingTxs({ includeProof: !!this.config.publishTxsWithProposals }),
|
|
1038
1083
|
tx => !txHashesAlreadyIncluded.has(tx.txHash.toString()),
|
|
1039
1084
|
);
|
|
1040
1085
|
|
|
1041
|
-
this.log.debug(
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1086
|
+
this.log.debug(`Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.targetSlot}`, {
|
|
1087
|
+
slot: this.targetSlot,
|
|
1088
|
+
blockNumber,
|
|
1089
|
+
indexWithinCheckpoint,
|
|
1090
|
+
});
|
|
1091
|
+
this.setState(SequencerState.CREATING_BLOCK);
|
|
1046
1092
|
|
|
1047
1093
|
// Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
|
|
1048
1094
|
// by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
|
|
@@ -1057,8 +1103,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1057
1103
|
deadline: buildDeadline,
|
|
1058
1104
|
isBuildingProposal: true,
|
|
1059
1105
|
minValidTxs,
|
|
1060
|
-
maxBlocksPerCheckpoint: this.timetable.
|
|
1106
|
+
maxBlocksPerCheckpoint: this.timetable.getMaxBlocksPerCheckpoint(),
|
|
1061
1107
|
perBlockAllocationMultiplier: this.config.perBlockAllocationMultiplier,
|
|
1108
|
+
perBlockDAAllocationMultiplier: this.config.perBlockDAAllocationMultiplier,
|
|
1062
1109
|
};
|
|
1063
1110
|
|
|
1064
1111
|
// Actually build the block by executing txs. The builder throws InsufficientValidTxsError
|
|
@@ -1132,7 +1179,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1132
1179
|
checkpointNumber: this.checkpointNumber,
|
|
1133
1180
|
indexWithinCheckpoint: block.indexWithinCheckpoint,
|
|
1134
1181
|
slot: this.targetSlot,
|
|
1135
|
-
buildSlot: this.
|
|
1182
|
+
buildSlot: this.getBuildSlot(),
|
|
1136
1183
|
});
|
|
1137
1184
|
this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe(), this.targetSlot);
|
|
1138
1185
|
|
|
@@ -1192,44 +1239,43 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1192
1239
|
blockNumber: BlockNumber;
|
|
1193
1240
|
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
1194
1241
|
buildDeadline: Date | undefined;
|
|
1195
|
-
}): Promise<{ canStartBuilding: boolean;
|
|
1242
|
+
}): Promise<{ canStartBuilding: boolean; minTxs: number }> {
|
|
1196
1243
|
const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts;
|
|
1197
1244
|
|
|
1198
1245
|
// We only allow a block with 0 txs in the first block of the checkpoint
|
|
1199
1246
|
const minTxs = indexWithinCheckpoint > 0 && this.config.minTxsPerBlock === 0 ? 1 : this.config.minTxsPerBlock;
|
|
1200
1247
|
|
|
1201
|
-
//
|
|
1248
|
+
// Latest time to keep waiting for txs: wait_for_txs_deadline = block_build_deadline(k) - min_block_duration.
|
|
1202
1249
|
const startBuildingDeadline = buildDeadline
|
|
1203
|
-
? new Date(buildDeadline.getTime() - this.timetable.
|
|
1250
|
+
? new Date(buildDeadline.getTime() - this.timetable.minBlockDuration * 1000)
|
|
1204
1251
|
: undefined;
|
|
1205
1252
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
while (!forceCreate &&
|
|
1253
|
+
// Gate on age-eligible txs so we don't start building on txs the builder would then filter out for being
|
|
1254
|
+
// too fresh. hasEligiblePendingTxs early-exits once minTxs are eligible instead of counting the whole pool.
|
|
1255
|
+
while (!forceCreate && !(await this.p2pClient.hasEligiblePendingTxs(minTxs))) {
|
|
1209
1256
|
// If we're past deadline, or we have no deadline, give up
|
|
1210
1257
|
const now = this.dateProvider.nowAsDate();
|
|
1211
1258
|
if (startBuildingDeadline === undefined || now >= startBuildingDeadline) {
|
|
1212
|
-
return { canStartBuilding: false,
|
|
1259
|
+
return { canStartBuilding: false, minTxs };
|
|
1213
1260
|
}
|
|
1214
1261
|
|
|
1215
1262
|
// Wait a bit before checking again
|
|
1216
|
-
this.
|
|
1263
|
+
this.setState(SequencerState.WAITING_FOR_TXS);
|
|
1217
1264
|
this.log.verbose(
|
|
1218
|
-
`Waiting for
|
|
1219
|
-
{ blockNumber, slot: this.targetSlot, indexWithinCheckpoint },
|
|
1265
|
+
`Waiting for ${minTxs} age-eligible txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot}`,
|
|
1266
|
+
{ blockNumber, slot: this.targetSlot, indexWithinCheckpoint, minTxs },
|
|
1220
1267
|
);
|
|
1221
1268
|
await this.waitForTxsPollingInterval();
|
|
1222
|
-
availableTxs = await this.p2pClient.getPendingTxCount();
|
|
1223
1269
|
}
|
|
1224
1270
|
|
|
1225
|
-
return { canStartBuilding: true,
|
|
1271
|
+
return { canStartBuilding: true, minTxs };
|
|
1226
1272
|
}
|
|
1227
1273
|
|
|
1228
1274
|
private async getSignedCommitteeAttestations(
|
|
1229
1275
|
broadcast: CheckpointProposalBroadcast,
|
|
1230
1276
|
): Promise<{ attestations: CommitteeAttestationsAndSigners; attestationsSignature: Signature } | undefined> {
|
|
1231
1277
|
const { proposal, blockProposedAt } = broadcast;
|
|
1232
|
-
this.
|
|
1278
|
+
this.setState(SequencerState.COLLECTING_ATTESTATIONS);
|
|
1233
1279
|
const attestations = await this.waitForAttestations(proposal);
|
|
1234
1280
|
if (!attestations) {
|
|
1235
1281
|
return undefined;
|
|
@@ -1294,12 +1340,14 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1294
1340
|
);
|
|
1295
1341
|
}
|
|
1296
1342
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
const attestationDeadline = new Date((this.getSlotStartBuildTimestamp() + attestationTimeAllowed) * 1000);
|
|
1343
|
+
// Hard attestation-collection cutoff = the single consensus attestation_deadline (target_slot_start + S - 2E).
|
|
1344
|
+
const attestationDeadlineSeconds = this.timetable.getAttestationDeadline(this.targetSlot);
|
|
1345
|
+
const attestationDeadline = new Date(attestationDeadlineSeconds * 1000);
|
|
1301
1346
|
|
|
1302
|
-
this.metrics.recordRequiredAttestations(
|
|
1347
|
+
this.metrics.recordRequiredAttestations(
|
|
1348
|
+
numberOfRequiredAttestations,
|
|
1349
|
+
Math.max(0, attestationDeadline.getTime() - this.dateProvider.now()),
|
|
1350
|
+
);
|
|
1303
1351
|
|
|
1304
1352
|
const collectAttestationsTimer = new Timer();
|
|
1305
1353
|
let collectedAttestationsCount: number = 0;
|
|
@@ -1336,6 +1384,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1336
1384
|
this.config.injectFakeAttestation ||
|
|
1337
1385
|
this.config.injectHighSValueAttestation ||
|
|
1338
1386
|
this.config.injectUnrecoverableSignatureAttestation ||
|
|
1387
|
+
this.config.injectYParityAttestation ||
|
|
1339
1388
|
this.config.shuffleAttestationOrdering
|
|
1340
1389
|
) {
|
|
1341
1390
|
return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
|
|
@@ -1437,6 +1486,19 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1437
1486
|
return new CommitteeAttestationsAndSigners(attestations, this.getSignatureContext());
|
|
1438
1487
|
}
|
|
1439
1488
|
|
|
1489
|
+
if (this.config.injectYParityAttestation) {
|
|
1490
|
+
// Force every non-proposer signed slot's recovery byte to yParity (v ∈ {0, 1}) form in the packed L1
|
|
1491
|
+
// tuple, after packAttestations has canonicalized it. The proposer's own slot is left canonical so
|
|
1492
|
+
// propose() still passes verifyProposer. Models a malicious proposer landing a checkpoint L1 accepts
|
|
1493
|
+
// but that can never be proven (ECDSA.recover rejects v ∉ {27, 28}).
|
|
1494
|
+
this.log.warn(`Injecting yParity attestations in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
|
|
1495
|
+
return new MaliciousYParityCommitteeAttestationsAndSigners(
|
|
1496
|
+
attestations,
|
|
1497
|
+
proposerIndex,
|
|
1498
|
+
this.getSignatureContext(),
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1440
1502
|
if (this.config.shuffleAttestationOrdering) {
|
|
1441
1503
|
this.log.warn(`Shuffling attestation ordering in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
|
|
1442
1504
|
|
|
@@ -1469,7 +1531,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1469
1531
|
}
|
|
1470
1532
|
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
1471
1533
|
const failedTxHashes = failedTxData.map(tx => tx.getTxHash());
|
|
1472
|
-
|
|
1534
|
+
const failures = failedTxs.map(fail => ({ txHash: fail.tx.getTxHash().toString(), reason: fail.error.message }));
|
|
1535
|
+
this.log.warn(
|
|
1536
|
+
`Dropping ${failedTxs.length} txs from mempool due to failures during block building for slot ${this.targetSlot}`,
|
|
1537
|
+
{ slot: this.targetSlot, checkpointNumber: this.checkpointNumber, failures },
|
|
1538
|
+
);
|
|
1473
1539
|
await this.p2pClient.handleFailedExecution(failedTxHashes);
|
|
1474
1540
|
}
|
|
1475
1541
|
|
|
@@ -1502,8 +1568,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1502
1568
|
* tip advances locally. Gossip doesn't echo our own messages back, so without this the proposer
|
|
1503
1569
|
* would never see its own proposed checkpoint and couldn't pipeline the next slot.
|
|
1504
1570
|
*
|
|
1505
|
-
*
|
|
1506
|
-
* skipped whenever proposed blocks aren't pushed (`skipPushProposedBlocksToArchiver`, fisherman
|
|
1571
|
+
* Skipped whenever proposed blocks aren't pushed (`skipPushProposedBlocksToArchiver`, fisherman
|
|
1507
1572
|
* mode): the archiver derives the checkpoint archive from its stored blocks, so without them the
|
|
1508
1573
|
* push would fail. All blocks were already added (and awaited) during block building, so this
|
|
1509
1574
|
* needs no retry — they are guaranteed present by the time we get here.
|
|
@@ -1516,9 +1581,6 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1516
1581
|
if (this.config.skipPushProposedBlocksToArchiver || this.config.fishermanMode) {
|
|
1517
1582
|
return;
|
|
1518
1583
|
}
|
|
1519
|
-
if (!this.epochCache.isProposerPipeliningEnabled()) {
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
1584
|
const startBlock = BlockNumber(this.syncedToBlockNumber + 1);
|
|
1523
1585
|
this.log.debug(`Syncing proposed checkpoint ${this.checkpointNumber} to archiver`, {
|
|
1524
1586
|
checkpointNumber: this.checkpointNumber,
|
|
@@ -1583,26 +1645,9 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
1583
1645
|
return false;
|
|
1584
1646
|
}
|
|
1585
1647
|
|
|
1586
|
-
/** Waits until a specific time within the current slot */
|
|
1587
|
-
@trackSpan('CheckpointProposalJob.waitUntilTimeInSlot')
|
|
1588
|
-
protected async waitUntilTimeInSlot(targetSecondsIntoSlot: number): Promise<void> {
|
|
1589
|
-
const slotStartTimestamp = this.getSlotStartBuildTimestamp();
|
|
1590
|
-
const targetTimestamp = slotStartTimestamp + targetSecondsIntoSlot;
|
|
1591
|
-
await sleepUntil(new Date(targetTimestamp * 1000), this.dateProvider.nowAsDate());
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
1648
|
/** Waits the polling interval for transactions. Extracted for test overriding. */
|
|
1595
1649
|
protected async waitForTxsPollingInterval(): Promise<void> {
|
|
1596
|
-
await
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
private getSlotStartBuildTimestamp(): number {
|
|
1600
|
-
return getSlotStartBuildTimestamp(this.slotNow, this.l1Constants);
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
private getSecondsIntoSlot(): number {
|
|
1604
|
-
const slotStartTimestamp = this.getSlotStartBuildTimestamp();
|
|
1605
|
-
return Number((this.dateProvider.now() / 1000 - slotStartTimestamp).toFixed(3));
|
|
1650
|
+
await this.awaitInterruptibleSleep(TXS_POLLING_MS);
|
|
1606
1651
|
}
|
|
1607
1652
|
|
|
1608
1653
|
public getPublisher() {
|