@aztec-labs/sequencer-client 6.0.0-nightly.20260829
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +307 -0
- package/dest/client/index.d.ts +2 -0
- package/dest/client/index.d.ts.map +1 -0
- package/dest/client/index.js +1 -0
- package/dest/client/sequencer-client.d.ts +94 -0
- package/dest/client/sequencer-client.d.ts.map +1 -0
- package/dest/client/sequencer-client.js +177 -0
- package/dest/config.d.ts +56 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +251 -0
- package/dest/global_variable_builder/fee_predictor.d.ts +49 -0
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_predictor.js +127 -0
- package/dest/global_variable_builder/fee_provider.d.ts +28 -0
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_provider.js +87 -0
- package/dest/global_variable_builder/global_builder.d.ts +28 -0
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -0
- package/dest/global_variable_builder/global_builder.js +40 -0
- package/dest/global_variable_builder/index.d.ts +4 -0
- package/dest/global_variable_builder/index.d.ts.map +1 -0
- package/dest/global_variable_builder/index.js +3 -0
- package/dest/index.d.ts +7 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +8 -0
- package/dest/publisher/config.d.ts +68 -0
- package/dest/publisher/config.d.ts.map +1 -0
- package/dest/publisher/config.js +137 -0
- package/dest/publisher/index.d.ts +5 -0
- package/dest/publisher/index.d.ts.map +1 -0
- package/dest/publisher/index.js +4 -0
- package/dest/publisher/l1_to_l2_messaging.d.ts +21 -0
- package/dest/publisher/l1_to_l2_messaging.d.ts.map +1 -0
- package/dest/publisher/l1_to_l2_messaging.js +70 -0
- package/dest/publisher/l1_tx_failed_store/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 +101 -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 +68 -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 +36 -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 +3 -0
- package/dest/publisher/sequencer-bundle-simulator.d.ts +96 -0
- package/dest/publisher/sequencer-bundle-simulator.d.ts.map +1 -0
- package/dest/publisher/sequencer-bundle-simulator.js +198 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +50 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -0
- package/dest/publisher/sequencer-publisher-factory.js +75 -0
- package/dest/publisher/sequencer-publisher-metrics.d.ts +26 -0
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -0
- package/dest/publisher/sequencer-publisher-metrics.js +101 -0
- package/dest/publisher/sequencer-publisher.d.ts +254 -0
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -0
- package/dest/publisher/sequencer-publisher.js +1784 -0
- package/dest/publisher/write_json.d.ts +11 -0
- package/dest/publisher/write_json.d.ts.map +1 -0
- package/dest/publisher/write_json.js +57 -0
- package/dest/sequencer/automine/automine_factory.d.ts +56 -0
- package/dest/sequencer/automine/automine_factory.d.ts.map +1 -0
- package/dest/sequencer/automine/automine_factory.js +85 -0
- package/dest/sequencer/automine/automine_sequencer.d.ts +189 -0
- package/dest/sequencer/automine/automine_sequencer.d.ts.map +1 -0
- package/dest/sequencer/automine/automine_sequencer.js +732 -0
- package/dest/sequencer/automine/index.d.ts +3 -0
- package/dest/sequencer/automine/index.d.ts.map +1 -0
- package/dest/sequencer/automine/index.js +2 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +169 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +1961 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
- package/dest/sequencer/checkpoint_voter.d.ts +34 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +106 -0
- package/dest/sequencer/config.d.ts +8 -0
- package/dest/sequencer/config.d.ts.map +1 -0
- package/dest/sequencer/config.js +1 -0
- package/dest/sequencer/errors.d.ts +11 -0
- package/dest/sequencer/errors.d.ts.map +1 -0
- package/dest/sequencer/errors.js +15 -0
- package/dest/sequencer/events.d.ts +103 -0
- package/dest/sequencer/events.d.ts.map +1 -0
- package/dest/sequencer/events.js +1 -0
- package/dest/sequencer/inbox_bucket_selector.d.ts +88 -0
- package/dest/sequencer/inbox_bucket_selector.d.ts.map +1 -0
- package/dest/sequencer/inbox_bucket_selector.js +80 -0
- package/dest/sequencer/index.d.ts +6 -0
- package/dest/sequencer/index.d.ts.map +1 -0
- package/dest/sequencer/index.js +5 -0
- package/dest/sequencer/metrics.d.ts +75 -0
- package/dest/sequencer/metrics.d.ts.map +1 -0
- package/dest/sequencer/metrics.js +343 -0
- package/dest/sequencer/missing_committee.d.ts +72 -0
- package/dest/sequencer/missing_committee.d.ts.map +1 -0
- package/dest/sequencer/missing_committee.js +139 -0
- package/dest/sequencer/requests_tracker.d.ts +22 -0
- package/dest/sequencer/requests_tracker.d.ts.map +1 -0
- package/dest/sequencer/requests_tracker.js +33 -0
- package/dest/sequencer/sequencer.d.ts +326 -0
- package/dest/sequencer/sequencer.d.ts.map +1 -0
- package/dest/sequencer/sequencer.js +1495 -0
- package/dest/sequencer/types.d.ts +3 -0
- package/dest/sequencer/types.d.ts.map +1 -0
- package/dest/sequencer/types.js +1 -0
- package/dest/sequencer/utils.d.ts +30 -0
- package/dest/sequencer/utils.d.ts.map +1 -0
- package/dest/sequencer/utils.js +18 -0
- package/dest/test/index.d.ts +23 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +6 -0
- package/dest/test/mock_checkpoint_builder.d.ts +95 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +230 -0
- package/dest/test/utils.d.ts +67 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +121 -0
- package/package.json +118 -0
- package/src/client/index.ts +1 -0
- package/src/client/sequencer-client.ts +281 -0
- package/src/config.ts +309 -0
- package/src/global_variable_builder/README.md +44 -0
- package/src/global_variable_builder/fee_predictor.ts +176 -0
- package/src/global_variable_builder/fee_provider.ts +104 -0
- package/src/global_variable_builder/global_builder.ts +68 -0
- package/src/global_variable_builder/index.ts +3 -0
- package/src/index.ts +15 -0
- package/src/publisher/config.ts +220 -0
- package/src/publisher/index.ts +8 -0
- package/src/publisher/l1_to_l2_messaging.ts +85 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +166 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +47 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-bundle-simulator.ts +253 -0
- package/src/publisher/sequencer-publisher-factory.ts +121 -0
- package/src/publisher/sequencer-publisher-metrics.ts +145 -0
- package/src/publisher/sequencer-publisher.ts +1719 -0
- package/src/publisher/write_json.ts +77 -0
- package/src/sequencer/README.md +243 -0
- package/src/sequencer/automine/README.md +60 -0
- package/src/sequencer/automine/automine_factory.ts +156 -0
- package/src/sequencer/automine/automine_sequencer.ts +841 -0
- package/src/sequencer/automine/index.ts +6 -0
- package/src/sequencer/checkpoint_proposal_job.ts +1889 -0
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +118 -0
- package/src/sequencer/config.ts +9 -0
- package/src/sequencer/errors.ts +21 -0
- package/src/sequencer/events.ts +93 -0
- package/src/sequencer/inbox_bucket_selector.ts +160 -0
- package/src/sequencer/index.ts +5 -0
- package/src/sequencer/metrics.ts +463 -0
- package/src/sequencer/missing_committee.ts +192 -0
- package/src/sequencer/requests_tracker.ts +43 -0
- package/src/sequencer/sequencer.ts +1401 -0
- package/src/sequencer/types.ts +6 -0
- package/src/sequencer/utils.ts +42 -0
- package/src/test/index.ts +26 -0
- package/src/test/mock_checkpoint_builder.ts +324 -0
- package/src/test/utils.ts +215 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { RollupContract } from '@aztec-labs/ethereum/contracts';
|
|
2
|
+
import type { L1FeeAnalysisResult } from '@aztec-labs/ethereum/l1-fee-analysis';
|
|
3
|
+
import type { SlotNumber } from '@aztec-labs/foundation/branded-types';
|
|
4
|
+
import { EthAddress } from '@aztec-labs/foundation/eth-address';
|
|
5
|
+
import { type TelemetryClient, type Tracer } from '@aztec-labs/telemetry-client';
|
|
6
|
+
import { type Hex } from 'viem';
|
|
7
|
+
import type { SequencerState } from './utils.js';
|
|
8
|
+
export declare class SequencerMetrics {
|
|
9
|
+
private rollup;
|
|
10
|
+
readonly tracer: Tracer;
|
|
11
|
+
private meter;
|
|
12
|
+
private blockCounter;
|
|
13
|
+
private blockBuildDuration;
|
|
14
|
+
private blockBuildManaPerSecond;
|
|
15
|
+
private stateTransitionBufferDuration;
|
|
16
|
+
private stateDuration;
|
|
17
|
+
private timeToCollectAttestations;
|
|
18
|
+
private allowanceToCollectAttestations;
|
|
19
|
+
private requiredAttestions;
|
|
20
|
+
private collectedAttestions;
|
|
21
|
+
private rewards;
|
|
22
|
+
private slots;
|
|
23
|
+
private filledSlots;
|
|
24
|
+
private blockProposalFailed;
|
|
25
|
+
private checkpointProposalSuccess;
|
|
26
|
+
private checkpointPrecheckFailed;
|
|
27
|
+
private checkpointProposalFailed;
|
|
28
|
+
private checkpointSuccess;
|
|
29
|
+
private slashingAttempts;
|
|
30
|
+
private pipelineDepth;
|
|
31
|
+
private pipelineDiscards;
|
|
32
|
+
private pipelineParentCheckpointMismatches;
|
|
33
|
+
private fishermanWouldBeIncluded;
|
|
34
|
+
private fishermanTimeBeforeBlock;
|
|
35
|
+
private fishermanPendingBlobTxCount;
|
|
36
|
+
private fishermanIncludedBlobTxCount;
|
|
37
|
+
private fishermanPendingBlobCount;
|
|
38
|
+
private fishermanIncludedBlobCount;
|
|
39
|
+
private fishermanBlockBlobsFull;
|
|
40
|
+
private fishermanMaxBlobCapacity;
|
|
41
|
+
private fishermanCalculatedPriorityFee;
|
|
42
|
+
private fishermanPriorityFeeDelta;
|
|
43
|
+
private fishermanEstimatedCost;
|
|
44
|
+
private fishermanEstimatedOverpayment;
|
|
45
|
+
private fishermanMinedBlobTxPriorityFee;
|
|
46
|
+
private fishermanMinedBlobTxTotalCost;
|
|
47
|
+
private blockInterBlockTime;
|
|
48
|
+
private lastBlockBuiltTimestamp?;
|
|
49
|
+
private lastBlockBuiltSlot?;
|
|
50
|
+
private lastSeenSlot?;
|
|
51
|
+
constructor(client: TelemetryClient, rollup: RollupContract, name?: string);
|
|
52
|
+
recordRequiredAttestations(requiredAttestationsCount: number, allowanceMs: number): void;
|
|
53
|
+
recordCollectedAttestations(count: number, durationMs: number): void;
|
|
54
|
+
recordBuiltBlock(buildDurationMs: number, totalMana: number, slot: SlotNumber): void;
|
|
55
|
+
recordFailedBlock(): void;
|
|
56
|
+
recordStateTransitionBufferMs(durationMs: number, state: SequencerState): void;
|
|
57
|
+
recordStateDuration(durationMs: number, state: SequencerState): void;
|
|
58
|
+
recordPipelineDepth(depth: number): void;
|
|
59
|
+
recordPipelineDiscard(count?: number): void;
|
|
60
|
+
recordPipelineParentCheckpointMismatch(reason: string): void;
|
|
61
|
+
incOpenSlot(slot: SlotNumber, proposer: string): void;
|
|
62
|
+
incFilledSlot(proposer: string, coinbase: Hex | EthAddress | undefined): Promise<void>;
|
|
63
|
+
recordCheckpointSuccess(): void;
|
|
64
|
+
recordBlockProposalFailed(reason?: string): void;
|
|
65
|
+
recordCheckpointProposalSuccess(): void;
|
|
66
|
+
recordCheckpointPrecheckFailed(checkType: 'slot_already_taken' | 'rollup_contract_check_failed' | 'slot_mismatch' | 'block_number_mismatch' | 'no_peers'): void;
|
|
67
|
+
recordCheckpointProposalFailed(reason?: string): void;
|
|
68
|
+
recordSlashingAttempt(actionCount: number): void;
|
|
69
|
+
/**
|
|
70
|
+
* Records metrics for a completed fisherman fee analysis
|
|
71
|
+
* @param analysis - The completed fee analysis result
|
|
72
|
+
*/
|
|
73
|
+
recordFishermanFeeAnalysis(analysis: L1FeeAnalysisResult): void;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcXVlbmNlci9tZXRyaWNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLGNBQWMsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ3JFLE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFDaEYsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFDdkUsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQ2hFLE9BQU8sRUFNTCxLQUFLLGVBQWUsRUFDcEIsS0FBSyxNQUFNLEVBR1osTUFBTSw4QkFBOEIsQ0FBQztBQUN0QyxPQUFPLEVBQUUsS0FBSyxHQUFHLEVBQWUsTUFBTSxNQUFNLENBQUM7QUFFN0MsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRWpELHFCQUFhLGdCQUFnQjtJQXVEekIsT0FBTyxDQUFDLE1BQU07SUF0RGhCLFNBQWdCLE1BQU0sRUFBRSxNQUFNLENBQUM7SUFDL0IsT0FBTyxDQUFDLEtBQUssQ0FBUTtJQUVyQixPQUFPLENBQUMsWUFBWSxDQUFnQjtJQUNwQyxPQUFPLENBQUMsa0JBQWtCLENBQVk7SUFDdEMsT0FBTyxDQUFDLHVCQUF1QixDQUFRO0lBQ3ZDLE9BQU8sQ0FBQyw2QkFBNkIsQ0FBWTtJQUNqRCxPQUFPLENBQUMsYUFBYSxDQUFZO0lBR2pDLE9BQU8sQ0FBQyx5QkFBeUIsQ0FBUTtJQUN6QyxPQUFPLENBQUMsOEJBQThCLENBQVE7SUFDOUMsT0FBTyxDQUFDLGtCQUFrQixDQUFRO0lBQ2xDLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBUTtJQUVuQyxPQUFPLENBQUMsT0FBTyxDQUFRO0lBRXZCLE9BQU8sQ0FBQyxLQUFLLENBQWdCO0lBQzdCLE9BQU8sQ0FBQyxXQUFXLENBQWdCO0lBRW5DLE9B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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/sequencer/metrics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { Attributes, Metrics, createUpDownCounterWithDefault } from '@aztec-labs/telemetry-client';
|
|
2
|
+
import { formatUnits } from 'viem';
|
|
3
|
+
export class SequencerMetrics {
|
|
4
|
+
rollup;
|
|
5
|
+
tracer;
|
|
6
|
+
meter;
|
|
7
|
+
blockCounter;
|
|
8
|
+
blockBuildDuration;
|
|
9
|
+
blockBuildManaPerSecond;
|
|
10
|
+
stateTransitionBufferDuration;
|
|
11
|
+
stateDuration;
|
|
12
|
+
// these are gauges because for individual sequencers building a block is not something that happens often enough to warrant a histogram
|
|
13
|
+
timeToCollectAttestations;
|
|
14
|
+
allowanceToCollectAttestations;
|
|
15
|
+
requiredAttestions;
|
|
16
|
+
collectedAttestions;
|
|
17
|
+
rewards;
|
|
18
|
+
slots;
|
|
19
|
+
filledSlots;
|
|
20
|
+
blockProposalFailed;
|
|
21
|
+
checkpointProposalSuccess;
|
|
22
|
+
checkpointPrecheckFailed;
|
|
23
|
+
checkpointProposalFailed;
|
|
24
|
+
checkpointSuccess;
|
|
25
|
+
slashingAttempts;
|
|
26
|
+
pipelineDepth;
|
|
27
|
+
pipelineDiscards;
|
|
28
|
+
pipelineParentCheckpointMismatches;
|
|
29
|
+
// Fisherman fee analysis metrics
|
|
30
|
+
fishermanWouldBeIncluded;
|
|
31
|
+
fishermanTimeBeforeBlock;
|
|
32
|
+
fishermanPendingBlobTxCount;
|
|
33
|
+
fishermanIncludedBlobTxCount;
|
|
34
|
+
fishermanPendingBlobCount;
|
|
35
|
+
fishermanIncludedBlobCount;
|
|
36
|
+
fishermanBlockBlobsFull;
|
|
37
|
+
fishermanMaxBlobCapacity;
|
|
38
|
+
fishermanCalculatedPriorityFee;
|
|
39
|
+
fishermanPriorityFeeDelta;
|
|
40
|
+
fishermanEstimatedCost;
|
|
41
|
+
fishermanEstimatedOverpayment;
|
|
42
|
+
fishermanMinedBlobTxPriorityFee;
|
|
43
|
+
fishermanMinedBlobTxTotalCost;
|
|
44
|
+
blockInterBlockTime;
|
|
45
|
+
lastBlockBuiltTimestamp;
|
|
46
|
+
lastBlockBuiltSlot;
|
|
47
|
+
lastSeenSlot;
|
|
48
|
+
constructor(client, rollup, name = 'Sequencer'){
|
|
49
|
+
this.rollup = rollup;
|
|
50
|
+
this.meter = client.getMeter(name);
|
|
51
|
+
this.tracer = client.getTracer(name);
|
|
52
|
+
this.blockCounter = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_BLOCK_COUNT, {
|
|
53
|
+
[Attributes.STATUS]: [
|
|
54
|
+
'failed',
|
|
55
|
+
'built'
|
|
56
|
+
]
|
|
57
|
+
});
|
|
58
|
+
this.blockBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_DURATION);
|
|
59
|
+
this.blockBuildManaPerSecond = this.meter.createGauge(Metrics.SEQUENCER_BLOCK_BUILD_MANA_PER_SECOND);
|
|
60
|
+
this.blockInterBlockTime = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_INTER_BLOCK_TIME);
|
|
61
|
+
this.stateTransitionBufferDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_TRANSITION_BUFFER_DURATION);
|
|
62
|
+
this.stateDuration = this.meter.createHistogram(Metrics.SEQUENCER_STATE_DURATION);
|
|
63
|
+
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_SLOT_REWARDS);
|
|
64
|
+
this.slots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLOT_COUNT);
|
|
65
|
+
/**
|
|
66
|
+
* NOTE: we do not track missed slots as a separate metric. That would be difficult to determine
|
|
67
|
+
* Instead, use a computed metric, `slots - filledSlots` to get the number of slots a sequencer has missed.
|
|
68
|
+
*/ this.filledSlots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_FILLED_SLOT_COUNT);
|
|
69
|
+
this.timeToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_DURATION);
|
|
70
|
+
this.allowanceToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_TIME_ALLOWANCE);
|
|
71
|
+
this.requiredAttestions = this.meter.createGauge(Metrics.SEQUENCER_REQUIRED_ATTESTATIONS_COUNT);
|
|
72
|
+
this.collectedAttestions = this.meter.createGauge(Metrics.SEQUENCER_COLLECTED_ATTESTATIONS_COUNT);
|
|
73
|
+
this.blockProposalFailed = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT);
|
|
74
|
+
this.checkpointProposalSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_SUCCESS_COUNT);
|
|
75
|
+
this.checkpointSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
|
|
76
|
+
this.checkpointPrecheckFailed = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_PRECHECK_FAILED_COUNT, {
|
|
77
|
+
[Attributes.ERROR_TYPE]: [
|
|
78
|
+
'slot_already_taken',
|
|
79
|
+
'rollup_contract_check_failed',
|
|
80
|
+
'slot_mismatch',
|
|
81
|
+
'block_number_mismatch'
|
|
82
|
+
]
|
|
83
|
+
});
|
|
84
|
+
this.checkpointProposalFailed = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_PROPOSAL_FAILED_COUNT);
|
|
85
|
+
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
86
|
+
this.pipelineDepth = this.meter.createGauge(Metrics.SEQUENCER_PIPELINE_DEPTH);
|
|
87
|
+
this.pipelineDiscards = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_PIPELINE_DISCARDS_COUNT);
|
|
88
|
+
this.pipelineParentCheckpointMismatches = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_PIPELINE_PARENT_CHECKPOINT_MISMATCH_COUNT, {
|
|
89
|
+
[Attributes.ERROR_TYPE]: [
|
|
90
|
+
'archiver-sync-timeout',
|
|
91
|
+
'parent-not-on-l1',
|
|
92
|
+
'parent-hash-mismatch',
|
|
93
|
+
'parent-invalid-attestations',
|
|
94
|
+
'unexpected-parent-appeared'
|
|
95
|
+
]
|
|
96
|
+
});
|
|
97
|
+
this.pipelineDepth.record(0);
|
|
98
|
+
// Fisherman fee analysis metrics
|
|
99
|
+
this.fishermanWouldBeIncluded = createUpDownCounterWithDefault(this.meter, Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED, {
|
|
100
|
+
[Attributes.OK]: [
|
|
101
|
+
true,
|
|
102
|
+
false
|
|
103
|
+
],
|
|
104
|
+
[Attributes.BLOCK_FULL]: [
|
|
105
|
+
'true',
|
|
106
|
+
'false'
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
this.fishermanTimeBeforeBlock = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_TIME_BEFORE_BLOCK);
|
|
110
|
+
this.fishermanPendingBlobTxCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PENDING_BLOB_TX_COUNT);
|
|
111
|
+
this.fishermanIncludedBlobTxCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_INCLUDED_BLOB_TX_COUNT);
|
|
112
|
+
this.fishermanCalculatedPriorityFee = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_CALCULATED_PRIORITY_FEE);
|
|
113
|
+
this.fishermanPriorityFeeDelta = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PRIORITY_FEE_DELTA);
|
|
114
|
+
this.fishermanEstimatedCost = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_ESTIMATED_COST);
|
|
115
|
+
this.fishermanEstimatedOverpayment = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_ESTIMATED_OVERPAYMENT);
|
|
116
|
+
this.fishermanMinedBlobTxPriorityFee = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_PRIORITY_FEE);
|
|
117
|
+
this.fishermanMinedBlobTxTotalCost = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_TOTAL_COST);
|
|
118
|
+
this.fishermanPendingBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PENDING_BLOB_COUNT);
|
|
119
|
+
this.fishermanIncludedBlobCount = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_INCLUDED_BLOB_COUNT);
|
|
120
|
+
this.fishermanBlockBlobsFull = createUpDownCounterWithDefault(this.meter, Metrics.FISHERMAN_FEE_ANALYSIS_BLOCK_BLOBS_FULL, {
|
|
121
|
+
[Attributes.OK]: [
|
|
122
|
+
true,
|
|
123
|
+
false
|
|
124
|
+
]
|
|
125
|
+
});
|
|
126
|
+
this.fishermanMaxBlobCapacity = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_MAX_BLOB_CAPACITY);
|
|
127
|
+
}
|
|
128
|
+
recordRequiredAttestations(requiredAttestationsCount, allowanceMs) {
|
|
129
|
+
this.requiredAttestions.record(requiredAttestationsCount);
|
|
130
|
+
this.allowanceToCollectAttestations.record(Math.ceil(allowanceMs));
|
|
131
|
+
// reset
|
|
132
|
+
this.collectedAttestions.record(0);
|
|
133
|
+
this.timeToCollectAttestations.record(0);
|
|
134
|
+
}
|
|
135
|
+
recordCollectedAttestations(count, durationMs) {
|
|
136
|
+
this.collectedAttestions.record(count);
|
|
137
|
+
this.timeToCollectAttestations.record(Math.ceil(durationMs));
|
|
138
|
+
}
|
|
139
|
+
recordBuiltBlock(buildDurationMs, totalMana, slot) {
|
|
140
|
+
this.blockCounter.add(1, {
|
|
141
|
+
[Attributes.STATUS]: 'built'
|
|
142
|
+
});
|
|
143
|
+
this.blockBuildDuration.record(Math.ceil(buildDurationMs));
|
|
144
|
+
this.blockBuildManaPerSecond.record(Math.ceil(totalMana * 1000 / buildDurationMs));
|
|
145
|
+
// Only record inter-block time between blocks built within the same slot.
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
if (this.lastBlockBuiltTimestamp !== undefined && this.lastBlockBuiltSlot === slot) {
|
|
148
|
+
this.blockInterBlockTime.record(now - this.lastBlockBuiltTimestamp);
|
|
149
|
+
}
|
|
150
|
+
this.lastBlockBuiltTimestamp = now;
|
|
151
|
+
this.lastBlockBuiltSlot = slot;
|
|
152
|
+
}
|
|
153
|
+
recordFailedBlock() {
|
|
154
|
+
this.blockCounter.add(1, {
|
|
155
|
+
[Attributes.STATUS]: 'failed'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
recordStateTransitionBufferMs(durationMs, state) {
|
|
159
|
+
this.stateTransitionBufferDuration.record(durationMs, {
|
|
160
|
+
[Attributes.SEQUENCER_STATE]: state
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
recordStateDuration(durationMs, state) {
|
|
164
|
+
this.stateDuration.record(Math.ceil(durationMs), {
|
|
165
|
+
[Attributes.SEQUENCER_STATE]: state
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
recordPipelineDepth(depth) {
|
|
169
|
+
this.pipelineDepth.record(depth);
|
|
170
|
+
}
|
|
171
|
+
recordPipelineDiscard(count = 1) {
|
|
172
|
+
this.pipelineDiscards.add(count);
|
|
173
|
+
}
|
|
174
|
+
recordPipelineParentCheckpointMismatch(reason) {
|
|
175
|
+
this.pipelineParentCheckpointMismatches.add(1, {
|
|
176
|
+
[Attributes.ERROR_TYPE]: reason
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
incOpenSlot(slot, proposer) {
|
|
180
|
+
// sequencer went through the loop a second time. Noop
|
|
181
|
+
if (slot === this.lastSeenSlot) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
this.slots.add(1, {
|
|
185
|
+
[Attributes.BLOCK_PROPOSER]: proposer
|
|
186
|
+
});
|
|
187
|
+
this.lastSeenSlot = slot;
|
|
188
|
+
}
|
|
189
|
+
async incFilledSlot(proposer, coinbase) {
|
|
190
|
+
this.filledSlots.add(1, {
|
|
191
|
+
[Attributes.BLOCK_PROPOSER]: proposer
|
|
192
|
+
});
|
|
193
|
+
this.lastSeenSlot = undefined;
|
|
194
|
+
if (coinbase) {
|
|
195
|
+
try {
|
|
196
|
+
const rewards = await this.rollup.getSequencerRewards(coinbase);
|
|
197
|
+
const fmt = parseFloat(formatUnits(rewards, 18));
|
|
198
|
+
this.rewards.record(fmt, {
|
|
199
|
+
[Attributes.COINBASE]: coinbase.toString()
|
|
200
|
+
});
|
|
201
|
+
} catch {
|
|
202
|
+
// no-op
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
recordCheckpointSuccess() {
|
|
207
|
+
this.checkpointSuccess.add(1);
|
|
208
|
+
}
|
|
209
|
+
recordBlockProposalFailed(reason) {
|
|
210
|
+
this.blockProposalFailed.add(1, {
|
|
211
|
+
...reason && {
|
|
212
|
+
[Attributes.ERROR_TYPE]: reason
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
recordCheckpointProposalSuccess() {
|
|
217
|
+
this.checkpointProposalSuccess.add(1);
|
|
218
|
+
}
|
|
219
|
+
recordCheckpointPrecheckFailed(checkType) {
|
|
220
|
+
this.checkpointPrecheckFailed.add(1, {
|
|
221
|
+
[Attributes.ERROR_TYPE]: checkType
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
recordCheckpointProposalFailed(reason) {
|
|
225
|
+
this.checkpointProposalFailed.add(1, {
|
|
226
|
+
...reason && {
|
|
227
|
+
[Attributes.ERROR_TYPE]: reason
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
recordSlashingAttempt(actionCount) {
|
|
232
|
+
this.slashingAttempts.add(actionCount);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Records metrics for a completed fisherman fee analysis
|
|
236
|
+
* @param analysis - The completed fee analysis result
|
|
237
|
+
*/ recordFishermanFeeAnalysis(analysis) {
|
|
238
|
+
// In fisherman mode, we should always have strategy results
|
|
239
|
+
if (!analysis.computedPrices.strategyResults || analysis.computedPrices.strategyResults.length === 0) {
|
|
240
|
+
// This should never happen in fisherman mode - log an error
|
|
241
|
+
// We don't record metrics without strategy IDs as that defeats the purpose
|
|
242
|
+
throw new Error(`No strategy results found in fisherman fee analysis ${analysis.id}. This indicates a bug in the fee analysis.`);
|
|
243
|
+
}
|
|
244
|
+
// Record metrics for each strategy separately
|
|
245
|
+
for (const strategyResult of analysis.computedPrices.strategyResults){
|
|
246
|
+
const strategyAttributes = {
|
|
247
|
+
[Attributes.FISHERMAN_FEE_STRATEGY_ID]: strategyResult.strategyId
|
|
248
|
+
};
|
|
249
|
+
// Record pending block snapshot data (once per strategy for comparison)
|
|
250
|
+
this.fishermanPendingBlobTxCount.record(analysis.pendingSnapshot.pendingBlobTxCount, strategyAttributes);
|
|
251
|
+
this.fishermanPendingBlobCount.record(analysis.pendingSnapshot.pendingBlobCount, strategyAttributes);
|
|
252
|
+
// Record mined block data if available
|
|
253
|
+
if (analysis.minedBlock) {
|
|
254
|
+
this.fishermanIncludedBlobTxCount.record(analysis.minedBlock.includedBlobTxCount, strategyAttributes);
|
|
255
|
+
this.fishermanIncludedBlobCount.record(analysis.minedBlock.includedBlobCount, strategyAttributes);
|
|
256
|
+
// Record actual fees from blob transactions in the mined block
|
|
257
|
+
for (const blobTx of analysis.minedBlock.includedBlobTxs){
|
|
258
|
+
// Record priority fee per gas in Gwei
|
|
259
|
+
const priorityFeeGwei = Number(blobTx.maxPriorityFeePerGas) / 1e9;
|
|
260
|
+
this.fishermanMinedBlobTxPriorityFee.record(priorityFeeGwei, strategyAttributes);
|
|
261
|
+
// Calculate total cost in ETH
|
|
262
|
+
// Cost = (gas * (baseFee + priorityFee)) + (blobCount * blobGasPerBlob * blobBaseFee)
|
|
263
|
+
const baseFee = analysis.minedBlock.baseFeePerGas;
|
|
264
|
+
const effectiveGasPrice = baseFee + blobTx.maxPriorityFeePerGas;
|
|
265
|
+
// Calculate execution cost using actual gas limit from the transaction
|
|
266
|
+
const executionCost = blobTx.gas * effectiveGasPrice;
|
|
267
|
+
// Calculate blob cost using maxFeePerBlobGas * blobCount * GAS_PER_BLOB
|
|
268
|
+
const blobCost = blobTx.maxFeePerBlobGas * BigInt(blobTx.blobCount) * 131072n; // 128KB per blob
|
|
269
|
+
const totalCostWei = executionCost + blobCost;
|
|
270
|
+
const totalCostEth = Number(totalCostWei) / 1e18;
|
|
271
|
+
this.fishermanMinedBlobTxTotalCost.record(totalCostEth, strategyAttributes);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Record the calculated priority fee for this strategy
|
|
275
|
+
const calculatedPriorityFeeGwei = Number(strategyResult.calculatedPriorityFee) / 1e9;
|
|
276
|
+
this.fishermanCalculatedPriorityFee.record(calculatedPriorityFeeGwei, strategyAttributes);
|
|
277
|
+
// Record analysis results if available
|
|
278
|
+
if (analysis.analysis) {
|
|
279
|
+
this.fishermanTimeBeforeBlock.record(Math.ceil(analysis.analysis.timeBeforeBlockMs), strategyAttributes);
|
|
280
|
+
// Record whether the block reached 100% blob capacity
|
|
281
|
+
if (analysis.analysis.blockBlobsFull) {
|
|
282
|
+
this.fishermanBlockBlobsFull.add(1, {
|
|
283
|
+
...strategyAttributes,
|
|
284
|
+
[Attributes.OK]: true
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
this.fishermanBlockBlobsFull.add(1, {
|
|
288
|
+
...strategyAttributes,
|
|
289
|
+
[Attributes.OK]: false
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Record the max blob capacity for this block
|
|
293
|
+
this.fishermanMaxBlobCapacity.record(analysis.analysis.maxBlobCapacity, strategyAttributes);
|
|
294
|
+
// Record strategy-specific inclusion result
|
|
295
|
+
if (strategyResult.wouldBeIncluded !== undefined) {
|
|
296
|
+
const inclusionAttributes = {
|
|
297
|
+
...strategyAttributes,
|
|
298
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false'
|
|
299
|
+
};
|
|
300
|
+
if (strategyResult.wouldBeIncluded) {
|
|
301
|
+
this.fishermanWouldBeIncluded.add(1, {
|
|
302
|
+
...inclusionAttributes,
|
|
303
|
+
[Attributes.OK]: true
|
|
304
|
+
});
|
|
305
|
+
} else {
|
|
306
|
+
this.fishermanWouldBeIncluded.add(1, {
|
|
307
|
+
...inclusionAttributes,
|
|
308
|
+
[Attributes.OK]: false,
|
|
309
|
+
...strategyResult.exclusionReason && {
|
|
310
|
+
[Attributes.ERROR_TYPE]: strategyResult.exclusionReason
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Record strategy-specific priority fee delta
|
|
316
|
+
if (strategyResult.priorityFeeDelta !== undefined) {
|
|
317
|
+
const priorityFeeDeltaGwei = Number(strategyResult.priorityFeeDelta) / 1e9;
|
|
318
|
+
const deltaAttributes = {
|
|
319
|
+
...strategyAttributes,
|
|
320
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false'
|
|
321
|
+
};
|
|
322
|
+
this.fishermanPriorityFeeDelta.record(priorityFeeDeltaGwei, deltaAttributes);
|
|
323
|
+
}
|
|
324
|
+
// Record estimated cost if available
|
|
325
|
+
if (strategyResult.estimatedCostEth !== undefined) {
|
|
326
|
+
const costAttributes = {
|
|
327
|
+
...strategyAttributes,
|
|
328
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false'
|
|
329
|
+
};
|
|
330
|
+
this.fishermanEstimatedCost.record(strategyResult.estimatedCostEth, costAttributes);
|
|
331
|
+
}
|
|
332
|
+
// Record estimated overpayment if available
|
|
333
|
+
if (strategyResult.estimatedOverpaymentEth !== undefined) {
|
|
334
|
+
const overpaymentAttributes = {
|
|
335
|
+
...strategyAttributes,
|
|
336
|
+
[Attributes.BLOCK_FULL]: analysis.analysis.blockBlobsFull ? 'true' : 'false'
|
|
337
|
+
};
|
|
338
|
+
this.fishermanEstimatedOverpayment.record(strategyResult.estimatedOverpaymentEth, overpaymentAttributes);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { EpochCache } from '@aztec-labs/epoch-cache';
|
|
2
|
+
import type { RollupContract } from '@aztec-labs/ethereum/contracts';
|
|
3
|
+
import { EpochNumber, type SlotNumber } from '@aztec-labs/foundation/branded-types';
|
|
4
|
+
import type { Logger } from '@aztec-labs/foundation/log';
|
|
5
|
+
import type { DateProvider } from '@aztec-labs/foundation/timer';
|
|
6
|
+
import type { L2BlockSource } from '@aztec-labs/stdlib/block';
|
|
7
|
+
import { type L1RollupConstants } from '@aztec-labs/stdlib/epoch-helpers';
|
|
8
|
+
/** Collaborators {@link logMissingCommittee} needs to diagnose and report why no committee exists. */
|
|
9
|
+
export interface MissingCommitteeContext {
|
|
10
|
+
epochCache: Pick<EpochCache, 'getL1Constants' | 'getLagInEpochsForValidatorSet'>;
|
|
11
|
+
rollupContract: Pick<RollupContract, 'getActiveAttesterCount' | 'getAttesterCountAtTime'>;
|
|
12
|
+
l2BlockSource: Pick<L2BlockSource, 'getBlockNumber'>;
|
|
13
|
+
l1Constants: Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration' | 'epochDuration'>;
|
|
14
|
+
dateProvider: Pick<DateProvider, 'nowInSeconds'>;
|
|
15
|
+
logger: Pick<Logger, 'info' | 'warn' | 'debug'>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Why the sequencer cannot find a validator committee for an upcoming slot. A committee for an epoch is
|
|
19
|
+
* sampled from the validator set as of `lagInEpochsForValidatorSet` epochs earlier, and L1 refuses to
|
|
20
|
+
* produce one until at least `targetCommitteeSize` validators were staked at that sampling time.
|
|
21
|
+
*/
|
|
22
|
+
export type MissingCommitteeCause =
|
|
23
|
+
/** Enough validators are staked now; the committee is just waiting for the sampling window to advance. */
|
|
24
|
+
'awaiting-sampling-lag'
|
|
25
|
+
/** The chain has not produced any block yet and too few validators are staked: normal bootstrap state. */
|
|
26
|
+
| 'awaiting-first-validators'
|
|
27
|
+
/** The chain has produced blocks before but the validator set has since dropped below the required size. */
|
|
28
|
+
| 'validator-set-shrank';
|
|
29
|
+
export interface MissingCommitteeDiagnosis {
|
|
30
|
+
cause: MissingCommitteeCause;
|
|
31
|
+
/** Log severity: bootstrap and lag are expected (`info`); a shrunken set on a live chain is not (`warn`). */
|
|
32
|
+
severity: 'info' | 'warn';
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Logs why no validator committee exists for the given slot. Rather than the cryptic "committee does not
|
|
36
|
+
* exist on L1", it diagnoses the cause from the live attester count and whether the chain has ever produced
|
|
37
|
+
* a block: a bootstrapping chain waiting for validators to stake, a full set still waiting for the sampling
|
|
38
|
+
* lag to elapse, or a validator set that has shrunk below the required size on a live chain. Only the last is
|
|
39
|
+
* a genuine problem, so only it is logged at `warn`.
|
|
40
|
+
*
|
|
41
|
+
* Best-effort: if the diagnostic L1 reads fail we fall back to a neutral message so this never throws on the
|
|
42
|
+
* propose path.
|
|
43
|
+
*/
|
|
44
|
+
export declare function logMissingCommittee(targetSlot: SlotNumber, targetEpoch: EpochNumber, ctx: MissingCommitteeContext): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Classifies why no committee exists for a slot, using only cheap live signals: the current attester count
|
|
47
|
+
* and whether the chain has ever produced a block. See {@link MissingCommitteeCause} for the reasoning.
|
|
48
|
+
*/
|
|
49
|
+
export declare function classifyMissingCommittee(args: {
|
|
50
|
+
attesterCount: number;
|
|
51
|
+
targetCommitteeSize: number;
|
|
52
|
+
hasProducedBlocks: boolean;
|
|
53
|
+
}): MissingCommitteeDiagnosis;
|
|
54
|
+
/**
|
|
55
|
+
* Given each candidate epoch after the one we failed to propose at, paired with the attester count staked at
|
|
56
|
+
* that epoch's validator-set sample time (`undefined` when the sample time is still in the future), returns
|
|
57
|
+
* the earliest epoch that will have a committee, or `undefined` if none qualifies.
|
|
58
|
+
*
|
|
59
|
+
* Candidates must be ordered ascending by epoch. A committee for epoch `E` exists iff at least
|
|
60
|
+
* `targetCommitteeSize` validators were staked at `E`'s sample time, so the first candidate whose sampled
|
|
61
|
+
* count meets the target is the answer; a candidate whose sample time is still in the future is assumed to
|
|
62
|
+
* have a committee if the set holds and wins as soon as it is reached. The predicate is not monotone in `E`
|
|
63
|
+
* (the set can dip below target between sample times), so this must scan ascending rather than binary-search.
|
|
64
|
+
*/
|
|
65
|
+
export declare function findFirstEpochWithCommittee(args: {
|
|
66
|
+
candidates: {
|
|
67
|
+
epoch: EpochNumber;
|
|
68
|
+
sampledAttesterCount: number | undefined;
|
|
69
|
+
}[];
|
|
70
|
+
targetCommitteeSize: number;
|
|
71
|
+
}): EpochNumber | undefined;
|
|
72
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlzc2luZ19jb21taXR0ZWUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXF1ZW5jZXIvbWlzc2luZ19jb21taXR0ZWUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDMUQsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDckUsT0FBTyxFQUFFLFdBQVcsRUFBRSxLQUFLLFVBQVUsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBRXBGLE9BQU8sS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRXpELE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQ2pFLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzlELE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUE2QixNQUFNLGtDQUFrQyxDQUFDO0FBRXJHLHNHQUFzRztBQUN0RyxNQUFNLFdBQVcsdUJBQXVCO0lBQ3RDLFVBQVUsRUFBRSxJQUFJLENBQUMsVUFBVSxFQUFFLGdCQUFnQixHQUFHLCtCQUErQixDQUFDLENBQUM7SUFDakYsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsd0JBQXdCLEdBQUcsd0JBQXdCLENBQUMsQ0FBQztJQUMxRixhQUFhLEVBQUUsSUFBSSxDQUFDLGFBQWEsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3JELFdBQVcsRUFBRSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsZUFBZSxHQUFHLGNBQWMsR0FBRyxlQUFlLENBQUMsQ0FBQztJQUN6RixZQUFZLEVBQUUsSUFBSSxDQUFDLFlBQVksRUFBRSxjQUFjLENBQUMsQ0FBQztJQUNqRCxNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU0sRUFBRSxNQUFNLEdBQUcsTUFBTSxHQUFHLE9BQU8sQ0FBQyxDQUFDO0NBQ2pEO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sTUFBTSxxQkFBcUI7QUFDL0IsMEdBQTBHO0FBQ3hHLHVCQUF1QjtBQUN6QiwwR0FBMEc7R0FDeEcsMkJBQTJCO0FBQzdCLDRHQUE0RztHQUMxRyxzQkFBc0IsQ0FBQztBQUUzQixNQUFNLFdBQVcseUJBQXlCO0lBQ3hDLEtBQUssRUFBRSxxQkFBcUIsQ0FBQztJQUM3Qiw2R0FBNkc7SUFDN0csUUFBUSxFQUFFLE1BQU0sR0FBRyxNQUFNLENBQUM7Q0FDM0I7QUFFRDs7Ozs7Ozs7O0dBU0c7QUFDSCx3QkFBc0IsbUJBQW1CLENBQ3ZDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFdBQVcsRUFBRSxXQUFXLEVBQ3hCLEdBQUcsRUFBRSx1QkFBdUIsR0FDM0IsT0FBTyxDQUFDLElBQUksQ0FBQyxDQTBEZjtBQUVEOzs7R0FHRztBQUNILHdCQUFnQix3QkFBd0IsQ0FBQyxJQUFJLEVBQUU7SUFDN0MsYUFBYSxFQUFFLE1BQU0sQ0FBQztJQUN0QixtQkFBbUIsRUFBRSxNQUFNLENBQUM7SUFDNUIsaUJBQWlCLEVBQUUsT0FBTyxDQUFDO0NBQzVCLEdBQUcseUJBQXlCLENBWTVCO0FBRUQ7Ozs7Ozs7Ozs7R0FVRztBQUNILHdCQUFnQiwyQkFBMkIsQ0FBQyxJQUFJLEVBQUU7SUFDaEQsVUFBVSxFQUFFO1FBQUUsS0FBSyxFQUFFLFdBQVcsQ0FBQztRQUFDLG9CQUFvQixFQUFFLE1BQU0sR0FBRyxTQUFTLENBQUE7S0FBRSxFQUFFLENBQUM7SUFDL0UsbUJBQW1CLEVBQUUsTUFBTSxDQUFDO0NBQzdCLEdBQUcsV0FBVyxHQUFHLFNBQVMsQ0FJMUIifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing_committee.d.ts","sourceRoot":"","sources":["../../src/sequencer/missing_committee.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAEpF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,KAAK,iBAAiB,EAA6B,MAAM,kCAAkC,CAAC;AAErG,sGAAsG;AACtG,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,gBAAgB,GAAG,+BAA+B,CAAC,CAAC;IACjF,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,wBAAwB,GAAG,wBAAwB,CAAC,CAAC;IAC1F,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC;IACrD,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,cAAc,GAAG,eAAe,CAAC,CAAC;IACzF,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IACjD,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB;AAC/B,0GAA0G;AACxG,uBAAuB;AACzB,0GAA0G;GACxG,2BAA2B;AAC7B,4GAA4G;GAC1G,sBAAsB,CAAC;AAE3B,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,6GAA6G;IAC7G,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,uBAAuB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA0Df;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;CAC5B,GAAG,yBAAyB,CAY5B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE;IAChD,UAAU,EAAE;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,EAAE,CAAC;IAC/E,mBAAmB,EAAE,MAAM,CAAC;CAC7B,GAAG,WAAW,GAAG,SAAS,CAI1B"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { EpochNumber } from '@aztec-labs/foundation/branded-types';
|
|
2
|
+
import { timesParallel } from '@aztec-labs/foundation/collection';
|
|
3
|
+
import { formatSeconds } from '@aztec-labs/foundation/string';
|
|
4
|
+
import { getStartTimestampForEpoch } from '@aztec-labs/stdlib/epoch-helpers';
|
|
5
|
+
/**
|
|
6
|
+
* Logs why no validator committee exists for the given slot. Rather than the cryptic "committee does not
|
|
7
|
+
* exist on L1", it diagnoses the cause from the live attester count and whether the chain has ever produced
|
|
8
|
+
* a block: a bootstrapping chain waiting for validators to stake, a full set still waiting for the sampling
|
|
9
|
+
* lag to elapse, or a validator set that has shrunk below the required size on a live chain. Only the last is
|
|
10
|
+
* a genuine problem, so only it is logged at `warn`.
|
|
11
|
+
*
|
|
12
|
+
* Best-effort: if the diagnostic L1 reads fail we fall back to a neutral message so this never throws on the
|
|
13
|
+
* propose path.
|
|
14
|
+
*/ export async function logMissingCommittee(targetSlot, targetEpoch, ctx) {
|
|
15
|
+
const targetCommitteeSize = ctx.epochCache.getL1Constants().targetCommitteeSize;
|
|
16
|
+
const lag = ctx.epochCache.getLagInEpochsForValidatorSet();
|
|
17
|
+
let attesterCount;
|
|
18
|
+
let hasProducedBlocks;
|
|
19
|
+
try {
|
|
20
|
+
[attesterCount, hasProducedBlocks] = await Promise.all([
|
|
21
|
+
ctx.rollupContract.getActiveAttesterCount(),
|
|
22
|
+
ctx.l2BlockSource.getBlockNumber().then((n)=>n > 0)
|
|
23
|
+
]);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
ctx.logger.warn(`No committee found for slot ${targetSlot}; could not determine validator set size`, {
|
|
26
|
+
targetSlot,
|
|
27
|
+
targetEpoch,
|
|
28
|
+
targetCommitteeSize,
|
|
29
|
+
err
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const diagnosis = classifyMissingCommittee({
|
|
34
|
+
attesterCount,
|
|
35
|
+
targetCommitteeSize,
|
|
36
|
+
hasProducedBlocks
|
|
37
|
+
});
|
|
38
|
+
const logCtx = {
|
|
39
|
+
targetSlot,
|
|
40
|
+
targetEpoch,
|
|
41
|
+
attesterCount,
|
|
42
|
+
targetCommitteeSize,
|
|
43
|
+
cause: diagnosis.cause
|
|
44
|
+
};
|
|
45
|
+
switch(diagnosis.cause){
|
|
46
|
+
case 'awaiting-sampling-lag':
|
|
47
|
+
{
|
|
48
|
+
const firstCommitteeEpoch = await estimateFirstCommitteeEpoch(targetEpoch, lag, targetCommitteeSize, ctx);
|
|
49
|
+
const staked = `${attesterCount} validators are staked (>= ${targetCommitteeSize} required)`;
|
|
50
|
+
const forming = `the committee is still forming as the ${lag}-epoch sampling window advances`;
|
|
51
|
+
// When we could not pin the exact epoch, report the safe upper bound rather than a misleading ETA.
|
|
52
|
+
const epoch = firstCommitteeEpoch ?? EpochNumber(targetEpoch + lag + 1);
|
|
53
|
+
const expectation = firstCommitteeEpoch === undefined ? `A committee should exist no later than epoch ${epoch}.` : `The first committee is expected at epoch ${epoch}${formatEpochEta(epoch, ctx)}.`;
|
|
54
|
+
ctx.logger.info(`No committee for slot ${targetSlot} yet: ${staked}, ${forming}. ${expectation}`, {
|
|
55
|
+
...logCtx,
|
|
56
|
+
firstCommitteeEpoch: epoch
|
|
57
|
+
});
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'awaiting-first-validators':
|
|
61
|
+
ctx.logger.info(`No committee for slot ${targetSlot}: the chain has not started producing blocks and only ${attesterCount} ` + `of the ${targetCommitteeSize} required validators are staked. Block production begins once at least ` + `${targetCommitteeSize} validators stake and the ${lag}-epoch sampling lag elapses.`, logCtx);
|
|
62
|
+
break;
|
|
63
|
+
case 'validator-set-shrank':
|
|
64
|
+
ctx.logger.warn(`No committee for slot ${targetSlot}: the validator set has dropped to ${attesterCount}, below the ` + `${targetCommitteeSize} required to form a committee. The chain cannot progress until enough validators ` + `are staked again — check for validators exiting or being slashed.`, logCtx);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Classifies why no committee exists for a slot, using only cheap live signals: the current attester count
|
|
70
|
+
* and whether the chain has ever produced a block. See {@link MissingCommitteeCause} for the reasoning.
|
|
71
|
+
*/ export function classifyMissingCommittee(args) {
|
|
72
|
+
const { attesterCount, targetCommitteeSize, hasProducedBlocks } = args;
|
|
73
|
+
if (attesterCount >= targetCommitteeSize) {
|
|
74
|
+
return {
|
|
75
|
+
cause: 'awaiting-sampling-lag',
|
|
76
|
+
severity: 'info'
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (hasProducedBlocks) {
|
|
80
|
+
return {
|
|
81
|
+
cause: 'validator-set-shrank',
|
|
82
|
+
severity: 'warn'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
cause: 'awaiting-first-validators',
|
|
87
|
+
severity: 'info'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Given each candidate epoch after the one we failed to propose at, paired with the attester count staked at
|
|
92
|
+
* that epoch's validator-set sample time (`undefined` when the sample time is still in the future), returns
|
|
93
|
+
* the earliest epoch that will have a committee, or `undefined` if none qualifies.
|
|
94
|
+
*
|
|
95
|
+
* Candidates must be ordered ascending by epoch. A committee for epoch `E` exists iff at least
|
|
96
|
+
* `targetCommitteeSize` validators were staked at `E`'s sample time, so the first candidate whose sampled
|
|
97
|
+
* count meets the target is the answer; a candidate whose sample time is still in the future is assumed to
|
|
98
|
+
* have a committee if the set holds and wins as soon as it is reached. The predicate is not monotone in `E`
|
|
99
|
+
* (the set can dip below target between sample times), so this must scan ascending rather than binary-search.
|
|
100
|
+
*/ export function findFirstEpochWithCommittee(args) {
|
|
101
|
+
return args.candidates.find((c)=>c.sampledAttesterCount === undefined || c.sampledAttesterCount >= args.targetCommitteeSize)?.epoch;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Earliest epoch expected to have a committee, replaying L1's validator-set sampling rule: a committee for
|
|
105
|
+
* epoch `E` exists iff at least `targetCommitteeSize` attesters were staked at `E`'s sample time
|
|
106
|
+
* (`epochStart(E) - lag * epochDuration`). Only epochs after the one we failed at can qualify, and the set
|
|
107
|
+
* must have crossed the target within the last `lag` epochs' sample window, so the answer lies in
|
|
108
|
+
* `(targetEpoch, targetEpoch + lag + 1]`. Reads the historical attester count at each candidate's sample time
|
|
109
|
+
* (at most `lag` on-chain reads; future sample times are assumed to hold). Returns `undefined` if no candidate
|
|
110
|
+
* qualifies or the reads fail, leaving the caller to report a bound instead of an ETA.
|
|
111
|
+
*/ async function estimateFirstCommitteeEpoch(targetEpoch, lag, targetCommitteeSize, ctx) {
|
|
112
|
+
try {
|
|
113
|
+
const epochDurationSeconds = BigInt(ctx.l1Constants.epochDuration * ctx.l1Constants.slotDuration);
|
|
114
|
+
const nowSeconds = BigInt(ctx.dateProvider.nowInSeconds());
|
|
115
|
+
const candidates = await timesParallel(lag + 1, async (i)=>{
|
|
116
|
+
const epoch = EpochNumber(targetEpoch + 1 + i);
|
|
117
|
+
const sampleTime = getStartTimestampForEpoch(epoch, ctx.l1Constants) - epochDurationSeconds * BigInt(lag);
|
|
118
|
+
const sampledAttesterCount = sampleTime > nowSeconds ? undefined : await ctx.rollupContract.getAttesterCountAtTime(sampleTime);
|
|
119
|
+
return {
|
|
120
|
+
epoch,
|
|
121
|
+
sampledAttesterCount
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
return findFirstEpochWithCommittee({
|
|
125
|
+
candidates,
|
|
126
|
+
targetCommitteeSize
|
|
127
|
+
});
|
|
128
|
+
} catch (err) {
|
|
129
|
+
ctx.logger.debug(`Could not estimate first committee epoch after epoch ${targetEpoch}`, {
|
|
130
|
+
targetEpoch,
|
|
131
|
+
err
|
|
132
|
+
});
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Formats the ETA to the start of the given epoch as ` (~12m from now)`, or empty if it is in the past. */ function formatEpochEta(epoch, ctx) {
|
|
137
|
+
const secondsUntil = Number(getStartTimestampForEpoch(epoch, ctx.l1Constants)) - ctx.dateProvider.nowInSeconds();
|
|
138
|
+
return secondsUntil > 0 ? ` (~${formatSeconds(secondsUntil)} from now)` : '';
|
|
139
|
+
}
|