@aztec/sequencer-client 0.69.0 → 0.69.1
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 +2 -0
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -4
- 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/l1-publisher.d.ts +36 -11
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +154 -88
- 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 +9 -16
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +61 -130
- 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/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 +38 -24
- package/package.json +21 -19
- package/src/client/sequencer-client.ts +5 -2
- package/src/config.ts +10 -0
- package/src/index.ts +3 -1
- package/src/publisher/config.ts +10 -0
- package/src/publisher/l1-publisher.ts +180 -97
- package/src/sequencer/index.ts +1 -0
- package/src/sequencer/sequencer.ts +70 -180
- 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/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 +82 -40
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type EpochProofClaim,
|
|
4
4
|
type EpochProofQuote,
|
|
5
5
|
type L2Block,
|
|
6
|
-
|
|
6
|
+
SignatureDomainSeparator,
|
|
7
7
|
type TxHash,
|
|
8
8
|
getHashedSignaturePayload,
|
|
9
9
|
} from '@aztec/circuit-types';
|
|
@@ -16,24 +16,18 @@ import {
|
|
|
16
16
|
type Proof,
|
|
17
17
|
} from '@aztec/circuits.js';
|
|
18
18
|
import { type FeeRecipient, type RootRollupPublicInputs } from '@aztec/circuits.js/rollup';
|
|
19
|
-
import {
|
|
20
|
-
type EthereumChain,
|
|
21
|
-
type L1ContractsConfig,
|
|
22
|
-
L1TxUtils,
|
|
23
|
-
type L1TxUtilsConfig,
|
|
24
|
-
createEthereumChain,
|
|
25
|
-
} from '@aztec/ethereum';
|
|
19
|
+
import { type EthereumChain, type L1ContractsConfig, L1TxUtils, createEthereumChain } from '@aztec/ethereum';
|
|
26
20
|
import { makeTuple } from '@aztec/foundation/array';
|
|
27
21
|
import { toHex } from '@aztec/foundation/bigint-buffer';
|
|
28
22
|
import { Blob } from '@aztec/foundation/blob';
|
|
29
23
|
import { areArraysEqual, compactArray, times } from '@aztec/foundation/collection';
|
|
30
24
|
import { type Signature } from '@aztec/foundation/eth-signature';
|
|
31
25
|
import { Fr } from '@aztec/foundation/fields';
|
|
32
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
26
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
33
27
|
import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
34
28
|
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
35
29
|
import { Timer } from '@aztec/foundation/timer';
|
|
36
|
-
import {
|
|
30
|
+
import { EmpireBaseAbi, RollupAbi, SlasherAbi } from '@aztec/l1-artifacts';
|
|
37
31
|
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
38
32
|
|
|
39
33
|
import pick from 'lodash.pick';
|
|
@@ -41,7 +35,7 @@ import {
|
|
|
41
35
|
type BaseError,
|
|
42
36
|
type Chain,
|
|
43
37
|
type Client,
|
|
44
|
-
ContractFunctionExecutionError,
|
|
38
|
+
type ContractFunctionExecutionError,
|
|
45
39
|
ContractFunctionRevertedError,
|
|
46
40
|
type GetContractReturnType,
|
|
47
41
|
type Hex,
|
|
@@ -101,6 +95,8 @@ export type MinimalTransactionReceipt = {
|
|
|
101
95
|
logs: any[];
|
|
102
96
|
/** Block number in which this tx was mined. */
|
|
103
97
|
blockNumber: bigint;
|
|
98
|
+
/** The block hash in which this tx was mined */
|
|
99
|
+
blockHash: `0x${string}`;
|
|
104
100
|
};
|
|
105
101
|
|
|
106
102
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -135,6 +131,13 @@ export type L1SubmitEpochProofArgs = {
|
|
|
135
131
|
proof: Proof;
|
|
136
132
|
};
|
|
137
133
|
|
|
134
|
+
export enum VoteType {
|
|
135
|
+
GOVERNANCE,
|
|
136
|
+
SLASHING,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
type GetSlashPayloadCallBack = (slotNumber: bigint) => Promise<EthAddress | undefined>;
|
|
140
|
+
|
|
138
141
|
/**
|
|
139
142
|
* Publishes L2 blocks to L1. This implementation does *not* retry a transaction in
|
|
140
143
|
* the event of network congestion, but should work for local development.
|
|
@@ -149,26 +152,33 @@ export class L1Publisher {
|
|
|
149
152
|
private interrupted = false;
|
|
150
153
|
private metrics: L1PublisherMetrics;
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
156
|
+
protected governanceProposerAddress?: EthAddress;
|
|
157
|
+
private governancePayload: EthAddress = EthAddress.ZERO;
|
|
158
|
+
|
|
159
|
+
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
160
|
+
protected slashingProposerAddress?: EthAddress;
|
|
161
|
+
private getSlashPayload?: GetSlashPayloadCallBack = undefined;
|
|
162
|
+
|
|
163
|
+
private myLastVotes: Record<VoteType, bigint> = {
|
|
164
|
+
[VoteType.GOVERNANCE]: 0n,
|
|
165
|
+
[VoteType.SLASHING]: 0n,
|
|
166
|
+
};
|
|
154
167
|
|
|
155
168
|
protected log = createLogger('sequencer:publisher');
|
|
156
|
-
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
157
169
|
|
|
158
170
|
protected rollupContract: GetContractReturnType<
|
|
159
171
|
typeof RollupAbi,
|
|
160
172
|
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
161
173
|
>;
|
|
162
|
-
protected governanceProposerContract?: GetContractReturnType<
|
|
163
|
-
typeof GovernanceProposerAbi,
|
|
164
|
-
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
165
|
-
> = undefined;
|
|
166
174
|
|
|
167
175
|
protected publicClient: PublicClient<HttpTransport, Chain>;
|
|
168
176
|
protected walletClient: WalletClient<HttpTransport, Chain, PrivateKeyAccount>;
|
|
169
177
|
protected account: PrivateKeyAccount;
|
|
170
178
|
protected ethereumSlotDuration: bigint;
|
|
171
179
|
|
|
180
|
+
private blobSinkUrl: string | undefined;
|
|
181
|
+
|
|
172
182
|
// @note - with blobs, the below estimate seems too large.
|
|
173
183
|
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
174
184
|
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
@@ -178,11 +188,12 @@ export class L1Publisher {
|
|
|
178
188
|
private readonly l1TxUtils: L1TxUtils;
|
|
179
189
|
|
|
180
190
|
constructor(
|
|
181
|
-
config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'
|
|
191
|
+
config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
182
192
|
client: TelemetryClient,
|
|
183
193
|
) {
|
|
184
194
|
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
|
|
185
195
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
196
|
+
this.blobSinkUrl = config.blobSinkUrl;
|
|
186
197
|
this.metrics = new L1PublisherMetrics(client, 'L1Publisher');
|
|
187
198
|
|
|
188
199
|
const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config;
|
|
@@ -205,16 +216,31 @@ export class L1Publisher {
|
|
|
205
216
|
});
|
|
206
217
|
|
|
207
218
|
if (l1Contracts.governanceProposerAddress) {
|
|
208
|
-
this.
|
|
209
|
-
address: getAddress(l1Contracts.governanceProposerAddress.toString()),
|
|
210
|
-
abi: GovernanceProposerAbi,
|
|
211
|
-
client: this.walletClient,
|
|
212
|
-
});
|
|
219
|
+
this.governanceProposerAddress = EthAddress.fromString(l1Contracts.governanceProposerAddress.toString());
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
this.l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient, this.log, config);
|
|
216
223
|
}
|
|
217
224
|
|
|
225
|
+
public registerSlashPayloadGetter(callback: GetSlashPayloadCallBack) {
|
|
226
|
+
this.getSlashPayload = callback;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private async getSlashingProposerAddress() {
|
|
230
|
+
if (this.slashingProposerAddress) {
|
|
231
|
+
return this.slashingProposerAddress;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const slasherAddress = await this.rollupContract.read.SLASHER();
|
|
235
|
+
const slasher = getContract({
|
|
236
|
+
address: getAddress(slasherAddress.toString()),
|
|
237
|
+
abi: SlasherAbi,
|
|
238
|
+
client: this.walletClient,
|
|
239
|
+
});
|
|
240
|
+
this.slashingProposerAddress = EthAddress.fromString(await slasher.read.PROPOSER());
|
|
241
|
+
return this.slashingProposerAddress;
|
|
242
|
+
}
|
|
243
|
+
|
|
218
244
|
get publisherAddress() {
|
|
219
245
|
return this.account.address;
|
|
220
246
|
}
|
|
@@ -230,12 +256,12 @@ export class L1Publisher {
|
|
|
230
256
|
});
|
|
231
257
|
}
|
|
232
258
|
|
|
233
|
-
public
|
|
234
|
-
return this.
|
|
259
|
+
public getGovernancePayload() {
|
|
260
|
+
return this.governancePayload;
|
|
235
261
|
}
|
|
236
262
|
|
|
237
|
-
public
|
|
238
|
-
this.
|
|
263
|
+
public setGovernancePayload(payload: EthAddress) {
|
|
264
|
+
this.governancePayload = payload;
|
|
239
265
|
}
|
|
240
266
|
|
|
241
267
|
public getSenderAddress(): EthAddress {
|
|
@@ -395,38 +421,6 @@ export class L1Publisher {
|
|
|
395
421
|
if (error instanceof ContractFunctionRevertedError) {
|
|
396
422
|
const err = error as ContractFunctionRevertedError;
|
|
397
423
|
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
424
|
}
|
|
431
425
|
throw error;
|
|
432
426
|
}
|
|
@@ -450,68 +444,106 @@ export class L1Publisher {
|
|
|
450
444
|
calldataGas: getCalldataGasUsage(calldata),
|
|
451
445
|
};
|
|
452
446
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (this.
|
|
447
|
+
public async castVote(slotNumber: bigint, timestamp: bigint, voteType: VoteType) {
|
|
448
|
+
// @todo This function can be optimized by doing some of the computations locally instead of calling the L1 contracts
|
|
449
|
+
if (this.myLastVotes[voteType] >= slotNumber) {
|
|
456
450
|
return false;
|
|
457
451
|
}
|
|
458
452
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
453
|
+
const voteConfig = async (): Promise<
|
|
454
|
+
{ payload: EthAddress; voteContractAddress: EthAddress; logger: Logger } | undefined
|
|
455
|
+
> => {
|
|
456
|
+
if (voteType === VoteType.GOVERNANCE) {
|
|
457
|
+
if (this.governancePayload.equals(EthAddress.ZERO)) {
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
if (!this.governanceProposerAddress) {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
payload: this.governancePayload,
|
|
465
|
+
voteContractAddress: this.governanceProposerAddress,
|
|
466
|
+
logger: this.governanceLog,
|
|
467
|
+
};
|
|
468
|
+
} else if (voteType === VoteType.SLASHING) {
|
|
469
|
+
if (!this.getSlashPayload) {
|
|
470
|
+
return undefined;
|
|
471
|
+
}
|
|
472
|
+
const slashingProposerAddress = await this.getSlashingProposerAddress();
|
|
473
|
+
if (!slashingProposerAddress) {
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const slashPayload = await this.getSlashPayload(slotNumber);
|
|
462
478
|
|
|
463
|
-
|
|
479
|
+
if (!slashPayload) {
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
payload: slashPayload,
|
|
485
|
+
voteContractAddress: slashingProposerAddress,
|
|
486
|
+
logger: this.slashingLog,
|
|
487
|
+
};
|
|
488
|
+
} else {
|
|
489
|
+
throw new Error('Invalid vote type');
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const vConfig = await voteConfig();
|
|
494
|
+
|
|
495
|
+
if (!vConfig) {
|
|
464
496
|
return false;
|
|
465
497
|
}
|
|
466
498
|
|
|
467
|
-
|
|
468
|
-
|
|
499
|
+
const { payload, voteContractAddress, logger } = vConfig;
|
|
500
|
+
|
|
501
|
+
const voteContract = getContract({
|
|
502
|
+
address: getAddress(voteContractAddress.toString()),
|
|
503
|
+
abi: EmpireBaseAbi,
|
|
504
|
+
client: this.walletClient,
|
|
505
|
+
});
|
|
469
506
|
|
|
470
507
|
const [proposer, roundNumber] = await Promise.all([
|
|
471
508
|
this.rollupContract.read.getProposerAt([timestamp]),
|
|
472
|
-
|
|
509
|
+
voteContract.read.computeRound([slotNumber]),
|
|
473
510
|
]);
|
|
474
511
|
|
|
475
512
|
if (proposer.toLowerCase() !== this.account.address.toLowerCase()) {
|
|
476
513
|
return false;
|
|
477
514
|
}
|
|
478
515
|
|
|
479
|
-
const [slotForLastVote] = await
|
|
480
|
-
this.rollupContract.address,
|
|
481
|
-
roundNumber,
|
|
482
|
-
]);
|
|
516
|
+
const [slotForLastVote] = await voteContract.read.rounds([this.rollupContract.address, roundNumber]);
|
|
483
517
|
|
|
484
518
|
if (slotForLastVote >= slotNumber) {
|
|
485
519
|
return false;
|
|
486
520
|
}
|
|
487
521
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const cachedMyLastVote = this.myLastVote;
|
|
491
|
-
this.myLastVote = slotNumber;
|
|
492
|
-
|
|
493
|
-
this.governanceLog.verbose(`Casting vote for ${this.payload}`);
|
|
522
|
+
const cachedMyLastVote = this.myLastVotes[voteType];
|
|
523
|
+
this.myLastVotes[voteType] = slotNumber;
|
|
494
524
|
|
|
495
525
|
let txHash;
|
|
496
526
|
try {
|
|
497
|
-
txHash = await
|
|
527
|
+
txHash = await voteContract.write.vote([payload.toString()], {
|
|
528
|
+
account: this.account,
|
|
529
|
+
});
|
|
498
530
|
} catch (err) {
|
|
499
531
|
const msg = prettyLogViemErrorMsg(err);
|
|
500
|
-
|
|
501
|
-
this.
|
|
532
|
+
logger.error(`Failed to vote`, msg);
|
|
533
|
+
this.myLastVotes[voteType] = cachedMyLastVote;
|
|
502
534
|
return false;
|
|
503
535
|
}
|
|
504
536
|
|
|
505
537
|
if (txHash) {
|
|
506
538
|
const receipt = await this.getTransactionReceipt(txHash);
|
|
507
539
|
if (!receipt) {
|
|
508
|
-
|
|
509
|
-
this.
|
|
540
|
+
logger.warn(`Failed to get receipt for tx ${txHash}`);
|
|
541
|
+
this.myLastVotes[voteType] = cachedMyLastVote;
|
|
510
542
|
return false;
|
|
511
543
|
}
|
|
512
544
|
}
|
|
513
545
|
|
|
514
|
-
|
|
546
|
+
logger.info(`Cast vote for ${payload}`);
|
|
515
547
|
return true;
|
|
516
548
|
}
|
|
517
549
|
|
|
@@ -534,16 +566,19 @@ export class L1Publisher {
|
|
|
534
566
|
|
|
535
567
|
const consensusPayload = new ConsensusPayload(block.header, block.archive.root, txHashes ?? []);
|
|
536
568
|
|
|
537
|
-
const digest = getHashedSignaturePayload(consensusPayload,
|
|
569
|
+
const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
|
|
570
|
+
|
|
571
|
+
const blobs = Blob.getBlobs(block.body.toBlobFields());
|
|
538
572
|
const proposeTxArgs = {
|
|
539
573
|
header: block.header.toBuffer(),
|
|
540
574
|
archive: block.archive.root.toBuffer(),
|
|
541
575
|
blockHash: block.header.hash().toBuffer(),
|
|
542
576
|
body: block.body.toBuffer(),
|
|
543
|
-
blobs
|
|
577
|
+
blobs,
|
|
544
578
|
attestations,
|
|
545
579
|
txHashes: txHashes ?? [],
|
|
546
580
|
};
|
|
581
|
+
|
|
547
582
|
// Publish body and propose block (if not already published)
|
|
548
583
|
if (this.interrupted) {
|
|
549
584
|
this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
|
|
@@ -588,6 +623,12 @@ export class L1Publisher {
|
|
|
588
623
|
};
|
|
589
624
|
this.log.verbose(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
590
625
|
this.metrics.recordProcessBlockTx(timer.ms(), stats);
|
|
626
|
+
|
|
627
|
+
// Send the blobs to the blob sink
|
|
628
|
+
this.sendBlobsToBlobSink(receipt.blockHash, blobs).catch(_err => {
|
|
629
|
+
this.log.error('Failed to send blobs to blob sink');
|
|
630
|
+
});
|
|
631
|
+
|
|
591
632
|
return true;
|
|
592
633
|
}
|
|
593
634
|
|
|
@@ -602,7 +643,7 @@ export class L1Publisher {
|
|
|
602
643
|
address: this.rollupContract.address,
|
|
603
644
|
},
|
|
604
645
|
{
|
|
605
|
-
blobs: proposeTxArgs.blobs.map(b => b.
|
|
646
|
+
blobs: proposeTxArgs.blobs.map(b => b.dataWithZeros),
|
|
606
647
|
kzg,
|
|
607
648
|
maxFeePerBlobGas: 10000000000n,
|
|
608
649
|
},
|
|
@@ -698,8 +739,7 @@ export class L1Publisher {
|
|
|
698
739
|
},
|
|
699
740
|
],
|
|
700
741
|
});
|
|
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.
|
|
742
|
+
// If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors.
|
|
703
743
|
// Strangely, the only way to throw the revert reason as an error and provide blobs is prepareTransactionRequest.
|
|
704
744
|
// See: https://github.com/wevm/viem/issues/2075
|
|
705
745
|
// This throws a EstimateGasExecutionError with the custom error information:
|
|
@@ -711,13 +751,13 @@ export class L1Publisher {
|
|
|
711
751
|
});
|
|
712
752
|
return undefined;
|
|
713
753
|
} catch (simulationErr: any) {
|
|
714
|
-
// If we don't have a ContractFunctionExecutionError, we have a blob related error => use
|
|
754
|
+
// If we don't have a ContractFunctionExecutionError, we have a blob related error => use getContractError to get the error msg.
|
|
715
755
|
const contractErr =
|
|
716
756
|
simulationErr.name === 'ContractFunctionExecutionError'
|
|
717
757
|
? simulationErr
|
|
718
758
|
: getContractError(simulationErr as BaseError, {
|
|
719
759
|
args: [],
|
|
720
|
-
abi:
|
|
760
|
+
abi: RollupAbi,
|
|
721
761
|
functionName: args.functionName,
|
|
722
762
|
address: args.address,
|
|
723
763
|
sender: this.account.address,
|
|
@@ -907,7 +947,7 @@ export class L1Publisher {
|
|
|
907
947
|
},
|
|
908
948
|
{},
|
|
909
949
|
{
|
|
910
|
-
blobs: encodedData.blobs.map(b => b.
|
|
950
|
+
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
911
951
|
kzg,
|
|
912
952
|
maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
|
|
913
953
|
},
|
|
@@ -997,7 +1037,7 @@ export class L1Publisher {
|
|
|
997
1037
|
fixedGas: gas,
|
|
998
1038
|
},
|
|
999
1039
|
{
|
|
1000
|
-
blobs: encodedData.blobs.map(b => b.
|
|
1040
|
+
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
1001
1041
|
kzg,
|
|
1002
1042
|
maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
|
|
1003
1043
|
},
|
|
@@ -1036,7 +1076,7 @@ export class L1Publisher {
|
|
|
1036
1076
|
},
|
|
1037
1077
|
{ fixedGas: gas },
|
|
1038
1078
|
{
|
|
1039
|
-
blobs: encodedData.blobs.map(b => b.
|
|
1079
|
+
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
1040
1080
|
kzg,
|
|
1041
1081
|
maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
|
|
1042
1082
|
},
|
|
@@ -1078,6 +1118,7 @@ export class L1Publisher {
|
|
|
1078
1118
|
gasPrice: receipt.effectiveGasPrice,
|
|
1079
1119
|
logs: receipt.logs,
|
|
1080
1120
|
blockNumber: receipt.blockNumber,
|
|
1121
|
+
blockHash: receipt.blockHash,
|
|
1081
1122
|
};
|
|
1082
1123
|
}
|
|
1083
1124
|
|
|
@@ -1093,9 +1134,51 @@ export class L1Publisher {
|
|
|
1093
1134
|
protected async sleepOrInterrupted() {
|
|
1094
1135
|
await this.interruptibleSleep.sleep(this.sleepTimeMs);
|
|
1095
1136
|
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Send blobs to the blob sink
|
|
1140
|
+
*
|
|
1141
|
+
* If a blob sink url is configured, then we send blobs to the blob sink
|
|
1142
|
+
* - for now we use the blockHash as the identifier for the blobs;
|
|
1143
|
+
* In the future this will move to be the beacon block id - which takes a bit more work
|
|
1144
|
+
* to calculate and will need to be mocked in e2e tests
|
|
1145
|
+
*/
|
|
1146
|
+
protected async sendBlobsToBlobSink(blockHash: string, blobs: Blob[]): Promise<boolean> {
|
|
1147
|
+
// TODO(md): for now we are assuming the indexes of the blobs will be 0, 1, 2
|
|
1148
|
+
// When in reality they will not, but for testing purposes this is fine
|
|
1149
|
+
if (!this.blobSinkUrl) {
|
|
1150
|
+
this.log.verbose('No blob sink url configured');
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
this.log.verbose(`Sending ${blobs.length} blobs to blob sink`);
|
|
1155
|
+
try {
|
|
1156
|
+
const res = await fetch(`${this.blobSinkUrl}/blob_sidecar`, {
|
|
1157
|
+
method: 'POST',
|
|
1158
|
+
headers: {
|
|
1159
|
+
'Content-Type': 'application/json',
|
|
1160
|
+
},
|
|
1161
|
+
body: JSON.stringify({
|
|
1162
|
+
// eslint-disable-next-line camelcase
|
|
1163
|
+
block_id: blockHash,
|
|
1164
|
+
blobs: blobs.map((b, i) => ({ blob: b.toBuffer(), index: i })),
|
|
1165
|
+
}),
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
if (res.ok) {
|
|
1169
|
+
return true;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
this.log.error('Failed to send blobs to blob sink', res.status);
|
|
1173
|
+
return false;
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
this.log.error(`Error sending blobs to blob sink`, err);
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1096
1179
|
}
|
|
1097
1180
|
|
|
1098
|
-
|
|
1181
|
+
/*
|
|
1099
1182
|
* Returns cost of calldata usage in Ethereum.
|
|
1100
1183
|
* @param data - Calldata.
|
|
1101
1184
|
* @returns 4 for each zero byte, 16 for each nonzero.
|
package/src/sequencer/index.ts
CHANGED