@aztec/sequencer-client 0.0.1-commit.1142ef1 → 0.0.1-commit.1bea0213
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/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +1 -3
- package/dest/global_variable_builder/global_builder.js +2 -2
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/sequencer/checkpoint_proposal_job.d.ts +6 -4
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +90 -14
- package/dest/sequencer/checkpoint_voter.d.ts +3 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +34 -10
- package/dest/sequencer/index.d.ts +1 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -1
- package/dest/sequencer/sequencer.d.ts +17 -9
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +67 -11
- package/dest/test/mock_checkpoint_builder.d.ts +17 -13
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +28 -8
- package/dest/test/utils.d.ts +8 -8
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +7 -7
- package/package.json +30 -28
- package/src/config.ts +1 -3
- package/src/global_variable_builder/global_builder.ts +2 -2
- package/src/index.ts +1 -6
- package/src/sequencer/checkpoint_proposal_job.ts +127 -24
- package/src/sequencer/checkpoint_voter.ts +32 -7
- package/src/sequencer/index.ts +0 -1
- package/src/sequencer/sequencer.ts +81 -9
- package/src/test/mock_checkpoint_builder.ts +65 -33
- package/src/test/utils.ts +19 -12
- package/dest/sequencer/block_builder.d.ts +0 -26
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -129
- package/src/sequencer/block_builder.ts +0 -216
package/dest/test/utils.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Body } from '@aztec/aztec.js/block';
|
|
2
|
-
import { CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
|
|
3
3
|
import { times } from '@aztec/foundation/collection';
|
|
4
4
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
5
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
6
6
|
import { PublicDataWrite } from '@aztec/stdlib/avm';
|
|
7
|
-
import { CommitteeAttestation,
|
|
7
|
+
import { CommitteeAttestation, L2Block } from '@aztec/stdlib/block';
|
|
8
8
|
import { BlockProposal, CheckpointAttestation, CheckpointProposal, ConsensusPayload } from '@aztec/stdlib/p2p';
|
|
9
9
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
10
10
|
import { makeAppendOnlyTreeSnapshot, mockTxForRollup } from '@aztec/stdlib/testing';
|
|
@@ -21,7 +21,7 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
|
|
|
21
21
|
return tx;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Creates an
|
|
24
|
+
* Creates an L2Block from transactions and global variables
|
|
25
25
|
*/ export async function makeBlock(txs, globalVariables) {
|
|
26
26
|
const processedTxs = await Promise.all(txs.map((tx)=>makeProcessedTxFromPrivateOnlyTx(tx, Fr.ZERO, new PublicDataWrite(Fr.random(), Fr.random()), globalVariables)));
|
|
27
27
|
const body = new Body(processedTxs.map((tx)=>tx.txEffect));
|
|
@@ -29,7 +29,7 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
|
|
|
29
29
|
globalVariables
|
|
30
30
|
});
|
|
31
31
|
const archive = makeAppendOnlyTreeSnapshot(globalVariables.blockNumber + 1);
|
|
32
|
-
return new
|
|
32
|
+
return new L2Block(archive, header, body, CheckpointNumber.fromBlockNumber(globalVariables.blockNumber), IndexWithinCheckpoint(0));
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Mocks the P2P client to return specific pending transactions
|
|
@@ -53,11 +53,11 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
|
|
|
53
53
|
];
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Creates a CheckpointHeader from an
|
|
57
|
-
* Uses mock values for blockHeadersHash, blobsHash and inHash since
|
|
56
|
+
* Creates a CheckpointHeader from an L2Block for testing purposes.
|
|
57
|
+
* Uses mock values for blockHeadersHash, blobsHash and inHash since L2Block doesn't have these fields.
|
|
58
58
|
*/ function createCheckpointHeaderFromBlock(block) {
|
|
59
59
|
const gv = block.header.globalVariables;
|
|
60
|
-
return new CheckpointHeader(block.header.lastArchive.root, Fr.random(), Fr.random(), Fr.random(), gv.slotNumber, gv.timestamp, gv.coinbase, gv.feeRecipient, gv.gasFees, block.header.totalManaUsed);
|
|
60
|
+
return new CheckpointHeader(block.header.lastArchive.root, Fr.random(), Fr.random(), Fr.random(), Fr.random(), gv.slotNumber, gv.timestamp, gv.coinbase, gv.feeRecipient, gv.gasFees, block.header.totalManaUsed);
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
63
|
* Creates a block proposal from a block and signature
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/sequencer-client",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.1bea0213",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -26,43 +26,45 @@
|
|
|
26
26
|
"test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --config jest.integration.config.json"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@aztec/aztec.js": "0.0.1-commit.
|
|
30
|
-
"@aztec/bb-prover": "0.0.1-commit.
|
|
31
|
-
"@aztec/blob-client": "0.0.1-commit.
|
|
32
|
-
"@aztec/blob-lib": "0.0.1-commit.
|
|
33
|
-
"@aztec/constants": "0.0.1-commit.
|
|
34
|
-
"@aztec/epoch-cache": "0.0.1-commit.
|
|
35
|
-
"@aztec/ethereum": "0.0.1-commit.
|
|
36
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
37
|
-
"@aztec/l1-artifacts": "0.0.1-commit.
|
|
38
|
-
"@aztec/merkle-tree": "0.0.1-commit.
|
|
39
|
-
"@aztec/node-keystore": "0.0.1-commit.
|
|
40
|
-
"@aztec/noir-acvm_js": "0.0.1-commit.
|
|
41
|
-
"@aztec/noir-contracts.js": "0.0.1-commit.
|
|
42
|
-
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.
|
|
43
|
-
"@aztec/noir-types": "0.0.1-commit.
|
|
44
|
-
"@aztec/p2p": "0.0.1-commit.
|
|
45
|
-
"@aztec/protocol-contracts": "0.0.1-commit.
|
|
46
|
-
"@aztec/prover-client": "0.0.1-commit.
|
|
47
|
-
"@aztec/simulator": "0.0.1-commit.
|
|
48
|
-
"@aztec/slasher": "0.0.1-commit.
|
|
49
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
50
|
-
"@aztec/telemetry-client": "0.0.1-commit.
|
|
51
|
-
"@aztec/validator-client": "0.0.1-commit.
|
|
52
|
-
"@aztec/
|
|
29
|
+
"@aztec/aztec.js": "0.0.1-commit.1bea0213",
|
|
30
|
+
"@aztec/bb-prover": "0.0.1-commit.1bea0213",
|
|
31
|
+
"@aztec/blob-client": "0.0.1-commit.1bea0213",
|
|
32
|
+
"@aztec/blob-lib": "0.0.1-commit.1bea0213",
|
|
33
|
+
"@aztec/constants": "0.0.1-commit.1bea0213",
|
|
34
|
+
"@aztec/epoch-cache": "0.0.1-commit.1bea0213",
|
|
35
|
+
"@aztec/ethereum": "0.0.1-commit.1bea0213",
|
|
36
|
+
"@aztec/foundation": "0.0.1-commit.1bea0213",
|
|
37
|
+
"@aztec/l1-artifacts": "0.0.1-commit.1bea0213",
|
|
38
|
+
"@aztec/merkle-tree": "0.0.1-commit.1bea0213",
|
|
39
|
+
"@aztec/node-keystore": "0.0.1-commit.1bea0213",
|
|
40
|
+
"@aztec/noir-acvm_js": "0.0.1-commit.1bea0213",
|
|
41
|
+
"@aztec/noir-contracts.js": "0.0.1-commit.1bea0213",
|
|
42
|
+
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.1bea0213",
|
|
43
|
+
"@aztec/noir-types": "0.0.1-commit.1bea0213",
|
|
44
|
+
"@aztec/p2p": "0.0.1-commit.1bea0213",
|
|
45
|
+
"@aztec/protocol-contracts": "0.0.1-commit.1bea0213",
|
|
46
|
+
"@aztec/prover-client": "0.0.1-commit.1bea0213",
|
|
47
|
+
"@aztec/simulator": "0.0.1-commit.1bea0213",
|
|
48
|
+
"@aztec/slasher": "0.0.1-commit.1bea0213",
|
|
49
|
+
"@aztec/stdlib": "0.0.1-commit.1bea0213",
|
|
50
|
+
"@aztec/telemetry-client": "0.0.1-commit.1bea0213",
|
|
51
|
+
"@aztec/validator-client": "0.0.1-commit.1bea0213",
|
|
52
|
+
"@aztec/validator-ha-signer": "0.0.1-commit.1bea0213",
|
|
53
|
+
"@aztec/world-state": "0.0.1-commit.1bea0213",
|
|
53
54
|
"lodash.chunk": "^4.2.0",
|
|
54
55
|
"tslib": "^2.4.0",
|
|
55
56
|
"viem": "npm:@aztec/viem@2.38.2"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
|
-
"@aztec/archiver": "0.0.1-commit.
|
|
59
|
-
"@aztec/kv-store": "0.0.1-commit.
|
|
59
|
+
"@aztec/archiver": "0.0.1-commit.1bea0213",
|
|
60
|
+
"@aztec/kv-store": "0.0.1-commit.1bea0213",
|
|
61
|
+
"@electric-sql/pglite": "^0.3.14",
|
|
60
62
|
"@jest/globals": "^30.0.0",
|
|
61
63
|
"@types/jest": "^30.0.0",
|
|
62
64
|
"@types/lodash.chunk": "^4.2.7",
|
|
63
65
|
"@types/lodash.pick": "^4.4.7",
|
|
64
66
|
"@types/node": "^22.15.17",
|
|
65
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
67
|
+
"@typescript/native-preview": "7.0.0-dev.20260113.1",
|
|
66
68
|
"concurrently": "^7.6.0",
|
|
67
69
|
"eslint": "^9.26.0",
|
|
68
70
|
"express": "^4.21.2",
|
package/src/config.ts
CHANGED
|
@@ -50,8 +50,7 @@ export const DefaultSequencerConfig: ResolvedSequencerConfig = {
|
|
|
50
50
|
injectFakeAttestation: false,
|
|
51
51
|
fishermanMode: false,
|
|
52
52
|
shuffleAttestationOrdering: false,
|
|
53
|
-
|
|
54
|
-
skipPushProposedBlocksToArchiver: true,
|
|
53
|
+
skipPushProposedBlocksToArchiver: false,
|
|
55
54
|
};
|
|
56
55
|
|
|
57
56
|
/**
|
|
@@ -204,7 +203,6 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
|
204
203
|
description: 'Have sequencer build and publish an empty checkpoint if there are no txs',
|
|
205
204
|
...booleanConfigHelper(DefaultSequencerConfig.buildCheckpointIfEmpty),
|
|
206
205
|
},
|
|
207
|
-
// TODO(palla/mbps): Change default to false once block sync is stable
|
|
208
206
|
skipPushProposedBlocksToArchiver: {
|
|
209
207
|
description: 'Skip pushing proposed blocks to archiver (default: true)',
|
|
210
208
|
...booleanConfigHelper(DefaultSequencerConfig.skipPushProposedBlocksToArchiver),
|
|
@@ -69,9 +69,9 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
69
69
|
// we need to fetch the last block written, and estimate the earliest timestamp for the next block.
|
|
70
70
|
// The timestamp of that last block will act as a lower bound for the next block.
|
|
71
71
|
|
|
72
|
-
const
|
|
72
|
+
const lastCheckpoint = await this.rollupContract.getPendingCheckpoint();
|
|
73
73
|
const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
|
|
74
|
-
SlotNumber.fromBigInt(BigInt(
|
|
74
|
+
SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
|
|
75
75
|
);
|
|
76
76
|
const nextEthTimestamp = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration));
|
|
77
77
|
const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
export * from './client/index.js';
|
|
2
2
|
export * from './config.js';
|
|
3
3
|
export * from './publisher/index.js';
|
|
4
|
-
export {
|
|
5
|
-
FullNodeBlockBuilder as BlockBuilder,
|
|
6
|
-
Sequencer,
|
|
7
|
-
SequencerState,
|
|
8
|
-
type SequencerEvents,
|
|
9
|
-
} from './sequencer/index.js';
|
|
4
|
+
export { Sequencer, SequencerState, type SequencerEvents } from './sequencer/index.js';
|
|
10
5
|
|
|
11
6
|
// Used by the node to simulate public parts of transactions. Should these be moved to a shared library?
|
|
12
7
|
// ISSUE(#9832)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NUM_CHECKPOINT_END_MARKER_FIELDS, getNumBlockEndBlobFields } from '@aztec/blob-lib/encoding';
|
|
1
2
|
import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB } from '@aztec/constants';
|
|
2
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
3
4
|
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
@@ -15,8 +16,9 @@ import type { SlasherClientInterface } from '@aztec/slasher';
|
|
|
15
16
|
import {
|
|
16
17
|
CommitteeAttestation,
|
|
17
18
|
CommitteeAttestationsAndSigners,
|
|
18
|
-
|
|
19
|
+
L2Block,
|
|
19
20
|
type L2BlockSink,
|
|
21
|
+
type L2BlockSource,
|
|
20
22
|
MaliciousCommitteeAttestationsAndSigners,
|
|
21
23
|
} from '@aztec/stdlib/block';
|
|
22
24
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
@@ -35,6 +37,7 @@ import { type FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
|
35
37
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
36
38
|
import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
37
39
|
import { CheckpointBuilder, type FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
|
|
40
|
+
import { DutyAlreadySignedError, SlashingProtectionError } from '@aztec/validator-ha-signer/errors';
|
|
38
41
|
|
|
39
42
|
import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
40
43
|
import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
@@ -57,6 +60,7 @@ const TXS_POLLING_MS = 500;
|
|
|
57
60
|
*/
|
|
58
61
|
export class CheckpointProposalJob implements Traceable {
|
|
59
62
|
constructor(
|
|
63
|
+
private readonly epoch: EpochNumber,
|
|
60
64
|
private readonly slot: SlotNumber,
|
|
61
65
|
private readonly checkpointNumber: CheckpointNumber,
|
|
62
66
|
private readonly syncedToBlockNumber: BlockNumber,
|
|
@@ -70,6 +74,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
70
74
|
private readonly p2pClient: P2P,
|
|
71
75
|
private readonly worldState: WorldStateSynchronizer,
|
|
72
76
|
private readonly l1ToL2MessageSource: L1ToL2MessageSource,
|
|
77
|
+
private readonly l2BlockSource: L2BlockSource,
|
|
73
78
|
private readonly checkpointsBuilder: FullNodeCheckpointsBuilder,
|
|
74
79
|
private readonly blockSink: L2BlockSink,
|
|
75
80
|
private readonly l1Constants: SequencerRollupConstants,
|
|
@@ -169,6 +174,12 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
169
174
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(this.checkpointNumber);
|
|
170
175
|
const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
171
176
|
|
|
177
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
178
|
+
const previousCheckpoints = (await this.l2BlockSource.getCheckpointsForEpoch(this.epoch)).filter(
|
|
179
|
+
c => c.number < this.checkpointNumber,
|
|
180
|
+
);
|
|
181
|
+
const previousCheckpointOutHashes = previousCheckpoints.map(c => c.getCheckpointOutHash());
|
|
182
|
+
|
|
172
183
|
// Create a long-lived forked world state for the checkpoint builder
|
|
173
184
|
using fork = await this.worldState.fork(this.syncedToBlockNumber, { closeDelayMs: 12_000 });
|
|
174
185
|
|
|
@@ -177,6 +188,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
177
188
|
this.checkpointNumber,
|
|
178
189
|
checkpointGlobalVariables,
|
|
179
190
|
l1ToL2Messages,
|
|
191
|
+
previousCheckpointOutHashes,
|
|
180
192
|
fork,
|
|
181
193
|
);
|
|
182
194
|
|
|
@@ -191,13 +203,40 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
191
203
|
broadcastInvalidCheckpointProposal: this.config.broadcastInvalidBlockProposal,
|
|
192
204
|
};
|
|
193
205
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
let blocksInCheckpoint: L2Block[] = [];
|
|
207
|
+
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
// Main loop: build blocks for the checkpoint
|
|
211
|
+
const result = await this.buildBlocksForCheckpoint(
|
|
212
|
+
checkpointBuilder,
|
|
213
|
+
checkpointGlobalVariables.timestamp,
|
|
214
|
+
inHash,
|
|
215
|
+
blockProposalOptions,
|
|
216
|
+
);
|
|
217
|
+
blocksInCheckpoint = result.blocksInCheckpoint;
|
|
218
|
+
blockPendingBroadcast = result.blockPendingBroadcast;
|
|
219
|
+
} catch (err) {
|
|
220
|
+
// These errors are expected in HA mode, so we yield and let another HA node handle the slot
|
|
221
|
+
// The only distinction between the 2 errors is SlashingProtectionError throws when the payload is different,
|
|
222
|
+
// which is normal for block building (may have picked different txs)
|
|
223
|
+
if (err instanceof DutyAlreadySignedError) {
|
|
224
|
+
this.log.info(`Checkpoint proposal for slot ${this.slot} already signed by another HA node, yielding`, {
|
|
225
|
+
slot: this.slot,
|
|
226
|
+
signedByNode: err.signedByNode,
|
|
227
|
+
});
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
if (err instanceof SlashingProtectionError) {
|
|
231
|
+
this.log.info(`Checkpoint proposal for slot ${this.slot} blocked by slashing protection, yielding`, {
|
|
232
|
+
slot: this.slot,
|
|
233
|
+
existingMessageHash: err.existingMessageHash,
|
|
234
|
+
attemptedMessageHash: err.attemptedMessageHash,
|
|
235
|
+
});
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
throw err;
|
|
239
|
+
}
|
|
201
240
|
|
|
202
241
|
if (blocksInCheckpoint.length === 0) {
|
|
203
242
|
this.log.warn(`No blocks were built for slot ${this.slot}`, { slot: this.slot });
|
|
@@ -252,7 +291,34 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
252
291
|
|
|
253
292
|
// Proposer must sign over the attestations before pushing them to L1
|
|
254
293
|
const signer = this.proposer ?? this.publisher.getSenderAddress();
|
|
255
|
-
|
|
294
|
+
let attestationsSignature: Signature;
|
|
295
|
+
try {
|
|
296
|
+
attestationsSignature = await this.validatorClient.signAttestationsAndSigners(
|
|
297
|
+
attestations,
|
|
298
|
+
signer,
|
|
299
|
+
this.slot,
|
|
300
|
+
this.checkpointNumber,
|
|
301
|
+
);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
// We shouldn't really get here since we yield to another HA node
|
|
304
|
+
// as soon as we see these errors when creating block proposals.
|
|
305
|
+
if (err instanceof DutyAlreadySignedError) {
|
|
306
|
+
this.log.info(`Attestations signature for slot ${this.slot} already signed by another HA node, yielding`, {
|
|
307
|
+
slot: this.slot,
|
|
308
|
+
signedByNode: err.signedByNode,
|
|
309
|
+
});
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
if (err instanceof SlashingProtectionError) {
|
|
313
|
+
this.log.info(`Attestations signature for slot ${this.slot} blocked by slashing protection, yielding`, {
|
|
314
|
+
slot: this.slot,
|
|
315
|
+
existingMessageHash: err.existingMessageHash,
|
|
316
|
+
attemptedMessageHash: err.attemptedMessageHash,
|
|
317
|
+
});
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
throw err;
|
|
321
|
+
}
|
|
256
322
|
|
|
257
323
|
// Enqueue publishing the checkpoint to L1
|
|
258
324
|
this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.slot);
|
|
@@ -266,6 +332,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
266
332
|
|
|
267
333
|
return checkpoint;
|
|
268
334
|
} catch (err) {
|
|
335
|
+
if (err && (err instanceof DutyAlreadySignedError || err instanceof SlashingProtectionError)) {
|
|
336
|
+
// swallow this error. It's already been logged by a function deeper in the stack
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
|
|
269
340
|
this.log.error(`Error building checkpoint at slot ${this.slot}`, err);
|
|
270
341
|
return undefined;
|
|
271
342
|
}
|
|
@@ -281,15 +352,18 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
281
352
|
inHash: Fr,
|
|
282
353
|
blockProposalOptions: BlockProposalOptions,
|
|
283
354
|
): Promise<{
|
|
284
|
-
blocksInCheckpoint:
|
|
285
|
-
blockPendingBroadcast: { block:
|
|
355
|
+
blocksInCheckpoint: L2Block[];
|
|
356
|
+
blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined;
|
|
286
357
|
}> {
|
|
287
|
-
const blocksInCheckpoint:
|
|
358
|
+
const blocksInCheckpoint: L2Block[] = [];
|
|
288
359
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
289
360
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
290
361
|
|
|
362
|
+
// Remaining blob fields available for blocks (checkpoint end marker already subtracted)
|
|
363
|
+
let remainingBlobFields = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
|
|
364
|
+
|
|
291
365
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
292
|
-
let blockPendingBroadcast: { block:
|
|
366
|
+
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
293
367
|
|
|
294
368
|
while (true) {
|
|
295
369
|
const blocksBuilt = blocksInCheckpoint.length;
|
|
@@ -320,6 +394,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
320
394
|
blockNumber,
|
|
321
395
|
indexWithinCheckpoint,
|
|
322
396
|
txHashesAlreadyIncluded,
|
|
397
|
+
remainingBlobFields,
|
|
323
398
|
});
|
|
324
399
|
|
|
325
400
|
if (!buildResult && timingInfo.isLastBlock) {
|
|
@@ -344,13 +419,21 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
344
419
|
break;
|
|
345
420
|
}
|
|
346
421
|
|
|
347
|
-
const { block, usedTxs } = buildResult;
|
|
422
|
+
const { block, usedTxs, remainingBlobFields: newRemainingBlobFields } = buildResult;
|
|
348
423
|
blocksInCheckpoint.push(block);
|
|
349
424
|
|
|
425
|
+
// Update remaining blob fields for the next block
|
|
426
|
+
remainingBlobFields = newRemainingBlobFields;
|
|
427
|
+
|
|
350
428
|
// Sync the proposed block to the archiver to make it available
|
|
351
429
|
// Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
|
|
352
430
|
// Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
|
|
353
|
-
|
|
431
|
+
// Fire and forget - don't block the critical path, but log errors
|
|
432
|
+
this.syncProposedBlockToArchiver(block).catch(err => {
|
|
433
|
+
this.log.error(`Failed to sync proposed block ${block.number} to archiver`, { blockNumber: block.number, err });
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
usedTxs.forEach(tx => txHashesAlreadyIncluded.add(tx.txHash.toString()));
|
|
354
437
|
|
|
355
438
|
// If this is the last block, exit the loop now so we start collecting attestations
|
|
356
439
|
if (timingInfo.isLastBlock) {
|
|
@@ -409,10 +492,18 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
409
492
|
indexWithinCheckpoint: number;
|
|
410
493
|
buildDeadline: Date | undefined;
|
|
411
494
|
txHashesAlreadyIncluded: Set<string>;
|
|
495
|
+
remainingBlobFields: number;
|
|
412
496
|
},
|
|
413
|
-
): Promise<{ block:
|
|
414
|
-
const {
|
|
415
|
-
|
|
497
|
+
): Promise<{ block: L2Block; usedTxs: Tx[]; remainingBlobFields: number } | { error: Error } | undefined> {
|
|
498
|
+
const {
|
|
499
|
+
blockTimestamp,
|
|
500
|
+
forceCreate,
|
|
501
|
+
blockNumber,
|
|
502
|
+
indexWithinCheckpoint,
|
|
503
|
+
buildDeadline,
|
|
504
|
+
txHashesAlreadyIncluded,
|
|
505
|
+
remainingBlobFields,
|
|
506
|
+
} = opts;
|
|
416
507
|
|
|
417
508
|
this.log.verbose(
|
|
418
509
|
`Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.slot}`,
|
|
@@ -445,18 +536,31 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
445
536
|
{ slot: this.slot, blockNumber, indexWithinCheckpoint },
|
|
446
537
|
);
|
|
447
538
|
this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
|
|
539
|
+
|
|
540
|
+
// Calculate blob fields limit for txs (remaining capacity - this block's end overhead)
|
|
541
|
+
const blockEndOverhead = getNumBlockEndBlobFields(indexWithinCheckpoint === 0);
|
|
542
|
+
const maxBlobFieldsForTxs = remainingBlobFields - blockEndOverhead;
|
|
543
|
+
|
|
448
544
|
const blockBuilderOptions: PublicProcessorLimits = {
|
|
449
545
|
maxTransactions: this.config.maxTxsPerBlock,
|
|
450
546
|
maxBlockSize: this.config.maxBlockSizeInBytes,
|
|
451
547
|
maxBlockGas: new Gas(this.config.maxDABlockGas, this.config.maxL2BlockGas),
|
|
452
|
-
maxBlobFields:
|
|
548
|
+
maxBlobFields: maxBlobFieldsForTxs,
|
|
453
549
|
deadline: buildDeadline,
|
|
454
550
|
};
|
|
455
551
|
|
|
456
552
|
// Actually build the block by executing txs
|
|
457
553
|
const workTimer = new Timer();
|
|
458
|
-
const {
|
|
459
|
-
|
|
554
|
+
const {
|
|
555
|
+
publicGas,
|
|
556
|
+
block,
|
|
557
|
+
publicProcessorDuration,
|
|
558
|
+
numTxs,
|
|
559
|
+
blockBuildingTimer,
|
|
560
|
+
usedTxs,
|
|
561
|
+
failedTxs,
|
|
562
|
+
usedTxBlobFields,
|
|
563
|
+
} = await checkpointBuilder.buildBlock(pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
|
|
460
564
|
const blockBuildDuration = workTimer.ms();
|
|
461
565
|
|
|
462
566
|
// If any txs failed during execution, drop them from the mempool so we don't pick them up again
|
|
@@ -500,7 +604,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
500
604
|
this.eventEmitter.emit('block-proposed', { blockNumber: block.number, slot: this.slot });
|
|
501
605
|
this.metrics.recordBuiltBlock(blockBuildDuration, publicGas.l2Gas);
|
|
502
606
|
|
|
503
|
-
return { block, usedTxs };
|
|
607
|
+
return { block, usedTxs, remainingBlobFields: maxBlobFieldsForTxs - usedTxBlobFields };
|
|
504
608
|
} catch (err: any) {
|
|
505
609
|
this.eventEmitter.emit('block-build-failed', { reason: err.message, slot: this.slot });
|
|
506
610
|
this.log.error(`Error building block`, err, { blockNumber, slot: this.slot });
|
|
@@ -678,8 +782,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
678
782
|
* Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
|
|
679
783
|
* would never receive its own block without this explicit sync.
|
|
680
784
|
*/
|
|
681
|
-
private async syncProposedBlockToArchiver(block:
|
|
682
|
-
// TODO(palla/mbps): Change default to false once block sync is stable.
|
|
785
|
+
private async syncProposedBlockToArchiver(block: L2Block): Promise<void> {
|
|
683
786
|
if (this.config.skipPushProposedBlocksToArchiver !== false) {
|
|
684
787
|
this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
|
|
685
788
|
blockNumber: block.number,
|
|
@@ -5,6 +5,8 @@ import type { SlasherClientInterface } from '@aztec/slasher';
|
|
|
5
5
|
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
6
6
|
import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
|
|
7
7
|
import type { ValidatorClient } from '@aztec/validator-client';
|
|
8
|
+
import { DutyAlreadySignedError } from '@aztec/validator-ha-signer/errors';
|
|
9
|
+
import { DutyType, type SigningContext } from '@aztec/validator-ha-signer/types';
|
|
8
10
|
|
|
9
11
|
import type { TypedDataDefinition } from 'viem';
|
|
10
12
|
|
|
@@ -17,7 +19,8 @@ import type { SequencerRollupConstants } from './types.js';
|
|
|
17
19
|
*/
|
|
18
20
|
export class CheckpointVoter {
|
|
19
21
|
private slotTimestamp: bigint;
|
|
20
|
-
private
|
|
22
|
+
private governanceSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
|
|
23
|
+
private slashingSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
|
|
21
24
|
|
|
22
25
|
constructor(
|
|
23
26
|
private readonly slot: SlotNumber,
|
|
@@ -31,8 +34,16 @@ export class CheckpointVoter {
|
|
|
31
34
|
private readonly log: Logger,
|
|
32
35
|
) {
|
|
33
36
|
this.slotTimestamp = getTimestampForSlot(this.slot, this.l1Constants);
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
|
|
38
|
+
// Create separate signers with appropriate duty contexts for governance and slashing votes
|
|
39
|
+
// These use HA protection to ensure only one node signs per slot/duty
|
|
40
|
+
const governanceContext: SigningContext = { slot: this.slot, dutyType: DutyType.GOVERNANCE_VOTE };
|
|
41
|
+
this.governanceSigner = (msg: TypedDataDefinition) =>
|
|
42
|
+
this.validatorClient.signWithAddress(this.attestorAddress, msg, governanceContext).then(s => s.toString());
|
|
43
|
+
|
|
44
|
+
const slashingContext: SigningContext = { slot: this.slot, dutyType: DutyType.SLASHING_VOTE };
|
|
45
|
+
this.slashingSigner = (msg: TypedDataDefinition) =>
|
|
46
|
+
this.validatorClient.signWithAddress(this.attestorAddress, msg, slashingContext).then(s => s.toString());
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
/**
|
|
@@ -68,10 +79,17 @@ export class CheckpointVoter {
|
|
|
68
79
|
this.slot,
|
|
69
80
|
this.slotTimestamp,
|
|
70
81
|
this.attestorAddress,
|
|
71
|
-
this.
|
|
82
|
+
this.governanceSigner,
|
|
72
83
|
);
|
|
73
84
|
} catch (err) {
|
|
74
|
-
|
|
85
|
+
if (err instanceof DutyAlreadySignedError) {
|
|
86
|
+
this.log.info(`Governance vote already signed by another node`, {
|
|
87
|
+
slot: this.slot,
|
|
88
|
+
signedByNode: err.signedByNode,
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
this.log.error(`Error enqueueing governance vote`, err);
|
|
92
|
+
}
|
|
75
93
|
return false;
|
|
76
94
|
}
|
|
77
95
|
}
|
|
@@ -95,10 +113,17 @@ export class CheckpointVoter {
|
|
|
95
113
|
this.slot,
|
|
96
114
|
this.slotTimestamp,
|
|
97
115
|
this.attestorAddress,
|
|
98
|
-
this.
|
|
116
|
+
this.slashingSigner,
|
|
99
117
|
);
|
|
100
118
|
} catch (err) {
|
|
101
|
-
|
|
119
|
+
if (err instanceof DutyAlreadySignedError) {
|
|
120
|
+
this.log.info(`Slashing vote already signed by another node`, {
|
|
121
|
+
slot: this.slot,
|
|
122
|
+
signedByNode: err.signedByNode,
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
this.log.error(`Error enqueueing slashing vote`, err);
|
|
126
|
+
}
|
|
102
127
|
return false;
|
|
103
128
|
}
|
|
104
129
|
}
|
package/src/sequencer/index.ts
CHANGED