@aztec/sequencer-client 0.69.0 → 0.69.1-devnet
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 -0
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +4 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +11 -1
- package/dest/index.d.ts +3 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +4 -2
- package/dest/publisher/config.d.ts +4 -0
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +6 -1
- package/dest/publisher/index.d.ts +0 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +1 -2
- package/dest/publisher/l1-publisher.d.ts +43 -12
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +184 -132
- package/dest/sequencer/index.d.ts +1 -0
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +2 -1
- package/dest/sequencer/sequencer.d.ts +22 -29
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +68 -132
- package/dest/sequencer/utils.d.ts +2 -2
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +3 -3
- package/dest/slasher/factory.d.ts +11 -0
- package/dest/slasher/factory.d.ts.map +1 -0
- package/dest/slasher/factory.js +10 -0
- package/dest/slasher/index.d.ts +3 -0
- package/dest/slasher/index.d.ts.map +1 -0
- package/dest/slasher/index.js +3 -0
- package/dest/slasher/slasher_client.d.ts +127 -0
- package/dest/slasher/slasher_client.d.ts.map +1 -0
- package/dest/slasher/slasher_client.js +305 -0
- package/dest/test/index.d.ts +18 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +8 -0
- package/dest/{publisher → test}/test-l1-publisher.d.ts +1 -1
- package/dest/test/test-l1-publisher.d.ts.map +1 -0
- package/dest/test/test-l1-publisher.js +11 -0
- package/dest/tx_validator/archive_cache.d.ts +14 -0
- package/dest/tx_validator/archive_cache.d.ts.map +1 -0
- package/dest/tx_validator/archive_cache.js +22 -0
- package/dest/tx_validator/gas_validator.d.ts +2 -3
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +9 -22
- package/dest/tx_validator/nullifier_cache.d.ts +16 -0
- package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
- package/dest/tx_validator/nullifier_cache.js +24 -0
- package/dest/tx_validator/phases_validator.d.ts +2 -3
- package/dest/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/tx_validator/phases_validator.js +15 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +15 -14
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +41 -24
- package/package.json +24 -20
- package/src/client/sequencer-client.ts +9 -3
- package/src/config.ts +10 -0
- package/src/index.ts +3 -1
- package/src/publisher/config.ts +10 -0
- package/src/publisher/index.ts +0 -1
- package/src/publisher/l1-publisher.ts +222 -137
- package/src/sequencer/index.ts +1 -0
- package/src/sequencer/sequencer.ts +91 -195
- package/src/sequencer/utils.ts +2 -2
- package/src/slasher/factory.ts +22 -0
- package/src/slasher/index.ts +2 -0
- package/src/slasher/slasher_client.ts +402 -0
- package/src/test/index.ts +23 -0
- package/src/{publisher → test}/test-l1-publisher.ts +1 -1
- package/src/tx_validator/archive_cache.ts +27 -0
- package/src/tx_validator/gas_validator.ts +11 -24
- package/src/tx_validator/nullifier_cache.ts +29 -0
- package/src/tx_validator/phases_validator.ts +22 -33
- package/src/tx_validator/tx_validator_factory.ts +89 -40
- package/dest/publisher/test-l1-publisher.d.ts.map +0 -1
- package/dest/publisher/test-l1-publisher.js +0 -11
- package/dest/publisher/utils.d.ts +0 -2
- package/dest/publisher/utils.d.ts.map +0 -1
- package/dest/publisher/utils.js +0 -13
- package/src/publisher/utils.ts +0 -14
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
1
2
|
import {
|
|
2
3
|
ConsensusPayload,
|
|
3
4
|
type EpochProofClaim,
|
|
4
5
|
type EpochProofQuote,
|
|
5
6
|
type L2Block,
|
|
6
|
-
|
|
7
|
+
SignatureDomainSeparator,
|
|
7
8
|
type TxHash,
|
|
8
9
|
getHashedSignaturePayload,
|
|
9
10
|
} from '@aztec/circuit-types';
|
|
@@ -18,10 +19,11 @@ import {
|
|
|
18
19
|
import { type FeeRecipient, type RootRollupPublicInputs } from '@aztec/circuits.js/rollup';
|
|
19
20
|
import {
|
|
20
21
|
type EthereumChain,
|
|
22
|
+
type GasPrice,
|
|
21
23
|
type L1ContractsConfig,
|
|
22
24
|
L1TxUtils,
|
|
23
|
-
type L1TxUtilsConfig,
|
|
24
25
|
createEthereumChain,
|
|
26
|
+
formatViemError,
|
|
25
27
|
} from '@aztec/ethereum';
|
|
26
28
|
import { makeTuple } from '@aztec/foundation/array';
|
|
27
29
|
import { toHex } from '@aztec/foundation/bigint-buffer';
|
|
@@ -29,19 +31,20 @@ import { Blob } from '@aztec/foundation/blob';
|
|
|
29
31
|
import { areArraysEqual, compactArray, times } from '@aztec/foundation/collection';
|
|
30
32
|
import { type Signature } from '@aztec/foundation/eth-signature';
|
|
31
33
|
import { Fr } from '@aztec/foundation/fields';
|
|
32
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
34
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
33
35
|
import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
34
36
|
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
35
37
|
import { Timer } from '@aztec/foundation/timer';
|
|
36
|
-
import {
|
|
38
|
+
import { EmpireBaseAbi, RollupAbi, SlasherAbi } from '@aztec/l1-artifacts';
|
|
37
39
|
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
40
|
+
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
|
|
38
41
|
|
|
39
42
|
import pick from 'lodash.pick';
|
|
40
43
|
import {
|
|
41
44
|
type BaseError,
|
|
42
45
|
type Chain,
|
|
43
46
|
type Client,
|
|
44
|
-
ContractFunctionExecutionError,
|
|
47
|
+
type ContractFunctionExecutionError,
|
|
45
48
|
ContractFunctionRevertedError,
|
|
46
49
|
type GetContractReturnType,
|
|
47
50
|
type Hex,
|
|
@@ -69,7 +72,6 @@ import { privateKeyToAccount } from 'viem/accounts';
|
|
|
69
72
|
|
|
70
73
|
import { type PublisherConfig, type TxSenderConfig } from './config.js';
|
|
71
74
|
import { L1PublisherMetrics } from './l1-publisher-metrics.js';
|
|
72
|
-
import { prettyLogViemErrorMsg } from './utils.js';
|
|
73
75
|
|
|
74
76
|
/**
|
|
75
77
|
* Stats for a sent transaction.
|
|
@@ -101,6 +103,8 @@ export type MinimalTransactionReceipt = {
|
|
|
101
103
|
logs: any[];
|
|
102
104
|
/** Block number in which this tx was mined. */
|
|
103
105
|
blockNumber: bigint;
|
|
106
|
+
/** The block hash in which this tx was mined */
|
|
107
|
+
blockHash: `0x${string}`;
|
|
104
108
|
};
|
|
105
109
|
|
|
106
110
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -121,6 +125,14 @@ type L1ProcessArgs = {
|
|
|
121
125
|
attestations?: Signature[];
|
|
122
126
|
};
|
|
123
127
|
|
|
128
|
+
type L1ProcessReturnType = {
|
|
129
|
+
receipt: TransactionReceipt | undefined;
|
|
130
|
+
args: any;
|
|
131
|
+
functionName: string;
|
|
132
|
+
data: Hex;
|
|
133
|
+
gasPrice: GasPrice;
|
|
134
|
+
};
|
|
135
|
+
|
|
124
136
|
/** Arguments to the submitEpochProof method of the rollup contract */
|
|
125
137
|
export type L1SubmitEpochProofArgs = {
|
|
126
138
|
epochSize: number;
|
|
@@ -135,6 +147,13 @@ export type L1SubmitEpochProofArgs = {
|
|
|
135
147
|
proof: Proof;
|
|
136
148
|
};
|
|
137
149
|
|
|
150
|
+
export enum VoteType {
|
|
151
|
+
GOVERNANCE,
|
|
152
|
+
SLASHING,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
type GetSlashPayloadCallBack = (slotNumber: bigint) => Promise<EthAddress | undefined>;
|
|
156
|
+
|
|
138
157
|
/**
|
|
139
158
|
* Publishes L2 blocks to L1. This implementation does *not* retry a transaction in
|
|
140
159
|
* the event of network congestion, but should work for local development.
|
|
@@ -149,26 +168,32 @@ export class L1Publisher {
|
|
|
149
168
|
private interrupted = false;
|
|
150
169
|
private metrics: L1PublisherMetrics;
|
|
151
170
|
|
|
152
|
-
|
|
153
|
-
|
|
171
|
+
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
172
|
+
protected governanceProposerAddress?: EthAddress;
|
|
173
|
+
private governancePayload: EthAddress = EthAddress.ZERO;
|
|
174
|
+
|
|
175
|
+
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
176
|
+
protected slashingProposerAddress?: EthAddress;
|
|
177
|
+
private getSlashPayload?: GetSlashPayloadCallBack = undefined;
|
|
178
|
+
|
|
179
|
+
private myLastVotes: Record<VoteType, bigint> = {
|
|
180
|
+
[VoteType.GOVERNANCE]: 0n,
|
|
181
|
+
[VoteType.SLASHING]: 0n,
|
|
182
|
+
};
|
|
154
183
|
|
|
155
184
|
protected log = createLogger('sequencer:publisher');
|
|
156
|
-
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
157
185
|
|
|
158
186
|
protected rollupContract: GetContractReturnType<
|
|
159
187
|
typeof RollupAbi,
|
|
160
188
|
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
161
189
|
>;
|
|
162
|
-
protected governanceProposerContract?: GetContractReturnType<
|
|
163
|
-
typeof GovernanceProposerAbi,
|
|
164
|
-
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
165
|
-
> = undefined;
|
|
166
190
|
|
|
167
191
|
protected publicClient: PublicClient<HttpTransport, Chain>;
|
|
168
192
|
protected walletClient: WalletClient<HttpTransport, Chain, PrivateKeyAccount>;
|
|
169
193
|
protected account: PrivateKeyAccount;
|
|
170
194
|
protected ethereumSlotDuration: bigint;
|
|
171
195
|
|
|
196
|
+
private blobSinkClient: BlobSinkClientInterface;
|
|
172
197
|
// @note - with blobs, the below estimate seems too large.
|
|
173
198
|
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
174
199
|
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
@@ -178,12 +203,16 @@ export class L1Publisher {
|
|
|
178
203
|
private readonly l1TxUtils: L1TxUtils;
|
|
179
204
|
|
|
180
205
|
constructor(
|
|
181
|
-
config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'
|
|
182
|
-
|
|
206
|
+
config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
207
|
+
deps: { telemetry?: TelemetryClient; blobSinkClient?: BlobSinkClientInterface } = {},
|
|
183
208
|
) {
|
|
184
209
|
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
|
|
185
210
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
186
|
-
|
|
211
|
+
|
|
212
|
+
const telemetry = deps.telemetry ?? new NoopTelemetryClient();
|
|
213
|
+
this.blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config.blobSinkUrl);
|
|
214
|
+
|
|
215
|
+
this.metrics = new L1PublisherMetrics(telemetry, 'L1Publisher');
|
|
187
216
|
|
|
188
217
|
const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config;
|
|
189
218
|
const chain = createEthereumChain(rpcUrl, chainId);
|
|
@@ -205,16 +234,31 @@ export class L1Publisher {
|
|
|
205
234
|
});
|
|
206
235
|
|
|
207
236
|
if (l1Contracts.governanceProposerAddress) {
|
|
208
|
-
this.
|
|
209
|
-
address: getAddress(l1Contracts.governanceProposerAddress.toString()),
|
|
210
|
-
abi: GovernanceProposerAbi,
|
|
211
|
-
client: this.walletClient,
|
|
212
|
-
});
|
|
237
|
+
this.governanceProposerAddress = EthAddress.fromString(l1Contracts.governanceProposerAddress.toString());
|
|
213
238
|
}
|
|
214
239
|
|
|
215
240
|
this.l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient, this.log, config);
|
|
216
241
|
}
|
|
217
242
|
|
|
243
|
+
public registerSlashPayloadGetter(callback: GetSlashPayloadCallBack) {
|
|
244
|
+
this.getSlashPayload = callback;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private async getSlashingProposerAddress() {
|
|
248
|
+
if (this.slashingProposerAddress) {
|
|
249
|
+
return this.slashingProposerAddress;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const slasherAddress = await this.rollupContract.read.SLASHER();
|
|
253
|
+
const slasher = getContract({
|
|
254
|
+
address: getAddress(slasherAddress.toString()),
|
|
255
|
+
abi: SlasherAbi,
|
|
256
|
+
client: this.walletClient,
|
|
257
|
+
});
|
|
258
|
+
this.slashingProposerAddress = EthAddress.fromString(await slasher.read.PROPOSER());
|
|
259
|
+
return this.slashingProposerAddress;
|
|
260
|
+
}
|
|
261
|
+
|
|
218
262
|
get publisherAddress() {
|
|
219
263
|
return this.account.address;
|
|
220
264
|
}
|
|
@@ -230,12 +274,12 @@ export class L1Publisher {
|
|
|
230
274
|
});
|
|
231
275
|
}
|
|
232
276
|
|
|
233
|
-
public
|
|
234
|
-
return this.
|
|
277
|
+
public getGovernancePayload() {
|
|
278
|
+
return this.governancePayload;
|
|
235
279
|
}
|
|
236
280
|
|
|
237
|
-
public
|
|
238
|
-
this.
|
|
281
|
+
public setGovernancePayload(payload: EthAddress) {
|
|
282
|
+
this.governancePayload = payload;
|
|
239
283
|
}
|
|
240
284
|
|
|
241
285
|
public getSenderAddress(): EthAddress {
|
|
@@ -395,38 +439,6 @@ export class L1Publisher {
|
|
|
395
439
|
if (error instanceof ContractFunctionRevertedError) {
|
|
396
440
|
const err = error as ContractFunctionRevertedError;
|
|
397
441
|
this.log.debug(`Validation failed: ${err.message}`, err.data);
|
|
398
|
-
} else if (error instanceof ContractFunctionExecutionError) {
|
|
399
|
-
let err = error as ContractFunctionRevertedError;
|
|
400
|
-
if (!tryGetCustomErrorName(err)) {
|
|
401
|
-
// If we get here, it's because the custom error no longer exists in Rollup.sol,
|
|
402
|
-
// but in another lib. The below reconstructs the error message.
|
|
403
|
-
try {
|
|
404
|
-
await this.publicClient.estimateGas({
|
|
405
|
-
data: encodeFunctionData({
|
|
406
|
-
abi: this.rollupContract.abi,
|
|
407
|
-
functionName: 'validateHeader',
|
|
408
|
-
args,
|
|
409
|
-
}),
|
|
410
|
-
account: this.account,
|
|
411
|
-
to: this.rollupContract.address,
|
|
412
|
-
});
|
|
413
|
-
} catch (estGasErr: unknown) {
|
|
414
|
-
const possibleAbis = [ExtRollupLibAbi, LeonidasLibAbi];
|
|
415
|
-
possibleAbis.forEach(abi => {
|
|
416
|
-
const possibleErr = getContractError(estGasErr as BaseError, {
|
|
417
|
-
args: [],
|
|
418
|
-
abi: abi,
|
|
419
|
-
functionName: 'validateHeader',
|
|
420
|
-
address: this.rollupContract.address,
|
|
421
|
-
sender: this.account.address,
|
|
422
|
-
});
|
|
423
|
-
err = tryGetCustomErrorName(possibleErr) ? possibleErr : err;
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
throw err;
|
|
427
|
-
}
|
|
428
|
-
} else {
|
|
429
|
-
this.log.debug(`Unexpected error during validation: ${error}`);
|
|
430
442
|
}
|
|
431
443
|
throw error;
|
|
432
444
|
}
|
|
@@ -450,68 +462,106 @@ export class L1Publisher {
|
|
|
450
462
|
calldataGas: getCalldataGasUsage(calldata),
|
|
451
463
|
};
|
|
452
464
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (this.
|
|
465
|
+
public async castVote(slotNumber: bigint, timestamp: bigint, voteType: VoteType) {
|
|
466
|
+
// @todo This function can be optimized by doing some of the computations locally instead of calling the L1 contracts
|
|
467
|
+
if (this.myLastVotes[voteType] >= slotNumber) {
|
|
456
468
|
return false;
|
|
457
469
|
}
|
|
458
470
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
471
|
+
const voteConfig = async (): Promise<
|
|
472
|
+
{ payload: EthAddress; voteContractAddress: EthAddress; logger: Logger } | undefined
|
|
473
|
+
> => {
|
|
474
|
+
if (voteType === VoteType.GOVERNANCE) {
|
|
475
|
+
if (this.governancePayload.equals(EthAddress.ZERO)) {
|
|
476
|
+
return undefined;
|
|
477
|
+
}
|
|
478
|
+
if (!this.governanceProposerAddress) {
|
|
479
|
+
return undefined;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
payload: this.governancePayload,
|
|
483
|
+
voteContractAddress: this.governanceProposerAddress,
|
|
484
|
+
logger: this.governanceLog,
|
|
485
|
+
};
|
|
486
|
+
} else if (voteType === VoteType.SLASHING) {
|
|
487
|
+
if (!this.getSlashPayload) {
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
const slashingProposerAddress = await this.getSlashingProposerAddress();
|
|
491
|
+
if (!slashingProposerAddress) {
|
|
492
|
+
return undefined;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const slashPayload = await this.getSlashPayload(slotNumber);
|
|
496
|
+
|
|
497
|
+
if (!slashPayload) {
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
462
500
|
|
|
463
|
-
|
|
501
|
+
return {
|
|
502
|
+
payload: slashPayload,
|
|
503
|
+
voteContractAddress: slashingProposerAddress,
|
|
504
|
+
logger: this.slashingLog,
|
|
505
|
+
};
|
|
506
|
+
} else {
|
|
507
|
+
throw new Error('Invalid vote type');
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const vConfig = await voteConfig();
|
|
512
|
+
|
|
513
|
+
if (!vConfig) {
|
|
464
514
|
return false;
|
|
465
515
|
}
|
|
466
516
|
|
|
467
|
-
|
|
468
|
-
|
|
517
|
+
const { payload, voteContractAddress, logger } = vConfig;
|
|
518
|
+
|
|
519
|
+
const voteContract = getContract({
|
|
520
|
+
address: getAddress(voteContractAddress.toString()),
|
|
521
|
+
abi: EmpireBaseAbi,
|
|
522
|
+
client: this.walletClient,
|
|
523
|
+
});
|
|
469
524
|
|
|
470
525
|
const [proposer, roundNumber] = await Promise.all([
|
|
471
526
|
this.rollupContract.read.getProposerAt([timestamp]),
|
|
472
|
-
|
|
527
|
+
voteContract.read.computeRound([slotNumber]),
|
|
473
528
|
]);
|
|
474
529
|
|
|
475
530
|
if (proposer.toLowerCase() !== this.account.address.toLowerCase()) {
|
|
476
531
|
return false;
|
|
477
532
|
}
|
|
478
533
|
|
|
479
|
-
const [slotForLastVote] = await
|
|
480
|
-
this.rollupContract.address,
|
|
481
|
-
roundNumber,
|
|
482
|
-
]);
|
|
534
|
+
const [slotForLastVote] = await voteContract.read.rounds([this.rollupContract.address, roundNumber]);
|
|
483
535
|
|
|
484
536
|
if (slotForLastVote >= slotNumber) {
|
|
485
537
|
return false;
|
|
486
538
|
}
|
|
487
539
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const cachedMyLastVote = this.myLastVote;
|
|
491
|
-
this.myLastVote = slotNumber;
|
|
492
|
-
|
|
493
|
-
this.governanceLog.verbose(`Casting vote for ${this.payload}`);
|
|
540
|
+
const cachedMyLastVote = this.myLastVotes[voteType];
|
|
541
|
+
this.myLastVotes[voteType] = slotNumber;
|
|
494
542
|
|
|
495
543
|
let txHash;
|
|
496
544
|
try {
|
|
497
|
-
txHash = await
|
|
545
|
+
txHash = await voteContract.write.vote([payload.toString()], {
|
|
546
|
+
account: this.account,
|
|
547
|
+
});
|
|
498
548
|
} catch (err) {
|
|
499
|
-
const msg =
|
|
500
|
-
|
|
501
|
-
this.
|
|
549
|
+
const msg = formatViemError(err);
|
|
550
|
+
logger.error(`Failed to vote`, msg);
|
|
551
|
+
this.myLastVotes[voteType] = cachedMyLastVote;
|
|
502
552
|
return false;
|
|
503
553
|
}
|
|
504
554
|
|
|
505
555
|
if (txHash) {
|
|
506
556
|
const receipt = await this.getTransactionReceipt(txHash);
|
|
507
557
|
if (!receipt) {
|
|
508
|
-
|
|
509
|
-
this.
|
|
558
|
+
logger.warn(`Failed to get receipt for tx ${txHash}`);
|
|
559
|
+
this.myLastVotes[voteType] = cachedMyLastVote;
|
|
510
560
|
return false;
|
|
511
561
|
}
|
|
512
562
|
}
|
|
513
563
|
|
|
514
|
-
|
|
564
|
+
logger.info(`Cast vote for ${payload}`);
|
|
515
565
|
return true;
|
|
516
566
|
}
|
|
517
567
|
|
|
@@ -525,6 +575,7 @@ export class L1Publisher {
|
|
|
525
575
|
attestations?: Signature[],
|
|
526
576
|
txHashes?: TxHash[],
|
|
527
577
|
proofQuote?: EpochProofQuote,
|
|
578
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
528
579
|
): Promise<boolean> {
|
|
529
580
|
const ctx = {
|
|
530
581
|
blockNumber: block.number,
|
|
@@ -534,16 +585,19 @@ export class L1Publisher {
|
|
|
534
585
|
|
|
535
586
|
const consensusPayload = new ConsensusPayload(block.header, block.archive.root, txHashes ?? []);
|
|
536
587
|
|
|
537
|
-
const digest = getHashedSignaturePayload(consensusPayload,
|
|
588
|
+
const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
|
|
589
|
+
|
|
590
|
+
const blobs = Blob.getBlobs(block.body.toBlobFields());
|
|
538
591
|
const proposeTxArgs = {
|
|
539
592
|
header: block.header.toBuffer(),
|
|
540
593
|
archive: block.archive.root.toBuffer(),
|
|
541
594
|
blockHash: block.header.hash().toBuffer(),
|
|
542
595
|
body: block.body.toBuffer(),
|
|
543
|
-
blobs
|
|
596
|
+
blobs,
|
|
544
597
|
attestations,
|
|
545
598
|
txHashes: txHashes ?? [],
|
|
546
599
|
};
|
|
600
|
+
|
|
547
601
|
// Publish body and propose block (if not already published)
|
|
548
602
|
if (this.interrupted) {
|
|
549
603
|
this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
|
|
@@ -563,15 +617,15 @@ export class L1Publisher {
|
|
|
563
617
|
|
|
564
618
|
this.log.debug(`Submitting propose transaction`);
|
|
565
619
|
const result = proofQuote
|
|
566
|
-
? await this.sendProposeAndClaimTx(proposeTxArgs, proofQuote)
|
|
567
|
-
: await this.sendProposeTx(proposeTxArgs);
|
|
620
|
+
? await this.sendProposeAndClaimTx(proposeTxArgs, proofQuote, opts)
|
|
621
|
+
: await this.sendProposeTx(proposeTxArgs, opts);
|
|
568
622
|
|
|
569
623
|
if (!result?.receipt) {
|
|
570
624
|
this.log.info(`Failed to publish block ${block.number} to L1`, ctx);
|
|
571
625
|
return false;
|
|
572
626
|
}
|
|
573
627
|
|
|
574
|
-
const { receipt, args, functionName, data } = result;
|
|
628
|
+
const { receipt, args, functionName, data, gasPrice } = result;
|
|
575
629
|
|
|
576
630
|
// Tx was mined successfully
|
|
577
631
|
if (receipt.status === 'success') {
|
|
@@ -588,6 +642,12 @@ export class L1Publisher {
|
|
|
588
642
|
};
|
|
589
643
|
this.log.verbose(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
590
644
|
this.metrics.recordProcessBlockTx(timer.ms(), stats);
|
|
645
|
+
|
|
646
|
+
// Send the blobs to the blob sink
|
|
647
|
+
this.sendBlobsToBlobSink(receipt.blockHash, blobs).catch(_err => {
|
|
648
|
+
this.log.error('Failed to send blobs to blob sink');
|
|
649
|
+
});
|
|
650
|
+
|
|
591
651
|
return true;
|
|
592
652
|
}
|
|
593
653
|
|
|
@@ -602,9 +662,9 @@ export class L1Publisher {
|
|
|
602
662
|
address: this.rollupContract.address,
|
|
603
663
|
},
|
|
604
664
|
{
|
|
605
|
-
blobs: proposeTxArgs.blobs.map(b => b.
|
|
665
|
+
blobs: proposeTxArgs.blobs.map(b => b.dataWithZeros),
|
|
606
666
|
kzg,
|
|
607
|
-
maxFeePerBlobGas: 10000000000n,
|
|
667
|
+
maxFeePerBlobGas: gasPrice.maxFeePerBlobGas ?? 10000000000n,
|
|
608
668
|
},
|
|
609
669
|
);
|
|
610
670
|
this.log.error(`Rollup process tx reverted. ${errorMsg}`, undefined, {
|
|
@@ -618,11 +678,10 @@ export class L1Publisher {
|
|
|
618
678
|
/** Calls claimEpochProofRight in the Rollup contract to submit a chosen prover quote for the previous epoch. */
|
|
619
679
|
public async claimEpochProofRight(proofQuote: EpochProofQuote) {
|
|
620
680
|
const timer = new Timer();
|
|
621
|
-
|
|
622
|
-
let receipt;
|
|
681
|
+
let result;
|
|
623
682
|
try {
|
|
624
683
|
this.log.debug(`Submitting claimEpochProofRight transaction`);
|
|
625
|
-
|
|
684
|
+
result = await this.l1TxUtils.sendAndMonitorTransaction({
|
|
626
685
|
to: this.rollupContract.address,
|
|
627
686
|
data: encodeFunctionData({
|
|
628
687
|
abi: RollupAbi,
|
|
@@ -631,12 +690,14 @@ export class L1Publisher {
|
|
|
631
690
|
}),
|
|
632
691
|
});
|
|
633
692
|
} catch (err) {
|
|
634
|
-
this.log.error(`Failed to claim epoch proof right
|
|
693
|
+
this.log.error(`Failed to claim epoch proof right`, err, {
|
|
635
694
|
proofQuote: proofQuote.toInspect(),
|
|
636
695
|
});
|
|
637
696
|
return false;
|
|
638
697
|
}
|
|
639
698
|
|
|
699
|
+
const { receipt } = result;
|
|
700
|
+
|
|
640
701
|
if (receipt.status === 'success') {
|
|
641
702
|
const tx = await this.getTransactionStats(receipt.transactionHash);
|
|
642
703
|
const stats: L1PublishStats = {
|
|
@@ -698,8 +759,7 @@ export class L1Publisher {
|
|
|
698
759
|
},
|
|
699
760
|
],
|
|
700
761
|
});
|
|
701
|
-
// If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors
|
|
702
|
-
// and viem provides no way to get the revert reason from a given tx.
|
|
762
|
+
// If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors.
|
|
703
763
|
// Strangely, the only way to throw the revert reason as an error and provide blobs is prepareTransactionRequest.
|
|
704
764
|
// See: https://github.com/wevm/viem/issues/2075
|
|
705
765
|
// This throws a EstimateGasExecutionError with the custom error information:
|
|
@@ -711,13 +771,13 @@ export class L1Publisher {
|
|
|
711
771
|
});
|
|
712
772
|
return undefined;
|
|
713
773
|
} catch (simulationErr: any) {
|
|
714
|
-
// If we don't have a ContractFunctionExecutionError, we have a blob related error => use
|
|
774
|
+
// If we don't have a ContractFunctionExecutionError, we have a blob related error => use getContractError to get the error msg.
|
|
715
775
|
const contractErr =
|
|
716
776
|
simulationErr.name === 'ContractFunctionExecutionError'
|
|
717
777
|
? simulationErr
|
|
718
778
|
: getContractError(simulationErr as BaseError, {
|
|
719
779
|
args: [],
|
|
720
|
-
abi:
|
|
780
|
+
abi: RollupAbi,
|
|
721
781
|
functionName: args.functionName,
|
|
722
782
|
address: args.address,
|
|
723
783
|
sender: this.account.address,
|
|
@@ -860,35 +920,42 @@ export class L1Publisher {
|
|
|
860
920
|
publicInputs: RootRollupPublicInputs;
|
|
861
921
|
proof: Proof;
|
|
862
922
|
}): Promise<string | undefined> {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const argsArray = this.getSubmitEpochProofArgs(args);
|
|
923
|
+
const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
|
|
924
|
+
const argsArray = this.getSubmitEpochProofArgs(args);
|
|
866
925
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
|
|
926
|
+
const txArgs = [
|
|
927
|
+
{
|
|
928
|
+
epochSize: argsArray[0],
|
|
929
|
+
args: argsArray[1],
|
|
930
|
+
fees: argsArray[2],
|
|
931
|
+
blobPublicInputs: argsArray[3],
|
|
932
|
+
aggregationObject: argsArray[4],
|
|
933
|
+
proof: proofHex,
|
|
934
|
+
},
|
|
935
|
+
] as const;
|
|
879
936
|
|
|
880
|
-
|
|
937
|
+
this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
|
|
938
|
+
const data = encodeFunctionData({
|
|
939
|
+
abi: this.rollupContract.abi,
|
|
940
|
+
functionName: 'submitEpochRootProof',
|
|
941
|
+
args: txArgs,
|
|
942
|
+
});
|
|
943
|
+
try {
|
|
944
|
+
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({
|
|
881
945
|
to: this.rollupContract.address,
|
|
882
|
-
data
|
|
883
|
-
abi: this.rollupContract.abi,
|
|
884
|
-
functionName: 'submitEpochRootProof',
|
|
885
|
-
args: txArgs,
|
|
886
|
-
}),
|
|
946
|
+
data,
|
|
887
947
|
});
|
|
888
948
|
|
|
889
|
-
return
|
|
949
|
+
return receipt.transactionHash;
|
|
890
950
|
} catch (err) {
|
|
891
951
|
this.log.error(`Rollup submit epoch proof failed`, err);
|
|
952
|
+
const errorMsg = await this.tryGetErrorFromRevertedTx(data, {
|
|
953
|
+
args: [...txArgs],
|
|
954
|
+
functionName: 'submitEpochRootProof',
|
|
955
|
+
abi: this.rollupContract.abi,
|
|
956
|
+
address: this.rollupContract.address,
|
|
957
|
+
});
|
|
958
|
+
this.log.error(`Rollup submit epoch proof tx reverted. ${errorMsg}`);
|
|
892
959
|
return undefined;
|
|
893
960
|
}
|
|
894
961
|
}
|
|
@@ -907,9 +974,8 @@ export class L1Publisher {
|
|
|
907
974
|
},
|
|
908
975
|
{},
|
|
909
976
|
{
|
|
910
|
-
blobs: encodedData.blobs.map(b => b.
|
|
977
|
+
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
911
978
|
kzg,
|
|
912
|
-
maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
|
|
913
979
|
},
|
|
914
980
|
);
|
|
915
981
|
|
|
@@ -976,7 +1042,8 @@ export class L1Publisher {
|
|
|
976
1042
|
|
|
977
1043
|
private async sendProposeTx(
|
|
978
1044
|
encodedData: L1ProcessArgs,
|
|
979
|
-
|
|
1045
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
1046
|
+
): Promise<L1ProcessReturnType | undefined> {
|
|
980
1047
|
if (this.interrupted) {
|
|
981
1048
|
return undefined;
|
|
982
1049
|
}
|
|
@@ -988,28 +1055,29 @@ export class L1Publisher {
|
|
|
988
1055
|
functionName: 'propose',
|
|
989
1056
|
args,
|
|
990
1057
|
});
|
|
991
|
-
const
|
|
1058
|
+
const result = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
992
1059
|
{
|
|
993
1060
|
to: this.rollupContract.address,
|
|
994
1061
|
data,
|
|
995
1062
|
},
|
|
996
1063
|
{
|
|
997
1064
|
fixedGas: gas,
|
|
1065
|
+
...opts,
|
|
998
1066
|
},
|
|
999
1067
|
{
|
|
1000
|
-
blobs: encodedData.blobs.map(b => b.
|
|
1068
|
+
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
1001
1069
|
kzg,
|
|
1002
|
-
maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
|
|
1003
1070
|
},
|
|
1004
1071
|
);
|
|
1005
1072
|
return {
|
|
1006
|
-
receipt,
|
|
1073
|
+
receipt: result.receipt,
|
|
1074
|
+
gasPrice: result.gasPrice,
|
|
1007
1075
|
args,
|
|
1008
1076
|
functionName: 'propose',
|
|
1009
1077
|
data,
|
|
1010
1078
|
};
|
|
1011
1079
|
} catch (err) {
|
|
1012
|
-
this.log.error(`Rollup publish failed
|
|
1080
|
+
this.log.error(`Rollup publish failed.`, err);
|
|
1013
1081
|
return undefined;
|
|
1014
1082
|
}
|
|
1015
1083
|
}
|
|
@@ -1017,7 +1085,8 @@ export class L1Publisher {
|
|
|
1017
1085
|
private async sendProposeAndClaimTx(
|
|
1018
1086
|
encodedData: L1ProcessArgs,
|
|
1019
1087
|
quote: EpochProofQuote,
|
|
1020
|
-
|
|
1088
|
+
opts: { txTimeoutAt?: Date } = {},
|
|
1089
|
+
): Promise<L1ProcessReturnType | undefined> {
|
|
1021
1090
|
if (this.interrupted) {
|
|
1022
1091
|
return undefined;
|
|
1023
1092
|
}
|
|
@@ -1029,27 +1098,30 @@ export class L1Publisher {
|
|
|
1029
1098
|
functionName: 'proposeAndClaim',
|
|
1030
1099
|
args: [...args, quote.toViemArgs()],
|
|
1031
1100
|
});
|
|
1032
|
-
const
|
|
1101
|
+
const result = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
1033
1102
|
{
|
|
1034
1103
|
to: this.rollupContract.address,
|
|
1035
1104
|
data,
|
|
1036
1105
|
},
|
|
1037
|
-
{ fixedGas: gas },
|
|
1038
1106
|
{
|
|
1039
|
-
|
|
1107
|
+
fixedGas: gas,
|
|
1108
|
+
...opts,
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
1040
1112
|
kzg,
|
|
1041
|
-
maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
|
|
1042
1113
|
},
|
|
1043
1114
|
);
|
|
1044
1115
|
|
|
1045
1116
|
return {
|
|
1046
|
-
receipt,
|
|
1117
|
+
receipt: result.receipt,
|
|
1118
|
+
gasPrice: result.gasPrice,
|
|
1047
1119
|
args: [...args, quote.toViemArgs()],
|
|
1048
1120
|
functionName: 'proposeAndClaim',
|
|
1049
1121
|
data,
|
|
1050
1122
|
};
|
|
1051
1123
|
} catch (err) {
|
|
1052
|
-
this.log.error(`Rollup publish failed
|
|
1124
|
+
this.log.error(`Rollup publish failed.`, err);
|
|
1053
1125
|
return undefined;
|
|
1054
1126
|
}
|
|
1055
1127
|
}
|
|
@@ -1078,6 +1150,7 @@ export class L1Publisher {
|
|
|
1078
1150
|
gasPrice: receipt.effectiveGasPrice,
|
|
1079
1151
|
logs: receipt.logs,
|
|
1080
1152
|
blockNumber: receipt.blockNumber,
|
|
1153
|
+
blockHash: receipt.blockHash,
|
|
1081
1154
|
};
|
|
1082
1155
|
}
|
|
1083
1156
|
|
|
@@ -1093,9 +1166,21 @@ export class L1Publisher {
|
|
|
1093
1166
|
protected async sleepOrInterrupted() {
|
|
1094
1167
|
await this.interruptibleSleep.sleep(this.sleepTimeMs);
|
|
1095
1168
|
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Send blobs to the blob sink
|
|
1172
|
+
*
|
|
1173
|
+
* If a blob sink url is configured, then we send blobs to the blob sink
|
|
1174
|
+
* - for now we use the blockHash as the identifier for the blobs;
|
|
1175
|
+
* In the future this will move to be the beacon block id - which takes a bit more work
|
|
1176
|
+
* to calculate and will need to be mocked in e2e tests
|
|
1177
|
+
*/
|
|
1178
|
+
protected sendBlobsToBlobSink(blockHash: string, blobs: Blob[]): Promise<boolean> {
|
|
1179
|
+
return this.blobSinkClient.sendBlobsToBlobSink(blockHash, blobs);
|
|
1180
|
+
}
|
|
1096
1181
|
}
|
|
1097
1182
|
|
|
1098
|
-
|
|
1183
|
+
/*
|
|
1099
1184
|
* Returns cost of calldata usage in Ethereum.
|
|
1100
1185
|
* @param data - Calldata.
|
|
1101
1186
|
* @returns 4 for each zero byte, 16 for each nonzero.
|