@aztec/sequencer-client 0.0.1-commit.381b1a9 → 0.0.1-commit.3a4ae741b
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 +4 -12
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +27 -76
- package/dest/config.d.ts +4 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +8 -1
- package/dest/global_variable_builder/global_builder.d.ts +13 -7
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +22 -21
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +17 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +23 -3
- 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 +3 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +16 -2
- package/dest/publisher/sequencer-publisher.d.ts +19 -4
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +291 -18
- package/dest/sequencer/checkpoint_proposal_job.d.ts +12 -4
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +141 -87
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +5 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +11 -0
- package/dest/sequencer/sequencer.d.ts +6 -4
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +60 -48
- package/dest/test/mock_checkpoint_builder.d.ts +4 -8
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +0 -2
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +37 -101
- package/src/config.ts +11 -0
- package/src/global_variable_builder/global_builder.ts +22 -23
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +41 -0
- 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 +18 -3
- package/src/publisher/sequencer-publisher.ts +276 -26
- package/src/sequencer/checkpoint_proposal_job.ts +178 -90
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +14 -0
- package/src/sequencer/sequencer.ts +85 -54
- package/src/test/mock_checkpoint_builder.ts +3 -5
|
@@ -19,15 +19,10 @@ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
|
19
19
|
import { L1Metrics, type TelemetryClient } from '@aztec/telemetry-client';
|
|
20
20
|
import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
|
|
21
21
|
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
type SequencerClientConfig,
|
|
25
|
-
getPublisherConfigFromSequencerConfig,
|
|
26
|
-
} from '../config.js';
|
|
27
|
-
import { GlobalVariableBuilder } from '../global_variable_builder/index.js';
|
|
22
|
+
import { type SequencerClientConfig, getPublisherConfigFromSequencerConfig } from '../config.js';
|
|
23
|
+
import type { GlobalVariableBuilder } from '../global_variable_builder/index.js';
|
|
28
24
|
import { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
|
|
29
25
|
import { Sequencer, type SequencerConfig } from '../sequencer/index.js';
|
|
30
|
-
import { SequencerTimetable } from '../sequencer/timetable.js';
|
|
31
26
|
|
|
32
27
|
/**
|
|
33
28
|
* Encapsulates the full sequencer and publisher.
|
|
@@ -70,7 +65,9 @@ export class SequencerClient {
|
|
|
70
65
|
dateProvider: DateProvider;
|
|
71
66
|
epochCache?: EpochCache;
|
|
72
67
|
l1TxUtils: L1TxUtils[];
|
|
68
|
+
funderL1TxUtils?: L1TxUtils;
|
|
73
69
|
nodeKeyStore: KeystoreManager;
|
|
70
|
+
globalVariableBuilder: GlobalVariableBuilder;
|
|
74
71
|
},
|
|
75
72
|
) {
|
|
76
73
|
const {
|
|
@@ -92,16 +89,14 @@ export class SequencerClient {
|
|
|
92
89
|
publicClient,
|
|
93
90
|
l1TxUtils.map(x => x.getSenderAddress()),
|
|
94
91
|
);
|
|
95
|
-
const publisherManager = new PublisherManager(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
);
|
|
92
|
+
const publisherManager = new PublisherManager(l1TxUtils, getPublisherConfigFromSequencerConfig(config), {
|
|
93
|
+
bindings: log.getBindings(),
|
|
94
|
+
funder: deps.funderL1TxUtils,
|
|
95
|
+
});
|
|
100
96
|
const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
|
|
101
|
-
const [l1GenesisTime, slotDuration,
|
|
97
|
+
const [l1GenesisTime, slotDuration, rollupManaLimit] = await Promise.all([
|
|
102
98
|
rollupContract.getL1GenesisTime(),
|
|
103
99
|
rollupContract.getSlotDuration(),
|
|
104
|
-
rollupContract.getVersion(),
|
|
105
100
|
rollupContract.getManaLimit().then(Number),
|
|
106
101
|
] as const);
|
|
107
102
|
|
|
@@ -118,6 +113,7 @@ export class SequencerClient {
|
|
|
118
113
|
l1ChainId: chainId,
|
|
119
114
|
viemPollingIntervalMS: config.viemPollingIntervalMS,
|
|
120
115
|
ethereumSlotDuration: config.ethereumSlotDuration,
|
|
116
|
+
enableProposerPipelining: config.enableProposerPipelining,
|
|
121
117
|
},
|
|
122
118
|
{ dateProvider: deps.dateProvider },
|
|
123
119
|
));
|
|
@@ -144,13 +140,7 @@ export class SequencerClient {
|
|
|
144
140
|
|
|
145
141
|
const ethereumSlotDuration = config.ethereumSlotDuration;
|
|
146
142
|
|
|
147
|
-
const globalsBuilder =
|
|
148
|
-
...config,
|
|
149
|
-
l1GenesisTime,
|
|
150
|
-
slotDuration: Number(slotDuration),
|
|
151
|
-
ethereumSlotDuration,
|
|
152
|
-
rollupVersion,
|
|
153
|
-
});
|
|
143
|
+
const globalsBuilder = deps.globalVariableBuilder;
|
|
154
144
|
|
|
155
145
|
// When running in anvil, assume we can post a tx up until one second before the end of an L1 slot.
|
|
156
146
|
// Otherwise, we need the full L1 slot duration for publishing to ensure inclusion.
|
|
@@ -160,12 +150,7 @@ export class SequencerClient {
|
|
|
160
150
|
const l1PublishingTimeBasedOnChain = isAnvilTestChain(config.l1ChainId) ? 1 : ethereumSlotDuration;
|
|
161
151
|
const l1PublishingTime = config.l1PublishingTime ?? l1PublishingTimeBasedOnChain;
|
|
162
152
|
|
|
163
|
-
const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock } =
|
|
164
|
-
config,
|
|
165
|
-
rollupManaLimit,
|
|
166
|
-
l1PublishingTime,
|
|
167
|
-
log,
|
|
168
|
-
);
|
|
153
|
+
const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock } = capPerBlockLimits(config, rollupManaLimit, log);
|
|
169
154
|
|
|
170
155
|
const l1Constants = { l1GenesisTime, slotDuration: Number(slotDuration), ethereumSlotDuration, rollupManaLimit };
|
|
171
156
|
|
|
@@ -211,7 +196,7 @@ export class SequencerClient {
|
|
|
211
196
|
await this.validatorClient?.start();
|
|
212
197
|
this.sequencer.start();
|
|
213
198
|
this.l1Metrics?.start();
|
|
214
|
-
await this.publisherManager.
|
|
199
|
+
await this.publisherManager.start();
|
|
215
200
|
}
|
|
216
201
|
|
|
217
202
|
/**
|
|
@@ -220,7 +205,7 @@ export class SequencerClient {
|
|
|
220
205
|
public async stop() {
|
|
221
206
|
await this.sequencer.stop();
|
|
222
207
|
await this.validatorClient?.stop();
|
|
223
|
-
this.publisherManager.
|
|
208
|
+
await this.publisherManager.stop();
|
|
224
209
|
this.l1Metrics?.stop();
|
|
225
210
|
}
|
|
226
211
|
|
|
@@ -248,88 +233,39 @@ export class SequencerClient {
|
|
|
248
233
|
}
|
|
249
234
|
|
|
250
235
|
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* Otherwise, derives it as (checkpointLimit / maxBlocks) * multiplier, capped at the checkpoint limit.
|
|
236
|
+
* Caps operator-provided per-block limits at checkpoint-level limits.
|
|
237
|
+
* Returns undefined for any limit the operator didn't set — the checkpoint builder handles redistribution.
|
|
254
238
|
*/
|
|
255
|
-
|
|
239
|
+
function capPerBlockLimits(
|
|
256
240
|
config: SequencerClientConfig,
|
|
257
241
|
rollupManaLimit: number,
|
|
258
|
-
l1PublishingTime: number,
|
|
259
242
|
log: ReturnType<typeof createLogger>,
|
|
260
|
-
): { maxL2BlockGas: number; maxDABlockGas: number; maxTxsPerBlock: number } {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
p2pPropagationTime: config.attestationPropagationTime,
|
|
266
|
-
blockDurationMs: config.blockDurationMs,
|
|
267
|
-
enforce: config.enforceTimeTable ?? DefaultSequencerConfig.enforceTimeTable,
|
|
268
|
-
}).maxNumberOfBlocks;
|
|
269
|
-
|
|
270
|
-
const multiplier = config.perBlockAllocationMultiplier ?? DefaultSequencerConfig.perBlockAllocationMultiplier;
|
|
271
|
-
|
|
272
|
-
// Compute maxL2BlockGas
|
|
273
|
-
let maxL2BlockGas: number;
|
|
274
|
-
if (config.maxL2BlockGas !== undefined) {
|
|
275
|
-
if (config.maxL2BlockGas > rollupManaLimit) {
|
|
276
|
-
log.warn(
|
|
277
|
-
`Provided MAX_L2_BLOCK_GAS ${config.maxL2BlockGas} exceeds L1 rollup mana limit ${rollupManaLimit} (capping)`,
|
|
278
|
-
);
|
|
279
|
-
maxL2BlockGas = rollupManaLimit;
|
|
280
|
-
} else {
|
|
281
|
-
maxL2BlockGas = config.maxL2BlockGas;
|
|
282
|
-
}
|
|
283
|
-
} else {
|
|
284
|
-
maxL2BlockGas = Math.min(rollupManaLimit, Math.ceil((rollupManaLimit / maxNumberOfBlocks) * multiplier));
|
|
243
|
+
): { maxL2BlockGas: number | undefined; maxDABlockGas: number | undefined; maxTxsPerBlock: number | undefined } {
|
|
244
|
+
let maxL2BlockGas = config.maxL2BlockGas;
|
|
245
|
+
if (maxL2BlockGas !== undefined && maxL2BlockGas > rollupManaLimit) {
|
|
246
|
+
log.warn(`Provided MAX_L2_BLOCK_GAS ${maxL2BlockGas} exceeds rollup mana limit ${rollupManaLimit} (capping)`);
|
|
247
|
+
maxL2BlockGas = rollupManaLimit;
|
|
285
248
|
}
|
|
286
249
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
`Provided MAX_DA_BLOCK_GAS ${config.maxDABlockGas} exceeds DA checkpoint limit ${daCheckpointLimit} (capping)`,
|
|
294
|
-
);
|
|
295
|
-
maxDABlockGas = daCheckpointLimit;
|
|
296
|
-
} else {
|
|
297
|
-
maxDABlockGas = config.maxDABlockGas;
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
maxDABlockGas = Math.min(daCheckpointLimit, Math.ceil((daCheckpointLimit / maxNumberOfBlocks) * multiplier));
|
|
250
|
+
let maxDABlockGas = config.maxDABlockGas;
|
|
251
|
+
if (maxDABlockGas !== undefined && maxDABlockGas > MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT) {
|
|
252
|
+
log.warn(
|
|
253
|
+
`Provided MAX_DA_BLOCK_GAS ${maxDABlockGas} exceeds DA checkpoint limit ${MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT} (capping)`,
|
|
254
|
+
);
|
|
255
|
+
maxDABlockGas = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT;
|
|
301
256
|
}
|
|
302
257
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
)
|
|
311
|
-
maxTxsPerBlock = config.maxTxsPerCheckpoint;
|
|
312
|
-
} else {
|
|
313
|
-
maxTxsPerBlock = config.maxTxsPerBlock;
|
|
314
|
-
}
|
|
315
|
-
} else if (config.maxTxsPerCheckpoint !== undefined) {
|
|
316
|
-
maxTxsPerBlock = Math.min(
|
|
317
|
-
config.maxTxsPerCheckpoint,
|
|
318
|
-
Math.ceil((config.maxTxsPerCheckpoint / maxNumberOfBlocks) * multiplier),
|
|
258
|
+
let maxTxsPerBlock = config.maxTxsPerBlock;
|
|
259
|
+
if (
|
|
260
|
+
maxTxsPerBlock !== undefined &&
|
|
261
|
+
config.maxTxsPerCheckpoint !== undefined &&
|
|
262
|
+
maxTxsPerBlock > config.maxTxsPerCheckpoint
|
|
263
|
+
) {
|
|
264
|
+
log.warn(
|
|
265
|
+
`Provided MAX_TX_PER_BLOCK ${maxTxsPerBlock} exceeds MAX_TX_PER_CHECKPOINT ${config.maxTxsPerCheckpoint} (capping)`,
|
|
319
266
|
);
|
|
320
|
-
|
|
321
|
-
maxTxsPerBlock = defaultMaxTxsPerBlock;
|
|
267
|
+
maxTxsPerBlock = config.maxTxsPerCheckpoint;
|
|
322
268
|
}
|
|
323
269
|
|
|
324
|
-
log.info(`Computed block limits L2=${maxL2BlockGas} DA=${maxDABlockGas} maxTxs=${maxTxsPerBlock}`, {
|
|
325
|
-
maxL2BlockGas,
|
|
326
|
-
maxDABlockGas,
|
|
327
|
-
maxTxsPerBlock,
|
|
328
|
-
rollupManaLimit,
|
|
329
|
-
daCheckpointLimit,
|
|
330
|
-
maxNumberOfBlocks,
|
|
331
|
-
multiplier,
|
|
332
|
-
});
|
|
333
|
-
|
|
334
270
|
return { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock };
|
|
335
271
|
}
|
package/src/config.ts
CHANGED
|
@@ -13,8 +13,10 @@ import { type P2PConfig, p2pConfigMappings } from '@aztec/p2p/config';
|
|
|
13
13
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
14
14
|
import {
|
|
15
15
|
type ChainConfig,
|
|
16
|
+
type PipelineConfig,
|
|
16
17
|
type SequencerConfig,
|
|
17
18
|
chainConfigMappings,
|
|
19
|
+
pipelineConfigMappings,
|
|
18
20
|
sharedSequencerConfigMappings,
|
|
19
21
|
} from '@aztec/stdlib/config';
|
|
20
22
|
import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
|
|
@@ -41,6 +43,7 @@ export const DefaultSequencerConfig = {
|
|
|
41
43
|
buildCheckpointIfEmpty: false,
|
|
42
44
|
publishTxsWithProposals: false,
|
|
43
45
|
perBlockAllocationMultiplier: 1.2,
|
|
46
|
+
redistributeCheckpointBudget: true,
|
|
44
47
|
enforceTimeTable: true,
|
|
45
48
|
attestationPropagationTime: DEFAULT_P2P_PROPAGATION_TIME,
|
|
46
49
|
secondsBeforeInvalidatingBlockAsCommitteeMember: 144, // 12 L1 blocks
|
|
@@ -67,6 +70,7 @@ export type SequencerClientConfig = SequencerPublisherConfig &
|
|
|
67
70
|
SequencerConfig &
|
|
68
71
|
L1ReaderConfig &
|
|
69
72
|
ChainConfig &
|
|
73
|
+
PipelineConfig &
|
|
70
74
|
Pick<P2PConfig, 'txPublicSetupAllowListExtend'> &
|
|
71
75
|
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration' | 'aztecEpochDuration'>;
|
|
72
76
|
|
|
@@ -112,6 +116,12 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
|
112
116
|
' Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks.',
|
|
113
117
|
...numberConfigHelper(DefaultSequencerConfig.perBlockAllocationMultiplier),
|
|
114
118
|
},
|
|
119
|
+
redistributeCheckpointBudget: {
|
|
120
|
+
env: 'SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET',
|
|
121
|
+
description:
|
|
122
|
+
'Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget.',
|
|
123
|
+
...booleanConfigHelper(DefaultSequencerConfig.redistributeCheckpointBudget),
|
|
124
|
+
},
|
|
115
125
|
coinbase: {
|
|
116
126
|
env: 'COINBASE',
|
|
117
127
|
parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
|
|
@@ -234,6 +244,7 @@ export const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientCo
|
|
|
234
244
|
...sequencerTxSenderConfigMappings,
|
|
235
245
|
...sequencerPublisherConfigMappings,
|
|
236
246
|
...chainConfigMappings,
|
|
247
|
+
...pipelineConfigMappings,
|
|
237
248
|
...pickConfigMappings(l1ContractsConfigMappings, ['ethereumSlotDuration', 'aztecSlotDuration', 'aztecEpochDuration']),
|
|
238
249
|
};
|
|
239
250
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { createEthereumChain } from '@aztec/ethereum/chain';
|
|
2
|
-
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
3
1
|
import { RollupContract } from '@aztec/ethereum/contracts';
|
|
4
|
-
import type {
|
|
2
|
+
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
|
|
5
3
|
import type { ViemPublicClient } from '@aztec/ethereum/types';
|
|
6
4
|
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
7
5
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
8
6
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
8
|
+
import type { DateProvider } from '@aztec/foundation/timer';
|
|
10
9
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
11
|
-
import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
10
|
+
import { type L1RollupConstants, getNextL1SlotTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
12
11
|
import { GasFees } from '@aztec/stdlib/gas';
|
|
13
12
|
import type {
|
|
14
13
|
CheckpointGlobalVariables,
|
|
@@ -16,7 +15,12 @@ import type {
|
|
|
16
15
|
} from '@aztec/stdlib/tx';
|
|
17
16
|
import { GlobalVariables } from '@aztec/stdlib/tx';
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
/** Configuration for the GlobalVariableBuilder (excludes L1 client config). */
|
|
19
|
+
export type GlobalVariableBuilderConfig = {
|
|
20
|
+
l1Contracts: Pick<L1ContractAddresses, 'rollupAddress'>;
|
|
21
|
+
ethereumSlotDuration: number;
|
|
22
|
+
rollupVersion: bigint;
|
|
23
|
+
} & Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'>;
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* Simple global variables builder.
|
|
@@ -27,7 +31,6 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
27
31
|
private currentL1BlockNumber: bigint | undefined = undefined;
|
|
28
32
|
|
|
29
33
|
private readonly rollupContract: RollupContract;
|
|
30
|
-
private readonly publicClient: ViemPublicClient;
|
|
31
34
|
private readonly ethereumSlotDuration: number;
|
|
32
35
|
private readonly aztecSlotDuration: number;
|
|
33
36
|
private readonly l1GenesisTime: bigint;
|
|
@@ -36,28 +39,18 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
36
39
|
private version: Fr;
|
|
37
40
|
|
|
38
41
|
constructor(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
private readonly dateProvider: DateProvider,
|
|
43
|
+
private readonly publicClient: ViemPublicClient,
|
|
44
|
+
config: GlobalVariableBuilderConfig,
|
|
42
45
|
) {
|
|
43
|
-
const { l1RpcUrls, l1ChainId: chainId, l1Contracts } = config;
|
|
44
|
-
|
|
45
|
-
const chain = createEthereumChain(l1RpcUrls, chainId);
|
|
46
|
-
|
|
47
46
|
this.version = new Fr(config.rollupVersion);
|
|
48
|
-
this.chainId = new Fr(
|
|
47
|
+
this.chainId = new Fr(this.publicClient.chain!.id);
|
|
49
48
|
|
|
50
49
|
this.ethereumSlotDuration = config.ethereumSlotDuration;
|
|
51
50
|
this.aztecSlotDuration = config.slotDuration;
|
|
52
51
|
this.l1GenesisTime = config.l1GenesisTime;
|
|
53
52
|
|
|
54
|
-
this.
|
|
55
|
-
chain: chain.chainInfo,
|
|
56
|
-
transport: fallback(chain.rpcUrls.map(url => http(url, { batch: false }))),
|
|
57
|
-
pollingInterval: config.viemPollingIntervalMS,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
this.rollupContract = new RollupContract(this.publicClient, l1Contracts.rollupAddress);
|
|
53
|
+
this.rollupContract = new RollupContract(this.publicClient, config.l1Contracts.rollupAddress);
|
|
61
54
|
}
|
|
62
55
|
|
|
63
56
|
/**
|
|
@@ -73,7 +66,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
73
66
|
const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
|
|
74
67
|
SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
|
|
75
68
|
);
|
|
76
|
-
const nextEthTimestamp =
|
|
69
|
+
const nextEthTimestamp = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
|
|
70
|
+
l1GenesisTime: this.l1GenesisTime,
|
|
71
|
+
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
72
|
+
});
|
|
77
73
|
const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
|
|
78
74
|
|
|
79
75
|
return new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true));
|
|
@@ -108,7 +104,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
108
104
|
const slot: SlotNumber =
|
|
109
105
|
maybeSlot ??
|
|
110
106
|
(await this.rollupContract.getSlotAt(
|
|
111
|
-
|
|
107
|
+
getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
|
|
108
|
+
l1GenesisTime: this.l1GenesisTime,
|
|
109
|
+
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
110
|
+
}),
|
|
112
111
|
));
|
|
113
112
|
|
|
114
113
|
const checkpointGlobalVariables = await this.buildCheckpointGlobalVariables(coinbase, feeRecipient, slot);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { GlobalVariableBuilder } from './global_builder.js';
|
|
1
|
+
export { GlobalVariableBuilder, type GlobalVariableBuilderConfig } from './global_builder.js';
|
package/src/publisher/config.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from '@aztec/ethereum/l
|
|
|
4
4
|
import { type ConfigMappingsType, SecretValue, booleanConfigHelper } from '@aztec/foundation/config';
|
|
5
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
6
|
|
|
7
|
+
import { parseEther } from 'viem';
|
|
8
|
+
|
|
7
9
|
/** Configuration of the transaction publisher. */
|
|
8
10
|
export type TxSenderConfig = L1ReaderConfig & {
|
|
9
11
|
/** The private key to be used by the publisher. */
|
|
@@ -48,13 +50,39 @@ export type PublisherConfig = L1TxUtilsConfig &
|
|
|
48
50
|
fishermanMode?: boolean;
|
|
49
51
|
/** Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only) */
|
|
50
52
|
publisherForwarderAddress?: EthAddress;
|
|
53
|
+
/** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */
|
|
54
|
+
l1TxFailedStore?: string;
|
|
55
|
+
/** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */
|
|
56
|
+
publisherFundingThreshold?: bigint;
|
|
57
|
+
/** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */
|
|
58
|
+
publisherFundingAmount?: bigint;
|
|
51
59
|
};
|
|
52
60
|
|
|
61
|
+
/** Shared config mappings for publisher funding, used by both sequencer and prover publisher configs. */
|
|
62
|
+
const publisherFundingConfigMappings = {
|
|
63
|
+
publisherFundingThreshold: {
|
|
64
|
+
env: 'PUBLISHER_FUNDING_THRESHOLD' as const,
|
|
65
|
+
description:
|
|
66
|
+
'Min ETH balance below which a publisher gets funded. Specified in ether (e.g. 0.1). Unset = funding disabled.',
|
|
67
|
+
parseEnv: (val: string) => parseEther(val),
|
|
68
|
+
},
|
|
69
|
+
publisherFundingAmount: {
|
|
70
|
+
env: 'PUBLISHER_FUNDING_AMOUNT' as const,
|
|
71
|
+
description:
|
|
72
|
+
'Amount of ETH to send when funding a publisher. Specified in ether (e.g. 0.5). Unset = funding disabled.',
|
|
73
|
+
parseEnv: (val: string) => parseEther(val),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
53
77
|
export type ProverPublisherConfig = L1TxUtilsConfig &
|
|
54
78
|
BlobClientConfig & {
|
|
55
79
|
fishermanMode?: boolean;
|
|
56
80
|
proverPublisherAllowInvalidStates?: boolean;
|
|
57
81
|
proverPublisherForwarderAddress?: EthAddress;
|
|
82
|
+
/** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */
|
|
83
|
+
publisherFundingThreshold?: bigint;
|
|
84
|
+
/** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */
|
|
85
|
+
publisherFundingAmount?: bigint;
|
|
58
86
|
};
|
|
59
87
|
|
|
60
88
|
export type SequencerPublisherConfig = L1TxUtilsConfig &
|
|
@@ -62,6 +90,12 @@ export type SequencerPublisherConfig = L1TxUtilsConfig &
|
|
|
62
90
|
fishermanMode?: boolean;
|
|
63
91
|
sequencerPublisherAllowInvalidStates?: boolean;
|
|
64
92
|
sequencerPublisherForwarderAddress?: EthAddress;
|
|
93
|
+
/** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */
|
|
94
|
+
l1TxFailedStore?: string;
|
|
95
|
+
/** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */
|
|
96
|
+
publisherFundingThreshold?: bigint;
|
|
97
|
+
/** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */
|
|
98
|
+
publisherFundingAmount?: bigint;
|
|
65
99
|
};
|
|
66
100
|
|
|
67
101
|
export function getPublisherConfigFromProverConfig(config: ProverPublisherConfig): PublisherConfig {
|
|
@@ -77,6 +111,7 @@ export function getPublisherConfigFromSequencerConfig(config: SequencerPublisher
|
|
|
77
111
|
...config,
|
|
78
112
|
publisherAllowInvalidStates: config.sequencerPublisherAllowInvalidStates,
|
|
79
113
|
publisherForwarderAddress: config.sequencerPublisherForwarderAddress,
|
|
114
|
+
l1TxFailedStore: config.l1TxFailedStore,
|
|
80
115
|
};
|
|
81
116
|
}
|
|
82
117
|
|
|
@@ -133,6 +168,11 @@ export const sequencerPublisherConfigMappings: ConfigMappingsType<SequencerPubli
|
|
|
133
168
|
description: 'Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only)',
|
|
134
169
|
parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
|
|
135
170
|
},
|
|
171
|
+
l1TxFailedStore: {
|
|
172
|
+
env: 'L1_TX_FAILED_STORE',
|
|
173
|
+
description: 'Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path',
|
|
174
|
+
},
|
|
175
|
+
...publisherFundingConfigMappings,
|
|
136
176
|
};
|
|
137
177
|
|
|
138
178
|
export const proverPublisherConfigMappings: ConfigMappingsType<ProverPublisherConfig & L1TxUtilsConfig> = {
|
|
@@ -154,4 +194,5 @@ export const proverPublisherConfigMappings: ConfigMappingsType<ProverPublisherCo
|
|
|
154
194
|
description: 'Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only)',
|
|
155
195
|
parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
|
|
156
196
|
},
|
|
197
|
+
...publisherFundingConfigMappings,
|
|
157
198
|
};
|
package/src/publisher/index.ts
CHANGED
|
@@ -3,3 +3,6 @@ export { SequencerPublisherFactory } from './sequencer-publisher-factory.js';
|
|
|
3
3
|
|
|
4
4
|
// Used for tests
|
|
5
5
|
export { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
6
|
+
|
|
7
|
+
// Failed L1 tx store (optional, for test networks)
|
|
8
|
+
export { type FailedL1Tx, type FailedL1TxUri, type L1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import { createFileStore } from '@aztec/stdlib/file-store';
|
|
3
|
+
|
|
4
|
+
import type { L1TxFailedStore } from './failed_tx_store.js';
|
|
5
|
+
import { FileStoreL1TxFailedStore } from './file_store_failed_tx_store.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates an L1TxFailedStore from a config string.
|
|
9
|
+
* Supports any backend that FileStore supports (GCS, S3, R2, local filesystem).
|
|
10
|
+
* @param config - Config string (e.g., 'gs://bucket/path', 's3://bucket/path', 'file:///path'). If undefined, returns undefined.
|
|
11
|
+
* @param logger - Optional logger.
|
|
12
|
+
* @returns The store instance, or undefined if config is not provided.
|
|
13
|
+
*/
|
|
14
|
+
export async function createL1TxFailedStore(
|
|
15
|
+
config: string | undefined,
|
|
16
|
+
logger: Logger = createLogger('sequencer:l1-tx-failed-store'),
|
|
17
|
+
): Promise<L1TxFailedStore | undefined> {
|
|
18
|
+
if (!config) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const fileStore = await createFileStore(config, logger);
|
|
23
|
+
if (!fileStore) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Failed to create file store from config: '${config}'. ` +
|
|
26
|
+
`Supported formats: 'gs://bucket/path', 's3://bucket/path', 'file:///path'.`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
logger.info(`Created L1 tx failed store`, { config });
|
|
31
|
+
return new FileStoreL1TxFailedStore(fileStore, logger);
|
|
32
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Hex } from 'viem';
|
|
2
|
+
|
|
3
|
+
/** URI pointing to a stored failed L1 transaction. */
|
|
4
|
+
export type FailedL1TxUri = string & { __brand: 'FailedL1TxUri' };
|
|
5
|
+
|
|
6
|
+
/** A failed L1 transaction captured for debugging and replay. */
|
|
7
|
+
export type FailedL1Tx = {
|
|
8
|
+
/** Tx hash (for reverts) or keccak256(request.data) (for simulation/send failures). */
|
|
9
|
+
id: Hex;
|
|
10
|
+
/** Unix timestamp (ms) when failure occurred. */
|
|
11
|
+
timestamp: number;
|
|
12
|
+
/** Whether the failure was during simulation or after sending. */
|
|
13
|
+
failureType: 'simulation' | 'revert' | 'send-error';
|
|
14
|
+
/** The actual L1 transaction for replay (multicall-encoded for bundled txs). */
|
|
15
|
+
request: {
|
|
16
|
+
to: Hex;
|
|
17
|
+
data: Hex;
|
|
18
|
+
value?: string; // bigint as string
|
|
19
|
+
};
|
|
20
|
+
/** Raw blob data as hex for replay. */
|
|
21
|
+
blobData?: Hex[];
|
|
22
|
+
/** L1 block number at time of failure (simulation target or receipt block). */
|
|
23
|
+
l1BlockNumber: string; // bigint as string
|
|
24
|
+
/** Receipt info (present only for on-chain reverts). */
|
|
25
|
+
receipt?: {
|
|
26
|
+
transactionHash: Hex;
|
|
27
|
+
blockNumber: string; // bigint as string
|
|
28
|
+
gasUsed: string; // bigint as string
|
|
29
|
+
status: 'reverted';
|
|
30
|
+
};
|
|
31
|
+
/** Error information. */
|
|
32
|
+
error: {
|
|
33
|
+
message: string;
|
|
34
|
+
/** Decoded error name (e.g., 'Rollup__InvalidProposer'). */
|
|
35
|
+
name?: string;
|
|
36
|
+
};
|
|
37
|
+
/** Context metadata. */
|
|
38
|
+
context: {
|
|
39
|
+
/** Actions involved (e.g., ['propose', 'governance-signal']). */
|
|
40
|
+
actions: string[];
|
|
41
|
+
/** Individual request data for each action (metadata, not used for replay). */
|
|
42
|
+
requests?: Array<{ action: string; to: Hex; data: Hex }>;
|
|
43
|
+
checkpointNumber?: number;
|
|
44
|
+
slot?: number;
|
|
45
|
+
sender: Hex;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Store for failed L1 transactions for debugging purposes. */
|
|
50
|
+
export interface L1TxFailedStore {
|
|
51
|
+
/** Saves a failed transaction and returns its URI. */
|
|
52
|
+
saveFailedTx(tx: FailedL1Tx): Promise<FailedL1TxUri>;
|
|
53
|
+
/** Retrieves a failed transaction by its URI. */
|
|
54
|
+
getFailedTx(uri: FailedL1TxUri): Promise<FailedL1Tx>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import type { FileStore } from '@aztec/stdlib/file-store';
|
|
3
|
+
|
|
4
|
+
import type { FailedL1Tx, FailedL1TxUri, L1TxFailedStore } from './failed_tx_store.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* L1TxFailedStore implementation using the FileStore abstraction.
|
|
8
|
+
* Supports any backend that FileStore supports (GCS, S3, R2, local filesystem).
|
|
9
|
+
*/
|
|
10
|
+
export class FileStoreL1TxFailedStore implements L1TxFailedStore {
|
|
11
|
+
private readonly log: Logger;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly fileStore: FileStore,
|
|
15
|
+
logger?: Logger,
|
|
16
|
+
) {
|
|
17
|
+
this.log = logger ?? createLogger('sequencer:l1-tx-failed-store');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async saveFailedTx(tx: FailedL1Tx): Promise<FailedL1TxUri> {
|
|
21
|
+
const prefix = tx.receipt ? 'tx' : 'data';
|
|
22
|
+
const path = `${tx.failureType}/${prefix}-${tx.id}.json`;
|
|
23
|
+
const json = JSON.stringify(tx, null, 2);
|
|
24
|
+
|
|
25
|
+
const uri = await this.fileStore.save(path, Buffer.from(json), {
|
|
26
|
+
metadata: {
|
|
27
|
+
'content-type': 'application/json',
|
|
28
|
+
actions: tx.context.actions.join(','),
|
|
29
|
+
'failure-type': tx.failureType,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.log.info(`Saved failed L1 tx to ${uri}`, {
|
|
34
|
+
id: tx.id,
|
|
35
|
+
failureType: tx.failureType,
|
|
36
|
+
actions: tx.context.actions.join(','),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return uri as FailedL1TxUri;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async getFailedTx(uri: FailedL1TxUri): Promise<FailedL1Tx> {
|
|
43
|
+
const data = await this.fileStore.read(uri);
|
|
44
|
+
return JSON.parse(data.toString()) as FailedL1Tx;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -81,8 +81,23 @@ export class SequencerPublisherFactory {
|
|
|
81
81
|
const rollup = this.deps.rollupContract;
|
|
82
82
|
const slashingProposerContract = await rollup.getSlashingProposer();
|
|
83
83
|
|
|
84
|
+
const getNextPublisher = async (excludeAddresses: EthAddress[]): Promise<L1TxUtils | undefined> => {
|
|
85
|
+
const exclusionFilter: PublisherFilter<L1TxUtils> = (utils: L1TxUtils) => {
|
|
86
|
+
if (excludeAddresses.some(addr => addr.equals(utils.getSenderAddress()))) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return filter(utils);
|
|
90
|
+
};
|
|
91
|
+
try {
|
|
92
|
+
return await this.deps.publisherManager.getAvailablePublisher(exclusionFilter);
|
|
93
|
+
} catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
84
98
|
const publisher = new SequencerPublisher(this.sequencerConfig, {
|
|
85
99
|
l1TxUtils: l1Publisher,
|
|
100
|
+
getNextPublisher,
|
|
86
101
|
telemetry: this.deps.telemetry,
|
|
87
102
|
blobClient: this.deps.blobClient,
|
|
88
103
|
rollupContract: this.deps.rollupContract,
|
|
@@ -102,8 +117,8 @@ export class SequencerPublisherFactory {
|
|
|
102
117
|
};
|
|
103
118
|
}
|
|
104
119
|
|
|
105
|
-
/**
|
|
106
|
-
public
|
|
107
|
-
this.deps.publisherManager.
|
|
120
|
+
/** Stops all publishers managed by this factory. Used during sequencer shutdown. */
|
|
121
|
+
public async stopAll(): Promise<void> {
|
|
122
|
+
await this.deps.publisherManager.stop();
|
|
108
123
|
}
|
|
109
124
|
}
|