@aztec/sequencer-client 0.0.1-commit.a5db02d → 0.0.1-commit.aa0c64f
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/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 +18 -3
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +244 -40
- package/dest/sequencer/errors.d.ts +8 -1
- package/dest/sequencer/errors.d.ts.map +1 -1
- package/dest/sequencer/errors.js +9 -0
- package/package.json +29 -28
- 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 +268 -50
- package/src/sequencer/README.md +243 -0
- package/src/sequencer/errors.ts +15 -0
|
@@ -373,8 +373,8 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
|
|
|
373
373
|
var _dec, _dec1, _initProto;
|
|
374
374
|
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
375
375
|
import { FeeAssetPriceOracle, MULTI_CALL_3_ADDRESS, Multicall3, MulticallForwarderRevertedError, buildSimulationOverridesStateOverride } from '@aztec/ethereum/contracts';
|
|
376
|
-
import { L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
377
|
-
import { MAX_L1_TX_LIMIT, WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
|
|
376
|
+
import { L1FeeAnalyzer, captureWindowBlockFees } from '@aztec/ethereum/l1-fee-analysis';
|
|
377
|
+
import { L1TxTimeoutError, MAX_L1_TX_LIMIT, WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
|
|
378
378
|
import { FormattedViemError, formatViemError, mergeAbis, tryDecodeRevertReason, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
379
379
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
380
380
|
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
@@ -512,20 +512,87 @@ export class SequencerPublisher {
|
|
|
512
512
|
});
|
|
513
513
|
}
|
|
514
514
|
/**
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
}
|
|
515
|
+
* Logs the gas-pricing data of a failed L1 transaction at warn — so underpricing is diagnosable
|
|
516
|
+
* from logs even with no failed-tx store configured — and backs the record up to the store when
|
|
517
|
+
* one is. When captureFeeSummary is true, also records the fee data of the already-mined L1
|
|
518
|
+
* blocks in the target slot's inclusion window.
|
|
519
|
+
*/ backupFailedTx(failedTx, opts) {
|
|
521
520
|
const tx = {
|
|
522
521
|
...failedTx,
|
|
523
522
|
timestamp: Date.now()
|
|
524
523
|
};
|
|
525
524
|
// Fire and forget - don't block on backup
|
|
526
|
-
void
|
|
527
|
-
|
|
528
|
-
|
|
525
|
+
void (async ()=>{
|
|
526
|
+
try {
|
|
527
|
+
// Prefer a pre-captured summary (shared across a batch of failures in the same slot) so we
|
|
528
|
+
// don't re-read the fee window per record. A capture error must not lose the record itself.
|
|
529
|
+
const feeSummary = opts?.sharedFeeSummary ?? (opts?.captureFeeSummary ? await this.captureFeeEnvironment(opts.targetSlot).catch(()=>undefined) : undefined);
|
|
530
|
+
if (feeSummary) {
|
|
531
|
+
tx.gasInfo = {
|
|
532
|
+
...tx.gasInfo,
|
|
533
|
+
...feeSummary
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (tx.gasInfo) {
|
|
537
|
+
this.log.warn(`Gas pricing data for failed L1 tx (${tx.failureType})`, {
|
|
538
|
+
failureType: tx.failureType,
|
|
539
|
+
actions: tx.context.actions,
|
|
540
|
+
slot: tx.context.slot,
|
|
541
|
+
...tx.gasInfo,
|
|
542
|
+
...tx.timing
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
const store = await this.failedTxStore;
|
|
546
|
+
if (store) {
|
|
547
|
+
await store.saveFailedTx(tx);
|
|
548
|
+
}
|
|
549
|
+
} catch (err) {
|
|
550
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
551
|
+
}
|
|
552
|
+
})();
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Captures per-block fee data for the L1 blocks in the target slot's inclusion window (the blocks the
|
|
556
|
+
* tx could have landed in) for underpricing diagnostics. Reads only already-mined blocks, so it never
|
|
557
|
+
* waits on the chain. Safe to call off the critical path: the underlying capture never throws, and this
|
|
558
|
+
* returns undefined when there is no target slot or the window is not yet mined (e.g. an early send
|
|
559
|
+
* failure), in which case the record simply carries no window data.
|
|
560
|
+
*/ async captureFeeEnvironment(targetL2Slot) {
|
|
561
|
+
if (targetL2Slot === undefined) {
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
565
|
+
// The inclusion window is [start of slot N, start of slot N+1): all L1 blocks that can include a tx
|
|
566
|
+
// for this L2 slot. getTimestampForSlot returns seconds, matching block.timestamp.
|
|
567
|
+
const windowStartS = getTimestampForSlot(targetL2Slot, l1Constants);
|
|
568
|
+
const windowEndS = getTimestampForSlot(SlotNumber(Number(targetL2Slot) + 1), l1Constants);
|
|
569
|
+
const windowBlocks = await captureWindowBlockFees(this.l1TxUtils.client, windowStartS, windowEndS);
|
|
570
|
+
if (windowBlocks.length === 0) {
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
return {
|
|
574
|
+
windowBlocks
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
/** Computes timing info relative to the L2 slot deadline. */ computeTimingInfo(targetL2Slot) {
|
|
578
|
+
if (targetL2Slot === undefined) {
|
|
579
|
+
return undefined;
|
|
580
|
+
}
|
|
581
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
582
|
+
const slotDeadlineS = getTimestampForSlot(SlotNumber(Number(targetL2Slot) + 1), l1Constants);
|
|
583
|
+
const slotDeadlineMs = Number(slotDeadlineS) * 1000;
|
|
584
|
+
return {
|
|
585
|
+
targetL2Slot: Number(targetL2Slot),
|
|
586
|
+
slotDeadlineTimestampS: slotDeadlineS,
|
|
587
|
+
msUntilSlotDeadline: slotDeadlineMs - this.dateProvider.now()
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Builds an id for a synthetic failure record (send-error/timeout) that has no on-chain tx hash.
|
|
592
|
+
* Includes the failure time so each attempt — including retries of the same slot — is stored as its
|
|
593
|
+
* own record rather than overwriting the previous one.
|
|
594
|
+
*/ failureRecordId(actions, targetSlot) {
|
|
595
|
+
return keccak256(toHex(`${actions.join(',')}:${targetSlot ?? ''}:${Date.now()}`));
|
|
529
596
|
}
|
|
530
597
|
getRollupContract() {
|
|
531
598
|
return this.rollupContract;
|
|
@@ -653,7 +720,7 @@ export class SequencerPublisher {
|
|
|
653
720
|
const bundleResult = await this.bundleSimulator.simulate(validRequests, currentL2Slot);
|
|
654
721
|
if (bundleResult.kind === 'aborted') {
|
|
655
722
|
this.logDroppedInSim(bundleResult.droppedRequests);
|
|
656
|
-
void this.backupDroppedInSim(bundleResult.droppedRequests).catch((err)=>this.log.error(`Failed to backup requests dropped in simulation`, err));
|
|
723
|
+
void this.backupDroppedInSim(bundleResult.droppedRequests, currentL2Slot).catch((err)=>this.log.error(`Failed to backup requests dropped in simulation`, err));
|
|
657
724
|
return undefined;
|
|
658
725
|
}
|
|
659
726
|
const { requests, droppedRequests, gasLimit } = bundleResult.kind === 'fallback' ? {
|
|
@@ -673,7 +740,7 @@ export class SequencerPublisher {
|
|
|
673
740
|
requests: requests.map((request)=>request.action),
|
|
674
741
|
txConfig
|
|
675
742
|
});
|
|
676
|
-
const result = await this.forwardWithPublisherRotation(requests, txConfig, blobConfig);
|
|
743
|
+
const result = await this.forwardWithPublisherRotation(requests, txConfig, blobConfig, currentL2Slot);
|
|
677
744
|
if (result === undefined) {
|
|
678
745
|
return undefined;
|
|
679
746
|
}
|
|
@@ -692,6 +759,47 @@ export class SequencerPublisher {
|
|
|
692
759
|
} catch (err) {
|
|
693
760
|
const viemError = formatViemError(err);
|
|
694
761
|
this.log.error(`Failed to publish bundled transactions`, viemError);
|
|
762
|
+
if (err instanceof TimeoutError) {
|
|
763
|
+
const timeoutState = err instanceof L1TxTimeoutError ? err.txState : undefined;
|
|
764
|
+
void (async ()=>{
|
|
765
|
+
// The RPC is likely degraded right after a timeout, so back up without the block number
|
|
766
|
+
// rather than leaking an unhandled rejection.
|
|
767
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber().catch(()=>0n);
|
|
768
|
+
this.backupFailedTx({
|
|
769
|
+
id: this.failureRecordId(validRequests.map((r)=>r.action), currentL2Slot),
|
|
770
|
+
failureType: 'timeout',
|
|
771
|
+
request: {
|
|
772
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
773
|
+
data: '0x'
|
|
774
|
+
},
|
|
775
|
+
l1BlockNumber,
|
|
776
|
+
error: {
|
|
777
|
+
message: viemError.message,
|
|
778
|
+
name: 'TimeoutError'
|
|
779
|
+
},
|
|
780
|
+
context: {
|
|
781
|
+
actions: validRequests.map((r)=>r.action),
|
|
782
|
+
requests: validRequests.filter((r)=>r.request.to !== null).map((r)=>({
|
|
783
|
+
action: r.action,
|
|
784
|
+
to: r.request.to,
|
|
785
|
+
data: r.request.data
|
|
786
|
+
})),
|
|
787
|
+
sender: this.getSenderAddress().toString(),
|
|
788
|
+
slot: Number(currentL2Slot)
|
|
789
|
+
},
|
|
790
|
+
timing: this.computeTimingInfo(currentL2Slot),
|
|
791
|
+
gasInfo: timeoutState ? {
|
|
792
|
+
sentGasPriceLadder: timeoutState.gasPriceHistory,
|
|
793
|
+
attempts: timeoutState.attempts,
|
|
794
|
+
gasLimit: timeoutState.gasLimit,
|
|
795
|
+
nonce: timeoutState.nonce
|
|
796
|
+
} : undefined
|
|
797
|
+
}, {
|
|
798
|
+
captureFeeSummary: true,
|
|
799
|
+
targetSlot: currentL2Slot
|
|
800
|
+
});
|
|
801
|
+
})();
|
|
802
|
+
}
|
|
695
803
|
return undefined;
|
|
696
804
|
} finally{
|
|
697
805
|
try {
|
|
@@ -712,37 +820,50 @@ export class SequencerPublisher {
|
|
|
712
820
|
});
|
|
713
821
|
}
|
|
714
822
|
}
|
|
715
|
-
/** Backs up entries dropped by bundle simulation, one record per dropped action. */ async backupDroppedInSim(dropped) {
|
|
823
|
+
/** Backs up entries dropped by bundle simulation, one record per dropped action. */ async backupDroppedInSim(dropped, targetSlot) {
|
|
716
824
|
if (dropped.length === 0) {
|
|
717
825
|
return;
|
|
718
826
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
this.
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
827
|
+
// Invoked as `void backupDroppedInSim(...)` on the publish path, so it must not throw.
|
|
828
|
+
try {
|
|
829
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
830
|
+
// Every dropped entry failed in the same slot against the same L1 fee conditions, so capture
|
|
831
|
+
// the fee environment once and share it rather than re-reading the window per entry.
|
|
832
|
+
const sharedFeeSummary = await this.captureFeeEnvironment(targetSlot).catch(()=>undefined);
|
|
833
|
+
const timing = this.computeTimingInfo(targetSlot);
|
|
834
|
+
for (const { request: req } of dropped){
|
|
835
|
+
this.backupFailedTx({
|
|
836
|
+
id: keccak256(req.request.data),
|
|
837
|
+
failureType: 'simulation',
|
|
838
|
+
request: {
|
|
839
|
+
to: req.request.to,
|
|
840
|
+
data: req.request.data
|
|
841
|
+
},
|
|
842
|
+
l1BlockNumber,
|
|
843
|
+
error: {
|
|
844
|
+
message: 'Bundle entry dropped: action reverted in sim'
|
|
845
|
+
},
|
|
846
|
+
context: {
|
|
847
|
+
actions: [
|
|
848
|
+
req.action
|
|
849
|
+
],
|
|
850
|
+
sender: this.getSenderAddress().toString(),
|
|
851
|
+
slot: targetSlot !== undefined ? Number(targetSlot) : undefined
|
|
852
|
+
},
|
|
853
|
+
timing
|
|
854
|
+
}, {
|
|
855
|
+
sharedFeeSummary
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
} catch (err) {
|
|
859
|
+
this.log.warn(`Failed to back up dropped-in-sim entries`, err);
|
|
739
860
|
}
|
|
740
861
|
}
|
|
741
862
|
/**
|
|
742
863
|
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
743
864
|
* failure occurs (i.e. the tx never reached the chain).
|
|
744
865
|
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
745
|
-
*/ async forwardWithPublisherRotation(validRequests, txConfig, blobConfig) {
|
|
866
|
+
*/ async forwardWithPublisherRotation(validRequests, txConfig, blobConfig, targetSlot) {
|
|
746
867
|
if (!txConfig?.gasLimit) {
|
|
747
868
|
throw new Error('gasLimit is required for bundled transactions');
|
|
748
869
|
}
|
|
@@ -771,11 +892,13 @@ export class SequencerPublisher {
|
|
|
771
892
|
this.log.error('Forwarder transaction reverted on-chain; not rotating publisher', err, {
|
|
772
893
|
transactionHash: err.receipt.transactionHash
|
|
773
894
|
});
|
|
895
|
+
this.backupRevertFailure(validRequests, err, currentPublisher, targetSlot);
|
|
774
896
|
return undefined;
|
|
775
897
|
}
|
|
776
898
|
const viemError = formatViemError(err);
|
|
777
899
|
if (!this.getNextPublisher) {
|
|
778
900
|
this.log.error('Failed to publish bundled transactions', viemError);
|
|
901
|
+
this.backupSendFailure(validRequests, viemError, currentPublisher, targetSlot);
|
|
779
902
|
return undefined;
|
|
780
903
|
}
|
|
781
904
|
this.log.warn(`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`, viemError);
|
|
@@ -786,12 +909,85 @@ export class SequencerPublisher {
|
|
|
786
909
|
this.log.error(`All available publishers exhausted (tried ${triedAddresses.length}), failed to publish bundled transactions`, viemError, {
|
|
787
910
|
triedAddresses: triedAddresses.map((a)=>a.toString())
|
|
788
911
|
});
|
|
912
|
+
this.backupSendFailure(validRequests, viemError, currentPublisher, targetSlot);
|
|
789
913
|
return undefined;
|
|
790
914
|
}
|
|
791
915
|
currentPublisher = nextPublisher;
|
|
792
916
|
}
|
|
793
917
|
}
|
|
794
918
|
}
|
|
919
|
+
/** Backs up an on-chain revert failure to the failed tx store. */ backupRevertFailure(requests, err, publisher, targetSlot) {
|
|
920
|
+
this.backupFailedTx({
|
|
921
|
+
id: err.receipt.transactionHash,
|
|
922
|
+
failureType: 'revert',
|
|
923
|
+
request: {
|
|
924
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
925
|
+
data: '0x'
|
|
926
|
+
},
|
|
927
|
+
l1BlockNumber: err.receipt.blockNumber,
|
|
928
|
+
receipt: {
|
|
929
|
+
transactionHash: err.receipt.transactionHash,
|
|
930
|
+
blockNumber: err.receipt.blockNumber,
|
|
931
|
+
gasUsed: err.receipt.gasUsed,
|
|
932
|
+
status: 'reverted'
|
|
933
|
+
},
|
|
934
|
+
error: {
|
|
935
|
+
message: err.message,
|
|
936
|
+
name: err.name
|
|
937
|
+
},
|
|
938
|
+
context: {
|
|
939
|
+
actions: requests.map((r)=>r.action),
|
|
940
|
+
requests: requests.filter((r)=>r.request.to !== null).map((r)=>({
|
|
941
|
+
action: r.action,
|
|
942
|
+
to: r.request.to,
|
|
943
|
+
data: r.request.data
|
|
944
|
+
})),
|
|
945
|
+
sender: publisher.getSenderAddress().toString(),
|
|
946
|
+
slot: targetSlot !== undefined ? Number(targetSlot) : undefined
|
|
947
|
+
},
|
|
948
|
+
gasInfo: err.txState ? {
|
|
949
|
+
sentGasPrice: err.txState.gasPrice,
|
|
950
|
+
gasLimit: err.txState.gasLimit,
|
|
951
|
+
nonce: err.txState.nonce
|
|
952
|
+
} : undefined,
|
|
953
|
+
timing: this.computeTimingInfo(targetSlot)
|
|
954
|
+
}, {
|
|
955
|
+
captureFeeSummary: true,
|
|
956
|
+
targetSlot
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
/** Backs up a send failure (tx never reached chain) to the failed tx store. */ backupSendFailure(requests, error, publisher, targetSlot) {
|
|
960
|
+
// If we can't get the block number, still back up without it.
|
|
961
|
+
void this.l1TxUtils.getBlockNumber().catch(()=>0n).then((l1BlockNumber)=>{
|
|
962
|
+
this.backupFailedTx({
|
|
963
|
+
id: this.failureRecordId(requests.map((r)=>r.action), targetSlot),
|
|
964
|
+
failureType: 'send-error',
|
|
965
|
+
request: {
|
|
966
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
967
|
+
data: '0x'
|
|
968
|
+
},
|
|
969
|
+
l1BlockNumber,
|
|
970
|
+
error: {
|
|
971
|
+
message: error.message,
|
|
972
|
+
name: 'name' in error ? error.name : undefined
|
|
973
|
+
},
|
|
974
|
+
context: {
|
|
975
|
+
actions: requests.map((r)=>r.action),
|
|
976
|
+
requests: requests.filter((r)=>r.request.to !== null).map((r)=>({
|
|
977
|
+
action: r.action,
|
|
978
|
+
to: r.request.to,
|
|
979
|
+
data: r.request.data
|
|
980
|
+
})),
|
|
981
|
+
sender: publisher.getSenderAddress().toString(),
|
|
982
|
+
slot: targetSlot !== undefined ? Number(targetSlot) : undefined
|
|
983
|
+
},
|
|
984
|
+
timing: this.computeTimingInfo(targetSlot)
|
|
985
|
+
}, {
|
|
986
|
+
captureFeeSummary: true,
|
|
987
|
+
targetSlot
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
}
|
|
795
991
|
/*
|
|
796
992
|
* Schedules sending all enqueued requests at (or after) the start of the given L2 slot.
|
|
797
993
|
*/ async sendRequestsAt(targetSlot) {
|
|
@@ -1040,9 +1236,9 @@ export class SequencerPublisher {
|
|
|
1040
1236
|
request: {
|
|
1041
1237
|
to: request.to,
|
|
1042
1238
|
data: request.data,
|
|
1043
|
-
value: request.value
|
|
1239
|
+
value: request.value
|
|
1044
1240
|
},
|
|
1045
|
-
l1BlockNumber
|
|
1241
|
+
l1BlockNumber,
|
|
1046
1242
|
error: {
|
|
1047
1243
|
message: viemError.message,
|
|
1048
1244
|
name: viemError.name
|
|
@@ -1053,7 +1249,11 @@ export class SequencerPublisher {
|
|
|
1053
1249
|
],
|
|
1054
1250
|
checkpointNumber,
|
|
1055
1251
|
sender: this.getSenderAddress().toString()
|
|
1056
|
-
}
|
|
1252
|
+
},
|
|
1253
|
+
timing: this.computeTimingInfo(this.getCurrentL2Slot())
|
|
1254
|
+
}, {
|
|
1255
|
+
captureFeeSummary: true,
|
|
1256
|
+
targetSlot: this.getCurrentL2Slot()
|
|
1057
1257
|
});
|
|
1058
1258
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
|
|
1059
1259
|
cause: viemError
|
|
@@ -1440,7 +1640,7 @@ export class SequencerPublisher {
|
|
|
1440
1640
|
data: validateBlobsData
|
|
1441
1641
|
},
|
|
1442
1642
|
blobData: encodedData.blobs.map((b)=>toHex(b.data)),
|
|
1443
|
-
l1BlockNumber
|
|
1643
|
+
l1BlockNumber,
|
|
1444
1644
|
error: {
|
|
1445
1645
|
message: viemError.message,
|
|
1446
1646
|
name: viemError.name
|
|
@@ -1450,7 +1650,11 @@ export class SequencerPublisher {
|
|
|
1450
1650
|
'validate-blobs'
|
|
1451
1651
|
],
|
|
1452
1652
|
sender: this.getSenderAddress().toString()
|
|
1453
|
-
}
|
|
1653
|
+
},
|
|
1654
|
+
timing: this.computeTimingInfo(this.getCurrentL2Slot())
|
|
1655
|
+
}, {
|
|
1656
|
+
captureFeeSummary: true,
|
|
1657
|
+
targetSlot: this.getCurrentL2Slot()
|
|
1454
1658
|
});
|
|
1455
1659
|
throw new Error('Failed to validate blobs');
|
|
1456
1660
|
});
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import type { SequencerState } from './utils.js';
|
|
2
|
+
export declare class SequencerTooSlowError extends Error {
|
|
3
|
+
readonly proposedState: SequencerState;
|
|
4
|
+
readonly maxAllowedTime: number;
|
|
5
|
+
readonly currentTime: number;
|
|
6
|
+
constructor(proposedState: SequencerState, maxAllowedTime: number, currentTime: number);
|
|
7
|
+
}
|
|
1
8
|
export declare class SequencerInterruptedError extends Error {
|
|
2
9
|
constructor();
|
|
3
10
|
}
|
|
4
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VxdWVuY2VyL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxjQUFjLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFFakQscUJBQWEscUJBQXNCLFNBQVEsS0FBSzthQUU1QixhQUFhLEVBQUUsY0FBYzthQUM3QixjQUFjLEVBQUUsTUFBTTthQUN0QixXQUFXLEVBQUUsTUFBTTtJQUhyQyxZQUNrQixhQUFhLEVBQUUsY0FBYyxFQUM3QixjQUFjLEVBQUUsTUFBTSxFQUN0QixXQUFXLEVBQUUsTUFBTSxFQU1wQztDQUNGO0FBRUQscUJBQWEseUJBQTBCLFNBQVEsS0FBSztJQUNsRCxjQUdDO0NBQ0YifQ==
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/sequencer/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,cAGC;CACF"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/sequencer/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,qBAAa,qBAAsB,SAAQ,KAAK;aAE5B,aAAa,EAAE,cAAc;aAC7B,cAAc,EAAE,MAAM;aACtB,WAAW,EAAE,MAAM;IAHrC,YACkB,aAAa,EAAE,cAAc,EAC7B,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EAMpC;CACF;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,cAGC;CACF"}
|
package/dest/sequencer/errors.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export class SequencerTooSlowError extends Error {
|
|
2
|
+
proposedState;
|
|
3
|
+
maxAllowedTime;
|
|
4
|
+
currentTime;
|
|
5
|
+
constructor(proposedState, maxAllowedTime, currentTime){
|
|
6
|
+
super(`Too far into slot for ${proposedState} (time into slot ${currentTime}s greater than ${maxAllowedTime}s allowance)`), this.proposedState = proposedState, this.maxAllowedTime = maxAllowedTime, this.currentTime = currentTime;
|
|
7
|
+
this.name = 'SequencerTooSlowError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
1
10
|
export class SequencerInterruptedError extends Error {
|
|
2
11
|
constructor(){
|
|
3
12
|
super(`Sequencer was interrupted`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/sequencer-client",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.aa0c64f",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -27,37 +27,38 @@
|
|
|
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": "0.0.1-commit.
|
|
31
|
-
"@aztec/bb-prover": "0.0.1-commit.
|
|
32
|
-
"@aztec/blob-client": "0.0.1-commit.
|
|
33
|
-
"@aztec/blob-lib": "0.0.1-commit.
|
|
34
|
-
"@aztec/constants": "0.0.1-commit.
|
|
35
|
-
"@aztec/epoch-cache": "0.0.1-commit.
|
|
36
|
-
"@aztec/ethereum": "0.0.1-commit.
|
|
37
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
38
|
-
"@aztec/l1-artifacts": "0.0.1-commit.
|
|
39
|
-
"@aztec/node-keystore": "0.0.1-commit.
|
|
40
|
-
"@aztec/noir-acvm_js": "0.0.1-commit.
|
|
41
|
-
"@aztec/noir-contracts.js": "0.0.1-commit.
|
|
42
|
-
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.
|
|
43
|
-
"@aztec/noir-types": "0.0.1-commit.
|
|
44
|
-
"@aztec/p2p": "0.0.1-commit.
|
|
45
|
-
"@aztec/protocol-contracts": "0.0.1-commit.
|
|
46
|
-
"@aztec/prover-client": "0.0.1-commit.
|
|
47
|
-
"@aztec/simulator": "0.0.1-commit.
|
|
48
|
-
"@aztec/slasher": "0.0.1-commit.
|
|
49
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
50
|
-
"@aztec/telemetry-client": "0.0.1-commit.
|
|
51
|
-
"@aztec/validator-client": "0.0.1-commit.
|
|
52
|
-
"@aztec/validator-ha-signer": "0.0.1-commit.
|
|
53
|
-
"@aztec/world-state": "0.0.1-commit.
|
|
30
|
+
"@aztec/aztec.js": "0.0.1-commit.aa0c64f",
|
|
31
|
+
"@aztec/bb-prover": "0.0.1-commit.aa0c64f",
|
|
32
|
+
"@aztec/blob-client": "0.0.1-commit.aa0c64f",
|
|
33
|
+
"@aztec/blob-lib": "0.0.1-commit.aa0c64f",
|
|
34
|
+
"@aztec/constants": "0.0.1-commit.aa0c64f",
|
|
35
|
+
"@aztec/epoch-cache": "0.0.1-commit.aa0c64f",
|
|
36
|
+
"@aztec/ethereum": "0.0.1-commit.aa0c64f",
|
|
37
|
+
"@aztec/foundation": "0.0.1-commit.aa0c64f",
|
|
38
|
+
"@aztec/l1-artifacts": "0.0.1-commit.aa0c64f",
|
|
39
|
+
"@aztec/node-keystore": "0.0.1-commit.aa0c64f",
|
|
40
|
+
"@aztec/noir-acvm_js": "0.0.1-commit.aa0c64f",
|
|
41
|
+
"@aztec/noir-contracts.js": "0.0.1-commit.aa0c64f",
|
|
42
|
+
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.aa0c64f",
|
|
43
|
+
"@aztec/noir-types": "0.0.1-commit.aa0c64f",
|
|
44
|
+
"@aztec/p2p": "0.0.1-commit.aa0c64f",
|
|
45
|
+
"@aztec/protocol-contracts": "0.0.1-commit.aa0c64f",
|
|
46
|
+
"@aztec/prover-client": "0.0.1-commit.aa0c64f",
|
|
47
|
+
"@aztec/simulator": "0.0.1-commit.aa0c64f",
|
|
48
|
+
"@aztec/slasher": "0.0.1-commit.aa0c64f",
|
|
49
|
+
"@aztec/stdlib": "0.0.1-commit.aa0c64f",
|
|
50
|
+
"@aztec/telemetry-client": "0.0.1-commit.aa0c64f",
|
|
51
|
+
"@aztec/validator-client": "0.0.1-commit.aa0c64f",
|
|
52
|
+
"@aztec/validator-ha-signer": "0.0.1-commit.aa0c64f",
|
|
53
|
+
"@aztec/world-state": "0.0.1-commit.aa0c64f",
|
|
54
54
|
"lodash.chunk": "^4.2.0",
|
|
55
55
|
"tslib": "^2.4.0",
|
|
56
|
-
"viem": "npm:@aztec/viem@2.38.2"
|
|
56
|
+
"viem": "npm:@aztec/viem@2.38.2",
|
|
57
|
+
"zod": "^4"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
|
-
"@aztec/archiver": "0.0.1-commit.
|
|
60
|
-
"@aztec/kv-store": "0.0.1-commit.
|
|
60
|
+
"@aztec/archiver": "0.0.1-commit.aa0c64f",
|
|
61
|
+
"@aztec/kv-store": "0.0.1-commit.aa0c64f",
|
|
61
62
|
"@electric-sql/pglite": "^0.3.14",
|
|
62
63
|
"@jest/globals": "^30.0.0",
|
|
63
64
|
"@types/jest": "^30.0.0",
|
|
@@ -1,33 +1,40 @@
|
|
|
1
|
+
import type { WindowBlockFees } from '@aztec/ethereum/l1-fee-analysis';
|
|
2
|
+
import type { GasPrice } from '@aztec/ethereum/l1-tx-utils';
|
|
1
3
|
import type { Branded } from '@aztec/foundation/branded-types';
|
|
4
|
+
import { type ZodFor, schemas } from '@aztec/foundation/schemas';
|
|
2
5
|
|
|
3
6
|
import type { Hex } from 'viem';
|
|
7
|
+
import { z } from 'zod';
|
|
4
8
|
|
|
5
9
|
/** URI pointing to a stored failed L1 transaction. */
|
|
6
10
|
export type FailedL1TxUri = Branded<string, 'FailedL1TxUri'>;
|
|
7
11
|
|
|
8
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* A failed L1 transaction captured for debugging and replay. Serialized with jsonStringify (bigints
|
|
14
|
+
* become decimal strings on disk) and parsed back via FailedL1TxSchema.
|
|
15
|
+
*/
|
|
9
16
|
export type FailedL1Tx = {
|
|
10
17
|
/** Tx hash (for reverts) or keccak256(request.data) (for simulation/send failures). */
|
|
11
18
|
id: Hex;
|
|
12
19
|
/** Unix timestamp (ms) when failure occurred. */
|
|
13
20
|
timestamp: number;
|
|
14
|
-
/**
|
|
15
|
-
failureType: 'simulation' | 'revert' | 'send-error';
|
|
21
|
+
/** How the failure occurred. */
|
|
22
|
+
failureType: 'simulation' | 'revert' | 'send-error' | 'timeout';
|
|
16
23
|
/** The actual L1 transaction for replay (multicall-encoded for bundled txs). */
|
|
17
24
|
request: {
|
|
18
25
|
to: Hex;
|
|
19
26
|
data: Hex;
|
|
20
|
-
value?:
|
|
27
|
+
value?: bigint;
|
|
21
28
|
};
|
|
22
29
|
/** Raw blob data as hex for replay. */
|
|
23
30
|
blobData?: Hex[];
|
|
24
31
|
/** L1 block number at time of failure (simulation target or receipt block). */
|
|
25
|
-
l1BlockNumber:
|
|
32
|
+
l1BlockNumber: bigint;
|
|
26
33
|
/** Receipt info (present only for on-chain reverts). */
|
|
27
34
|
receipt?: {
|
|
28
35
|
transactionHash: Hex;
|
|
29
|
-
blockNumber:
|
|
30
|
-
gasUsed:
|
|
36
|
+
blockNumber: bigint;
|
|
37
|
+
gasUsed: bigint;
|
|
31
38
|
status: 'reverted';
|
|
32
39
|
};
|
|
33
40
|
/** Error information. */
|
|
@@ -46,8 +53,108 @@ export type FailedL1Tx = {
|
|
|
46
53
|
slot?: number;
|
|
47
54
|
sender: Hex;
|
|
48
55
|
};
|
|
56
|
+
/** Gas pricing info at time of failure for underpricing diagnosis. */
|
|
57
|
+
gasInfo?: {
|
|
58
|
+
/** Gas prices the tx was sent with (present for revert/send-error/timeout, not simulation). */
|
|
59
|
+
sentGasPrice?: GasPrice;
|
|
60
|
+
/** Gas limit used or estimated. */
|
|
61
|
+
gasLimit?: bigint;
|
|
62
|
+
/** Nonce used for the sent tx. */
|
|
63
|
+
nonce?: number;
|
|
64
|
+
/**
|
|
65
|
+
* For timeouts: the escalating gas prices used across the initial send and each speed-up retry,
|
|
66
|
+
* in order. Compare against windowBlocks[].minIncludedPriorityFee to see if any attempt cleared the bar.
|
|
67
|
+
*/
|
|
68
|
+
sentGasPriceLadder?: GasPrice[];
|
|
69
|
+
/** Number of send attempts (initial send + speed-ups). */
|
|
70
|
+
attempts?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Per-block fee data for the L1 blocks the tx could have been included in (the target L2 slot's
|
|
73
|
+
* inclusion window), in chronological order. Compare sentGasPrice against these to see whether
|
|
74
|
+
* the tx was underpriced for each block it competed for. May be a partial or empty list if the
|
|
75
|
+
* window was not yet mined when the failure was recorded (e.g. an early send failure).
|
|
76
|
+
*/
|
|
77
|
+
windowBlocks?: WindowBlockFees[];
|
|
78
|
+
};
|
|
79
|
+
/** Timing info relative to the L2 slot. */
|
|
80
|
+
timing?: {
|
|
81
|
+
/** The target L2 slot this tx was for. */
|
|
82
|
+
targetL2Slot?: number;
|
|
83
|
+
/** Unix timestamp (seconds) when the target slot ends. */
|
|
84
|
+
slotDeadlineTimestampS?: bigint;
|
|
85
|
+
/** Milliseconds remaining until the slot deadline. Negative = past deadline. */
|
|
86
|
+
msUntilSlotDeadline?: number;
|
|
87
|
+
};
|
|
49
88
|
};
|
|
50
89
|
|
|
90
|
+
const hexSchema = schemas.HexStringWith0x;
|
|
91
|
+
|
|
92
|
+
const gasPriceSchema = z.object({
|
|
93
|
+
maxFeePerGas: schemas.BigInt,
|
|
94
|
+
maxPriorityFeePerGas: schemas.BigInt,
|
|
95
|
+
maxFeePerBlobGas: schemas.BigInt.optional(),
|
|
96
|
+
}) satisfies ZodFor<GasPrice>;
|
|
97
|
+
|
|
98
|
+
const windowBlockFeesSchema = z.object({
|
|
99
|
+
blockNumber: schemas.BigInt,
|
|
100
|
+
timestamp: schemas.BigInt,
|
|
101
|
+
baseFeePerGas: schemas.BigInt,
|
|
102
|
+
p75PriorityFee: schemas.BigInt,
|
|
103
|
+
minIncludedPriorityFee: schemas.BigInt,
|
|
104
|
+
blockBlobsFull: z.boolean(),
|
|
105
|
+
includedBlobCount: z.number(),
|
|
106
|
+
}) satisfies ZodFor<WindowBlockFees>;
|
|
107
|
+
|
|
108
|
+
/** Parses a stored failed-tx record, coercing the on-disk decimal strings back to bigints. */
|
|
109
|
+
export const FailedL1TxSchema: ZodFor<FailedL1Tx> = z.object({
|
|
110
|
+
id: hexSchema,
|
|
111
|
+
timestamp: z.number(),
|
|
112
|
+
failureType: z.enum(['simulation', 'revert', 'send-error', 'timeout']),
|
|
113
|
+
request: z.object({
|
|
114
|
+
to: hexSchema,
|
|
115
|
+
data: hexSchema,
|
|
116
|
+
value: schemas.BigInt.optional(),
|
|
117
|
+
}),
|
|
118
|
+
blobData: z.array(hexSchema).optional(),
|
|
119
|
+
l1BlockNumber: schemas.BigInt,
|
|
120
|
+
receipt: z
|
|
121
|
+
.object({
|
|
122
|
+
transactionHash: hexSchema,
|
|
123
|
+
blockNumber: schemas.BigInt,
|
|
124
|
+
gasUsed: schemas.BigInt,
|
|
125
|
+
status: z.literal('reverted'),
|
|
126
|
+
})
|
|
127
|
+
.optional(),
|
|
128
|
+
error: z.object({
|
|
129
|
+
message: z.string(),
|
|
130
|
+
name: z.string().optional(),
|
|
131
|
+
}),
|
|
132
|
+
context: z.object({
|
|
133
|
+
actions: z.array(z.string()),
|
|
134
|
+
requests: z.array(z.object({ action: z.string(), to: hexSchema, data: hexSchema })).optional(),
|
|
135
|
+
checkpointNumber: z.number().optional(),
|
|
136
|
+
slot: z.number().optional(),
|
|
137
|
+
sender: hexSchema,
|
|
138
|
+
}),
|
|
139
|
+
gasInfo: z
|
|
140
|
+
.object({
|
|
141
|
+
sentGasPrice: gasPriceSchema.optional(),
|
|
142
|
+
gasLimit: schemas.BigInt.optional(),
|
|
143
|
+
nonce: z.number().optional(),
|
|
144
|
+
sentGasPriceLadder: z.array(gasPriceSchema).optional(),
|
|
145
|
+
attempts: z.number().optional(),
|
|
146
|
+
windowBlocks: z.array(windowBlockFeesSchema).optional(),
|
|
147
|
+
})
|
|
148
|
+
.optional(),
|
|
149
|
+
timing: z
|
|
150
|
+
.object({
|
|
151
|
+
targetL2Slot: z.number().optional(),
|
|
152
|
+
slotDeadlineTimestampS: schemas.BigInt.optional(),
|
|
153
|
+
msUntilSlotDeadline: z.number().optional(),
|
|
154
|
+
})
|
|
155
|
+
.optional(),
|
|
156
|
+
});
|
|
157
|
+
|
|
51
158
|
/** Store for failed L1 transactions for debugging purposes. */
|
|
52
159
|
export interface L1TxFailedStore {
|
|
53
160
|
/** Saves a failed transaction and returns its URI. */
|