@aztec/sequencer-client 0.0.1-commit.6d3c34e → 0.0.1-commit.7035c9bd6
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 +12 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +56 -17
- package/dest/config.d.ts +26 -7
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +47 -30
- package/dest/global_variable_builder/global_builder.d.ts +2 -4
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +7 -6
- 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 +35 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +106 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +27 -2
- package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-metrics.js +12 -4
- package/dest/publisher/sequencer-publisher.d.ts +30 -10
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +362 -56
- package/dest/sequencer/checkpoint_proposal_job.d.ts +42 -11
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +322 -122
- package/dest/sequencer/checkpoint_voter.d.ts +3 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +34 -10
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/index.d.ts +1 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -1
- package/dest/sequencer/metrics.d.ts +21 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +122 -30
- package/dest/sequencer/sequencer.d.ts +43 -20
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +151 -82
- package/dest/sequencer/timetable.d.ts +4 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +7 -11
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/index.d.ts +3 -5
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +23 -19
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +67 -38
- package/dest/test/utils.d.ts +8 -8
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +12 -11
- package/package.json +29 -28
- package/src/client/sequencer-client.ts +77 -18
- package/src/config.ts +66 -41
- package/src/global_variable_builder/global_builder.ts +6 -5
- package/src/index.ts +1 -6
- package/src/publisher/config.ts +121 -43
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +38 -6
- package/src/publisher/sequencer-publisher-metrics.ts +7 -3
- package/src/publisher/sequencer-publisher.ts +360 -69
- package/src/sequencer/checkpoint_proposal_job.ts +449 -142
- package/src/sequencer/checkpoint_voter.ts +32 -7
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/index.ts +0 -1
- package/src/sequencer/metrics.ts +138 -32
- package/src/sequencer/sequencer.ts +200 -91
- package/src/sequencer/timetable.ts +13 -12
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +122 -78
- package/src/test/utils.ts +24 -14
- package/dest/sequencer/block_builder.d.ts +0 -26
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -129
- package/src/sequencer/block_builder.ts +0 -216
|
@@ -372,24 +372,28 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
|
|
|
372
372
|
}
|
|
373
373
|
var _dec, _dec1, _dec2, _initProto;
|
|
374
374
|
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
375
|
-
import { MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
|
|
375
|
+
import { FeeAssetPriceOracle, MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
|
|
376
376
|
import { L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
377
|
-
import { WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
|
|
378
|
-
import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
377
|
+
import { MAX_L1_TX_LIMIT, WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
|
|
378
|
+
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
379
379
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
380
380
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
381
381
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
382
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
382
383
|
import { pick } from '@aztec/foundation/collection';
|
|
384
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
383
385
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
384
386
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
385
387
|
import { createLogger } from '@aztec/foundation/log';
|
|
388
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
386
389
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
387
390
|
import { Timer } from '@aztec/foundation/timer';
|
|
388
391
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
389
392
|
import { encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
390
393
|
import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
391
394
|
import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
392
|
-
import { encodeFunctionData, toHex } from 'viem';
|
|
395
|
+
import { encodeFunctionData, keccak256, multicall3Abi, toHex } from 'viem';
|
|
396
|
+
import { createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
393
397
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
394
398
|
export const Actions = [
|
|
395
399
|
'invalidate-by-invalid-attestation',
|
|
@@ -429,19 +433,20 @@ export class SequencerPublisher {
|
|
|
429
433
|
interrupted;
|
|
430
434
|
metrics;
|
|
431
435
|
epochCache;
|
|
436
|
+
failedTxStore;
|
|
432
437
|
governanceLog;
|
|
433
438
|
slashingLog;
|
|
434
439
|
lastActions;
|
|
435
440
|
isPayloadEmptyCache;
|
|
441
|
+
payloadProposedCache;
|
|
436
442
|
log;
|
|
437
443
|
ethereumSlotDuration;
|
|
444
|
+
aztecSlotDuration;
|
|
438
445
|
blobClient;
|
|
439
446
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
|
|
447
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */ getNextPublisher;
|
|
440
448
|
/** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
|
|
441
|
-
|
|
442
|
-
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
443
|
-
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
444
|
-
static PROPOSE_GAS_GUESS = 12_000_000n;
|
|
449
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
|
|
445
450
|
// A CALL to a cold address is 2700 gas
|
|
446
451
|
static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
447
452
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
@@ -460,9 +465,11 @@ export class SequencerPublisher {
|
|
|
460
465
|
this.slashingLog = createLogger('sequencer:publisher:slashing');
|
|
461
466
|
this.lastActions = {};
|
|
462
467
|
this.isPayloadEmptyCache = new Map();
|
|
468
|
+
this.payloadProposedCache = new Set();
|
|
463
469
|
this.requests = [];
|
|
464
470
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
465
471
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
472
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
466
473
|
this.epochCache = deps.epochCache;
|
|
467
474
|
this.lastActions = deps.lastActions;
|
|
468
475
|
this.blobClient = deps.blobClient;
|
|
@@ -470,6 +477,7 @@ export class SequencerPublisher {
|
|
|
470
477
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
471
478
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
472
479
|
this.l1TxUtils = deps.l1TxUtils;
|
|
480
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
473
481
|
this.rollupContract = deps.rollupContract;
|
|
474
482
|
this.govProposerContract = deps.governanceProposerContract;
|
|
475
483
|
this.slashingProposerContract = deps.slashingProposerContract;
|
|
@@ -483,10 +491,36 @@ export class SequencerPublisher {
|
|
|
483
491
|
if (config.fishermanMode) {
|
|
484
492
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
|
|
485
493
|
}
|
|
494
|
+
// Initialize fee asset price oracle
|
|
495
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(this.l1TxUtils.client, this.rollupContract, createLogger('sequencer:publisher:price-oracle'));
|
|
496
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
497
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
501
|
+
* Does nothing if no store is configured.
|
|
502
|
+
*/ backupFailedTx(failedTx) {
|
|
503
|
+
if (!this.failedTxStore) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const tx = {
|
|
507
|
+
...failedTx,
|
|
508
|
+
timestamp: Date.now()
|
|
509
|
+
};
|
|
510
|
+
// Fire and forget - don't block on backup
|
|
511
|
+
void this.failedTxStore.then((store)=>store?.saveFailedTx(tx)).catch((err)=>{
|
|
512
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
513
|
+
});
|
|
486
514
|
}
|
|
487
515
|
getRollupContract() {
|
|
488
516
|
return this.rollupContract;
|
|
489
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Gets the fee asset price modifier from the oracle.
|
|
520
|
+
* Returns 0n if the oracle query fails.
|
|
521
|
+
*/ getFeeAssetPriceModifier() {
|
|
522
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
523
|
+
}
|
|
490
524
|
getSenderAddress() {
|
|
491
525
|
return this.l1TxUtils.getSenderAddress();
|
|
492
526
|
}
|
|
@@ -505,7 +539,7 @@ export class SequencerPublisher {
|
|
|
505
539
|
this.requests.push(request);
|
|
506
540
|
}
|
|
507
541
|
getCurrentL2Slot() {
|
|
508
|
-
return this.epochCache.
|
|
542
|
+
return this.epochCache.getSlotNow();
|
|
509
543
|
}
|
|
510
544
|
/**
|
|
511
545
|
* Clears all pending requests without sending them.
|
|
@@ -544,7 +578,7 @@ export class SequencerPublisher {
|
|
|
544
578
|
// Get the transaction requests
|
|
545
579
|
const l1Requests = requestsToAnalyze.map((r)=>r.request);
|
|
546
580
|
// Start the analysis
|
|
547
|
-
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit :
|
|
581
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT, l1Requests, blobConfig, onComplete);
|
|
548
582
|
this.log.info('Started L1 fee analysis', {
|
|
549
583
|
analysisId,
|
|
550
584
|
l2SlotNumber: l2SlotNumber.toString(),
|
|
@@ -594,15 +628,24 @@ export class SequencerPublisher {
|
|
|
594
628
|
// @note - we can only have one blob config per bundle
|
|
595
629
|
// find requests with gas and blob configs
|
|
596
630
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
597
|
-
const gasConfigs =
|
|
598
|
-
const blobConfigs =
|
|
631
|
+
const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
|
|
632
|
+
const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
|
|
599
633
|
if (blobConfigs.length > 1) {
|
|
600
634
|
throw new Error('Multiple blob configs found');
|
|
601
635
|
}
|
|
602
636
|
const blobConfig = blobConfigs[0];
|
|
603
637
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
604
638
|
const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
|
|
605
|
-
|
|
639
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
640
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
641
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
642
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
643
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
644
|
+
requested: gasLimit,
|
|
645
|
+
capped: maxGas
|
|
646
|
+
});
|
|
647
|
+
gasLimit = maxGas;
|
|
648
|
+
}
|
|
606
649
|
const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
|
|
607
650
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
|
|
608
651
|
const txConfig = {
|
|
@@ -613,12 +656,34 @@ export class SequencerPublisher {
|
|
|
613
656
|
// This ensures the committee gets precomputed correctly
|
|
614
657
|
validRequests.sort((a, b)=>compareActions(a.action, b.action));
|
|
615
658
|
try {
|
|
659
|
+
// Capture context for failed tx backup before sending
|
|
660
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
661
|
+
const multicallData = encodeFunctionData({
|
|
662
|
+
abi: multicall3Abi,
|
|
663
|
+
functionName: 'aggregate3',
|
|
664
|
+
args: [
|
|
665
|
+
validRequests.map((r)=>({
|
|
666
|
+
target: r.request.to,
|
|
667
|
+
callData: r.request.data,
|
|
668
|
+
allowFailure: true
|
|
669
|
+
}))
|
|
670
|
+
]
|
|
671
|
+
});
|
|
672
|
+
const blobDataHex = blobConfig?.blobs?.map((b)=>toHex(b));
|
|
673
|
+
const txContext = {
|
|
674
|
+
multicallData,
|
|
675
|
+
blobData: blobDataHex,
|
|
676
|
+
l1BlockNumber
|
|
677
|
+
};
|
|
616
678
|
this.log.debug('Forwarding transactions', {
|
|
617
679
|
validRequests: validRequests.map((request)=>request.action),
|
|
618
680
|
txConfig
|
|
619
681
|
});
|
|
620
|
-
const result = await
|
|
621
|
-
|
|
682
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
683
|
+
if (result === undefined) {
|
|
684
|
+
return undefined;
|
|
685
|
+
}
|
|
686
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result, txContext);
|
|
622
687
|
return {
|
|
623
688
|
result,
|
|
624
689
|
expiredActions,
|
|
@@ -638,17 +703,83 @@ export class SequencerPublisher {
|
|
|
638
703
|
}
|
|
639
704
|
}
|
|
640
705
|
}
|
|
641
|
-
|
|
706
|
+
/**
|
|
707
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
708
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
709
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
710
|
+
*/ async forwardWithPublisherRotation(validRequests, txConfig, blobConfig) {
|
|
711
|
+
const triedAddresses = [];
|
|
712
|
+
let currentPublisher = this.l1TxUtils;
|
|
713
|
+
while(true){
|
|
714
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
715
|
+
try {
|
|
716
|
+
const result = await Multicall3.forward(validRequests.map((r)=>r.request), currentPublisher, txConfig, blobConfig, this.rollupContract.address, this.log);
|
|
717
|
+
this.l1TxUtils = currentPublisher;
|
|
718
|
+
return result;
|
|
719
|
+
} catch (err) {
|
|
720
|
+
if (err instanceof TimeoutError) {
|
|
721
|
+
throw err;
|
|
722
|
+
}
|
|
723
|
+
const viemError = formatViemError(err);
|
|
724
|
+
if (!this.getNextPublisher) {
|
|
725
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
726
|
+
return undefined;
|
|
727
|
+
}
|
|
728
|
+
this.log.warn(`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`, viemError);
|
|
729
|
+
const nextPublisher = await this.getNextPublisher([
|
|
730
|
+
...triedAddresses
|
|
731
|
+
]);
|
|
732
|
+
if (!nextPublisher) {
|
|
733
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
734
|
+
return undefined;
|
|
735
|
+
}
|
|
736
|
+
currentPublisher = nextPublisher;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
callbackBundledTransactions(requests, result, txContext) {
|
|
642
741
|
const actionsListStr = requests.map((r)=>r.action).join(', ');
|
|
643
742
|
if (result instanceof FormattedViemError) {
|
|
644
743
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
744
|
+
this.backupFailedTx({
|
|
745
|
+
id: keccak256(txContext.multicallData),
|
|
746
|
+
failureType: 'send-error',
|
|
747
|
+
request: {
|
|
748
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
749
|
+
data: txContext.multicallData
|
|
750
|
+
},
|
|
751
|
+
blobData: txContext.blobData,
|
|
752
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
753
|
+
error: {
|
|
754
|
+
message: result.message,
|
|
755
|
+
name: result.name
|
|
756
|
+
},
|
|
757
|
+
context: {
|
|
758
|
+
actions: requests.map((r)=>r.action),
|
|
759
|
+
requests: requests.map((r)=>({
|
|
760
|
+
action: r.action,
|
|
761
|
+
to: r.request.to,
|
|
762
|
+
data: r.request.data
|
|
763
|
+
})),
|
|
764
|
+
sender: this.getSenderAddress().toString()
|
|
765
|
+
}
|
|
766
|
+
});
|
|
645
767
|
return {
|
|
646
768
|
failedActions: requests.map((r)=>r.action)
|
|
647
769
|
};
|
|
648
770
|
} else {
|
|
649
771
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
650
772
|
result,
|
|
651
|
-
requests
|
|
773
|
+
requests: requests.map((r)=>({
|
|
774
|
+
...r,
|
|
775
|
+
// Avoid logging large blob data
|
|
776
|
+
blobConfig: r.blobConfig ? {
|
|
777
|
+
...r.blobConfig,
|
|
778
|
+
blobs: r.blobConfig.blobs.map((b)=>({
|
|
779
|
+
size: trimmedBytesLength(b)
|
|
780
|
+
}))
|
|
781
|
+
} : undefined
|
|
782
|
+
}))
|
|
652
783
|
});
|
|
653
784
|
const successfulActions = [];
|
|
654
785
|
const failedActions = [];
|
|
@@ -659,6 +790,37 @@ export class SequencerPublisher {
|
|
|
659
790
|
failedActions.push(request.action);
|
|
660
791
|
}
|
|
661
792
|
}
|
|
793
|
+
// Single backup for the whole reverted tx
|
|
794
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
795
|
+
this.backupFailedTx({
|
|
796
|
+
id: result.receipt.transactionHash,
|
|
797
|
+
failureType: 'revert',
|
|
798
|
+
request: {
|
|
799
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
800
|
+
data: txContext.multicallData
|
|
801
|
+
},
|
|
802
|
+
blobData: txContext.blobData,
|
|
803
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
804
|
+
receipt: {
|
|
805
|
+
transactionHash: result.receipt.transactionHash,
|
|
806
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
807
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
808
|
+
status: 'reverted'
|
|
809
|
+
},
|
|
810
|
+
error: {
|
|
811
|
+
message: result.errorMsg ?? 'Transaction reverted'
|
|
812
|
+
},
|
|
813
|
+
context: {
|
|
814
|
+
actions: failedActions,
|
|
815
|
+
requests: requests.filter((r)=>failedActions.includes(r.action)).map((r)=>({
|
|
816
|
+
action: r.action,
|
|
817
|
+
to: r.request.to,
|
|
818
|
+
data: r.request.data
|
|
819
|
+
})),
|
|
820
|
+
sender: this.getSenderAddress().toString()
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
}
|
|
662
824
|
return {
|
|
663
825
|
successfulActions,
|
|
664
826
|
failedActions
|
|
@@ -666,17 +828,19 @@ export class SequencerPublisher {
|
|
|
666
828
|
}
|
|
667
829
|
}
|
|
668
830
|
/**
|
|
669
|
-
* @notice Will call `
|
|
831
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
670
832
|
* @param tipArchive - The archive to check
|
|
671
833
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
672
|
-
*/
|
|
834
|
+
*/ canProposeAt(tipArchive, msgSender, opts = {}) {
|
|
673
835
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
674
836
|
const ignoredErrors = [
|
|
675
837
|
'SlotAlreadyInChain',
|
|
676
838
|
'InvalidProposer',
|
|
677
839
|
'InvalidArchive'
|
|
678
840
|
];
|
|
679
|
-
|
|
841
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
842
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
843
|
+
return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, slotOffset, {
|
|
680
844
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
|
|
681
845
|
}).catch((err)=>{
|
|
682
846
|
if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
|
|
@@ -761,8 +925,12 @@ export class SequencerPublisher {
|
|
|
761
925
|
...logData,
|
|
762
926
|
request
|
|
763
927
|
});
|
|
928
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
764
929
|
try {
|
|
765
|
-
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined,
|
|
930
|
+
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
|
|
931
|
+
request.abi ?? [],
|
|
932
|
+
ErrorsAbi
|
|
933
|
+
]));
|
|
766
934
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
767
935
|
...logData,
|
|
768
936
|
request,
|
|
@@ -779,7 +947,7 @@ export class SequencerPublisher {
|
|
|
779
947
|
const viemError = formatViemError(err);
|
|
780
948
|
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
781
949
|
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
782
|
-
if (viemError.message?.includes('
|
|
950
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
783
951
|
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`, {
|
|
784
952
|
...logData,
|
|
785
953
|
request,
|
|
@@ -800,6 +968,27 @@ export class SequencerPublisher {
|
|
|
800
968
|
}
|
|
801
969
|
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
802
970
|
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
971
|
+
this.backupFailedTx({
|
|
972
|
+
id: keccak256(request.data),
|
|
973
|
+
failureType: 'simulation',
|
|
974
|
+
request: {
|
|
975
|
+
to: request.to,
|
|
976
|
+
data: request.data,
|
|
977
|
+
value: request.value?.toString()
|
|
978
|
+
},
|
|
979
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
980
|
+
error: {
|
|
981
|
+
message: viemError.message,
|
|
982
|
+
name: viemError.name
|
|
983
|
+
},
|
|
984
|
+
context: {
|
|
985
|
+
actions: [
|
|
986
|
+
`invalidate-${reason}`
|
|
987
|
+
],
|
|
988
|
+
checkpointNumber,
|
|
989
|
+
sender: this.getSenderAddress().toString()
|
|
990
|
+
}
|
|
991
|
+
});
|
|
803
992
|
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
|
|
804
993
|
cause: viemError
|
|
805
994
|
});
|
|
@@ -826,30 +1015,18 @@ export class SequencerPublisher {
|
|
|
826
1015
|
}
|
|
827
1016
|
}
|
|
828
1017
|
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
|
|
829
|
-
|
|
830
|
-
//
|
|
831
|
-
|
|
832
|
-
// so that the committee is recalculated correctly
|
|
833
|
-
// const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
834
|
-
// if (ignoreSignatures) {
|
|
835
|
-
// const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
836
|
-
// if (!committee) {
|
|
837
|
-
// this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
838
|
-
// throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
839
|
-
// }
|
|
840
|
-
// attestationsAndSigners.attestations = committee.map(committeeMember =>
|
|
841
|
-
// CommitteeAttestation.fromAddress(committeeMember),
|
|
842
|
-
// );
|
|
843
|
-
// }
|
|
1018
|
+
// Anchor the simulation timestamp to the checkpoint's own slot start time
|
|
1019
|
+
// rather than the current L1 block timestamp, which may overshoot into the next slot if the build ran late.
|
|
1020
|
+
const ts = checkpoint.header.timestamp;
|
|
844
1021
|
const blobFields = checkpoint.toBlobFields();
|
|
845
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1022
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
846
1023
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
847
1024
|
const args = [
|
|
848
1025
|
{
|
|
849
1026
|
header: checkpoint.header.toViem(),
|
|
850
1027
|
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
851
1028
|
oracleInput: {
|
|
852
|
-
feeAssetPriceModifier:
|
|
1029
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
853
1030
|
}
|
|
854
1031
|
},
|
|
855
1032
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -884,6 +1061,28 @@ export class SequencerPublisher {
|
|
|
884
1061
|
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
885
1062
|
return false;
|
|
886
1063
|
}
|
|
1064
|
+
// Check if payload was already submitted to governance
|
|
1065
|
+
const cacheKey = payload.toString();
|
|
1066
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
1067
|
+
try {
|
|
1068
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
1069
|
+
const proposed = await retry(()=>base.hasPayloadBeenProposed(payload.toString(), l1StartBlock), 'Check if payload was proposed', makeBackoff([
|
|
1070
|
+
0,
|
|
1071
|
+
1,
|
|
1072
|
+
2
|
|
1073
|
+
]), this.log, true);
|
|
1074
|
+
if (proposed) {
|
|
1075
|
+
this.payloadProposedCache.add(cacheKey);
|
|
1076
|
+
}
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
1083
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
887
1086
|
const cachedLastVote = this.lastActions[signalType];
|
|
888
1087
|
this.lastActions[signalType] = slotNumber;
|
|
889
1088
|
const action = signalType;
|
|
@@ -894,15 +1093,41 @@ export class SequencerPublisher {
|
|
|
894
1093
|
signer: this.l1TxUtils.client.account?.address,
|
|
895
1094
|
lastValidL2Slot: slotNumber
|
|
896
1095
|
});
|
|
1096
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
897
1097
|
try {
|
|
898
1098
|
await this.l1TxUtils.simulate(request, {
|
|
899
1099
|
time: timestamp
|
|
900
|
-
}, [],
|
|
1100
|
+
}, [], mergeAbis([
|
|
1101
|
+
request.abi ?? [],
|
|
1102
|
+
ErrorsAbi
|
|
1103
|
+
]));
|
|
901
1104
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, {
|
|
902
1105
|
request
|
|
903
1106
|
});
|
|
904
1107
|
} catch (err) {
|
|
905
|
-
|
|
1108
|
+
const viemError = formatViemError(err);
|
|
1109
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
1110
|
+
this.backupFailedTx({
|
|
1111
|
+
id: keccak256(request.data),
|
|
1112
|
+
failureType: 'simulation',
|
|
1113
|
+
request: {
|
|
1114
|
+
to: request.to,
|
|
1115
|
+
data: request.data,
|
|
1116
|
+
value: request.value?.toString()
|
|
1117
|
+
},
|
|
1118
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1119
|
+
error: {
|
|
1120
|
+
message: viemError.message,
|
|
1121
|
+
name: viemError.name
|
|
1122
|
+
},
|
|
1123
|
+
context: {
|
|
1124
|
+
actions: [
|
|
1125
|
+
action
|
|
1126
|
+
],
|
|
1127
|
+
slot: slotNumber,
|
|
1128
|
+
sender: this.getSenderAddress().toString()
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
906
1131
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
907
1132
|
}
|
|
908
1133
|
// TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
|
|
@@ -1041,13 +1266,14 @@ export class SequencerPublisher {
|
|
|
1041
1266
|
/** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
1042
1267
|
const checkpointHeader = checkpoint.header;
|
|
1043
1268
|
const blobFields = checkpoint.toBlobFields();
|
|
1044
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1269
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
1045
1270
|
const proposeTxArgs = {
|
|
1046
1271
|
header: checkpointHeader,
|
|
1047
1272
|
archive: checkpoint.archive.root.toBuffer(),
|
|
1048
1273
|
blobs,
|
|
1049
1274
|
attestationsAndSigners,
|
|
1050
|
-
attestationsAndSignersSignature
|
|
1275
|
+
attestationsAndSignersSignature,
|
|
1276
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
1051
1277
|
};
|
|
1052
1278
|
let ts;
|
|
1053
1279
|
try {
|
|
@@ -1123,28 +1349,60 @@ export class SequencerPublisher {
|
|
|
1123
1349
|
const cachedLastActionSlot = this.lastActions[action];
|
|
1124
1350
|
this.lastActions[action] = slotNumber;
|
|
1125
1351
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1352
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1126
1353
|
let gasUsed;
|
|
1354
|
+
const simulateAbi = mergeAbis([
|
|
1355
|
+
request.abi ?? [],
|
|
1356
|
+
ErrorsAbi
|
|
1357
|
+
]);
|
|
1127
1358
|
try {
|
|
1128
1359
|
({ gasUsed } = await this.l1TxUtils.simulate(request, {
|
|
1129
1360
|
time: timestamp
|
|
1130
|
-
}, [],
|
|
1361
|
+
}, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
1131
1362
|
this.log.verbose(`Simulation for ${action} succeeded`, {
|
|
1132
1363
|
...logData,
|
|
1133
1364
|
request,
|
|
1134
1365
|
gasUsed
|
|
1135
1366
|
});
|
|
1136
1367
|
} catch (err) {
|
|
1137
|
-
const viemError = formatViemError(err);
|
|
1368
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
1138
1369
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1370
|
+
this.backupFailedTx({
|
|
1371
|
+
id: keccak256(request.data),
|
|
1372
|
+
failureType: 'simulation',
|
|
1373
|
+
request: {
|
|
1374
|
+
to: request.to,
|
|
1375
|
+
data: request.data,
|
|
1376
|
+
value: request.value?.toString()
|
|
1377
|
+
},
|
|
1378
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1379
|
+
error: {
|
|
1380
|
+
message: viemError.message,
|
|
1381
|
+
name: viemError.name
|
|
1382
|
+
},
|
|
1383
|
+
context: {
|
|
1384
|
+
actions: [
|
|
1385
|
+
action
|
|
1386
|
+
],
|
|
1387
|
+
slot: slotNumber,
|
|
1388
|
+
sender: this.getSenderAddress().toString()
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1139
1391
|
return false;
|
|
1140
1392
|
}
|
|
1141
1393
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
1142
1394
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(gasUsed) * 64 / 63)));
|
|
1143
1395
|
logData.gasLimit = gasLimit;
|
|
1396
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1397
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1398
|
+
const requestWithAbi = {
|
|
1399
|
+
...request,
|
|
1400
|
+
abi: simulateAbi
|
|
1401
|
+
};
|
|
1144
1402
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
1145
1403
|
this.addRequest({
|
|
1146
1404
|
action,
|
|
1147
|
-
request,
|
|
1405
|
+
request: requestWithAbi,
|
|
1148
1406
|
gasConfig: {
|
|
1149
1407
|
gasLimit
|
|
1150
1408
|
},
|
|
@@ -1208,10 +1466,38 @@ export class SequencerPublisher {
|
|
|
1208
1466
|
}, {}, {
|
|
1209
1467
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
1210
1468
|
kzg
|
|
1211
|
-
}).catch((err)=>{
|
|
1212
|
-
const
|
|
1213
|
-
this.log.error(`Failed to validate blobs`, message, {
|
|
1214
|
-
metaMessages
|
|
1469
|
+
}).catch(async (err)=>{
|
|
1470
|
+
const viemError = formatViemError(err);
|
|
1471
|
+
this.log.error(`Failed to validate blobs`, viemError.message, {
|
|
1472
|
+
metaMessages: viemError.metaMessages
|
|
1473
|
+
});
|
|
1474
|
+
const validateBlobsData = encodeFunctionData({
|
|
1475
|
+
abi: RollupAbi,
|
|
1476
|
+
functionName: 'validateBlobs',
|
|
1477
|
+
args: [
|
|
1478
|
+
blobInput
|
|
1479
|
+
]
|
|
1480
|
+
});
|
|
1481
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1482
|
+
this.backupFailedTx({
|
|
1483
|
+
id: keccak256(validateBlobsData),
|
|
1484
|
+
failureType: 'simulation',
|
|
1485
|
+
request: {
|
|
1486
|
+
to: this.rollupContract.address,
|
|
1487
|
+
data: validateBlobsData
|
|
1488
|
+
},
|
|
1489
|
+
blobData: encodedData.blobs.map((b)=>toHex(b.data)),
|
|
1490
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1491
|
+
error: {
|
|
1492
|
+
message: viemError.message,
|
|
1493
|
+
name: viemError.name
|
|
1494
|
+
},
|
|
1495
|
+
context: {
|
|
1496
|
+
actions: [
|
|
1497
|
+
'validate-blobs'
|
|
1498
|
+
],
|
|
1499
|
+
sender: this.getSenderAddress().toString()
|
|
1500
|
+
}
|
|
1215
1501
|
});
|
|
1216
1502
|
throw new Error('Failed to validate blobs');
|
|
1217
1503
|
});
|
|
@@ -1222,8 +1508,7 @@ export class SequencerPublisher {
|
|
|
1222
1508
|
header: encodedData.header.toViem(),
|
|
1223
1509
|
archive: toHex(encodedData.archive),
|
|
1224
1510
|
oracleInput: {
|
|
1225
|
-
|
|
1226
|
-
feeAssetPriceModifier: 0n
|
|
1511
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier
|
|
1227
1512
|
}
|
|
1228
1513
|
},
|
|
1229
1514
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -1272,10 +1557,11 @@ export class SequencerPublisher {
|
|
|
1272
1557
|
balance: 10n * WEI_CONST * WEI_CONST
|
|
1273
1558
|
});
|
|
1274
1559
|
}
|
|
1560
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1275
1561
|
const simulationResult = await this.l1TxUtils.simulate({
|
|
1276
1562
|
to: this.rollupContract.address,
|
|
1277
1563
|
data: rollupData,
|
|
1278
|
-
gas:
|
|
1564
|
+
gas: MAX_L1_TX_LIMIT,
|
|
1279
1565
|
...this.proposerAddressForSimulation && {
|
|
1280
1566
|
from: this.proposerAddressForSimulation.toString()
|
|
1281
1567
|
}
|
|
@@ -1283,10 +1569,10 @@ export class SequencerPublisher {
|
|
|
1283
1569
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1284
1570
|
time: timestamp + 1n,
|
|
1285
1571
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1286
|
-
gasLimit:
|
|
1572
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n
|
|
1287
1573
|
}, stateOverrides, RollupAbi, {
|
|
1288
1574
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1289
|
-
fallbackGasEstimate:
|
|
1575
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT
|
|
1290
1576
|
}).catch((err)=>{
|
|
1291
1577
|
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
1292
1578
|
const viemError = formatViemError(err);
|
|
@@ -1294,11 +1580,31 @@ export class SequencerPublisher {
|
|
|
1294
1580
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
1295
1581
|
// Return a minimal simulation result with the fallback gas estimate
|
|
1296
1582
|
return {
|
|
1297
|
-
gasUsed:
|
|
1583
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
1298
1584
|
logs: []
|
|
1299
1585
|
};
|
|
1300
1586
|
}
|
|
1301
1587
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1588
|
+
this.backupFailedTx({
|
|
1589
|
+
id: keccak256(rollupData),
|
|
1590
|
+
failureType: 'simulation',
|
|
1591
|
+
request: {
|
|
1592
|
+
to: this.rollupContract.address,
|
|
1593
|
+
data: rollupData
|
|
1594
|
+
},
|
|
1595
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1596
|
+
error: {
|
|
1597
|
+
message: viemError.message,
|
|
1598
|
+
name: viemError.name
|
|
1599
|
+
},
|
|
1600
|
+
context: {
|
|
1601
|
+
actions: [
|
|
1602
|
+
'propose'
|
|
1603
|
+
],
|
|
1604
|
+
slot: Number(args[0].header.slotNumber),
|
|
1605
|
+
sender: this.getSenderAddress().toString()
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1302
1608
|
throw err;
|
|
1303
1609
|
});
|
|
1304
1610
|
return {
|