@aztec/sequencer-client 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/client/sequencer-client.d.ts +16 -3
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +17 -3
- package/dest/config.d.ts +2 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +7 -2
- 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 +15 -2
- 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/sequencer-publisher.d.ts +1 -1
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +6 -2
- 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_sequencer.d.ts +1 -1
- package/dest/sequencer/automine/automine_sequencer.d.ts.map +1 -1
- package/dest/sequencer/automine/automine_sequencer.js +11 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts +4 -6
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +27 -24
- package/dest/sequencer/events.d.ts +1 -2
- package/dest/sequencer/events.d.ts.map +1 -1
- 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 +39 -4
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +81 -21
- package/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +1 -0
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +19 -3
- package/src/config.ts +7 -2
- package/src/global_variable_builder/fee_predictor.ts +11 -1
- package/src/global_variable_builder/fee_provider.ts +19 -2
- package/src/publisher/l1_to_l2_messaging.ts +85 -0
- package/src/publisher/sequencer-publisher.ts +8 -2
- package/src/publisher/write_json.ts +78 -0
- package/src/sequencer/automine/automine_sequencer.ts +11 -10
- package/src/sequencer/checkpoint_proposal_job.ts +40 -29
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/requests_tracker.ts +43 -0
- package/src/sequencer/sequencer.ts +83 -15
- package/src/test/utils.ts +1 -0
|
@@ -393,6 +393,7 @@ import { CheckpointProposalJobMetrics } from './checkpoint_proposal_job_metrics.
|
|
|
393
393
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
394
394
|
import { SequencerInterruptedError } from './errors.js';
|
|
395
395
|
import { SequencerMetrics } from './metrics.js';
|
|
396
|
+
import { RequestsTracker } from './requests_tracker.js';
|
|
396
397
|
import { SequencerState } from './utils.js';
|
|
397
398
|
export { SequencerState };
|
|
398
399
|
_dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpointProposal'), _dec2 = trackSpan('Sequencer.tryVoteAndPruneWhenCannotBuild', ({ slot })=>({
|
|
@@ -462,11 +463,20 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
462
463
|
/** Last successful checkpoint proposed */ lastCheckpointProposed;
|
|
463
464
|
/** The last epoch for which we logged strategy comparison in fisherman mode. */ lastEpochForStrategyComparison;
|
|
464
465
|
/** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */ lastCheckpointProposalJob;
|
|
466
|
+
/**
|
|
467
|
+
* In-flight fire-and-forget requests that {@link stop} interrupts and drains, and {@link pause} awaits
|
|
468
|
+
* untouched: the checkpoint proposal jobs' backgrounded L1 submissions (each job is handed this shared
|
|
469
|
+
* tracker) plus the sequencer's own fallback submissions (votes/prune when we cannot build, or
|
|
470
|
+
* escape-hatch votes). Each fallback send is gated by its wrapper publisher's interruptible target-slot
|
|
471
|
+
* sleep; the tracked interrupt wakes that sleep so the send short-circuits without publishing. A wrapper
|
|
472
|
+
* is created for a single send and is never restarted, so once interrupted its sleeper can never publish
|
|
473
|
+
* a stale-slot tx, even after the pooled publishers are restarted by a later {@link start}.
|
|
474
|
+
*/ pendingRequests;
|
|
465
475
|
/** Proposer schedule and block sub-slot timetable for the sequencer, rebuilt on every config update. */ timetable;
|
|
466
476
|
/** Config for the sequencer */ config;
|
|
467
477
|
signatureContext;
|
|
468
478
|
constructor(publisherFactory, validatorClient, globalsBuilder, p2pClient, worldState, slasherClient, l2BlockSource, l1ToL2MessageSource, checkpointsBuilder, l1Constants, dateProvider, epochCache, rollupContract, config, telemetry = getTelemetryClient(), log = createLogger('sequencer')){
|
|
469
|
-
super(), this.publisherFactory = publisherFactory, this.validatorClient = validatorClient, this.globalsBuilder = globalsBuilder, this.p2pClient = p2pClient, this.worldState = worldState, this.slasherClient = slasherClient, this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.checkpointsBuilder = checkpointsBuilder, this.l1Constants = l1Constants, this.dateProvider = dateProvider, this.epochCache = epochCache, this.rollupContract = rollupContract, this.telemetry = telemetry, this.log = log, this.state = (_initProto(this), SequencerState.STOPPED), this.config = DefaultSequencerConfig;
|
|
479
|
+
super(), this.publisherFactory = publisherFactory, this.validatorClient = validatorClient, this.globalsBuilder = globalsBuilder, this.p2pClient = p2pClient, this.worldState = worldState, this.slasherClient = slasherClient, this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.checkpointsBuilder = checkpointsBuilder, this.l1Constants = l1Constants, this.dateProvider = dateProvider, this.epochCache = epochCache, this.rollupContract = rollupContract, this.telemetry = telemetry, this.log = log, this.state = (_initProto(this), SequencerState.STOPPED), this.pendingRequests = new RequestsTracker(), this.config = DefaultSequencerConfig;
|
|
470
480
|
this.stateLog = log.createChild('state');
|
|
471
481
|
this.stateEnteredAtMs = this.dateProvider.now();
|
|
472
482
|
// Add [FISHERMAN] prefix to logger if in fisherman mode
|
|
@@ -580,7 +590,21 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
580
590
|
/** Initializes the sequencer (precomputes tables). Takes about 3s. */ init() {
|
|
581
591
|
getKzg();
|
|
582
592
|
}
|
|
583
|
-
/**
|
|
593
|
+
/**
|
|
594
|
+
* Starts (or restarts) the sequencer and moves it to the IDLE state. Idempotent: a start while already
|
|
595
|
+
* running is a no-op, so it never orphans the previous poll loop. A start while STOPPING throws, since the
|
|
596
|
+
* in-flight stop would mark the fresh loop's state STOPPED and orphan it — silently doing nothing would
|
|
597
|
+
* leave the caller believing the sequencer is running when it is on its way to STOPPED. Safe to call after
|
|
598
|
+
* a previous {@link stop} has resolved; the caller must also restart the publishers (see
|
|
599
|
+
* {@link SequencerClient.start}) so L1 publishing is re-enabled.
|
|
600
|
+
*/ start() {
|
|
601
|
+
if (this.state === SequencerState.STOPPING) {
|
|
602
|
+
throw new Error('Cannot start sequencer while it is stopping');
|
|
603
|
+
}
|
|
604
|
+
if (this.runningPromise?.isRunning()) {
|
|
605
|
+
this.log.warn('Attempted to start sequencer that is already running');
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
584
608
|
this.runningPromise = new RunningPromise(this.safeWork.bind(this), this.log, this.config.sequencerPollingIntervalMS);
|
|
585
609
|
this.setState(SequencerState.IDLE, undefined, {
|
|
586
610
|
force: true
|
|
@@ -591,20 +615,56 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
591
615
|
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */ trigger() {
|
|
592
616
|
return this.runningPromise?.trigger();
|
|
593
617
|
}
|
|
594
|
-
/**
|
|
618
|
+
/**
|
|
619
|
+
* Stops the sequencer from building blocks and moves it to STOPPED state, interrupting the in-flight
|
|
620
|
+
* work() iteration and its pending L1 submissions for a fast shutdown, then draining before returning.
|
|
621
|
+
* Idempotent: a second call while already stopped or stopping is a no-op. Lifecycle calls are expected
|
|
622
|
+
* to be serialized by the caller. For a restartable pause that lets in-flight work finish untouched
|
|
623
|
+
* (e.g. around a test clock warp), use {@link pause} instead.
|
|
624
|
+
*/ async stop() {
|
|
625
|
+
if (this.state === SequencerState.STOPPED || this.state === SequencerState.STOPPING) {
|
|
626
|
+
this.log.debug(`Sequencer already ${this.state.toLowerCase()}, ignoring stop`);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
595
629
|
this.log.info(`Stopping sequencer`);
|
|
596
630
|
this.setState(SequencerState.STOPPING, undefined, {
|
|
597
631
|
force: true
|
|
598
632
|
});
|
|
599
633
|
this.lastCheckpointProposalJob?.interrupt();
|
|
600
634
|
await this.publisherFactory.stopAll();
|
|
635
|
+
// Stop the poll loop and await the in-flight work() iteration. work() registers its fire-and-forget
|
|
636
|
+
// requests synchronously before returning, so once the loop has stopped, pendingRequests holds every
|
|
637
|
+
// request that will ever exist. Interrupting any earlier could miss a request registered by the last
|
|
638
|
+
// in-flight iteration.
|
|
601
639
|
await this.runningPromise?.stop();
|
|
602
|
-
|
|
640
|
+
this.pendingRequests.interruptRequests();
|
|
641
|
+
await this.pendingRequests.awaitRequests();
|
|
603
642
|
this.setState(SequencerState.STOPPED, undefined, {
|
|
604
643
|
force: true
|
|
605
644
|
});
|
|
606
645
|
this.log.info('Stopped sequencer');
|
|
607
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Gracefully pauses block production so the sequencer can later be resumed with {@link start}: halts the
|
|
649
|
+
* poll loop and waits for the in-flight work() iteration and every pending L1 submission / fallback send
|
|
650
|
+
* to finish, without interrupting them. No interrupt lands mid-build, so no spurious checkpoint-error is
|
|
651
|
+
* emitted and no enqueued checkpoint is dropped. Deliberately does not stop inner services (validator/HA
|
|
652
|
+
* signer, publishers), so the slashing-protection store stays open and the sequencer stays restartable.
|
|
653
|
+
* Used by tests that pause sequencers around an L1 clock warp. Idempotent.
|
|
654
|
+
*/ async pause() {
|
|
655
|
+
if (!this.runningPromise?.isRunning()) {
|
|
656
|
+
this.log.debug('Sequencer not running, ignoring pause');
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
this.log.info('Pausing sequencer');
|
|
660
|
+
// Halt the poll loop and let the in-flight iteration finish naturally — no interrupt, and no STOPPING
|
|
661
|
+
// state, since entering STOPPING would make the iteration's own setState calls throw and fail it.
|
|
662
|
+
// work() registers its fire-and-forget requests synchronously before returning, so once the loop has
|
|
663
|
+
// stopped pendingRequests holds every request that will ever exist; awaiting it lets them all finish.
|
|
664
|
+
await this.runningPromise.stop();
|
|
665
|
+
await this.pendingRequests.awaitRequests();
|
|
666
|
+
this.log.info('Paused sequencer');
|
|
667
|
+
}
|
|
608
668
|
/** Main sequencer loop with a try/catch */ async safeWork() {
|
|
609
669
|
try {
|
|
610
670
|
await this.work();
|
|
@@ -731,18 +791,6 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
731
791
|
}
|
|
732
792
|
return undefined;
|
|
733
793
|
}
|
|
734
|
-
// We are the proposer and the escape hatch is closed: now run the full sync check before building.
|
|
735
|
-
const syncedTo = await this.checkSync({
|
|
736
|
-
ts,
|
|
737
|
-
slot
|
|
738
|
-
});
|
|
739
|
-
if (!syncedTo) {
|
|
740
|
-
await this.tryVoteAndPruneWhenCannotBuild({
|
|
741
|
-
slot,
|
|
742
|
-
targetSlot
|
|
743
|
-
});
|
|
744
|
-
return undefined;
|
|
745
|
-
}
|
|
746
794
|
// Explicit build-loop entry gate: if we are past the latest useful block-building start for the
|
|
747
795
|
// target slot, abandon building for this slot. The proposer prioritizes the ideal L1-publish path
|
|
748
796
|
// and does not plan around the late consensus-handoff path. This is the proposer build path's
|
|
@@ -766,6 +814,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
766
814
|
this.lastSlotForCheckpointProposalJob = targetSlot;
|
|
767
815
|
return undefined;
|
|
768
816
|
}
|
|
817
|
+
// We are the proposer, the escape hatch is closed, and we have time before the build start deadline.
|
|
818
|
+
// Now run the full sync check before building.
|
|
819
|
+
const syncedTo = await this.checkSync({
|
|
820
|
+
ts,
|
|
821
|
+
slot
|
|
822
|
+
});
|
|
823
|
+
if (!syncedTo) {
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
769
826
|
// Next checkpoint follows from the last synced one
|
|
770
827
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
771
828
|
const logCtx = {
|
|
@@ -926,7 +983,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
926
983
|
return this.createCheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, syncedTo.checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
|
|
927
984
|
}
|
|
928
985
|
createCheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, proposedCheckpointData) {
|
|
929
|
-
return new CheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, this.validatorClient, this.globalsBuilder, this.p2pClient, this.worldState, this.l1ToL2MessageSource, this.l2BlockSource, this.checkpointsBuilder, this.l2BlockSource, this.l1Constants, this.signatureContext, this.config, this.timetable, this.slasherClient, this.epochCache, this.dateProvider, this.metrics, this.checkpointProposalJobMetrics.createRecorder(), this, this.setState.bind(this), this.tracer, this.log.getBindings(), proposedCheckpointData);
|
|
986
|
+
return new CheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, this.validatorClient, this.globalsBuilder, this.p2pClient, this.worldState, this.l1ToL2MessageSource, this.l2BlockSource, this.checkpointsBuilder, this.l2BlockSource, this.l1Constants, this.signatureContext, this.config, this.timetable, this.slasherClient, this.epochCache, this.dateProvider, this.metrics, this.checkpointProposalJobMetrics.createRecorder(), this, this.pendingRequests, this.setState.bind(this), this.tracer, this.log.getBindings(), proposedCheckpointData);
|
|
930
987
|
}
|
|
931
988
|
/**
|
|
932
989
|
* Returns the current sequencer state.
|
|
@@ -1207,12 +1264,13 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1207
1264
|
// Votes are EIP-712-signed for `targetSlot` (the pipelined slot in which the multicall is
|
|
1208
1265
|
// expected to mine). Delay submission to the start of `targetSlot` so the tx mines in the
|
|
1209
1266
|
// slot the votes were signed for. We fire-and-forget so we don't block the sequencer's
|
|
1210
|
-
// work loop while waiting for the target slot to start.
|
|
1211
|
-
|
|
1267
|
+
// work loop while waiting for the target slot to start, but track it so stop() can drain it.
|
|
1268
|
+
const send = publisher.sendRequestsAt(targetSlot).catch((err)=>{
|
|
1212
1269
|
this.log.error(`Failed to publish fallback requests despite sync failure for slot ${slot}`, err, {
|
|
1213
1270
|
slot
|
|
1214
1271
|
});
|
|
1215
1272
|
});
|
|
1273
|
+
this.pendingRequests.trackRequest(send, ()=>publisher.interrupt());
|
|
1216
1274
|
}
|
|
1217
1275
|
async tryEnqueuePruneIfPrunable(targetSlot, publisher) {
|
|
1218
1276
|
try {
|
|
@@ -1261,13 +1319,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1261
1319
|
// the multicall mines in the slot the votes were signed for; otherwise the L1 contract reads
|
|
1262
1320
|
// `signaler = getCurrentProposer()` against the wrong slot and signature verification fails
|
|
1263
1321
|
// silently inside Multicall3. Fire-and-forget so we don't block the sequencer's work loop while
|
|
1264
|
-
// waiting for the target slot to start, mirroring tryVoteAndPruneWhenCannotBuild
|
|
1265
|
-
|
|
1322
|
+
// waiting for the target slot to start, mirroring tryVoteAndPruneWhenCannotBuild, but tracked so
|
|
1323
|
+
// stop() can drain it.
|
|
1324
|
+
const send = publisher.sendRequestsAt(targetSlot).catch((err)=>{
|
|
1266
1325
|
this.log.error(`Failed to publish escape-hatch votes for slot ${slot}`, err, {
|
|
1267
1326
|
slot,
|
|
1268
1327
|
targetSlot
|
|
1269
1328
|
});
|
|
1270
1329
|
});
|
|
1330
|
+
this.pendingRequests.trackRequest(send, ()=>publisher.interrupt());
|
|
1271
1331
|
}
|
|
1272
1332
|
/**
|
|
1273
1333
|
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
package/dest/test/utils.d.ts
CHANGED
|
@@ -64,4 +64,4 @@ export declare function setupTxsAndBlock(p2p: MockProxy<P2P>, globalVariables: G
|
|
|
64
64
|
txs: Tx[];
|
|
65
65
|
block: L2Block;
|
|
66
66
|
}>;
|
|
67
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
67
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQ0FBMkMsQ0FBQztBQUM1RSxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDcEQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUV0QyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsT0FBTyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFcEUsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNyRSxPQUFPLEVBQUUsYUFBYSxFQUFFLHFCQUFxQixFQUFFLGtCQUFrQixFQUFvQixNQUFNLG1CQUFtQixDQUFDO0FBTy9HLE9BQU8sRUFLTCxpQkFBaUIsRUFDbEIsTUFBTSx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEVBQWUsZUFBZSxFQUFFLEtBQUssRUFBRSxFQUFvQyxNQUFNLGtCQUFrQixDQUFDO0FBRTNHLE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBR3BELE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRTdGOzs7O0dBSUc7QUFDSCx3QkFBZ0IscUJBQXFCLENBQUMsSUFBSSxFQUFFO0lBQzFDLFdBQVcsRUFBRSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsZUFBZSxHQUFHLGNBQWMsR0FBRyxzQkFBc0IsQ0FBQyxDQUFDO0lBQ2hHLGVBQWUsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUN6QixnQkFBZ0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUMxQixrQkFBa0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUM1Qiw2QkFBNkIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUN4QyxHQUFHLGlCQUFpQixDQVNwQjtBQUVEOztHQUVHO0FBQ0gsd0JBQXNCLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FNckU7QUFFRDs7R0FFRztBQUNILHdCQUFzQixTQUFTLENBQUMsR0FBRyxFQUFFLEVBQUUsRUFBRSxFQUFFLGVBQWUsRUFBRSxlQUFlLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQWdCN0Y7QUFFRDs7R0FFRztBQUNILHdCQUFnQixjQUFjLENBQUMsR0FBRyxFQUFFLFNBQVMsQ0FBQyxHQUFHLENBQUMsRUFBRSxHQUFHLEVBQUUsRUFBRSxFQUFFLEdBQUcsSUFBSSxDQUtuRTtBQUVEOztHQUVHO0FBQ0gsd0JBQXVCLGNBQWMsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxDQUFDLEdBQUcscUJBQXFCLENBQUMsRUFBRSxDQUFDLENBSW5GO0FBRUQ7O0dBRUc7QUFDSCx3QkFBZ0Isb0JBQW9CLENBQUMsTUFBTSxFQUFFLGVBQWUsR0FBRyxvQkFBb0IsRUFBRSxDQUdwRjtBQXdCRDs7R0FFRztBQUNILHdCQUFnQixtQkFBbUIsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxTQUFTLEdBQUcsYUFBYSxDQVd2RjtBQUVEOztHQUVHO0FBQ0gsd0JBQWdCLHdCQUF3QixDQUN0QyxLQUFLLEVBQUUsT0FBTyxFQUNkLG1CQUFtQixFQUFFLFNBQVMsRUFDOUIsY0FBYyxDQUFDLEVBQUUsU0FBUyxFQUMxQixxQkFBcUIsR0FBRSxNQUFXLEdBQ2pDLGtCQUFrQixDQWdCcEI7QUFFRDs7OztHQUlHO0FBQ0gsd0JBQWdCLDJCQUEyQixDQUN6QyxLQUFLLEVBQUUsT0FBTyxFQUNkLFNBQVMsRUFBRSxTQUFTLEVBQ3BCLE1BQU0sRUFBRSxVQUFVLEVBQ2xCLHFCQUFxQixHQUFFLE1BQVcsR0FDakMscUJBQXFCLENBYXZCO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQXNCLGdCQUFnQixDQUNwQyxHQUFHLEVBQUUsU0FBUyxDQUFDLEdBQUcsQ0FBQyxFQUNuQixlQUFlLEVBQUUsZUFBZSxFQUNoQyxPQUFPLEVBQUUsTUFBTSxFQUNmLE9BQU8sRUFBRSxFQUFFLEdBQ1YsT0FBTyxDQUFDO0lBQUUsR0FBRyxFQUFFLEVBQUUsRUFBRSxDQUFDO0lBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQTtDQUFFLENBQUMsQ0FLeEMifQ==
|
package/dest/test/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAoB,MAAM,mBAAmB,CAAC;AAO/G,OAAO,EAKL,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAe,eAAe,EAAE,KAAK,EAAE,EAAoC,MAAM,kBAAkB,CAAC;AAE3G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAE7F;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,cAAc,GAAG,sBAAsB,CAAC,CAAC;IAChG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6BAA6B,CAAC,EAAE,MAAM,CAAC;CACxC,GAAG,iBAAiB,CASpB;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAMrE;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB7F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAoB,MAAM,mBAAmB,CAAC;AAO/G,OAAO,EAKL,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAe,eAAe,EAAE,KAAK,EAAE,EAAoC,MAAM,kBAAkB,CAAC;AAE3G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAE7F;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,cAAc,GAAG,sBAAsB,CAAC,CAAC;IAChG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6BAA6B,CAAC,EAAE,MAAM,CAAC;CACxC,GAAG,iBAAiB,CASpB;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAMrE;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB7F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,IAAI,CAKnE;AAED;;GAEG;AACH,wBAAuB,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,qBAAqB,CAAC,EAAE,CAAC,CAInF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,oBAAoB,EAAE,CAGpF;AAwBD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,aAAa,CAWvF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,mBAAmB,EAAE,SAAS,EAC9B,cAAc,CAAC,EAAE,SAAS,EAC1B,qBAAqB,GAAE,MAAW,GACjC,kBAAkB,CAgBpB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,UAAU,EAClB,qBAAqB,GAAE,MAAW,GACjC,qBAAqB,CAavB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EACnB,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,EAAE,GACV,OAAO,CAAC;IAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAKxC"}
|
package/dest/test/utils.js
CHANGED
|
@@ -51,6 +51,7 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
|
|
|
51
51
|
* Mocks the P2P client to return specific pending transactions
|
|
52
52
|
*/ export function mockPendingTxs(p2p, txs) {
|
|
53
53
|
p2p.getPendingTxCount.mockResolvedValue(txs.length);
|
|
54
|
+
p2p.hasEligiblePendingTxs.mockImplementation((minCount)=>Promise.resolve(txs.length >= minCount));
|
|
54
55
|
p2p.iteratePendingTxs.mockImplementation(()=>mockTxIterator(Promise.resolve(txs)));
|
|
55
56
|
p2p.iterateEligiblePendingTxs.mockImplementation(()=>mockTxIterator(Promise.resolve(txs)));
|
|
56
57
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/sequencer-client",
|
|
3
|
-
"version": "5.0.0
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -27,37 +27,37 @@
|
|
|
27
27
|
"test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --config jest.integration.config.json"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@aztec/aztec.js": "5.0.0
|
|
31
|
-
"@aztec/bb-prover": "5.0.0
|
|
32
|
-
"@aztec/blob-client": "5.0.0
|
|
33
|
-
"@aztec/blob-lib": "5.0.0
|
|
34
|
-
"@aztec/constants": "5.0.0
|
|
35
|
-
"@aztec/epoch-cache": "5.0.0
|
|
36
|
-
"@aztec/ethereum": "5.0.0
|
|
37
|
-
"@aztec/foundation": "5.0.0
|
|
38
|
-
"@aztec/l1-artifacts": "5.0.0
|
|
39
|
-
"@aztec/node-keystore": "5.0.0
|
|
40
|
-
"@aztec/noir-acvm_js": "5.0.0
|
|
41
|
-
"@aztec/noir-contracts.js": "5.0.0
|
|
42
|
-
"@aztec/noir-protocol-circuits-types": "5.0.0
|
|
43
|
-
"@aztec/noir-types": "5.0.0
|
|
44
|
-
"@aztec/p2p": "5.0.0
|
|
45
|
-
"@aztec/protocol-contracts": "5.0.0
|
|
46
|
-
"@aztec/prover-client": "5.0.0
|
|
47
|
-
"@aztec/simulator": "5.0.0
|
|
48
|
-
"@aztec/slasher": "5.0.0
|
|
49
|
-
"@aztec/stdlib": "5.0.0
|
|
50
|
-
"@aztec/telemetry-client": "5.0.0
|
|
51
|
-
"@aztec/validator-client": "5.0.0
|
|
52
|
-
"@aztec/validator-ha-signer": "5.0.0
|
|
53
|
-
"@aztec/world-state": "5.0.0
|
|
30
|
+
"@aztec/aztec.js": "5.0.0",
|
|
31
|
+
"@aztec/bb-prover": "5.0.0",
|
|
32
|
+
"@aztec/blob-client": "5.0.0",
|
|
33
|
+
"@aztec/blob-lib": "5.0.0",
|
|
34
|
+
"@aztec/constants": "5.0.0",
|
|
35
|
+
"@aztec/epoch-cache": "5.0.0",
|
|
36
|
+
"@aztec/ethereum": "5.0.0",
|
|
37
|
+
"@aztec/foundation": "5.0.0",
|
|
38
|
+
"@aztec/l1-artifacts": "5.0.0",
|
|
39
|
+
"@aztec/node-keystore": "5.0.0",
|
|
40
|
+
"@aztec/noir-acvm_js": "5.0.0",
|
|
41
|
+
"@aztec/noir-contracts.js": "5.0.0",
|
|
42
|
+
"@aztec/noir-protocol-circuits-types": "5.0.0",
|
|
43
|
+
"@aztec/noir-types": "5.0.0",
|
|
44
|
+
"@aztec/p2p": "5.0.0",
|
|
45
|
+
"@aztec/protocol-contracts": "5.0.0",
|
|
46
|
+
"@aztec/prover-client": "5.0.0",
|
|
47
|
+
"@aztec/simulator": "5.0.0",
|
|
48
|
+
"@aztec/slasher": "5.0.0",
|
|
49
|
+
"@aztec/stdlib": "5.0.0",
|
|
50
|
+
"@aztec/telemetry-client": "5.0.0",
|
|
51
|
+
"@aztec/validator-client": "5.0.0",
|
|
52
|
+
"@aztec/validator-ha-signer": "5.0.0",
|
|
53
|
+
"@aztec/world-state": "5.0.0",
|
|
54
54
|
"lodash.chunk": "^4.2.0",
|
|
55
55
|
"tslib": "^2.4.0",
|
|
56
56
|
"viem": "npm:@aztec/viem@2.38.2"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@aztec/archiver": "5.0.0
|
|
60
|
-
"@aztec/kv-store": "5.0.0
|
|
59
|
+
"@aztec/archiver": "5.0.0",
|
|
60
|
+
"@aztec/kv-store": "5.0.0",
|
|
61
61
|
"@electric-sql/pglite": "^0.3.14",
|
|
62
62
|
"@jest/globals": "^30.0.0",
|
|
63
63
|
"@types/jest": "^30.0.0",
|
|
@@ -180,16 +180,23 @@ export class SequencerClient {
|
|
|
180
180
|
this.validatorClient?.updateConfig(config);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Starts (or resumes) the sequencer, validator, publishers, and metrics. Each underlying start is
|
|
185
|
+
* idempotent, so this is safe to call after a previous {@link pause} to resume building and publishing.
|
|
186
|
+
* The publisher manager is started before the sequencer's poll loop so publishing is ready before the
|
|
187
|
+
* sequencer first tries to publish to L1.
|
|
188
|
+
*/
|
|
184
189
|
public async start() {
|
|
185
190
|
await this.validatorClient?.start();
|
|
191
|
+
await this.publisherManager.start();
|
|
186
192
|
this.sequencer.start();
|
|
187
193
|
this.l1Metrics?.start();
|
|
188
|
-
await this.publisherManager.start();
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
/**
|
|
192
|
-
* Stops the sequencer
|
|
197
|
+
* Stops the sequencer, validator, publishers, and metrics for good, draining in-flight work. This is the
|
|
198
|
+
* final teardown path: stopping the validator client closes its slashing-protection database. For a
|
|
199
|
+
* restartable pause (e.g. around a test clock warp) use {@link pause} instead.
|
|
193
200
|
*/
|
|
194
201
|
public async stop() {
|
|
195
202
|
await this.sequencer.stop();
|
|
@@ -198,6 +205,15 @@ export class SequencerClient {
|
|
|
198
205
|
this.l1Metrics?.stop();
|
|
199
206
|
}
|
|
200
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Gracefully pauses block production, waiting for in-flight work to finish rather than interrupting it,
|
|
210
|
+
* while leaving the validator, publishers, and metrics running so the sequencer can be resumed with
|
|
211
|
+
* {@link start}. Used by tests that pause sequencers around an L1 clock warp.
|
|
212
|
+
*/
|
|
213
|
+
public async pause() {
|
|
214
|
+
await this.sequencer.pause();
|
|
215
|
+
}
|
|
216
|
+
|
|
201
217
|
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */
|
|
202
218
|
public trigger() {
|
|
203
219
|
return this.sequencer.trigger();
|
package/src/config.ts
CHANGED
|
@@ -62,6 +62,7 @@ export const DefaultSequencerConfig = {
|
|
|
62
62
|
injectFakeAttestation: false,
|
|
63
63
|
injectHighSValueAttestation: false,
|
|
64
64
|
injectUnrecoverableSignatureAttestation: false,
|
|
65
|
+
injectYParityAttestation: false,
|
|
65
66
|
fishermanMode: false,
|
|
66
67
|
shuffleAttestationOrdering: false,
|
|
67
68
|
skipPushProposedBlocksToArchiver: false,
|
|
@@ -129,7 +130,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
|
129
130
|
description:
|
|
130
131
|
'Per-block budget multiplier applied to DA gas and blob fields in place of perBlockAllocationMultiplier.' +
|
|
131
132
|
' Defaults higher than the general multiplier so the largest contract class deploy fits a single block.',
|
|
132
|
-
...
|
|
133
|
+
...floatConfigHelper(DefaultSequencerConfig.perBlockDAAllocationMultiplier),
|
|
133
134
|
},
|
|
134
135
|
redistributeCheckpointBudget: {
|
|
135
136
|
env: 'SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET',
|
|
@@ -225,6 +226,10 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
|
225
226
|
description: 'Inject an attestation with an unrecoverable signature (for testing only)',
|
|
226
227
|
...booleanConfigHelper(DefaultSequencerConfig.injectUnrecoverableSignatureAttestation),
|
|
227
228
|
},
|
|
229
|
+
injectYParityAttestation: {
|
|
230
|
+
description: 'Inject a non-proposer attestation slot in yParity form in the packed L1 tuple (for testing only)',
|
|
231
|
+
...booleanConfigHelper(DefaultSequencerConfig.injectYParityAttestation),
|
|
232
|
+
},
|
|
228
233
|
fishermanMode: {
|
|
229
234
|
env: 'FISHERMAN_MODE',
|
|
230
235
|
description:
|
|
@@ -242,7 +247,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
|
242
247
|
...booleanConfigHelper(DefaultSequencerConfig.buildCheckpointIfEmpty),
|
|
243
248
|
},
|
|
244
249
|
skipPushProposedBlocksToArchiver: {
|
|
245
|
-
description: 'Skip pushing proposed blocks to archiver (
|
|
250
|
+
description: 'Skip pushing proposed blocks to archiver (test only)',
|
|
246
251
|
...booleanConfigHelper(DefaultSequencerConfig.skipPushProposedBlocksToArchiver),
|
|
247
252
|
},
|
|
248
253
|
minBlocksForCheckpoint: {
|
|
@@ -61,7 +61,17 @@ export class FeePredictor {
|
|
|
61
61
|
const blockNumber = await this.publicClient.getBlockNumber({ cacheTime: 0 });
|
|
62
62
|
if (this.cachedL1BlockNumber === undefined || blockNumber > this.cachedL1BlockNumber) {
|
|
63
63
|
this.cachedL1BlockNumber = blockNumber;
|
|
64
|
-
|
|
64
|
+
// Reset the cached block number on failure so a transient L1 RPC error does not leave a
|
|
65
|
+
// rejected promise cached for this block, which would replay the same rejection on every
|
|
66
|
+
// subsequent call until the next L1 block arrives. Only clear it if it still points at the
|
|
67
|
+
// block this attempt was for, so a stale rejection from an older block cannot wipe a marker
|
|
68
|
+
// a newer call already advanced (which would also defeat the monotonic block-number guard).
|
|
69
|
+
this.cachedState = this.fetchState(blockNumber).catch(err => {
|
|
70
|
+
if (this.cachedL1BlockNumber === blockNumber) {
|
|
71
|
+
this.cachedL1BlockNumber = undefined;
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
});
|
|
65
75
|
}
|
|
66
76
|
return this.cachedState!;
|
|
67
77
|
}
|
|
@@ -61,10 +61,27 @@ export class FeeProviderImpl implements FeeProvider {
|
|
|
61
61
|
// Get the current block number
|
|
62
62
|
const blockNumber = await this.publicClient.getBlockNumber({ cacheTime: 0 });
|
|
63
63
|
|
|
64
|
-
// If the L1 block number has changed then chain a new promise to get the current min fees
|
|
64
|
+
// If the L1 block number has changed then chain a new promise to get the current min fees.
|
|
65
|
+
// We chain off the previous promise's settlement (via a swallowing catch) rather than its
|
|
66
|
+
// fulfillment, so a prior rejection does not short-circuit the new computation. If the new
|
|
67
|
+
// computation fails (e.g. a transient L1 RPC error), reset the cached block number so the
|
|
68
|
+
// next call recomputes instead of permanently replaying the rejected promise — otherwise a
|
|
69
|
+
// single transient failure would wedge fee estimation until the next L1 block arrives. Only
|
|
70
|
+
// clear it if it still points at the block this attempt was for, so a stale rejection from an
|
|
71
|
+
// older block cannot wipe a marker a newer call already advanced (which would also defeat the
|
|
72
|
+
// monotonic block-number guard).
|
|
65
73
|
if (this.currentL1BlockNumber === undefined || blockNumber > this.currentL1BlockNumber) {
|
|
66
74
|
this.currentL1BlockNumber = blockNumber;
|
|
67
|
-
this.currentMinFees = this.currentMinFees
|
|
75
|
+
this.currentMinFees = this.currentMinFees
|
|
76
|
+
.catch(() => undefined)
|
|
77
|
+
.then(() =>
|
|
78
|
+
this.computeCurrentMinFees().catch(err => {
|
|
79
|
+
if (this.currentL1BlockNumber === blockNumber) {
|
|
80
|
+
this.currentL1BlockNumber = undefined;
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
68
85
|
}
|
|
69
86
|
return this.currentMinFees;
|
|
70
87
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { RollupContract } from '@aztec/ethereum/contracts';
|
|
2
|
+
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
|
|
3
|
+
import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
|
|
4
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
|
+
import { tryJsonStringify } from '@aztec/foundation/json-rpc';
|
|
6
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
7
|
+
import { InboxAbi } from '@aztec/l1-artifacts';
|
|
8
|
+
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
9
|
+
|
|
10
|
+
import { decodeEventLog, getContract } from 'viem';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sends an L1-to-L2 message via the Inbox contract and returns its hash and global leaf index.
|
|
14
|
+
* Test helper copied from the end-to-end fixtures so this integration test can live in sequencer-client.
|
|
15
|
+
*/
|
|
16
|
+
export async function sendL1ToL2Message(
|
|
17
|
+
message: { recipient: AztecAddress; content: Fr; secretHash: Fr },
|
|
18
|
+
ctx: {
|
|
19
|
+
l1Client: ExtendedViemWalletClient;
|
|
20
|
+
l1ContractAddresses: Pick<L1ContractAddresses, 'inboxAddress' | 'rollupAddress'>;
|
|
21
|
+
},
|
|
22
|
+
) {
|
|
23
|
+
const logger = createLogger('sequencer-client:l1_to_l2_messaging');
|
|
24
|
+
const inbox = getContract({
|
|
25
|
+
address: ctx.l1ContractAddresses.inboxAddress.toString(),
|
|
26
|
+
abi: InboxAbi,
|
|
27
|
+
client: ctx.l1Client,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const { recipient, content, secretHash } = message;
|
|
31
|
+
|
|
32
|
+
const version = await new RollupContract(ctx.l1Client, ctx.l1ContractAddresses.rollupAddress.toString()).getVersion();
|
|
33
|
+
|
|
34
|
+
// We inject the message to Inbox
|
|
35
|
+
const txHash = await inbox.write.sendL2Message(
|
|
36
|
+
[{ actor: recipient.toString(), version: BigInt(version) }, content.toString(), secretHash.toString()],
|
|
37
|
+
{
|
|
38
|
+
gas: 1_000_000n,
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
logger.info(`L1 to L2 message sent in tx ${txHash}`);
|
|
42
|
+
|
|
43
|
+
// We check that the message was correctly injected by checking the emitted event
|
|
44
|
+
const txReceipt = await ctx.l1Client.waitForTransactionReceipt({ hash: txHash });
|
|
45
|
+
|
|
46
|
+
if (txReceipt.status !== 'success') {
|
|
47
|
+
throw new Error(`L1 to L2 message failed to be sent in tx ${txHash}. Status: ${txReceipt.status}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
logger.info(`L1 to L2 message receipt retrieved for tx ${txReceipt.transactionHash}`, txReceipt);
|
|
51
|
+
|
|
52
|
+
if (txReceipt.transactionHash !== txHash) {
|
|
53
|
+
throw new Error(`Receipt transaction hash mismatch: ${txReceipt.transactionHash} !== ${txHash}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Filter for MessageSent events from the Inbox contract by trying to decode each log
|
|
57
|
+
const messageSentLogs = txReceipt.logs
|
|
58
|
+
.filter(log => log.address.toLowerCase() === ctx.l1ContractAddresses.inboxAddress.toString().toLowerCase())
|
|
59
|
+
.map(log => {
|
|
60
|
+
try {
|
|
61
|
+
const decoded = decodeEventLog({
|
|
62
|
+
abi: InboxAbi,
|
|
63
|
+
data: log.data,
|
|
64
|
+
topics: log.topics,
|
|
65
|
+
});
|
|
66
|
+
return { log, decoded };
|
|
67
|
+
} catch {
|
|
68
|
+
return null; // Not a decodable event from this ABI
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.filter((item): item is { log: any; decoded: any } => item !== null && item.decoded.eventName === 'MessageSent');
|
|
72
|
+
|
|
73
|
+
if (messageSentLogs.length !== 1) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Wrong number of MessageSent logs found in ${txHash} transaction (got ${messageSentLogs.length} expected 1)\n${tryJsonStringify(messageSentLogs.map(item => item.log))}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// We already have the decoded event
|
|
80
|
+
const topics = messageSentLogs[0].decoded;
|
|
81
|
+
const receivedMsgHash = topics.args.hash;
|
|
82
|
+
const receivedGlobalLeafIndex = topics.args.index;
|
|
83
|
+
|
|
84
|
+
return { msgHash: Fr.fromHexString(receivedMsgHash), globalLeafIndex: new Fr(receivedGlobalLeafIndex), txReceipt };
|
|
85
|
+
}
|
|
@@ -469,7 +469,9 @@ export class SequencerPublisher {
|
|
|
469
469
|
|
|
470
470
|
if (bundleResult.kind === 'aborted') {
|
|
471
471
|
this.logDroppedInSim(bundleResult.droppedRequests);
|
|
472
|
-
void this.backupDroppedInSim(bundleResult.droppedRequests)
|
|
472
|
+
void this.backupDroppedInSim(bundleResult.droppedRequests).catch(err =>
|
|
473
|
+
this.log.error(`Failed to backup requests dropped in simulation`, err),
|
|
474
|
+
);
|
|
473
475
|
return undefined;
|
|
474
476
|
}
|
|
475
477
|
|
|
@@ -912,7 +914,11 @@ export class SequencerPublisher {
|
|
|
912
914
|
const logData = { ...checkpoint, reason };
|
|
913
915
|
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
914
916
|
|
|
915
|
-
|
|
917
|
+
// Use the exact packed tuple posted to L1 verbatim. A repack via `packAttestations` is not a
|
|
918
|
+
// byte-faithful inverse of `fromPacked` (a canonicalized yParity byte or an all-zero signature slot
|
|
919
|
+
// round-trips differently), so it would diverge from the stored `attestationsHash` and revert the
|
|
920
|
+
// invalidation.
|
|
921
|
+
const attestationsAndSigners = validationResult.verbatimAttestations;
|
|
916
922
|
|
|
917
923
|
if (reason === 'invalid-attestation') {
|
|
918
924
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AztecAddress } from '@aztec/aztec.js/addresses';
|
|
2
|
+
import { Fr } from '@aztec/aztec.js/fields';
|
|
3
|
+
import { BatchedBlob, Blob, getEthBlobEvaluationInputs, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
4
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
|
+
import { L2Block } from '@aztec/stdlib/block';
|
|
6
|
+
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
7
|
+
|
|
8
|
+
import { writeFile } from 'fs/promises';
|
|
9
|
+
|
|
10
|
+
const AZTEC_GENERATE_TEST_DATA = !!process.env.AZTEC_GENERATE_TEST_DATA;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a json object that can be used to test the solidity contract.
|
|
14
|
+
* The json object must be put into
|
|
15
|
+
*/
|
|
16
|
+
export async function writeJson(
|
|
17
|
+
fileName: string,
|
|
18
|
+
checkpointHeader: CheckpointHeader,
|
|
19
|
+
block: L2Block,
|
|
20
|
+
l1ToL2Content: Fr[],
|
|
21
|
+
blobs: Blob[],
|
|
22
|
+
batchedBlob: BatchedBlob,
|
|
23
|
+
recipientAddress: AztecAddress,
|
|
24
|
+
deployerAddress: `0x${string}`,
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
if (!AZTEC_GENERATE_TEST_DATA) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Path relative to the package.json in the end-to-end folder
|
|
30
|
+
const path = `../../l1-contracts/test/fixtures/${fileName}.json`;
|
|
31
|
+
|
|
32
|
+
const asHex = (value: Fr | Buffer | EthAddress | AztecAddress, size = 64) => {
|
|
33
|
+
const buffer = Buffer.isBuffer(value) ? value : value.toBuffer();
|
|
34
|
+
return `0x${buffer.toString('hex').padStart(size, '0')}`;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const jsonObject = {
|
|
38
|
+
populate: {
|
|
39
|
+
l1ToL2Content: l1ToL2Content.map(value => asHex(value)),
|
|
40
|
+
recipient: asHex(recipientAddress.toField()),
|
|
41
|
+
sender: deployerAddress,
|
|
42
|
+
},
|
|
43
|
+
messages: {
|
|
44
|
+
l2ToL1Messages: block.body.txEffects.flatMap(txEffect => txEffect.l2ToL1Msgs).map(value => asHex(value)),
|
|
45
|
+
},
|
|
46
|
+
checkpoint: {
|
|
47
|
+
// The json formatting in forge is a bit brittle, so we convert Fr to a number in the few values below.
|
|
48
|
+
// This should not be a problem for testing as long as the values are not larger than u32.
|
|
49
|
+
archive: asHex(block.archive.root),
|
|
50
|
+
blobCommitments: getPrefixedEthBlobCommitments(blobs),
|
|
51
|
+
batchedBlobInputs: getEthBlobEvaluationInputs(batchedBlob),
|
|
52
|
+
checkpointNumber: block.number,
|
|
53
|
+
body: `0x${block.body.toBuffer().toString('hex')}`,
|
|
54
|
+
header: {
|
|
55
|
+
lastArchiveRoot: asHex(checkpointHeader.lastArchiveRoot),
|
|
56
|
+
blockHeadersHash: asHex(checkpointHeader.blockHeadersHash),
|
|
57
|
+
blobsHash: asHex(checkpointHeader.blobsHash),
|
|
58
|
+
inHash: asHex(checkpointHeader.inHash),
|
|
59
|
+
outHash: asHex(checkpointHeader.epochOutHash),
|
|
60
|
+
slotNumber: Number(checkpointHeader.slotNumber),
|
|
61
|
+
timestamp: Number(checkpointHeader.timestamp),
|
|
62
|
+
coinbase: asHex(checkpointHeader.coinbase, 40),
|
|
63
|
+
feeRecipient: asHex(checkpointHeader.feeRecipient),
|
|
64
|
+
gasFees: {
|
|
65
|
+
feePerDaGas: Number(checkpointHeader.gasFees.feePerDaGas),
|
|
66
|
+
feePerL2Gas: Number(checkpointHeader.gasFees.feePerL2Gas),
|
|
67
|
+
},
|
|
68
|
+
totalManaUsed: checkpointHeader.totalManaUsed.toNumber(),
|
|
69
|
+
accumulatedFees: checkpointHeader.accumulatedFees.toNumber(),
|
|
70
|
+
},
|
|
71
|
+
headerHash: asHex(checkpointHeader.hash()),
|
|
72
|
+
numTxs: block.body.txEffects.length,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const output = JSON.stringify(jsonObject, null, 2);
|
|
77
|
+
await writeFile(path, output, 'utf8');
|
|
78
|
+
}
|