@aztec/sequencer-client 0.72.1 → 0.74.0
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 +11 -6
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +46 -10
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +3 -4
- package/dest/publisher/config.d.ts +5 -0
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +9 -2
- package/dest/publisher/index.d.ts +1 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +2 -2
- package/dest/publisher/{l1-publisher-metrics.d.ts → sequencer-publisher-metrics.d.ts} +6 -2
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -0
- package/dest/publisher/sequencer-publisher-metrics.js +111 -0
- package/dest/publisher/sequencer-publisher.d.ts +163 -0
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -0
- package/dest/publisher/sequencer-publisher.js +528 -0
- package/dest/sequencer/allowed.d.ts +1 -1
- package/dest/sequencer/allowed.d.ts.map +1 -1
- package/dest/sequencer/allowed.js +4 -4
- package/dest/sequencer/metrics.d.ts +1 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +3 -4
- package/dest/sequencer/sequencer.d.ts +17 -10
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +103 -109
- package/dest/sequencer/utils.d.ts +1 -1
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +3 -3
- package/dest/slasher/factory.d.ts +2 -2
- package/dest/slasher/factory.d.ts.map +1 -1
- package/dest/slasher/factory.js +2 -2
- package/dest/slasher/slasher_client.d.ts +4 -4
- package/dest/slasher/slasher_client.d.ts.map +1 -1
- package/dest/slasher/slasher_client.js +38 -26
- package/dest/test/index.d.ts +3 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +1 -2
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +4 -3
- package/dest/tx_validator/test_utils.d.ts +4 -4
- package/dest/tx_validator/test_utils.d.ts.map +1 -1
- package/dest/tx_validator/test_utils.js +3 -3
- package/package.json +21 -20
- package/src/client/sequencer-client.ts +81 -14
- package/src/config.ts +3 -3
- package/src/publisher/config.ts +13 -1
- package/src/publisher/index.ts +1 -1
- package/src/publisher/{l1-publisher-metrics.ts → sequencer-publisher-metrics.ts} +41 -2
- package/src/publisher/sequencer-publisher.ts +710 -0
- package/src/sequencer/allowed.ts +3 -3
- package/src/sequencer/metrics.ts +2 -3
- package/src/sequencer/sequencer.ts +138 -125
- package/src/sequencer/utils.ts +5 -2
- package/src/slasher/factory.ts +3 -3
- package/src/slasher/slasher_client.ts +44 -30
- package/src/test/index.ts +2 -4
- package/src/tx_validator/gas_validator.ts +5 -4
- package/src/tx_validator/test_utils.ts +5 -5
- package/dest/publisher/l1-publisher-metrics.d.ts.map +0 -1
- package/dest/publisher/l1-publisher-metrics.js +0 -85
- package/dest/publisher/l1-publisher.d.ts +0 -195
- package/dest/publisher/l1-publisher.d.ts.map +0 -1
- package/dest/publisher/l1-publisher.js +0 -930
- package/dest/test/test-l1-publisher.d.ts +0 -9
- package/dest/test/test-l1-publisher.d.ts.map +0 -1
- package/dest/test/test-l1-publisher.js +0 -11
- package/src/publisher/l1-publisher.ts +0 -1288
- package/src/test/test-l1-publisher.ts +0 -20
|
@@ -1,1288 +0,0 @@
|
|
|
1
|
-
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
2
|
-
import {
|
|
3
|
-
ConsensusPayload,
|
|
4
|
-
type EpochProofClaim,
|
|
5
|
-
type EpochProofQuote,
|
|
6
|
-
type L2Block,
|
|
7
|
-
SignatureDomainSeparator,
|
|
8
|
-
type TxHash,
|
|
9
|
-
getHashedSignaturePayload,
|
|
10
|
-
} from '@aztec/circuit-types';
|
|
11
|
-
import { type L1PublishBlockStats, type L1PublishProofStats, type L1PublishStats } from '@aztec/circuit-types/stats';
|
|
12
|
-
import {
|
|
13
|
-
AGGREGATION_OBJECT_LENGTH,
|
|
14
|
-
AZTEC_MAX_EPOCH_DURATION,
|
|
15
|
-
type BlockHeader,
|
|
16
|
-
EthAddress,
|
|
17
|
-
type Proof,
|
|
18
|
-
} from '@aztec/circuits.js';
|
|
19
|
-
import { type FeeRecipient, type RootRollupPublicInputs } from '@aztec/circuits.js/rollup';
|
|
20
|
-
import {
|
|
21
|
-
type EthereumChain,
|
|
22
|
-
FormattedViemError,
|
|
23
|
-
type GasPrice,
|
|
24
|
-
type L1ContractsConfig,
|
|
25
|
-
L1TxUtils,
|
|
26
|
-
createEthereumChain,
|
|
27
|
-
formatViemError,
|
|
28
|
-
} from '@aztec/ethereum';
|
|
29
|
-
import { makeTuple } from '@aztec/foundation/array';
|
|
30
|
-
import { toHex } from '@aztec/foundation/bigint-buffer';
|
|
31
|
-
import { Blob } from '@aztec/foundation/blob';
|
|
32
|
-
import { areArraysEqual, compactArray, times } from '@aztec/foundation/collection';
|
|
33
|
-
import { type Signature } from '@aztec/foundation/eth-signature';
|
|
34
|
-
import { Fr } from '@aztec/foundation/fields';
|
|
35
|
-
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
36
|
-
import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
37
|
-
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
38
|
-
import { Timer } from '@aztec/foundation/timer';
|
|
39
|
-
import { EmpireBaseAbi, RollupAbi, SlasherAbi } from '@aztec/l1-artifacts';
|
|
40
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
41
|
-
|
|
42
|
-
import pick from 'lodash.pick';
|
|
43
|
-
import {
|
|
44
|
-
type BaseError,
|
|
45
|
-
type Chain,
|
|
46
|
-
type Client,
|
|
47
|
-
type ContractFunctionExecutionError,
|
|
48
|
-
ContractFunctionRevertedError,
|
|
49
|
-
type GetContractReturnType,
|
|
50
|
-
type Hex,
|
|
51
|
-
type HttpTransport,
|
|
52
|
-
type PrivateKeyAccount,
|
|
53
|
-
type PublicActions,
|
|
54
|
-
type PublicClient,
|
|
55
|
-
type PublicRpcSchema,
|
|
56
|
-
type TransactionReceipt,
|
|
57
|
-
type WalletActions,
|
|
58
|
-
type WalletClient,
|
|
59
|
-
type WalletRpcSchema,
|
|
60
|
-
createPublicClient,
|
|
61
|
-
createWalletClient,
|
|
62
|
-
encodeFunctionData,
|
|
63
|
-
getAbiItem,
|
|
64
|
-
getAddress,
|
|
65
|
-
getContract,
|
|
66
|
-
getContractError,
|
|
67
|
-
hexToBytes,
|
|
68
|
-
http,
|
|
69
|
-
publicActions,
|
|
70
|
-
} from 'viem';
|
|
71
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
72
|
-
|
|
73
|
-
import { type PublisherConfig, type TxSenderConfig } from './config.js';
|
|
74
|
-
import { L1PublisherMetrics } from './l1-publisher-metrics.js';
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Stats for a sent transaction.
|
|
78
|
-
*/
|
|
79
|
-
export type TransactionStats = {
|
|
80
|
-
/** Address of the sender. */
|
|
81
|
-
sender: string;
|
|
82
|
-
/** Hash of the transaction. */
|
|
83
|
-
transactionHash: string;
|
|
84
|
-
/** Size in bytes of the tx calldata */
|
|
85
|
-
calldataSize: number;
|
|
86
|
-
/** Gas required to pay for the calldata inclusion (depends on size and number of zeros) */
|
|
87
|
-
calldataGas: number;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Minimal information from a tx receipt.
|
|
92
|
-
*/
|
|
93
|
-
export type MinimalTransactionReceipt = {
|
|
94
|
-
/** True if the tx was successful, false if reverted. */
|
|
95
|
-
status: boolean;
|
|
96
|
-
/** Hash of the transaction. */
|
|
97
|
-
transactionHash: `0x${string}`;
|
|
98
|
-
/** Effective gas used by the tx. */
|
|
99
|
-
gasUsed: bigint;
|
|
100
|
-
/** Effective gas price paid by the tx. */
|
|
101
|
-
gasPrice: bigint;
|
|
102
|
-
/** Logs emitted in this tx. */
|
|
103
|
-
logs: any[];
|
|
104
|
-
/** Block number in which this tx was mined. */
|
|
105
|
-
blockNumber: bigint;
|
|
106
|
-
/** The block hash in which this tx was mined */
|
|
107
|
-
blockHash: `0x${string}`;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
/** Arguments to the process method of the rollup contract */
|
|
111
|
-
type L1ProcessArgs = {
|
|
112
|
-
/** The L2 block header. */
|
|
113
|
-
header: Buffer;
|
|
114
|
-
/** A root of the archive tree after the L2 block is applied. */
|
|
115
|
-
archive: Buffer;
|
|
116
|
-
/** The L2 block's leaf in the archive tree. */
|
|
117
|
-
blockHash: Buffer;
|
|
118
|
-
/** L2 block body. TODO(#9101): Remove block body once we can extract blobs. */
|
|
119
|
-
body: Buffer;
|
|
120
|
-
/** L2 block blobs containing all tx effects. */
|
|
121
|
-
blobs: Blob[];
|
|
122
|
-
/** L2 block tx hashes */
|
|
123
|
-
txHashes: TxHash[];
|
|
124
|
-
/** Attestations */
|
|
125
|
-
attestations?: Signature[];
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
type L1ProcessReturnType = {
|
|
129
|
-
receipt: TransactionReceipt | undefined;
|
|
130
|
-
args: any;
|
|
131
|
-
functionName: string;
|
|
132
|
-
data: Hex;
|
|
133
|
-
gasPrice: GasPrice;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/** Arguments to the submitEpochProof method of the rollup contract */
|
|
137
|
-
export type L1SubmitEpochProofArgs = {
|
|
138
|
-
epochSize: number;
|
|
139
|
-
previousArchive: Fr;
|
|
140
|
-
endArchive: Fr;
|
|
141
|
-
previousBlockHash: Fr;
|
|
142
|
-
endBlockHash: Fr;
|
|
143
|
-
endTimestamp: Fr;
|
|
144
|
-
outHash: Fr;
|
|
145
|
-
proverId: Fr;
|
|
146
|
-
fees: Tuple<FeeRecipient, typeof AZTEC_MAX_EPOCH_DURATION>;
|
|
147
|
-
proof: Proof;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
export enum VoteType {
|
|
151
|
-
GOVERNANCE,
|
|
152
|
-
SLASHING,
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
type GetSlashPayloadCallBack = (slotNumber: bigint) => Promise<EthAddress | undefined>;
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Publishes L2 blocks to L1. This implementation does *not* retry a transaction in
|
|
159
|
-
* the event of network congestion, but should work for local development.
|
|
160
|
-
* - If sending (not mining) a tx fails, it retries indefinitely at 1-minute intervals.
|
|
161
|
-
* - If the tx is not mined, keeps polling indefinitely at 1-second intervals.
|
|
162
|
-
*
|
|
163
|
-
* Adapted from https://github.com/AztecProtocol/aztec2-internal/blob/master/falafel/src/rollup_publisher.ts.
|
|
164
|
-
*/
|
|
165
|
-
export class L1Publisher {
|
|
166
|
-
private interruptibleSleep = new InterruptibleSleep();
|
|
167
|
-
private sleepTimeMs: number;
|
|
168
|
-
private interrupted = false;
|
|
169
|
-
private metrics: L1PublisherMetrics;
|
|
170
|
-
|
|
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
|
-
};
|
|
183
|
-
|
|
184
|
-
protected log = createLogger('sequencer:publisher');
|
|
185
|
-
|
|
186
|
-
protected rollupContract: GetContractReturnType<
|
|
187
|
-
typeof RollupAbi,
|
|
188
|
-
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
189
|
-
>;
|
|
190
|
-
|
|
191
|
-
protected publicClient: PublicClient<HttpTransport, Chain>;
|
|
192
|
-
protected walletClient: WalletClient<HttpTransport, Chain, PrivateKeyAccount>;
|
|
193
|
-
protected account: PrivateKeyAccount;
|
|
194
|
-
protected ethereumSlotDuration: bigint;
|
|
195
|
-
|
|
196
|
-
private blobSinkClient: BlobSinkClientInterface;
|
|
197
|
-
// @note - with blobs, the below estimate seems too large.
|
|
198
|
-
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
199
|
-
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
200
|
-
public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
|
|
201
|
-
public static PROPOSE_AND_CLAIM_GAS_GUESS: bigint = this.PROPOSE_GAS_GUESS + 100_000n;
|
|
202
|
-
|
|
203
|
-
private readonly l1TxUtils: L1TxUtils;
|
|
204
|
-
|
|
205
|
-
constructor(
|
|
206
|
-
config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
207
|
-
deps: { telemetry?: TelemetryClient; blobSinkClient?: BlobSinkClientInterface } = {},
|
|
208
|
-
) {
|
|
209
|
-
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
|
|
210
|
-
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
211
|
-
|
|
212
|
-
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
213
|
-
this.blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config.blobSinkUrl);
|
|
214
|
-
|
|
215
|
-
this.metrics = new L1PublisherMetrics(telemetry, 'L1Publisher');
|
|
216
|
-
|
|
217
|
-
const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config;
|
|
218
|
-
const chain = createEthereumChain(rpcUrl, chainId);
|
|
219
|
-
this.account = privateKeyToAccount(publisherPrivateKey);
|
|
220
|
-
this.log.debug(`Publishing from address ${this.account.address}`);
|
|
221
|
-
|
|
222
|
-
this.walletClient = this.createWalletClient(this.account, chain);
|
|
223
|
-
|
|
224
|
-
this.publicClient = createPublicClient({
|
|
225
|
-
chain: chain.chainInfo,
|
|
226
|
-
transport: http(chain.rpcUrl),
|
|
227
|
-
pollingInterval: config.viemPollingIntervalMS,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
this.rollupContract = getContract({
|
|
231
|
-
address: getAddress(l1Contracts.rollupAddress.toString()),
|
|
232
|
-
abi: RollupAbi,
|
|
233
|
-
client: this.walletClient,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
if (l1Contracts.governanceProposerAddress) {
|
|
237
|
-
this.governanceProposerAddress = EthAddress.fromString(l1Contracts.governanceProposerAddress.toString());
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
this.l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient, this.log, config);
|
|
241
|
-
}
|
|
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
|
-
|
|
262
|
-
get publisherAddress() {
|
|
263
|
-
return this.account.address;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
protected createWalletClient(
|
|
267
|
-
account: PrivateKeyAccount,
|
|
268
|
-
chain: EthereumChain,
|
|
269
|
-
): WalletClient<HttpTransport, Chain, PrivateKeyAccount> {
|
|
270
|
-
return createWalletClient({
|
|
271
|
-
account,
|
|
272
|
-
chain: chain.chainInfo,
|
|
273
|
-
transport: http(chain.rpcUrl),
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
public getGovernancePayload() {
|
|
278
|
-
return this.governancePayload;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
public setGovernancePayload(payload: EthAddress) {
|
|
282
|
-
this.governancePayload = payload;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
public getSenderAddress(): EthAddress {
|
|
286
|
-
return EthAddress.fromString(this.account.address);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
public getClient(): Client<
|
|
290
|
-
HttpTransport,
|
|
291
|
-
Chain,
|
|
292
|
-
PrivateKeyAccount,
|
|
293
|
-
[...WalletRpcSchema, ...PublicRpcSchema],
|
|
294
|
-
PublicActions<HttpTransport, Chain> & WalletActions<Chain, PrivateKeyAccount>
|
|
295
|
-
> {
|
|
296
|
-
return this.walletClient.extend(publicActions);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
public getRollupContract(): GetContractReturnType<
|
|
300
|
-
typeof RollupAbi,
|
|
301
|
-
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
302
|
-
> {
|
|
303
|
-
return this.rollupContract;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* @notice Calls `canProposeAtTime` with the time of the next Ethereum block and the sender address
|
|
308
|
-
*
|
|
309
|
-
* @dev Throws if unable to propose
|
|
310
|
-
*
|
|
311
|
-
* @param archive - The archive that we expect to be current state
|
|
312
|
-
* @return slot - The L2 slot number of the next Ethereum block,
|
|
313
|
-
* @return blockNumber - The L2 block number of the next L2 block
|
|
314
|
-
*/
|
|
315
|
-
public async canProposeAtNextEthBlock(archive: Buffer): Promise<[bigint, bigint]> {
|
|
316
|
-
// FIXME: This should not throw if unable to propose but return a falsey value, so
|
|
317
|
-
// we can differentiate between errors when hitting the L1 rollup contract (eg RPC error)
|
|
318
|
-
// which may require a retry, vs actually not being the turn for proposing.
|
|
319
|
-
const timeOfNextL1Slot = BigInt((await this.publicClient.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
320
|
-
const [slot, blockNumber] = await this.rollupContract.read.canProposeAtTime([
|
|
321
|
-
timeOfNextL1Slot,
|
|
322
|
-
`0x${archive.toString('hex')}`,
|
|
323
|
-
]);
|
|
324
|
-
return [slot, blockNumber];
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
public async getClaimableEpoch(): Promise<bigint | undefined> {
|
|
328
|
-
try {
|
|
329
|
-
return await this.rollupContract.read.getClaimableEpoch();
|
|
330
|
-
} catch (err: any) {
|
|
331
|
-
const errorName = tryGetCustomErrorName(err);
|
|
332
|
-
// getting the error name from the abi is redundant,
|
|
333
|
-
// but it enforces that the error name is correct.
|
|
334
|
-
// That is, if the error name is not found, this will not compile.
|
|
335
|
-
const acceptedErrors = (['Rollup__NoEpochToProve', 'Rollup__ProofRightAlreadyClaimed'] as const).map(
|
|
336
|
-
name => getAbiItem({ abi: RollupAbi, name }).name,
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
if (errorName && acceptedErrors.includes(errorName as any)) {
|
|
340
|
-
return undefined;
|
|
341
|
-
}
|
|
342
|
-
throw err;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
public async getEpochForSlotNumber(slotNumber: bigint): Promise<bigint> {
|
|
347
|
-
return await this.rollupContract.read.getEpochAtSlot([slotNumber]);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
public async getEpochToProve(): Promise<bigint | undefined> {
|
|
351
|
-
try {
|
|
352
|
-
return await this.rollupContract.read.getEpochToProve();
|
|
353
|
-
} catch (err: any) {
|
|
354
|
-
// If this is a revert with Rollup__NoEpochToProve, it means there is no epoch to prove, so we return undefined
|
|
355
|
-
// See https://viem.sh/docs/contract/simulateContract#handling-custom-errors
|
|
356
|
-
const errorName = tryGetCustomErrorName(err);
|
|
357
|
-
if (errorName === getAbiItem({ abi: RollupAbi, name: 'Rollup__NoEpochToProve' }).name) {
|
|
358
|
-
return undefined;
|
|
359
|
-
}
|
|
360
|
-
throw err;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
public async getProofClaim(): Promise<EpochProofClaim | undefined> {
|
|
365
|
-
const {
|
|
366
|
-
epochToProve,
|
|
367
|
-
basisPointFee,
|
|
368
|
-
bondAmount,
|
|
369
|
-
bondProvider: bondProviderHex,
|
|
370
|
-
proposerClaimant: proposerClaimantHex,
|
|
371
|
-
} = await this.rollupContract.read.getProofClaim();
|
|
372
|
-
|
|
373
|
-
const bondProvider = EthAddress.fromString(bondProviderHex);
|
|
374
|
-
const proposerClaimant = EthAddress.fromString(proposerClaimantHex);
|
|
375
|
-
|
|
376
|
-
if (bondProvider.isZero() && proposerClaimant.isZero() && epochToProve === 0n) {
|
|
377
|
-
return undefined;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return {
|
|
381
|
-
epochToProve,
|
|
382
|
-
basisPointFee,
|
|
383
|
-
bondAmount,
|
|
384
|
-
bondProvider,
|
|
385
|
-
proposerClaimant,
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
public async validateProofQuote(quote: EpochProofQuote): Promise<EpochProofQuote | undefined> {
|
|
390
|
-
const timeOfNextL1Slot = BigInt((await this.publicClient.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
391
|
-
const args = [timeOfNextL1Slot, quote.toViemArgs()] as const;
|
|
392
|
-
try {
|
|
393
|
-
await this.rollupContract.read.validateEpochProofRightClaimAtTime(args, { account: this.account });
|
|
394
|
-
} catch (err) {
|
|
395
|
-
let errorName = tryGetCustomErrorName(err);
|
|
396
|
-
if (!errorName) {
|
|
397
|
-
errorName = tryGetCustomErrorNameContractFunction(err as ContractFunctionExecutionError);
|
|
398
|
-
}
|
|
399
|
-
this.log.warn(`Proof quote validation failed: ${errorName}`, quote);
|
|
400
|
-
return undefined;
|
|
401
|
-
}
|
|
402
|
-
return quote;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* @notice Will call `validateHeader` to make sure that it is possible to propose
|
|
407
|
-
*
|
|
408
|
-
* @dev Throws if unable to propose
|
|
409
|
-
*
|
|
410
|
-
* @param header - The header to propose
|
|
411
|
-
* @param digest - The digest that attestations are signing over
|
|
412
|
-
*
|
|
413
|
-
*/
|
|
414
|
-
public async validateBlockForSubmission(
|
|
415
|
-
header: BlockHeader,
|
|
416
|
-
attestationData: { digest: Buffer; signatures: Signature[] } = {
|
|
417
|
-
digest: Buffer.alloc(32),
|
|
418
|
-
signatures: [],
|
|
419
|
-
},
|
|
420
|
-
): Promise<bigint> {
|
|
421
|
-
const ts = BigInt((await this.publicClient.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
422
|
-
|
|
423
|
-
const formattedSignatures = attestationData.signatures.map(attest => attest.toViemSignature());
|
|
424
|
-
const flags = { ignoreDA: true, ignoreSignatures: formattedSignatures.length == 0 };
|
|
425
|
-
|
|
426
|
-
const args = [
|
|
427
|
-
`0x${header.toBuffer().toString('hex')}`,
|
|
428
|
-
formattedSignatures,
|
|
429
|
-
`0x${attestationData.digest.toString('hex')}`,
|
|
430
|
-
ts,
|
|
431
|
-
`0x${header.contentCommitment.blobsHash.toString('hex')}`,
|
|
432
|
-
flags,
|
|
433
|
-
] as const;
|
|
434
|
-
|
|
435
|
-
try {
|
|
436
|
-
await this.rollupContract.read.validateHeader(args, { account: this.account });
|
|
437
|
-
} catch (error: unknown) {
|
|
438
|
-
// Specify the type of error
|
|
439
|
-
if (error instanceof ContractFunctionRevertedError) {
|
|
440
|
-
const err = error as ContractFunctionRevertedError;
|
|
441
|
-
this.log.debug(`Validation failed: ${err.message}`, err.data);
|
|
442
|
-
}
|
|
443
|
-
throw error;
|
|
444
|
-
}
|
|
445
|
-
return ts;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
public async getCurrentEpochCommittee(): Promise<EthAddress[]> {
|
|
449
|
-
const committee = await this.rollupContract.read.getCurrentEpochCommittee();
|
|
450
|
-
return committee.map(EthAddress.fromString);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
async getTransactionStats(txHash: string): Promise<TransactionStats | undefined> {
|
|
454
|
-
const tx = await this.publicClient.getTransaction({ hash: txHash as Hex });
|
|
455
|
-
if (!tx) {
|
|
456
|
-
return undefined;
|
|
457
|
-
}
|
|
458
|
-
const calldata = hexToBytes(tx.input);
|
|
459
|
-
return {
|
|
460
|
-
sender: tx.from.toString(),
|
|
461
|
-
transactionHash: tx.hash,
|
|
462
|
-
calldataSize: calldata.length,
|
|
463
|
-
calldataGas: getCalldataGasUsage(calldata),
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
public async castVote(slotNumber: bigint, timestamp: bigint, voteType: VoteType) {
|
|
467
|
-
// @todo This function can be optimized by doing some of the computations locally instead of calling the L1 contracts
|
|
468
|
-
if (this.myLastVotes[voteType] >= slotNumber) {
|
|
469
|
-
return false;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const voteConfig = async (): Promise<
|
|
473
|
-
{ payload: EthAddress; voteContractAddress: EthAddress; logger: Logger } | undefined
|
|
474
|
-
> => {
|
|
475
|
-
if (voteType === VoteType.GOVERNANCE) {
|
|
476
|
-
if (this.governancePayload.equals(EthAddress.ZERO)) {
|
|
477
|
-
return undefined;
|
|
478
|
-
}
|
|
479
|
-
if (!this.governanceProposerAddress) {
|
|
480
|
-
return undefined;
|
|
481
|
-
}
|
|
482
|
-
return {
|
|
483
|
-
payload: this.governancePayload,
|
|
484
|
-
voteContractAddress: this.governanceProposerAddress,
|
|
485
|
-
logger: this.governanceLog,
|
|
486
|
-
};
|
|
487
|
-
} else if (voteType === VoteType.SLASHING) {
|
|
488
|
-
if (!this.getSlashPayload) {
|
|
489
|
-
return undefined;
|
|
490
|
-
}
|
|
491
|
-
const slashingProposerAddress = await this.getSlashingProposerAddress();
|
|
492
|
-
if (!slashingProposerAddress) {
|
|
493
|
-
return undefined;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const slashPayload = await this.getSlashPayload(slotNumber);
|
|
497
|
-
|
|
498
|
-
if (!slashPayload) {
|
|
499
|
-
return undefined;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return {
|
|
503
|
-
payload: slashPayload,
|
|
504
|
-
voteContractAddress: slashingProposerAddress,
|
|
505
|
-
logger: this.slashingLog,
|
|
506
|
-
};
|
|
507
|
-
} else {
|
|
508
|
-
throw new Error('Invalid vote type');
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
const vConfig = await voteConfig();
|
|
513
|
-
|
|
514
|
-
if (!vConfig) {
|
|
515
|
-
return false;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const { payload, voteContractAddress, logger } = vConfig;
|
|
519
|
-
|
|
520
|
-
const voteContract = getContract({
|
|
521
|
-
address: getAddress(voteContractAddress.toString()),
|
|
522
|
-
abi: EmpireBaseAbi,
|
|
523
|
-
client: this.walletClient,
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
const [proposer, roundNumber] = await Promise.all([
|
|
527
|
-
this.rollupContract.read.getProposerAt([timestamp]),
|
|
528
|
-
voteContract.read.computeRound([slotNumber]),
|
|
529
|
-
]);
|
|
530
|
-
|
|
531
|
-
if (proposer.toLowerCase() !== this.account.address.toLowerCase()) {
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const [slotForLastVote] = await voteContract.read.rounds([this.rollupContract.address, roundNumber]);
|
|
536
|
-
|
|
537
|
-
if (slotForLastVote >= slotNumber) {
|
|
538
|
-
return false;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const cachedMyLastVote = this.myLastVotes[voteType];
|
|
542
|
-
this.myLastVotes[voteType] = slotNumber;
|
|
543
|
-
|
|
544
|
-
let txHash;
|
|
545
|
-
try {
|
|
546
|
-
txHash = await voteContract.write.vote([payload.toString()], {
|
|
547
|
-
account: this.account,
|
|
548
|
-
});
|
|
549
|
-
} catch (err) {
|
|
550
|
-
const { message, metaMessages } = formatViemError(err);
|
|
551
|
-
logger.error(`Failed to vote`, message, { metaMessages });
|
|
552
|
-
this.myLastVotes[voteType] = cachedMyLastVote;
|
|
553
|
-
return false;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (txHash) {
|
|
557
|
-
const receipt = await this.getTransactionReceipt(txHash);
|
|
558
|
-
if (!receipt) {
|
|
559
|
-
logger.warn(`Failed to get receipt for tx ${txHash}`);
|
|
560
|
-
this.myLastVotes[voteType] = cachedMyLastVote;
|
|
561
|
-
return false;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
logger.info(`Cast vote for ${payload}`);
|
|
566
|
-
return true;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Proposes a L2 block on L1.
|
|
571
|
-
* @param block - L2 block to propose.
|
|
572
|
-
* @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise.
|
|
573
|
-
*/
|
|
574
|
-
public async proposeL2Block(
|
|
575
|
-
block: L2Block,
|
|
576
|
-
attestations?: Signature[],
|
|
577
|
-
txHashes?: TxHash[],
|
|
578
|
-
proofQuote?: EpochProofQuote,
|
|
579
|
-
opts: { txTimeoutAt?: Date } = {},
|
|
580
|
-
): Promise<boolean> {
|
|
581
|
-
const ctx = {
|
|
582
|
-
blockNumber: block.number,
|
|
583
|
-
slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
|
|
584
|
-
blockHash: block.hash().toString(),
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
const consensusPayload = new ConsensusPayload(block.header, block.archive.root, txHashes ?? []);
|
|
588
|
-
|
|
589
|
-
const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
|
|
590
|
-
|
|
591
|
-
const blobs = Blob.getBlobs(block.body.toBlobFields());
|
|
592
|
-
const proposeTxArgs = {
|
|
593
|
-
header: block.header.toBuffer(),
|
|
594
|
-
archive: block.archive.root.toBuffer(),
|
|
595
|
-
blockHash: block.header.hash().toBuffer(),
|
|
596
|
-
body: block.body.toBuffer(),
|
|
597
|
-
blobs,
|
|
598
|
-
attestations,
|
|
599
|
-
txHashes: txHashes ?? [],
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
// Publish body and propose block (if not already published)
|
|
603
|
-
if (this.interrupted) {
|
|
604
|
-
this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
|
|
605
|
-
return false;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
const timer = new Timer();
|
|
609
|
-
|
|
610
|
-
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
611
|
-
// This means that we can avoid the simulation issues in later checks.
|
|
612
|
-
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
613
|
-
// make time consistency checks break.
|
|
614
|
-
const ts = await this.validateBlockForSubmission(block.header, {
|
|
615
|
-
digest: digest.toBuffer(),
|
|
616
|
-
signatures: attestations ?? [],
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
this.log.debug(`Submitting propose transaction`);
|
|
620
|
-
const result = proofQuote
|
|
621
|
-
? await this.sendProposeAndClaimTx(proposeTxArgs, proofQuote, opts, ts)
|
|
622
|
-
: await this.sendProposeTx(proposeTxArgs, opts, ts);
|
|
623
|
-
|
|
624
|
-
if (!result?.receipt) {
|
|
625
|
-
this.log.info(`Failed to publish block ${block.number} to L1`, ctx);
|
|
626
|
-
return false;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
const { receipt, args, functionName, data, gasPrice } = result;
|
|
630
|
-
|
|
631
|
-
// Tx was mined successfully
|
|
632
|
-
if (receipt.status === 'success') {
|
|
633
|
-
const tx = await this.getTransactionStats(receipt.transactionHash);
|
|
634
|
-
const stats: L1PublishBlockStats = {
|
|
635
|
-
gasPrice: receipt.effectiveGasPrice,
|
|
636
|
-
gasUsed: receipt.gasUsed,
|
|
637
|
-
blobGasUsed: receipt.blobGasUsed ?? 0n,
|
|
638
|
-
blobDataGas: receipt.blobGasPrice ?? 0n,
|
|
639
|
-
transactionHash: receipt.transactionHash,
|
|
640
|
-
...pick(tx!, 'calldataGas', 'calldataSize', 'sender'),
|
|
641
|
-
...block.getStats(),
|
|
642
|
-
eventName: 'rollup-published-to-l1',
|
|
643
|
-
};
|
|
644
|
-
this.log.verbose(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
645
|
-
this.metrics.recordProcessBlockTx(timer.ms(), stats);
|
|
646
|
-
|
|
647
|
-
// Send the blobs to the blob sink
|
|
648
|
-
this.sendBlobsToBlobSink(receipt.blockHash, blobs).catch(_err => {
|
|
649
|
-
this.log.error('Failed to send blobs to blob sink');
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
return true;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
this.metrics.recordFailedTx('process');
|
|
656
|
-
const kzg = Blob.getViemKzgInstance();
|
|
657
|
-
const errorMsg = await this.tryGetErrorFromRevertedTx(
|
|
658
|
-
data,
|
|
659
|
-
{
|
|
660
|
-
args,
|
|
661
|
-
functionName,
|
|
662
|
-
abi: RollupAbi,
|
|
663
|
-
address: this.rollupContract.address,
|
|
664
|
-
},
|
|
665
|
-
{
|
|
666
|
-
blobs: proposeTxArgs.blobs.map(b => b.dataWithZeros),
|
|
667
|
-
kzg,
|
|
668
|
-
maxFeePerBlobGas: gasPrice.maxFeePerBlobGas ?? 10000000000n,
|
|
669
|
-
},
|
|
670
|
-
);
|
|
671
|
-
this.log.error(`Rollup process tx reverted. ${errorMsg}`, undefined, {
|
|
672
|
-
...ctx,
|
|
673
|
-
txHash: receipt.transactionHash,
|
|
674
|
-
});
|
|
675
|
-
await this.sleepOrInterrupted();
|
|
676
|
-
return false;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/** Calls claimEpochProofRight in the Rollup contract to submit a chosen prover quote for the previous epoch. */
|
|
680
|
-
public async claimEpochProofRight(proofQuote: EpochProofQuote) {
|
|
681
|
-
const timer = new Timer();
|
|
682
|
-
let result;
|
|
683
|
-
try {
|
|
684
|
-
this.log.debug(`Submitting claimEpochProofRight transaction`);
|
|
685
|
-
result = await this.l1TxUtils.sendAndMonitorTransaction({
|
|
686
|
-
to: this.rollupContract.address,
|
|
687
|
-
data: encodeFunctionData({
|
|
688
|
-
abi: RollupAbi,
|
|
689
|
-
functionName: 'claimEpochProofRight',
|
|
690
|
-
args: [proofQuote.toViemArgs()],
|
|
691
|
-
}),
|
|
692
|
-
});
|
|
693
|
-
} catch (err) {
|
|
694
|
-
if (err instanceof FormattedViemError) {
|
|
695
|
-
const { message, metaMessages } = err;
|
|
696
|
-
this.log.error(`Failed to claim epoch proof right`, message, {
|
|
697
|
-
metaMessages,
|
|
698
|
-
proofQuote: proofQuote.toInspect(),
|
|
699
|
-
});
|
|
700
|
-
} else {
|
|
701
|
-
this.log.error(`Failed to claim epoch proof right`, err, {
|
|
702
|
-
proofQuote: proofQuote.toInspect(),
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
return false;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
const { receipt } = result;
|
|
709
|
-
|
|
710
|
-
if (receipt.status === 'success') {
|
|
711
|
-
const tx = await this.getTransactionStats(receipt.transactionHash);
|
|
712
|
-
const stats: L1PublishStats = {
|
|
713
|
-
gasPrice: receipt.effectiveGasPrice,
|
|
714
|
-
gasUsed: receipt.gasUsed,
|
|
715
|
-
transactionHash: receipt.transactionHash,
|
|
716
|
-
blobDataGas: 0n,
|
|
717
|
-
blobGasUsed: 0n,
|
|
718
|
-
...pick(tx!, 'calldataGas', 'calldataSize', 'sender'),
|
|
719
|
-
};
|
|
720
|
-
this.log.verbose(`Submitted claim epoch proof right to L1 rollup contract`, {
|
|
721
|
-
...stats,
|
|
722
|
-
...proofQuote.toInspect(),
|
|
723
|
-
});
|
|
724
|
-
this.metrics.recordClaimEpochProofRightTx(timer.ms(), stats);
|
|
725
|
-
return true;
|
|
726
|
-
} else {
|
|
727
|
-
this.metrics.recordFailedTx('claimEpochProofRight');
|
|
728
|
-
// TODO: Get the error message from the reverted tx
|
|
729
|
-
this.log.error(`Claim epoch proof right tx reverted`, {
|
|
730
|
-
txHash: receipt.transactionHash,
|
|
731
|
-
...proofQuote.toInspect(),
|
|
732
|
-
});
|
|
733
|
-
return false;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
private async tryGetErrorFromRevertedTx(
|
|
738
|
-
data: Hex,
|
|
739
|
-
args: {
|
|
740
|
-
args: any[];
|
|
741
|
-
functionName: string;
|
|
742
|
-
abi: any;
|
|
743
|
-
address: Hex;
|
|
744
|
-
},
|
|
745
|
-
_blobInputs?: {
|
|
746
|
-
blobs: Uint8Array[];
|
|
747
|
-
kzg: any;
|
|
748
|
-
maxFeePerBlobGas: bigint;
|
|
749
|
-
},
|
|
750
|
-
) {
|
|
751
|
-
const blobInputs = _blobInputs || {};
|
|
752
|
-
try {
|
|
753
|
-
// NB: If this fn starts unexpectedly giving incorrect blob hash errors, it may be because the checkBlob
|
|
754
|
-
// bool is no longer at the slot below. To find the slot, run: forge inspect src/core/Rollup.sol:Rollup storage
|
|
755
|
-
const checkBlobSlot = 9n;
|
|
756
|
-
await this.publicClient.simulateContract({
|
|
757
|
-
...args,
|
|
758
|
-
account: this.walletClient.account,
|
|
759
|
-
stateOverride: [
|
|
760
|
-
{
|
|
761
|
-
address: args.address,
|
|
762
|
-
stateDiff: [
|
|
763
|
-
{
|
|
764
|
-
slot: toHex(checkBlobSlot, true),
|
|
765
|
-
value: toHex(0n, true),
|
|
766
|
-
},
|
|
767
|
-
],
|
|
768
|
-
},
|
|
769
|
-
],
|
|
770
|
-
});
|
|
771
|
-
// If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors.
|
|
772
|
-
// Strangely, the only way to throw the revert reason as an error and provide blobs is prepareTransactionRequest.
|
|
773
|
-
// See: https://github.com/wevm/viem/issues/2075
|
|
774
|
-
// This throws a EstimateGasExecutionError with the custom error information:
|
|
775
|
-
await this.walletClient.prepareTransactionRequest({
|
|
776
|
-
account: this.walletClient.account,
|
|
777
|
-
to: this.rollupContract.address,
|
|
778
|
-
data,
|
|
779
|
-
...blobInputs,
|
|
780
|
-
});
|
|
781
|
-
return undefined;
|
|
782
|
-
} catch (simulationErr: any) {
|
|
783
|
-
// If we don't have a ContractFunctionExecutionError, we have a blob related error => use getContractError to get the error msg.
|
|
784
|
-
const contractErr =
|
|
785
|
-
simulationErr.name === 'ContractFunctionExecutionError'
|
|
786
|
-
? simulationErr
|
|
787
|
-
: getContractError(simulationErr as BaseError, {
|
|
788
|
-
args: [],
|
|
789
|
-
abi: RollupAbi,
|
|
790
|
-
functionName: args.functionName,
|
|
791
|
-
address: args.address,
|
|
792
|
-
sender: this.account.address,
|
|
793
|
-
});
|
|
794
|
-
if (contractErr.name === 'ContractFunctionExecutionError') {
|
|
795
|
-
const execErr = contractErr as ContractFunctionExecutionError;
|
|
796
|
-
return tryGetCustomErrorNameContractFunction(execErr);
|
|
797
|
-
}
|
|
798
|
-
this.log.error(`Error getting error from simulation`, simulationErr);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
public async submitEpochProof(args: {
|
|
803
|
-
epochNumber: number;
|
|
804
|
-
fromBlock: number;
|
|
805
|
-
toBlock: number;
|
|
806
|
-
publicInputs: RootRollupPublicInputs;
|
|
807
|
-
proof: Proof;
|
|
808
|
-
}): Promise<boolean> {
|
|
809
|
-
const { epochNumber, fromBlock, toBlock } = args;
|
|
810
|
-
const ctx = { epochNumber, fromBlock, toBlock };
|
|
811
|
-
if (!this.interrupted) {
|
|
812
|
-
const timer = new Timer();
|
|
813
|
-
|
|
814
|
-
// Validate epoch proof range and hashes are correct before submitting
|
|
815
|
-
await this.validateEpochProofSubmission(args);
|
|
816
|
-
|
|
817
|
-
const txHash = await this.sendSubmitEpochProofTx(args);
|
|
818
|
-
if (!txHash) {
|
|
819
|
-
return false;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
const receipt = await this.getTransactionReceipt(txHash);
|
|
823
|
-
if (!receipt) {
|
|
824
|
-
return false;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// Tx was mined successfully
|
|
828
|
-
if (receipt.status) {
|
|
829
|
-
const tx = await this.getTransactionStats(txHash);
|
|
830
|
-
const stats: L1PublishProofStats = {
|
|
831
|
-
...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'),
|
|
832
|
-
...pick(tx!, 'calldataGas', 'calldataSize', 'sender'),
|
|
833
|
-
blobDataGas: 0n,
|
|
834
|
-
blobGasUsed: 0n,
|
|
835
|
-
eventName: 'proof-published-to-l1',
|
|
836
|
-
};
|
|
837
|
-
this.log.info(`Published epoch proof to L1 rollup contract`, { ...stats, ...ctx });
|
|
838
|
-
this.metrics.recordSubmitProof(timer.ms(), stats);
|
|
839
|
-
return true;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
this.metrics.recordFailedTx('submitProof');
|
|
843
|
-
this.log.error(`Rollup.submitEpochProof tx status failed: ${receipt.transactionHash}`, ctx);
|
|
844
|
-
await this.sleepOrInterrupted();
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
|
|
848
|
-
return false;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
private async validateEpochProofSubmission(args: {
|
|
852
|
-
fromBlock: number;
|
|
853
|
-
toBlock: number;
|
|
854
|
-
publicInputs: RootRollupPublicInputs;
|
|
855
|
-
proof: Proof;
|
|
856
|
-
}) {
|
|
857
|
-
const { fromBlock, toBlock, publicInputs, proof } = args;
|
|
858
|
-
|
|
859
|
-
// Check that the block numbers match the expected epoch to be proven
|
|
860
|
-
const { pendingBlockNumber: pending, provenBlockNumber: proven } = await this.rollupContract.read.getTips();
|
|
861
|
-
if (proven !== BigInt(fromBlock) - 1n) {
|
|
862
|
-
throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as proven block is ${proven}`);
|
|
863
|
-
}
|
|
864
|
-
if (toBlock > pending) {
|
|
865
|
-
throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as pending block is ${pending}`);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// Check the block hash and archive for the immediate block before the epoch
|
|
869
|
-
const blockLog = await this.rollupContract.read.getBlock([proven]);
|
|
870
|
-
if (publicInputs.previousArchive.root.toString() !== blockLog.archive) {
|
|
871
|
-
throw new Error(
|
|
872
|
-
`Previous archive root mismatch: ${publicInputs.previousArchive.root.toString()} !== ${blockLog.archive}`,
|
|
873
|
-
);
|
|
874
|
-
}
|
|
875
|
-
// TODO: Remove zero check once we inject the proper zero blockhash
|
|
876
|
-
if (blockLog.blockHash !== Fr.ZERO.toString() && publicInputs.previousBlockHash.toString() !== blockLog.blockHash) {
|
|
877
|
-
throw new Error(
|
|
878
|
-
`Previous block hash mismatch: ${publicInputs.previousBlockHash.toString()} !== ${blockLog.blockHash}`,
|
|
879
|
-
);
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// Check the block hash and archive for the last block in the epoch
|
|
883
|
-
const endBlockLog = await this.rollupContract.read.getBlock([BigInt(toBlock)]);
|
|
884
|
-
if (publicInputs.endArchive.root.toString() !== endBlockLog.archive) {
|
|
885
|
-
throw new Error(
|
|
886
|
-
`End archive root mismatch: ${publicInputs.endArchive.root.toString()} !== ${endBlockLog.archive}`,
|
|
887
|
-
);
|
|
888
|
-
}
|
|
889
|
-
if (publicInputs.endBlockHash.toString() !== endBlockLog.blockHash) {
|
|
890
|
-
throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockLog.blockHash}`);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// Compare the public inputs computed by the contract with the ones injected
|
|
894
|
-
const rollupPublicInputs = await this.rollupContract.read.getEpochProofPublicInputs(
|
|
895
|
-
this.getSubmitEpochProofArgs(args),
|
|
896
|
-
);
|
|
897
|
-
const aggregationObject = proof.isEmpty()
|
|
898
|
-
? times(AGGREGATION_OBJECT_LENGTH, Fr.zero)
|
|
899
|
-
: proof.extractAggregationObject();
|
|
900
|
-
const argsPublicInputs = [...publicInputs.toFields(), ...aggregationObject];
|
|
901
|
-
|
|
902
|
-
if (!areArraysEqual(rollupPublicInputs.map(Fr.fromHexString), argsPublicInputs, (a, b) => a.equals(b))) {
|
|
903
|
-
const fmt = (inputs: Fr[] | readonly string[]) => inputs.map(x => x.toString()).join(', ');
|
|
904
|
-
throw new Error(
|
|
905
|
-
`Root rollup public inputs mismatch:\nRollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
|
|
906
|
-
);
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
|
|
912
|
-
* Be warned, the call may return false even if the tx subsequently gets successfully mined.
|
|
913
|
-
* In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail.
|
|
914
|
-
* A call to `restart` is required before you can continue publishing.
|
|
915
|
-
*/
|
|
916
|
-
public interrupt() {
|
|
917
|
-
this.interrupted = true;
|
|
918
|
-
this.interruptibleSleep.interrupt();
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
/** Restarts the publisher after calling `interrupt`. */
|
|
922
|
-
public restart() {
|
|
923
|
-
this.interrupted = false;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
private async sendSubmitEpochProofTx(args: {
|
|
927
|
-
fromBlock: number;
|
|
928
|
-
toBlock: number;
|
|
929
|
-
publicInputs: RootRollupPublicInputs;
|
|
930
|
-
proof: Proof;
|
|
931
|
-
}): Promise<string | undefined> {
|
|
932
|
-
const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
|
|
933
|
-
const argsArray = this.getSubmitEpochProofArgs(args);
|
|
934
|
-
|
|
935
|
-
const txArgs = [
|
|
936
|
-
{
|
|
937
|
-
epochSize: argsArray[0],
|
|
938
|
-
args: argsArray[1],
|
|
939
|
-
fees: argsArray[2],
|
|
940
|
-
blobPublicInputs: argsArray[3],
|
|
941
|
-
aggregationObject: argsArray[4],
|
|
942
|
-
proof: proofHex,
|
|
943
|
-
},
|
|
944
|
-
] as const;
|
|
945
|
-
|
|
946
|
-
this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
|
|
947
|
-
const data = encodeFunctionData({
|
|
948
|
-
abi: this.rollupContract.abi,
|
|
949
|
-
functionName: 'submitEpochRootProof',
|
|
950
|
-
args: txArgs,
|
|
951
|
-
});
|
|
952
|
-
try {
|
|
953
|
-
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({
|
|
954
|
-
to: this.rollupContract.address,
|
|
955
|
-
data,
|
|
956
|
-
});
|
|
957
|
-
|
|
958
|
-
return receipt.transactionHash;
|
|
959
|
-
} catch (err) {
|
|
960
|
-
this.log.error(`Rollup submit epoch proof failed`, err);
|
|
961
|
-
const errorMsg = await this.tryGetErrorFromRevertedTx(data, {
|
|
962
|
-
args: [...txArgs],
|
|
963
|
-
functionName: 'submitEpochRootProof',
|
|
964
|
-
abi: this.rollupContract.abi,
|
|
965
|
-
address: this.rollupContract.address,
|
|
966
|
-
});
|
|
967
|
-
this.log.error(`Rollup submit epoch proof tx reverted. ${errorMsg}`);
|
|
968
|
-
return undefined;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
private async prepareProposeTx(encodedData: L1ProcessArgs) {
|
|
973
|
-
const kzg = Blob.getViemKzgInstance();
|
|
974
|
-
const blobInput = Blob.getEthBlobEvaluationInputs(encodedData.blobs);
|
|
975
|
-
this.log.debug('Validating blob input', { blobInput });
|
|
976
|
-
const blobEvaluationGas = await this.l1TxUtils.estimateGas(
|
|
977
|
-
this.account,
|
|
978
|
-
{
|
|
979
|
-
to: this.rollupContract.address,
|
|
980
|
-
data: encodeFunctionData({
|
|
981
|
-
abi: this.rollupContract.abi,
|
|
982
|
-
functionName: 'validateBlobs',
|
|
983
|
-
args: [blobInput],
|
|
984
|
-
}),
|
|
985
|
-
},
|
|
986
|
-
{},
|
|
987
|
-
{
|
|
988
|
-
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
989
|
-
kzg,
|
|
990
|
-
},
|
|
991
|
-
);
|
|
992
|
-
|
|
993
|
-
const attestations = encodedData.attestations
|
|
994
|
-
? encodedData.attestations.map(attest => attest.toViemSignature())
|
|
995
|
-
: [];
|
|
996
|
-
const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.toString()) : [];
|
|
997
|
-
const args = [
|
|
998
|
-
{
|
|
999
|
-
header: `0x${encodedData.header.toString('hex')}`,
|
|
1000
|
-
archive: `0x${encodedData.archive.toString('hex')}`,
|
|
1001
|
-
oracleInput: {
|
|
1002
|
-
// We are currently not modifying these. See #9963
|
|
1003
|
-
feeAssetPriceModifier: 0n,
|
|
1004
|
-
provingCostModifier: 0n,
|
|
1005
|
-
},
|
|
1006
|
-
blockHash: `0x${encodedData.blockHash.toString('hex')}`,
|
|
1007
|
-
txHashes,
|
|
1008
|
-
},
|
|
1009
|
-
attestations,
|
|
1010
|
-
// TODO(#9101): Extract blobs from beacon chain => calldata will only contain what's needed to verify blob and body input can be removed
|
|
1011
|
-
`0x${encodedData.body.toString('hex')}`,
|
|
1012
|
-
blobInput,
|
|
1013
|
-
] as const;
|
|
1014
|
-
|
|
1015
|
-
return { args, blobEvaluationGas };
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
private getSubmitEpochProofArgs(args: {
|
|
1019
|
-
fromBlock: number;
|
|
1020
|
-
toBlock: number;
|
|
1021
|
-
publicInputs: RootRollupPublicInputs;
|
|
1022
|
-
proof: Proof;
|
|
1023
|
-
}) {
|
|
1024
|
-
return [
|
|
1025
|
-
BigInt(args.toBlock - args.fromBlock + 1),
|
|
1026
|
-
[
|
|
1027
|
-
args.publicInputs.previousArchive.root.toString(),
|
|
1028
|
-
args.publicInputs.endArchive.root.toString(),
|
|
1029
|
-
args.publicInputs.previousBlockHash.toString(),
|
|
1030
|
-
args.publicInputs.endBlockHash.toString(),
|
|
1031
|
-
args.publicInputs.endTimestamp.toString(),
|
|
1032
|
-
args.publicInputs.outHash.toString(),
|
|
1033
|
-
args.publicInputs.proverId.toString(),
|
|
1034
|
-
],
|
|
1035
|
-
makeTuple(AZTEC_MAX_EPOCH_DURATION * 2, i =>
|
|
1036
|
-
i % 2 === 0
|
|
1037
|
-
? args.publicInputs.fees[i / 2].recipient.toField().toString()
|
|
1038
|
-
: args.publicInputs.fees[(i - 1) / 2].value.toString(),
|
|
1039
|
-
),
|
|
1040
|
-
`0x${args.publicInputs.blobPublicInputs
|
|
1041
|
-
.filter((_, i) => i < args.toBlock - args.fromBlock + 1)
|
|
1042
|
-
.map(b => b.toString())
|
|
1043
|
-
.join(``)}`,
|
|
1044
|
-
`0x${serializeToBuffer(args.proof.extractAggregationObject()).toString('hex')}`,
|
|
1045
|
-
] as const;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
private async sendProposeTx(
|
|
1049
|
-
encodedData: L1ProcessArgs,
|
|
1050
|
-
opts: { txTimeoutAt?: Date } = {},
|
|
1051
|
-
timestamp: bigint,
|
|
1052
|
-
): Promise<L1ProcessReturnType | undefined> {
|
|
1053
|
-
if (this.interrupted) {
|
|
1054
|
-
return undefined;
|
|
1055
|
-
}
|
|
1056
|
-
try {
|
|
1057
|
-
const kzg = Blob.getViemKzgInstance();
|
|
1058
|
-
const { args, blobEvaluationGas } = await this.prepareProposeTx(encodedData);
|
|
1059
|
-
const data = encodeFunctionData({
|
|
1060
|
-
abi: this.rollupContract.abi,
|
|
1061
|
-
functionName: 'propose',
|
|
1062
|
-
args,
|
|
1063
|
-
});
|
|
1064
|
-
|
|
1065
|
-
const simulationResult = await this.l1TxUtils.simulateGasUsed(
|
|
1066
|
-
{
|
|
1067
|
-
to: this.rollupContract.address,
|
|
1068
|
-
data,
|
|
1069
|
-
gas: L1Publisher.PROPOSE_GAS_GUESS,
|
|
1070
|
-
},
|
|
1071
|
-
{
|
|
1072
|
-
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1073
|
-
time: timestamp + 1n,
|
|
1074
|
-
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit
|
|
1075
|
-
gasLimit: L1Publisher.PROPOSE_GAS_GUESS * 2n,
|
|
1076
|
-
},
|
|
1077
|
-
[
|
|
1078
|
-
{
|
|
1079
|
-
address: this.rollupContract.address,
|
|
1080
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1081
|
-
stateDiff: [
|
|
1082
|
-
{
|
|
1083
|
-
slot: toHex(9n, true),
|
|
1084
|
-
value: toHex(0n, true),
|
|
1085
|
-
},
|
|
1086
|
-
],
|
|
1087
|
-
},
|
|
1088
|
-
],
|
|
1089
|
-
{
|
|
1090
|
-
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1091
|
-
fallbackGasEstimate: L1Publisher.PROPOSE_GAS_GUESS,
|
|
1092
|
-
},
|
|
1093
|
-
);
|
|
1094
|
-
|
|
1095
|
-
const result = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
1096
|
-
{
|
|
1097
|
-
to: this.rollupContract.address,
|
|
1098
|
-
data,
|
|
1099
|
-
},
|
|
1100
|
-
{
|
|
1101
|
-
...opts,
|
|
1102
|
-
gasLimit: this.l1TxUtils.bumpGasLimit(simulationResult + blobEvaluationGas),
|
|
1103
|
-
},
|
|
1104
|
-
{
|
|
1105
|
-
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
1106
|
-
kzg,
|
|
1107
|
-
},
|
|
1108
|
-
);
|
|
1109
|
-
return {
|
|
1110
|
-
receipt: result.receipt,
|
|
1111
|
-
gasPrice: result.gasPrice,
|
|
1112
|
-
args,
|
|
1113
|
-
functionName: 'propose',
|
|
1114
|
-
data,
|
|
1115
|
-
};
|
|
1116
|
-
} catch (err) {
|
|
1117
|
-
if (err instanceof FormattedViemError) {
|
|
1118
|
-
const { message, metaMessages } = err;
|
|
1119
|
-
this.log.error(`Rollup publish failed.`, message, { metaMessages });
|
|
1120
|
-
} else {
|
|
1121
|
-
this.log.error(`Rollup publish failed.`, err);
|
|
1122
|
-
}
|
|
1123
|
-
return undefined;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
private async sendProposeAndClaimTx(
|
|
1128
|
-
encodedData: L1ProcessArgs,
|
|
1129
|
-
quote: EpochProofQuote,
|
|
1130
|
-
opts: { txTimeoutAt?: Date } = {},
|
|
1131
|
-
timestamp: bigint,
|
|
1132
|
-
): Promise<L1ProcessReturnType | undefined> {
|
|
1133
|
-
if (this.interrupted) {
|
|
1134
|
-
return undefined;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
try {
|
|
1138
|
-
const kzg = Blob.getViemKzgInstance();
|
|
1139
|
-
const { args, blobEvaluationGas } = await this.prepareProposeTx(encodedData);
|
|
1140
|
-
const data = encodeFunctionData({
|
|
1141
|
-
abi: this.rollupContract.abi,
|
|
1142
|
-
functionName: 'proposeAndClaim',
|
|
1143
|
-
args: [...args, quote.toViemArgs()],
|
|
1144
|
-
});
|
|
1145
|
-
|
|
1146
|
-
const simulationResult = await this.l1TxUtils.simulateGasUsed(
|
|
1147
|
-
{
|
|
1148
|
-
to: this.rollupContract.address,
|
|
1149
|
-
data,
|
|
1150
|
-
gas: L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS,
|
|
1151
|
-
},
|
|
1152
|
-
{
|
|
1153
|
-
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
1154
|
-
time: timestamp + 1n,
|
|
1155
|
-
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit
|
|
1156
|
-
gasLimit: L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS * 2n,
|
|
1157
|
-
},
|
|
1158
|
-
[
|
|
1159
|
-
{
|
|
1160
|
-
address: this.rollupContract.address,
|
|
1161
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1162
|
-
stateDiff: [
|
|
1163
|
-
{
|
|
1164
|
-
slot: toHex(9n, true),
|
|
1165
|
-
value: toHex(0n, true),
|
|
1166
|
-
},
|
|
1167
|
-
],
|
|
1168
|
-
},
|
|
1169
|
-
],
|
|
1170
|
-
{
|
|
1171
|
-
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
1172
|
-
fallbackGasEstimate: L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS,
|
|
1173
|
-
},
|
|
1174
|
-
);
|
|
1175
|
-
const result = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
1176
|
-
{
|
|
1177
|
-
to: this.rollupContract.address,
|
|
1178
|
-
data,
|
|
1179
|
-
},
|
|
1180
|
-
{
|
|
1181
|
-
...opts,
|
|
1182
|
-
gasLimit: this.l1TxUtils.bumpGasLimit(simulationResult + blobEvaluationGas),
|
|
1183
|
-
},
|
|
1184
|
-
{
|
|
1185
|
-
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
1186
|
-
kzg,
|
|
1187
|
-
},
|
|
1188
|
-
);
|
|
1189
|
-
|
|
1190
|
-
return {
|
|
1191
|
-
receipt: result.receipt,
|
|
1192
|
-
gasPrice: result.gasPrice,
|
|
1193
|
-
args: [...args, quote.toViemArgs()],
|
|
1194
|
-
functionName: 'proposeAndClaim',
|
|
1195
|
-
data,
|
|
1196
|
-
};
|
|
1197
|
-
} catch (err) {
|
|
1198
|
-
if (err instanceof FormattedViemError) {
|
|
1199
|
-
const { message, metaMessages } = err;
|
|
1200
|
-
this.log.error(`Rollup publish failed.`, message, { metaMessages });
|
|
1201
|
-
} else {
|
|
1202
|
-
this.log.error(`Rollup publish failed.`, err);
|
|
1203
|
-
}
|
|
1204
|
-
return undefined;
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
/**
|
|
1209
|
-
* Returns a tx receipt if the tx has been mined.
|
|
1210
|
-
* @param txHash - Hash of the tx to look for.
|
|
1211
|
-
* @returns Undefined if the tx hasn't been mined yet, the receipt otherwise.
|
|
1212
|
-
*/
|
|
1213
|
-
async getTransactionReceipt(txHash: string): Promise<MinimalTransactionReceipt | undefined> {
|
|
1214
|
-
while (!this.interrupted) {
|
|
1215
|
-
try {
|
|
1216
|
-
const receipt = await this.publicClient.getTransactionReceipt({
|
|
1217
|
-
hash: txHash as Hex,
|
|
1218
|
-
});
|
|
1219
|
-
|
|
1220
|
-
if (receipt) {
|
|
1221
|
-
if (receipt.transactionHash !== txHash) {
|
|
1222
|
-
throw new Error(`Tx hash mismatch: ${receipt.transactionHash} !== ${txHash}`);
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
return {
|
|
1226
|
-
status: receipt.status === 'success',
|
|
1227
|
-
transactionHash: txHash,
|
|
1228
|
-
gasUsed: receipt.gasUsed,
|
|
1229
|
-
gasPrice: receipt.effectiveGasPrice,
|
|
1230
|
-
logs: receipt.logs,
|
|
1231
|
-
blockNumber: receipt.blockNumber,
|
|
1232
|
-
blockHash: receipt.blockHash,
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
this.log.debug(`Receipt not found for tx hash ${txHash}`);
|
|
1237
|
-
return undefined;
|
|
1238
|
-
} catch (err) {
|
|
1239
|
-
//this.log.error(`Error getting tx receipt`, err);
|
|
1240
|
-
await this.sleepOrInterrupted();
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
protected async sleepOrInterrupted() {
|
|
1246
|
-
await this.interruptibleSleep.sleep(this.sleepTimeMs);
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
/**
|
|
1250
|
-
* Send blobs to the blob sink
|
|
1251
|
-
*
|
|
1252
|
-
* If a blob sink url is configured, then we send blobs to the blob sink
|
|
1253
|
-
* - for now we use the blockHash as the identifier for the blobs;
|
|
1254
|
-
* In the future this will move to be the beacon block id - which takes a bit more work
|
|
1255
|
-
* to calculate and will need to be mocked in e2e tests
|
|
1256
|
-
*/
|
|
1257
|
-
protected sendBlobsToBlobSink(blockHash: string, blobs: Blob[]): Promise<boolean> {
|
|
1258
|
-
return this.blobSinkClient.sendBlobsToBlobSink(blockHash, blobs);
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
/*
|
|
1263
|
-
* Returns cost of calldata usage in Ethereum.
|
|
1264
|
-
* @param data - Calldata.
|
|
1265
|
-
* @returns 4 for each zero byte, 16 for each nonzero.
|
|
1266
|
-
*/
|
|
1267
|
-
function getCalldataGasUsage(data: Uint8Array) {
|
|
1268
|
-
return data.filter(byte => byte === 0).length * 4 + data.filter(byte => byte !== 0).length * 16;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
function tryGetCustomErrorNameContractFunction(err: ContractFunctionExecutionError) {
|
|
1272
|
-
return compactArray([err.shortMessage, ...(err.metaMessages ?? []).slice(0, 2).map(s => s.trim())]).join(' ');
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
function tryGetCustomErrorName(err: any) {
|
|
1276
|
-
try {
|
|
1277
|
-
// See https://viem.sh/docs/contract/simulateContract#handling-custom-errors
|
|
1278
|
-
if (err.name === 'ViemError' || err.name === 'ContractFunctionExecutionError') {
|
|
1279
|
-
const baseError = err as BaseError;
|
|
1280
|
-
const revertError = baseError.walk(err => (err as Error).name === 'ContractFunctionRevertedError');
|
|
1281
|
-
if (revertError) {
|
|
1282
|
-
return (revertError as ContractFunctionRevertedError).data?.errorName;
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
} catch (_e) {
|
|
1286
|
-
return undefined;
|
|
1287
|
-
}
|
|
1288
|
-
}
|