@aztec/sequencer-client 0.0.1-commit.dbf9cec → 0.0.1-commit.e0f15ab9b
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 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +46 -23
- package/dest/config.d.ts +25 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +31 -17
- 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 +13 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +17 -2
- 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 +13 -4
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +78 -14
- package/dest/sequencer/checkpoint_proposal_job.d.ts +13 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +198 -128
- 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 +14 -9
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +72 -62
- package/dest/sequencer/timetable.d.ts +4 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +6 -7
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +7 -9
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +39 -30
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +56 -21
- package/src/config.ts +39 -19
- package/src/global_variable_builder/global_builder.ts +22 -23
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +32 -0
- package/src/publisher/sequencer-publisher-factory.ts +18 -3
- package/src/publisher/sequencer-publisher.ts +100 -20
- package/src/sequencer/checkpoint_proposal_job.ts +263 -140
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +14 -0
- package/src/sequencer/sequencer.ts +98 -69
- package/src/sequencer/timetable.ts +7 -7
- package/src/sequencer/types.ts +1 -1
- package/src/test/mock_checkpoint_builder.ts +51 -48
|
@@ -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. */
|
|
@@ -50,13 +52,37 @@ export type PublisherConfig = L1TxUtilsConfig &
|
|
|
50
52
|
publisherForwarderAddress?: EthAddress;
|
|
51
53
|
/** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */
|
|
52
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;
|
|
53
59
|
};
|
|
54
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
|
+
|
|
55
77
|
export type ProverPublisherConfig = L1TxUtilsConfig &
|
|
56
78
|
BlobClientConfig & {
|
|
57
79
|
fishermanMode?: boolean;
|
|
58
80
|
proverPublisherAllowInvalidStates?: boolean;
|
|
59
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;
|
|
60
86
|
};
|
|
61
87
|
|
|
62
88
|
export type SequencerPublisherConfig = L1TxUtilsConfig &
|
|
@@ -66,6 +92,10 @@ export type SequencerPublisherConfig = L1TxUtilsConfig &
|
|
|
66
92
|
sequencerPublisherForwarderAddress?: EthAddress;
|
|
67
93
|
/** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */
|
|
68
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;
|
|
69
99
|
};
|
|
70
100
|
|
|
71
101
|
export function getPublisherConfigFromProverConfig(config: ProverPublisherConfig): PublisherConfig {
|
|
@@ -142,6 +172,7 @@ export const sequencerPublisherConfigMappings: ConfigMappingsType<SequencerPubli
|
|
|
142
172
|
env: 'L1_TX_FAILED_STORE',
|
|
143
173
|
description: 'Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path',
|
|
144
174
|
},
|
|
175
|
+
...publisherFundingConfigMappings,
|
|
145
176
|
};
|
|
146
177
|
|
|
147
178
|
export const proverPublisherConfigMappings: ConfigMappingsType<ProverPublisherConfig & L1TxUtilsConfig> = {
|
|
@@ -163,4 +194,5 @@ export const proverPublisherConfigMappings: ConfigMappingsType<ProverPublisherCo
|
|
|
163
194
|
description: 'Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only)',
|
|
164
195
|
parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
|
|
165
196
|
},
|
|
197
|
+
...publisherFundingConfigMappings,
|
|
166
198
|
};
|
|
@@ -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
|
}
|
|
@@ -28,8 +28,10 @@ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from
|
|
|
28
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
30
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
31
32
|
import { pick } from '@aztec/foundation/collection';
|
|
32
33
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
34
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
33
35
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
34
36
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
35
37
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
@@ -40,6 +42,7 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
|
40
42
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
41
43
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
42
44
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
45
|
+
import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
43
46
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
44
47
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
45
48
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
@@ -131,12 +134,17 @@ export class SequencerPublisher {
|
|
|
131
134
|
|
|
132
135
|
protected log: Logger;
|
|
133
136
|
protected ethereumSlotDuration: bigint;
|
|
137
|
+
protected aztecSlotDuration: bigint;
|
|
138
|
+
private dateProvider: DateProvider;
|
|
134
139
|
|
|
135
140
|
private blobClient: BlobClientInterface;
|
|
136
141
|
|
|
137
142
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */
|
|
138
143
|
private proposerAddressForSimulation?: EthAddress;
|
|
139
144
|
|
|
145
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */
|
|
146
|
+
private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
147
|
+
|
|
140
148
|
/** L1 fee analyzer for fisherman mode */
|
|
141
149
|
private l1FeeAnalyzer?: L1FeeAnalyzer;
|
|
142
150
|
|
|
@@ -161,7 +169,7 @@ export class SequencerPublisher {
|
|
|
161
169
|
|
|
162
170
|
constructor(
|
|
163
171
|
private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
|
|
164
|
-
Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
|
|
172
|
+
Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
|
|
165
173
|
deps: {
|
|
166
174
|
telemetry?: TelemetryClient;
|
|
167
175
|
blobClient: BlobClientInterface;
|
|
@@ -175,10 +183,13 @@ export class SequencerPublisher {
|
|
|
175
183
|
metrics: SequencerPublisherMetrics;
|
|
176
184
|
lastActions: Partial<Record<Action, SlotNumber>>;
|
|
177
185
|
log?: Logger;
|
|
186
|
+
getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
|
|
178
187
|
},
|
|
179
188
|
) {
|
|
180
189
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
181
190
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
191
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
192
|
+
this.dateProvider = deps.dateProvider;
|
|
182
193
|
this.epochCache = deps.epochCache;
|
|
183
194
|
this.lastActions = deps.lastActions;
|
|
184
195
|
|
|
@@ -188,6 +199,7 @@ export class SequencerPublisher {
|
|
|
188
199
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
189
200
|
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
190
201
|
this.l1TxUtils = deps.l1TxUtils;
|
|
202
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
191
203
|
|
|
192
204
|
this.rollupContract = deps.rollupContract;
|
|
193
205
|
|
|
@@ -279,7 +291,7 @@ export class SequencerPublisher {
|
|
|
279
291
|
}
|
|
280
292
|
|
|
281
293
|
public getCurrentL2Slot(): SlotNumber {
|
|
282
|
-
return this.epochCache.
|
|
294
|
+
return this.epochCache.getSlotNow();
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
/**
|
|
@@ -392,8 +404,8 @@ export class SequencerPublisher {
|
|
|
392
404
|
// @note - we can only have one blob config per bundle
|
|
393
405
|
// find requests with gas and blob configs
|
|
394
406
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
395
|
-
const gasConfigs =
|
|
396
|
-
const blobConfigs =
|
|
407
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
408
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
397
409
|
|
|
398
410
|
if (blobConfigs.length > 1) {
|
|
399
411
|
throw new Error('Multiple blob configs found');
|
|
@@ -437,19 +449,16 @@ export class SequencerPublisher {
|
|
|
437
449
|
});
|
|
438
450
|
const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
|
|
439
451
|
|
|
452
|
+
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
453
|
+
|
|
440
454
|
this.log.debug('Forwarding transactions', {
|
|
441
455
|
validRequests: validRequests.map(request => request.action),
|
|
442
456
|
txConfig,
|
|
443
457
|
});
|
|
444
|
-
const result = await
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
blobConfig,
|
|
449
|
-
this.rollupContract.address,
|
|
450
|
-
this.log,
|
|
451
|
-
);
|
|
452
|
-
const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
|
|
458
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
459
|
+
if (result === undefined) {
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
453
462
|
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
|
|
454
463
|
validRequests,
|
|
455
464
|
result,
|
|
@@ -472,6 +481,55 @@ export class SequencerPublisher {
|
|
|
472
481
|
}
|
|
473
482
|
}
|
|
474
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
486
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
487
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
488
|
+
*/
|
|
489
|
+
private async forwardWithPublisherRotation(
|
|
490
|
+
validRequests: RequestWithExpiry[],
|
|
491
|
+
txConfig: RequestWithExpiry['gasConfig'],
|
|
492
|
+
blobConfig: L1BlobInputs | undefined,
|
|
493
|
+
) {
|
|
494
|
+
const triedAddresses: EthAddress[] = [];
|
|
495
|
+
let currentPublisher = this.l1TxUtils;
|
|
496
|
+
|
|
497
|
+
while (true) {
|
|
498
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
499
|
+
try {
|
|
500
|
+
const result = await Multicall3.forward(
|
|
501
|
+
validRequests.map(r => r.request),
|
|
502
|
+
currentPublisher,
|
|
503
|
+
txConfig,
|
|
504
|
+
blobConfig,
|
|
505
|
+
this.rollupContract.address,
|
|
506
|
+
this.log,
|
|
507
|
+
);
|
|
508
|
+
this.l1TxUtils = currentPublisher;
|
|
509
|
+
return result;
|
|
510
|
+
} catch (err) {
|
|
511
|
+
if (err instanceof TimeoutError) {
|
|
512
|
+
throw err;
|
|
513
|
+
}
|
|
514
|
+
const viemError = formatViemError(err);
|
|
515
|
+
if (!this.getNextPublisher) {
|
|
516
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
this.log.warn(
|
|
520
|
+
`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
|
|
521
|
+
viemError,
|
|
522
|
+
);
|
|
523
|
+
const nextPublisher = await this.getNextPublisher([...triedAddresses]);
|
|
524
|
+
if (!nextPublisher) {
|
|
525
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
currentPublisher = nextPublisher;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
475
533
|
private callbackBundledTransactions(
|
|
476
534
|
requests: RequestWithExpiry[],
|
|
477
535
|
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
@@ -495,7 +553,16 @@ export class SequencerPublisher {
|
|
|
495
553
|
});
|
|
496
554
|
return { failedActions: requests.map(r => r.action) };
|
|
497
555
|
} else {
|
|
498
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
556
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
557
|
+
result,
|
|
558
|
+
requests: requests.map(r => ({
|
|
559
|
+
...r,
|
|
560
|
+
// Avoid logging large blob data
|
|
561
|
+
blobConfig: r.blobConfig
|
|
562
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
563
|
+
: undefined,
|
|
564
|
+
})),
|
|
565
|
+
});
|
|
499
566
|
const successfulActions: Action[] = [];
|
|
500
567
|
const failedActions: Action[] = [];
|
|
501
568
|
for (const request of requests) {
|
|
@@ -534,20 +601,24 @@ export class SequencerPublisher {
|
|
|
534
601
|
}
|
|
535
602
|
|
|
536
603
|
/**
|
|
537
|
-
* @notice Will call `
|
|
604
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
538
605
|
* @param tipArchive - The archive to check
|
|
539
606
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
540
607
|
*/
|
|
541
|
-
public
|
|
608
|
+
public canProposeAt(
|
|
542
609
|
tipArchive: Fr,
|
|
543
610
|
msgSender: EthAddress,
|
|
544
|
-
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
611
|
+
opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
|
|
545
612
|
) {
|
|
546
613
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
547
614
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
548
615
|
|
|
616
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
617
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
618
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
619
|
+
|
|
549
620
|
return this.rollupContract
|
|
550
|
-
.
|
|
621
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
551
622
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
552
623
|
})
|
|
553
624
|
.catch(err => {
|
|
@@ -561,6 +632,7 @@ export class SequencerPublisher {
|
|
|
561
632
|
return undefined;
|
|
562
633
|
});
|
|
563
634
|
}
|
|
635
|
+
|
|
564
636
|
/**
|
|
565
637
|
* @notice Will simulate `validateHeader` to make sure that the block header is valid
|
|
566
638
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
@@ -584,7 +656,7 @@ export class SequencerPublisher {
|
|
|
584
656
|
flags,
|
|
585
657
|
] as const;
|
|
586
658
|
|
|
587
|
-
const ts =
|
|
659
|
+
const ts = this.getNextL1SlotTimestamp();
|
|
588
660
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
589
661
|
opts?.forcePendingCheckpointNumber,
|
|
590
662
|
);
|
|
@@ -749,7 +821,9 @@ export class SequencerPublisher {
|
|
|
749
821
|
attestationsAndSignersSignature: Signature,
|
|
750
822
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
751
823
|
): Promise<bigint> {
|
|
752
|
-
|
|
824
|
+
// Anchor the simulation timestamp to the checkpoint's own slot start time
|
|
825
|
+
// rather than the current L1 block timestamp, which may overshoot into the next slot if the build ran late.
|
|
826
|
+
const ts = checkpoint.header.timestamp;
|
|
753
827
|
const blobFields = checkpoint.toBlobFields();
|
|
754
828
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
755
829
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
@@ -1515,4 +1589,10 @@ export class SequencerPublisher {
|
|
|
1515
1589
|
},
|
|
1516
1590
|
});
|
|
1517
1591
|
}
|
|
1592
|
+
|
|
1593
|
+
/** Returns the timestamp to use when simulating L1 proposal calls */
|
|
1594
|
+
private getNextL1SlotTimestamp(): bigint {
|
|
1595
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1596
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1597
|
+
}
|
|
1518
1598
|
}
|