@aztec/sequencer-client 0.68.1 → 0.69.0-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 +2 -0
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -3
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -1
- package/dest/publisher/l1-publisher.d.ts +23 -11
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +96 -40
- package/dest/sequencer/sequencer.d.ts +3 -1
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +14 -8
- 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/package.json +21 -25
- package/src/client/sequencer-client.ts +4 -0
- package/src/index.ts +1 -0
- package/src/publisher/l1-publisher.ts +111 -52
- package/src/sequencer/sequencer.ts +14 -7
- package/src/slasher/factory.ts +22 -0
- package/src/slasher/index.ts +2 -0
- package/src/slasher/slasher_client.ts +402 -0
|
@@ -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, ExtRollupLibAbi, LeonidasLibAbi, RollupAbi, SlasherAbi } from '@aztec/l1-artifacts';
|
|
37
31
|
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
38
32
|
|
|
39
33
|
import pick from 'lodash.pick';
|
|
@@ -135,6 +129,13 @@ export type L1SubmitEpochProofArgs = {
|
|
|
135
129
|
proof: Proof;
|
|
136
130
|
};
|
|
137
131
|
|
|
132
|
+
export enum VoteType {
|
|
133
|
+
GOVERNANCE,
|
|
134
|
+
SLASHING,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
type GetSlashPayloadCallBack = (slotNumber: bigint) => Promise<EthAddress | undefined>;
|
|
138
|
+
|
|
138
139
|
/**
|
|
139
140
|
* Publishes L2 blocks to L1. This implementation does *not* retry a transaction in
|
|
140
141
|
* the event of network congestion, but should work for local development.
|
|
@@ -149,20 +150,25 @@ export class L1Publisher {
|
|
|
149
150
|
private interrupted = false;
|
|
150
151
|
private metrics: L1PublisherMetrics;
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
154
|
+
protected governanceProposerAddress?: EthAddress;
|
|
155
|
+
private governancePayload: EthAddress = EthAddress.ZERO;
|
|
156
|
+
|
|
157
|
+
protected slashingLog = createLogger('sequencer:publisher:slashing');
|
|
158
|
+
protected slashingProposerAddress?: EthAddress;
|
|
159
|
+
private getSlashPayload?: GetSlashPayloadCallBack = undefined;
|
|
160
|
+
|
|
161
|
+
private myLastVotes: Record<VoteType, bigint> = {
|
|
162
|
+
[VoteType.GOVERNANCE]: 0n,
|
|
163
|
+
[VoteType.SLASHING]: 0n,
|
|
164
|
+
};
|
|
154
165
|
|
|
155
166
|
protected log = createLogger('sequencer:publisher');
|
|
156
|
-
protected governanceLog = createLogger('sequencer:publisher:governance');
|
|
157
167
|
|
|
158
168
|
protected rollupContract: GetContractReturnType<
|
|
159
169
|
typeof RollupAbi,
|
|
160
170
|
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
161
171
|
>;
|
|
162
|
-
protected governanceProposerContract?: GetContractReturnType<
|
|
163
|
-
typeof GovernanceProposerAbi,
|
|
164
|
-
WalletClient<HttpTransport, Chain, PrivateKeyAccount>
|
|
165
|
-
> = undefined;
|
|
166
172
|
|
|
167
173
|
protected publicClient: PublicClient<HttpTransport, Chain>;
|
|
168
174
|
protected walletClient: WalletClient<HttpTransport, Chain, PrivateKeyAccount>;
|
|
@@ -178,7 +184,7 @@ export class L1Publisher {
|
|
|
178
184
|
private readonly l1TxUtils: L1TxUtils;
|
|
179
185
|
|
|
180
186
|
constructor(
|
|
181
|
-
config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'
|
|
187
|
+
config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
|
|
182
188
|
client: TelemetryClient,
|
|
183
189
|
) {
|
|
184
190
|
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
|
|
@@ -205,16 +211,31 @@ export class L1Publisher {
|
|
|
205
211
|
});
|
|
206
212
|
|
|
207
213
|
if (l1Contracts.governanceProposerAddress) {
|
|
208
|
-
this.
|
|
209
|
-
address: getAddress(l1Contracts.governanceProposerAddress.toString()),
|
|
210
|
-
abi: GovernanceProposerAbi,
|
|
211
|
-
client: this.walletClient,
|
|
212
|
-
});
|
|
214
|
+
this.governanceProposerAddress = EthAddress.fromString(l1Contracts.governanceProposerAddress.toString());
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
this.l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient, this.log, config);
|
|
216
218
|
}
|
|
217
219
|
|
|
220
|
+
public registerSlashPayloadGetter(callback: GetSlashPayloadCallBack) {
|
|
221
|
+
this.getSlashPayload = callback;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private async getSlashingProposerAddress() {
|
|
225
|
+
if (this.slashingProposerAddress) {
|
|
226
|
+
return this.slashingProposerAddress;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const slasherAddress = await this.rollupContract.read.SLASHER();
|
|
230
|
+
const slasher = getContract({
|
|
231
|
+
address: getAddress(slasherAddress.toString()),
|
|
232
|
+
abi: SlasherAbi,
|
|
233
|
+
client: this.walletClient,
|
|
234
|
+
});
|
|
235
|
+
this.slashingProposerAddress = EthAddress.fromString(await slasher.read.PROPOSER());
|
|
236
|
+
return this.slashingProposerAddress;
|
|
237
|
+
}
|
|
238
|
+
|
|
218
239
|
get publisherAddress() {
|
|
219
240
|
return this.account.address;
|
|
220
241
|
}
|
|
@@ -230,12 +251,12 @@ export class L1Publisher {
|
|
|
230
251
|
});
|
|
231
252
|
}
|
|
232
253
|
|
|
233
|
-
public
|
|
234
|
-
return this.
|
|
254
|
+
public getGovernancePayload() {
|
|
255
|
+
return this.governancePayload;
|
|
235
256
|
}
|
|
236
257
|
|
|
237
|
-
public
|
|
238
|
-
this.
|
|
258
|
+
public setGovernancePayload(payload: EthAddress) {
|
|
259
|
+
this.governancePayload = payload;
|
|
239
260
|
}
|
|
240
261
|
|
|
241
262
|
public getSenderAddress(): EthAddress {
|
|
@@ -450,68 +471,106 @@ export class L1Publisher {
|
|
|
450
471
|
calldataGas: getCalldataGasUsage(calldata),
|
|
451
472
|
};
|
|
452
473
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (this.
|
|
474
|
+
public async castVote(slotNumber: bigint, timestamp: bigint, voteType: VoteType) {
|
|
475
|
+
// @todo This function can be optimized by doing some of the computations locally instead of calling the L1 contracts
|
|
476
|
+
if (this.myLastVotes[voteType] >= slotNumber) {
|
|
456
477
|
return false;
|
|
457
478
|
}
|
|
458
479
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
480
|
+
const voteConfig = async (): Promise<
|
|
481
|
+
{ payload: EthAddress; voteContractAddress: EthAddress; logger: Logger } | undefined
|
|
482
|
+
> => {
|
|
483
|
+
if (voteType === VoteType.GOVERNANCE) {
|
|
484
|
+
if (this.governancePayload.equals(EthAddress.ZERO)) {
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
if (!this.governanceProposerAddress) {
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
payload: this.governancePayload,
|
|
492
|
+
voteContractAddress: this.governanceProposerAddress,
|
|
493
|
+
logger: this.governanceLog,
|
|
494
|
+
};
|
|
495
|
+
} else if (voteType === VoteType.SLASHING) {
|
|
496
|
+
if (!this.getSlashPayload) {
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
const slashingProposerAddress = await this.getSlashingProposerAddress();
|
|
500
|
+
if (!slashingProposerAddress) {
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const slashPayload = await this.getSlashPayload(slotNumber);
|
|
505
|
+
|
|
506
|
+
if (!slashPayload) {
|
|
507
|
+
return undefined;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
payload: slashPayload,
|
|
512
|
+
voteContractAddress: slashingProposerAddress,
|
|
513
|
+
logger: this.slashingLog,
|
|
514
|
+
};
|
|
515
|
+
} else {
|
|
516
|
+
throw new Error('Invalid vote type');
|
|
517
|
+
}
|
|
518
|
+
};
|
|
462
519
|
|
|
463
|
-
|
|
520
|
+
const vConfig = await voteConfig();
|
|
521
|
+
|
|
522
|
+
if (!vConfig) {
|
|
464
523
|
return false;
|
|
465
524
|
}
|
|
466
525
|
|
|
467
|
-
|
|
468
|
-
|
|
526
|
+
const { payload, voteContractAddress, logger } = vConfig;
|
|
527
|
+
|
|
528
|
+
const voteContract = getContract({
|
|
529
|
+
address: getAddress(voteContractAddress.toString()),
|
|
530
|
+
abi: EmpireBaseAbi,
|
|
531
|
+
client: this.walletClient,
|
|
532
|
+
});
|
|
469
533
|
|
|
470
534
|
const [proposer, roundNumber] = await Promise.all([
|
|
471
535
|
this.rollupContract.read.getProposerAt([timestamp]),
|
|
472
|
-
|
|
536
|
+
voteContract.read.computeRound([slotNumber]),
|
|
473
537
|
]);
|
|
474
538
|
|
|
475
539
|
if (proposer.toLowerCase() !== this.account.address.toLowerCase()) {
|
|
476
540
|
return false;
|
|
477
541
|
}
|
|
478
542
|
|
|
479
|
-
const [slotForLastVote] = await
|
|
480
|
-
this.rollupContract.address,
|
|
481
|
-
roundNumber,
|
|
482
|
-
]);
|
|
543
|
+
const [slotForLastVote] = await voteContract.read.rounds([this.rollupContract.address, roundNumber]);
|
|
483
544
|
|
|
484
545
|
if (slotForLastVote >= slotNumber) {
|
|
485
546
|
return false;
|
|
486
547
|
}
|
|
487
548
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const cachedMyLastVote = this.myLastVote;
|
|
491
|
-
this.myLastVote = slotNumber;
|
|
492
|
-
|
|
493
|
-
this.governanceLog.verbose(`Casting vote for ${this.payload}`);
|
|
549
|
+
const cachedMyLastVote = this.myLastVotes[voteType];
|
|
550
|
+
this.myLastVotes[voteType] = slotNumber;
|
|
494
551
|
|
|
495
552
|
let txHash;
|
|
496
553
|
try {
|
|
497
|
-
txHash = await
|
|
554
|
+
txHash = await voteContract.write.vote([payload.toString()], {
|
|
555
|
+
account: this.account,
|
|
556
|
+
});
|
|
498
557
|
} catch (err) {
|
|
499
558
|
const msg = prettyLogViemErrorMsg(err);
|
|
500
|
-
|
|
501
|
-
this.
|
|
559
|
+
logger.error(`Failed to vote`, msg);
|
|
560
|
+
this.myLastVotes[voteType] = cachedMyLastVote;
|
|
502
561
|
return false;
|
|
503
562
|
}
|
|
504
563
|
|
|
505
564
|
if (txHash) {
|
|
506
565
|
const receipt = await this.getTransactionReceipt(txHash);
|
|
507
566
|
if (!receipt) {
|
|
508
|
-
|
|
509
|
-
this.
|
|
567
|
+
logger.warn(`Failed to get receipt for tx ${txHash}`);
|
|
568
|
+
this.myLastVotes[voteType] = cachedMyLastVote;
|
|
510
569
|
return false;
|
|
511
570
|
}
|
|
512
571
|
}
|
|
513
572
|
|
|
514
|
-
|
|
573
|
+
logger.info(`Cast vote for ${payload}`);
|
|
515
574
|
return true;
|
|
516
575
|
}
|
|
517
576
|
|
|
@@ -36,8 +36,9 @@ import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec
|
|
|
36
36
|
import { type ValidatorClient } from '@aztec/validator-client';
|
|
37
37
|
|
|
38
38
|
import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
39
|
-
import { type L1Publisher } from '../publisher/l1-publisher.js';
|
|
39
|
+
import { type L1Publisher, VoteType } from '../publisher/l1-publisher.js';
|
|
40
40
|
import { prettyLogViemErrorMsg } from '../publisher/utils.js';
|
|
41
|
+
import { type SlasherClient } from '../slasher/slasher_client.js';
|
|
41
42
|
import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
|
|
42
43
|
import { getDefaultAllowedSetupFunctions } from './allowed.js';
|
|
43
44
|
import { type SequencerConfig } from './config.js';
|
|
@@ -60,7 +61,7 @@ export class SequencerTooSlowError extends Error {
|
|
|
60
61
|
public readonly currentTime: number,
|
|
61
62
|
) {
|
|
62
63
|
super(
|
|
63
|
-
`Too far into slot to transition to ${proposedState}
|
|
64
|
+
`Too far into slot to transition to ${proposedState} (max allowed: ${maxAllowedTime}s, time into slot: ${currentTime}s)`,
|
|
64
65
|
);
|
|
65
66
|
this.name = 'SequencerTooSlowError';
|
|
66
67
|
}
|
|
@@ -106,6 +107,7 @@ export class Sequencer {
|
|
|
106
107
|
private globalsBuilder: GlobalVariableBuilder,
|
|
107
108
|
private p2pClient: P2P,
|
|
108
109
|
private worldState: WorldStateSynchronizer,
|
|
110
|
+
private slasherClient: SlasherClient,
|
|
109
111
|
private blockBuilderFactory: BlockBuilderFactory,
|
|
110
112
|
private l2BlockSource: L2BlockSource,
|
|
111
113
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
@@ -122,6 +124,9 @@ export class Sequencer {
|
|
|
122
124
|
|
|
123
125
|
// Register the block builder with the validator client for re-execution
|
|
124
126
|
this.validatorClient?.registerBlockBuilder(this.buildBlock.bind(this));
|
|
127
|
+
|
|
128
|
+
// Register the slasher on the publisher to fetch slashing payloads
|
|
129
|
+
this.publisher.registerSlashPayloadGetter(this.slasherClient.getSlashPayload.bind(this.slasherClient));
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
get tracer(): Tracer {
|
|
@@ -157,7 +162,7 @@ export class Sequencer {
|
|
|
157
162
|
this.maxBlockSizeInBytes = config.maxBlockSizeInBytes;
|
|
158
163
|
}
|
|
159
164
|
if (config.governanceProposerPayload) {
|
|
160
|
-
this.publisher.
|
|
165
|
+
this.publisher.setGovernancePayload(config.governanceProposerPayload);
|
|
161
166
|
}
|
|
162
167
|
if (config.maxL1TxInclusionTimeIntoSlot !== undefined) {
|
|
163
168
|
this.maxL1TxInclusionTimeIntoSlot = config.maxL1TxInclusionTimeIntoSlot;
|
|
@@ -172,10 +177,10 @@ export class Sequencer {
|
|
|
172
177
|
|
|
173
178
|
private setTimeTable() {
|
|
174
179
|
// How late into the slot can we be to start working
|
|
175
|
-
const initialTime =
|
|
180
|
+
const initialTime = 2;
|
|
176
181
|
|
|
177
182
|
// How long it takes to validate the txs collected and get ready to start building
|
|
178
|
-
const blockPrepareTime =
|
|
183
|
+
const blockPrepareTime = 1;
|
|
179
184
|
|
|
180
185
|
// How long it takes to for attestations to travel across the p2p layer.
|
|
181
186
|
const attestationPropagationTime = 2;
|
|
@@ -245,6 +250,7 @@ export class Sequencer {
|
|
|
245
250
|
this.log.debug(`Stopping sequencer`);
|
|
246
251
|
await this.validatorClient?.stop();
|
|
247
252
|
await this.runningPromise?.stop();
|
|
253
|
+
await this.slasherClient?.stop();
|
|
248
254
|
this.publisher.interrupt();
|
|
249
255
|
this.setState(SequencerState.STOPPED, 0n, true /** force */);
|
|
250
256
|
this.log.info('Stopped sequencer');
|
|
@@ -314,7 +320,8 @@ export class Sequencer {
|
|
|
314
320
|
slot,
|
|
315
321
|
);
|
|
316
322
|
|
|
317
|
-
void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt());
|
|
323
|
+
void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.GOVERNANCE);
|
|
324
|
+
void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.SLASHING);
|
|
318
325
|
|
|
319
326
|
if (!this.shouldProposeBlock(historicalHeader, {})) {
|
|
320
327
|
return;
|
|
@@ -430,7 +437,7 @@ export class Sequencer {
|
|
|
430
437
|
const bufferSeconds = maxAllowedTime - secondsIntoSlot;
|
|
431
438
|
|
|
432
439
|
if (bufferSeconds < 0) {
|
|
433
|
-
this.log.
|
|
440
|
+
this.log.debug(`Too far into slot to transition to ${proposedState}`, { maxAllowedTime, secondsIntoSlot });
|
|
434
441
|
return false;
|
|
435
442
|
}
|
|
436
443
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { L2BlockSource } from '@aztec/circuit-types';
|
|
2
|
+
import { type L1ContractsConfig, type L1ReaderConfig } from '@aztec/ethereum';
|
|
3
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { type AztecKVStore } from '@aztec/kv-store';
|
|
5
|
+
import { type DataStoreConfig } from '@aztec/kv-store/config';
|
|
6
|
+
import { createStore } from '@aztec/kv-store/lmdb';
|
|
7
|
+
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
8
|
+
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
|
|
9
|
+
|
|
10
|
+
import { SlasherClient } from './slasher_client.js';
|
|
11
|
+
import { type SlasherConfig } from './slasher_client.js';
|
|
12
|
+
|
|
13
|
+
export const createSlasherClient = async (
|
|
14
|
+
_config: SlasherConfig & DataStoreConfig & L1ContractsConfig & L1ReaderConfig,
|
|
15
|
+
l2BlockSource: L2BlockSource,
|
|
16
|
+
telemetry: TelemetryClient = new NoopTelemetryClient(),
|
|
17
|
+
deps: { store?: AztecKVStore } = {},
|
|
18
|
+
) => {
|
|
19
|
+
const config = { ..._config };
|
|
20
|
+
const store = deps.store ?? (await createStore('slasher', config, createLogger('slasher:lmdb')));
|
|
21
|
+
return new SlasherClient(config, store, l2BlockSource, telemetry);
|
|
22
|
+
};
|