@aztec/validator-client 3.0.0-nightly.20250921 → 3.0.0-nightly.20250923
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/validator.d.ts +4 -2
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +45 -31
- package/package.json +11 -11
- package/src/validator.ts +62 -41
package/dest/validator.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
|
12
12
|
import type { CommitteeAttestationsAndSigners, L2BlockSource } from '@aztec/stdlib/block';
|
|
13
13
|
import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server';
|
|
14
14
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
15
|
-
import type
|
|
15
|
+
import { type BlockAttestation, type BlockProposal, type BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
16
16
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
17
17
|
import { type StateReference, type Tx } from '@aztec/stdlib/tx';
|
|
18
18
|
import { type TelemetryClient, type Tracer } from '@aztec/telemetry-client';
|
|
@@ -37,6 +37,7 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
37
37
|
readonly tracer: Tracer;
|
|
38
38
|
private validationService;
|
|
39
39
|
private metrics;
|
|
40
|
+
private hasRegisteredHandlers;
|
|
40
41
|
private previousProposal?;
|
|
41
42
|
private lastEpochForCommitteeUpdateLoop;
|
|
42
43
|
private epochCacheUpdateLoop;
|
|
@@ -54,7 +55,8 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
54
55
|
updateConfig(config: Partial<ValidatorClientFullConfig>): void;
|
|
55
56
|
start(): Promise<void>;
|
|
56
57
|
stop(): Promise<void>;
|
|
57
|
-
|
|
58
|
+
/** Register handlers on the p2p client */
|
|
59
|
+
registerHandlers(): Promise<void>;
|
|
58
60
|
attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined>;
|
|
59
61
|
private getReexecutionDeadline;
|
|
60
62
|
/**
|
package/dest/validator.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAC9C,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAIlE,OAAO,EAAE,YAAY,EAAS,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAiD,UAAU,EAAE,MAAM,YAAY,CAAC;AAGvF,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,+BAA+B,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE1F,OAAO,KAAK,EAAE,qBAAqB,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AACnH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAC9C,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAIlE,OAAO,EAAE,YAAY,EAAS,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAiD,UAAU,EAAE,MAAM,YAAY,CAAC;AAGvF,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,+BAA+B,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE1F,OAAO,KAAK,EAAE,qBAAqB,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AACnH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAE1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAmB,KAAK,cAAc,EAAE,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAQjF,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,MAAM,EAAsB,MAAM,yBAAyB,CAAC;AAGhG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAEhD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;oCAUrB,UAAU,cAAc;AAH9E;;GAEG;AACH,qBAAa,eAAgB,SAAQ,oBAA2C,YAAW,SAAS,EAAE,OAAO;IAmBzG,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IAEpB,OAAO,CAAC,GAAG;IA5Bb,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,OAAO,CAAmB;IAGlC,OAAO,CAAC,qBAAqB,CAAS;IAGtC,OAAO,CAAC,gBAAgB,CAAC,CAAgB;IAEzC,OAAO,CAAC,+BAA+B,CAAqB;IAC5D,OAAO,CAAC,oBAAoB,CAAiB;IAE7C,OAAO,CAAC,sBAAsB,CAAyB;IAEvD,OAAO,CAAC,wBAAwB,CAA0B;IAE1D,SAAS,aACC,YAAY,EAAE,qBAAqB,EACnC,QAAQ,EAAE,mBAAmB,EAC7B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,GAAG,EACd,WAAW,EAAE,aAAa,EAC1B,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,yBAAyB,EACjC,YAAY,GAAE,YAAiC,EACvD,SAAS,GAAE,eAAsC,EACzC,GAAG,SAA4B;WAiB3B,6BAA6B,CAAC,eAAe,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM;YAyB/E,0BAA0B;IA2BxC,MAAM,CAAC,GAAG,CACR,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC,aAAa,EAAE,qCAAqC,CAAC,EAC1F,YAAY,EAAE,qBAAqB,EACnC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,GAAG,EACd,WAAW,EAAE,aAAa,EAC1B,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,eAAe,EAChC,YAAY,GAAE,YAAiC,EAC/C,SAAS,GAAE,eAAsC;IAkB5C,qBAAqB;IAMrB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,mBAAmB;IAI1D,sBAAsB,CAAC,QAAQ,EAAE,UAAU,GAAG,UAAU;IAIxD,0BAA0B,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY;IAI9D,SAAS,IAAI,yBAAyB;IAItC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,yBAAyB,CAAC;IAIjD,KAAK;IAwBL,IAAI;IAIjB,0CAA0C;IAC7B,gBAAgB;IAevB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,GAAG,SAAS,CAAC;IA4IhH,OAAO,CAAC,sBAAsB;IAS9B;;;OAGG;IACG,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgEpG,OAAO,CAAC,iBAAiB;IAqBnB,mBAAmB,CACvB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,EAAE,EACX,cAAc,EAAE,cAAc,EAC9B,GAAG,EAAE,EAAE,EAAE,EACT,eAAe,EAAE,UAAU,GAAG,SAAS,EACvC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAmB/B,sBAAsB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D,0BAA0B,CAC9B,sBAAsB,EAAE,+BAA+B,EACvD,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,SAAS,CAAC;IAIf,sBAAsB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAO5E,mBAAmB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;YA+CnG,kBAAkB;YAMlB,iBAAiB;CAsBhC"}
|
package/dest/validator.js
CHANGED
|
@@ -10,6 +10,7 @@ import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
|
10
10
|
import { computeInHashFromL1ToL2Messages } from '@aztec/prover-client/helpers';
|
|
11
11
|
import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
|
|
12
12
|
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
13
|
+
import { ConsensusPayload } from '@aztec/stdlib/p2p';
|
|
13
14
|
import { GlobalVariables } from '@aztec/stdlib/tx';
|
|
14
15
|
import { AttestationTimeoutError, ReExFailedTxsError, ReExStateMismatchError, ReExTimeoutError, TransactionsNotAvailableError } from '@aztec/stdlib/validators';
|
|
15
16
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
@@ -36,6 +37,8 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
36
37
|
tracer;
|
|
37
38
|
validationService;
|
|
38
39
|
metrics;
|
|
40
|
+
// Whether it has already registered handlers on the p2p client
|
|
41
|
+
hasRegisteredHandlers;
|
|
39
42
|
// Used to check if we are sending the same proposal twice
|
|
40
43
|
previousProposal;
|
|
41
44
|
lastEpochForCommitteeUpdateLoop;
|
|
@@ -43,7 +46,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
43
46
|
blockProposalValidator;
|
|
44
47
|
proposersOfInvalidBlocks;
|
|
45
48
|
constructor(blockBuilder, keyStore, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, config, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
46
|
-
super(), this.blockBuilder = blockBuilder, this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockSource = blockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.txProvider = txProvider, this.config = config, this.dateProvider = dateProvider, this.log = log, this.proposersOfInvalidBlocks = new Set();
|
|
49
|
+
super(), this.blockBuilder = blockBuilder, this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockSource = blockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.txProvider = txProvider, this.config = config, this.dateProvider = dateProvider, this.log = log, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
|
|
47
50
|
this.tracer = telemetry.getTracer('Validator');
|
|
48
51
|
this.metrics = new ValidatorMetrics(telemetry);
|
|
49
52
|
this.validationService = new ValidationService(keyStore);
|
|
@@ -126,9 +129,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
126
129
|
this.log.warn(`Validator client already started`);
|
|
127
130
|
return;
|
|
128
131
|
}
|
|
129
|
-
this.
|
|
130
|
-
// Sync the committee from the smart contract
|
|
131
|
-
// https://github.com/AztecProtocol/aztec-packages/issues/7962
|
|
132
|
+
await this.registerHandlers();
|
|
132
133
|
const myAddresses = this.getValidatorAddresses();
|
|
133
134
|
const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
|
|
134
135
|
if (inCommittee.length > 0) {
|
|
@@ -137,16 +138,20 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
137
138
|
this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
|
|
138
139
|
}
|
|
139
140
|
this.epochCacheUpdateLoop.start();
|
|
140
|
-
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
141
|
-
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
142
141
|
return Promise.resolve();
|
|
143
142
|
}
|
|
144
143
|
async stop() {
|
|
145
144
|
await this.epochCacheUpdateLoop.stop();
|
|
146
145
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
/** Register handlers on the p2p client */ async registerHandlers() {
|
|
147
|
+
if (!this.hasRegisteredHandlers) {
|
|
148
|
+
this.hasRegisteredHandlers = true;
|
|
149
|
+
const handler = (block, proposalSender)=>this.attestToProposal(block, proposalSender);
|
|
150
|
+
this.p2pClient.registerBlockProposalHandler(handler);
|
|
151
|
+
const myAddresses = this.getValidatorAddresses();
|
|
152
|
+
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
153
|
+
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
154
|
+
}
|
|
150
155
|
}
|
|
151
156
|
async attestToProposal(proposal, proposalSender) {
|
|
152
157
|
const slotNumber = proposal.slotNumber.toBigInt();
|
|
@@ -155,19 +160,21 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
155
160
|
// Check that I have any address in current committee before attesting
|
|
156
161
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
157
162
|
const partOfCommittee = inCommittee.length > 0;
|
|
163
|
+
const incFailedAttestation = partOfCommittee ? (reason)=>this.metrics.incFailedAttestations(1, reason) : ()=>{};
|
|
158
164
|
const proposalInfo = {
|
|
159
165
|
...proposal.toBlockInfo(),
|
|
160
166
|
proposer: proposer.toString()
|
|
161
167
|
};
|
|
162
168
|
this.log.info(`Received proposal for slot ${slotNumber}`, {
|
|
163
169
|
...proposalInfo,
|
|
164
|
-
txHashes: proposal.txHashes.map((
|
|
170
|
+
txHashes: proposal.txHashes.map((t)=>t.toString())
|
|
165
171
|
});
|
|
166
172
|
// Collect txs from the proposal. Note that we do this before checking if we have an address in the
|
|
167
173
|
// current committee, since we want to collect txs anyway to facilitate propagation.
|
|
174
|
+
const config = this.blockBuilder.getConfig();
|
|
168
175
|
const { txs, missingTxs } = await this.txProvider.getTxsForBlockProposal(proposal, {
|
|
169
176
|
pinnedPeer: proposalSender,
|
|
170
|
-
deadline: this.getReexecutionDeadline(proposal,
|
|
177
|
+
deadline: this.getReexecutionDeadline(proposal, config)
|
|
171
178
|
});
|
|
172
179
|
// Check that I have any address in current committee before attesting
|
|
173
180
|
if (!partOfCommittee) {
|
|
@@ -179,9 +186,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
179
186
|
const invalidProposal = await this.blockProposalValidator.validate(proposal);
|
|
180
187
|
if (invalidProposal) {
|
|
181
188
|
this.log.warn(`Proposal is not valid, skipping attestation`, proposalInfo);
|
|
182
|
-
|
|
183
|
-
this.metrics.incFailedAttestations(1, 'invalid_proposal');
|
|
184
|
-
}
|
|
189
|
+
incFailedAttestation('invalid_proposal');
|
|
185
190
|
return undefined;
|
|
186
191
|
}
|
|
187
192
|
// Check that the parent proposal is a block we know, otherwise reexecution would fail.
|
|
@@ -190,7 +195,6 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
190
195
|
// would not be rebroadcasted. But it also means that nodes that have not fully synced would
|
|
191
196
|
// not rebroadcast the proposal.
|
|
192
197
|
if (blockNumber > INITIAL_L2_BLOCK_NUM) {
|
|
193
|
-
const config = this.blockBuilder.getConfig();
|
|
194
198
|
const deadline = this.getReexecutionDeadline(proposal, config);
|
|
195
199
|
const currentTime = this.dateProvider.now();
|
|
196
200
|
const timeoutDurationMs = deadline.getTime() - currentTime;
|
|
@@ -204,9 +208,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
204
208
|
}, 'Force Archiver Sync', timeoutDurationMs / 1000, 0.5);
|
|
205
209
|
if (parentBlock === undefined) {
|
|
206
210
|
this.log.warn(`Parent block for ${blockNumber} not found, skipping attestation`, proposalInfo);
|
|
207
|
-
|
|
208
|
-
this.metrics.incFailedAttestations(1, 'parent_block_not_found');
|
|
209
|
-
}
|
|
211
|
+
incFailedAttestation('parent_block_not_found');
|
|
210
212
|
return undefined;
|
|
211
213
|
}
|
|
212
214
|
if (!proposal.payload.header.lastArchiveRoot.equals(parentBlock.archive.root)) {
|
|
@@ -215,9 +217,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
215
217
|
parentBlockArchiveRoot: parentBlock.archive.root.toString(),
|
|
216
218
|
...proposalInfo
|
|
217
219
|
});
|
|
218
|
-
|
|
219
|
-
this.metrics.incFailedAttestations(1, 'parent_block_does_not_match');
|
|
220
|
-
}
|
|
220
|
+
incFailedAttestation('parent_block_does_not_match');
|
|
221
221
|
return undefined;
|
|
222
222
|
}
|
|
223
223
|
}
|
|
@@ -232,9 +232,15 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
232
232
|
computedInHash: computedInHash.toString(),
|
|
233
233
|
...proposalInfo
|
|
234
234
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
incFailedAttestation('in_hash_mismatch');
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
// Check that this block number does not exist already, otherwise the proposer could signal
|
|
239
|
+
// an arbitrary block number in the past, though this would most likely fail on the rollup contract.
|
|
240
|
+
const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
|
|
241
|
+
if (existingBlock) {
|
|
242
|
+
this.log.warn(`Block number ${blockNumber} already exists, skipping attestation`, proposalInfo);
|
|
243
|
+
incFailedAttestation('block_number_already_exists');
|
|
238
244
|
return undefined;
|
|
239
245
|
}
|
|
240
246
|
// Check that all of the transactions in the proposal are available in the tx pool before attesting
|
|
@@ -243,9 +249,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
243
249
|
...proposalInfo,
|
|
244
250
|
missingTxs
|
|
245
251
|
});
|
|
246
|
-
|
|
247
|
-
this.metrics.incFailedAttestations(1, 'TransactionsNotAvailableError');
|
|
248
|
-
}
|
|
252
|
+
incFailedAttestation('TransactionsNotAvailableError');
|
|
249
253
|
return undefined;
|
|
250
254
|
}
|
|
251
255
|
// Try re-executing the transactions in the proposal
|
|
@@ -256,7 +260,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
256
260
|
await this.reExecuteTransactions(proposal, txs, l1ToL2Messages);
|
|
257
261
|
}
|
|
258
262
|
} catch (error) {
|
|
259
|
-
|
|
263
|
+
incFailedAttestation(error instanceof Error ? error.name : 'unknown');
|
|
260
264
|
this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
|
|
261
265
|
if (error instanceof ReExStateMismatchError && this.config.slashBroadcastedInvalidBlockPenalty > 0n) {
|
|
262
266
|
this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
|
|
@@ -290,8 +294,12 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
290
294
|
// Use the sequencer's block building logic to re-execute the transactions
|
|
291
295
|
const timer = new Timer();
|
|
292
296
|
const config = this.blockBuilder.getConfig();
|
|
297
|
+
// We source most global variables from the proposal
|
|
293
298
|
const globalVariables = GlobalVariables.from({
|
|
294
|
-
|
|
299
|
+
slotNumber: proposal.payload.header.slotNumber,
|
|
300
|
+
coinbase: proposal.payload.header.coinbase,
|
|
301
|
+
feeRecipient: proposal.payload.header.feeRecipient,
|
|
302
|
+
gasFees: proposal.payload.header.gasFees,
|
|
295
303
|
blockNumber: proposal.blockNumber,
|
|
296
304
|
timestamp: header.timestamp,
|
|
297
305
|
chainId: new Fr(config.l1ChainId),
|
|
@@ -310,8 +318,14 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
310
318
|
this.metrics.recordFailedReexecution(proposal);
|
|
311
319
|
throw new ReExTimeoutError();
|
|
312
320
|
}
|
|
313
|
-
//
|
|
314
|
-
|
|
321
|
+
// Throw a ReExStateMismatchError error if state updates do not match.
|
|
322
|
+
// Note that we check the entire proposal payload here, since it could be inconsistent within itself,
|
|
323
|
+
// as in the archive root not being actually derived by its other tree roots.
|
|
324
|
+
if (!ConsensusPayload.fromBlock(block).equals(proposal.payload)) {
|
|
325
|
+
this.log.warn(`Re-execution state mismatch for slot ${proposal.slotNumber.toBigInt()}`, {
|
|
326
|
+
expected: ConsensusPayload.fromBlock(block).toInspect(),
|
|
327
|
+
actual: proposal.payload.toInspect()
|
|
328
|
+
});
|
|
315
329
|
this.metrics.recordFailedReexecution(proposal);
|
|
316
330
|
throw new ReExStateMismatchError(proposal.archive, block.archive.root, proposal.payload.stateReference, block.header.state);
|
|
317
331
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "3.0.0-nightly.
|
|
3
|
+
"version": "3.0.0-nightly.20250923",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -64,16 +64,16 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@aztec/constants": "3.0.0-nightly.
|
|
68
|
-
"@aztec/epoch-cache": "3.0.0-nightly.
|
|
69
|
-
"@aztec/ethereum": "3.0.0-nightly.
|
|
70
|
-
"@aztec/foundation": "3.0.0-nightly.
|
|
71
|
-
"@aztec/node-keystore": "3.0.0-nightly.
|
|
72
|
-
"@aztec/p2p": "3.0.0-nightly.
|
|
73
|
-
"@aztec/prover-client": "3.0.0-nightly.
|
|
74
|
-
"@aztec/slasher": "3.0.0-nightly.
|
|
75
|
-
"@aztec/stdlib": "3.0.0-nightly.
|
|
76
|
-
"@aztec/telemetry-client": "3.0.0-nightly.
|
|
67
|
+
"@aztec/constants": "3.0.0-nightly.20250923",
|
|
68
|
+
"@aztec/epoch-cache": "3.0.0-nightly.20250923",
|
|
69
|
+
"@aztec/ethereum": "3.0.0-nightly.20250923",
|
|
70
|
+
"@aztec/foundation": "3.0.0-nightly.20250923",
|
|
71
|
+
"@aztec/node-keystore": "3.0.0-nightly.20250923",
|
|
72
|
+
"@aztec/p2p": "3.0.0-nightly.20250923",
|
|
73
|
+
"@aztec/prover-client": "3.0.0-nightly.20250923",
|
|
74
|
+
"@aztec/slasher": "3.0.0-nightly.20250923",
|
|
75
|
+
"@aztec/stdlib": "3.0.0-nightly.20250923",
|
|
76
|
+
"@aztec/telemetry-client": "3.0.0-nightly.20250923",
|
|
77
77
|
"koa": "^2.16.1",
|
|
78
78
|
"koa-router": "^12.0.0",
|
|
79
79
|
"tslib": "^2.4.0",
|
package/src/validator.ts
CHANGED
|
@@ -25,7 +25,12 @@ import type { CommitteeAttestationsAndSigners, L2BlockSource } from '@aztec/stdl
|
|
|
25
25
|
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
26
26
|
import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server';
|
|
27
27
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
28
|
-
import
|
|
28
|
+
import {
|
|
29
|
+
type BlockAttestation,
|
|
30
|
+
type BlockProposal,
|
|
31
|
+
type BlockProposalOptions,
|
|
32
|
+
ConsensusPayload,
|
|
33
|
+
} from '@aztec/stdlib/p2p';
|
|
29
34
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
30
35
|
import { GlobalVariables, type StateReference, type Tx } from '@aztec/stdlib/tx';
|
|
31
36
|
import {
|
|
@@ -57,6 +62,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
57
62
|
private validationService: ValidationService;
|
|
58
63
|
private metrics: ValidatorMetrics;
|
|
59
64
|
|
|
65
|
+
// Whether it has already registered handlers on the p2p client
|
|
66
|
+
private hasRegisteredHandlers = false;
|
|
67
|
+
|
|
60
68
|
// Used to check if we are sending the same proposal twice
|
|
61
69
|
private previousProposal?: BlockProposal;
|
|
62
70
|
|
|
@@ -207,12 +215,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
207
215
|
return;
|
|
208
216
|
}
|
|
209
217
|
|
|
210
|
-
this.
|
|
218
|
+
await this.registerHandlers();
|
|
211
219
|
|
|
212
|
-
// Sync the committee from the smart contract
|
|
213
|
-
// https://github.com/AztecProtocol/aztec-packages/issues/7962
|
|
214
220
|
const myAddresses = this.getValidatorAddresses();
|
|
215
|
-
|
|
216
221
|
const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
|
|
217
222
|
if (inCommittee.length > 0) {
|
|
218
223
|
this.log.info(
|
|
@@ -225,9 +230,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
225
230
|
}
|
|
226
231
|
this.epochCacheUpdateLoop.start();
|
|
227
232
|
|
|
228
|
-
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
229
|
-
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
230
|
-
|
|
231
233
|
return Promise.resolve();
|
|
232
234
|
}
|
|
233
235
|
|
|
@@ -235,10 +237,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
235
237
|
await this.epochCacheUpdateLoop.stop();
|
|
236
238
|
}
|
|
237
239
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
/** Register handlers on the p2p client */
|
|
241
|
+
public async registerHandlers() {
|
|
242
|
+
if (!this.hasRegisteredHandlers) {
|
|
243
|
+
this.hasRegisteredHandlers = true;
|
|
244
|
+
|
|
245
|
+
const handler = (block: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> =>
|
|
246
|
+
this.attestToProposal(block, proposalSender);
|
|
247
|
+
this.p2pClient.registerBlockProposalHandler(handler);
|
|
248
|
+
|
|
249
|
+
const myAddresses = this.getValidatorAddresses();
|
|
250
|
+
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
251
|
+
|
|
252
|
+
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
253
|
+
}
|
|
242
254
|
}
|
|
243
255
|
|
|
244
256
|
async attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined> {
|
|
@@ -249,22 +261,22 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
249
261
|
// Check that I have any address in current committee before attesting
|
|
250
262
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
251
263
|
const partOfCommittee = inCommittee.length > 0;
|
|
264
|
+
const incFailedAttestation = partOfCommittee
|
|
265
|
+
? (reason: string) => this.metrics.incFailedAttestations(1, reason)
|
|
266
|
+
: () => {};
|
|
252
267
|
|
|
253
|
-
const proposalInfo = {
|
|
254
|
-
...proposal.toBlockInfo(),
|
|
255
|
-
proposer: proposer.toString(),
|
|
256
|
-
};
|
|
257
|
-
|
|
268
|
+
const proposalInfo = { ...proposal.toBlockInfo(), proposer: proposer.toString() };
|
|
258
269
|
this.log.info(`Received proposal for slot ${slotNumber}`, {
|
|
259
270
|
...proposalInfo,
|
|
260
|
-
txHashes: proposal.txHashes.map(
|
|
271
|
+
txHashes: proposal.txHashes.map(t => t.toString()),
|
|
261
272
|
});
|
|
262
273
|
|
|
263
274
|
// Collect txs from the proposal. Note that we do this before checking if we have an address in the
|
|
264
275
|
// current committee, since we want to collect txs anyway to facilitate propagation.
|
|
276
|
+
const config = this.blockBuilder.getConfig();
|
|
265
277
|
const { txs, missingTxs } = await this.txProvider.getTxsForBlockProposal(proposal, {
|
|
266
278
|
pinnedPeer: proposalSender,
|
|
267
|
-
deadline: this.getReexecutionDeadline(proposal,
|
|
279
|
+
deadline: this.getReexecutionDeadline(proposal, config),
|
|
268
280
|
});
|
|
269
281
|
|
|
270
282
|
// Check that I have any address in current committee before attesting
|
|
@@ -278,9 +290,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
278
290
|
const invalidProposal = await this.blockProposalValidator.validate(proposal);
|
|
279
291
|
if (invalidProposal) {
|
|
280
292
|
this.log.warn(`Proposal is not valid, skipping attestation`, proposalInfo);
|
|
281
|
-
|
|
282
|
-
this.metrics.incFailedAttestations(1, 'invalid_proposal');
|
|
283
|
-
}
|
|
293
|
+
incFailedAttestation('invalid_proposal');
|
|
284
294
|
return undefined;
|
|
285
295
|
}
|
|
286
296
|
|
|
@@ -290,7 +300,6 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
290
300
|
// would not be rebroadcasted. But it also means that nodes that have not fully synced would
|
|
291
301
|
// not rebroadcast the proposal.
|
|
292
302
|
if (blockNumber > INITIAL_L2_BLOCK_NUM) {
|
|
293
|
-
const config = this.blockBuilder.getConfig();
|
|
294
303
|
const deadline = this.getReexecutionDeadline(proposal, config);
|
|
295
304
|
const currentTime = this.dateProvider.now();
|
|
296
305
|
const timeoutDurationMs = deadline.getTime() - currentTime;
|
|
@@ -313,9 +322,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
313
322
|
|
|
314
323
|
if (parentBlock === undefined) {
|
|
315
324
|
this.log.warn(`Parent block for ${blockNumber} not found, skipping attestation`, proposalInfo);
|
|
316
|
-
|
|
317
|
-
this.metrics.incFailedAttestations(1, 'parent_block_not_found');
|
|
318
|
-
}
|
|
325
|
+
incFailedAttestation('parent_block_not_found');
|
|
319
326
|
return undefined;
|
|
320
327
|
}
|
|
321
328
|
|
|
@@ -325,9 +332,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
325
332
|
parentBlockArchiveRoot: parentBlock.archive.root.toString(),
|
|
326
333
|
...proposalInfo,
|
|
327
334
|
});
|
|
328
|
-
|
|
329
|
-
this.metrics.incFailedAttestations(1, 'parent_block_does_not_match');
|
|
330
|
-
}
|
|
335
|
+
incFailedAttestation('parent_block_does_not_match');
|
|
331
336
|
return undefined;
|
|
332
337
|
}
|
|
333
338
|
}
|
|
@@ -343,18 +348,23 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
343
348
|
computedInHash: computedInHash.toString(),
|
|
344
349
|
...proposalInfo,
|
|
345
350
|
});
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
351
|
+
incFailedAttestation('in_hash_mismatch');
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Check that this block number does not exist already, otherwise the proposer could signal
|
|
356
|
+
// an arbitrary block number in the past, though this would most likely fail on the rollup contract.
|
|
357
|
+
const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
|
|
358
|
+
if (existingBlock) {
|
|
359
|
+
this.log.warn(`Block number ${blockNumber} already exists, skipping attestation`, proposalInfo);
|
|
360
|
+
incFailedAttestation('block_number_already_exists');
|
|
349
361
|
return undefined;
|
|
350
362
|
}
|
|
351
363
|
|
|
352
364
|
// Check that all of the transactions in the proposal are available in the tx pool before attesting
|
|
353
365
|
if (missingTxs.length > 0) {
|
|
354
366
|
this.log.warn(`Missing ${missingTxs.length} txs to attest to proposal`, { ...proposalInfo, missingTxs });
|
|
355
|
-
|
|
356
|
-
this.metrics.incFailedAttestations(1, 'TransactionsNotAvailableError');
|
|
357
|
-
}
|
|
367
|
+
incFailedAttestation('TransactionsNotAvailableError');
|
|
358
368
|
return undefined;
|
|
359
369
|
}
|
|
360
370
|
|
|
@@ -366,7 +376,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
366
376
|
await this.reExecuteTransactions(proposal, txs, l1ToL2Messages);
|
|
367
377
|
}
|
|
368
378
|
} catch (error: any) {
|
|
369
|
-
|
|
379
|
+
incFailedAttestation(error instanceof Error ? error.name : 'unknown');
|
|
370
380
|
this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
|
|
371
381
|
if (error instanceof ReExStateMismatchError && this.config.slashBroadcastedInvalidBlockPenalty > 0n) {
|
|
372
382
|
this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
|
|
@@ -410,10 +420,15 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
410
420
|
// Use the sequencer's block building logic to re-execute the transactions
|
|
411
421
|
const timer = new Timer();
|
|
412
422
|
const config = this.blockBuilder.getConfig();
|
|
423
|
+
|
|
424
|
+
// We source most global variables from the proposal
|
|
413
425
|
const globalVariables = GlobalVariables.from({
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
426
|
+
slotNumber: proposal.payload.header.slotNumber, // checked in the block proposal validator
|
|
427
|
+
coinbase: proposal.payload.header.coinbase, // set arbitrarily by the proposer
|
|
428
|
+
feeRecipient: proposal.payload.header.feeRecipient, // set arbitrarily by the proposer
|
|
429
|
+
gasFees: proposal.payload.header.gasFees, // validated by the rollup contract
|
|
430
|
+
blockNumber: proposal.blockNumber, // checked blockNumber-1 exists in archiver but blockNumber doesnt
|
|
431
|
+
timestamp: header.timestamp, // checked in the rollup contract against the slot number
|
|
417
432
|
chainId: new Fr(config.l1ChainId),
|
|
418
433
|
version: new Fr(config.rollupVersion),
|
|
419
434
|
});
|
|
@@ -435,8 +450,14 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
435
450
|
throw new ReExTimeoutError();
|
|
436
451
|
}
|
|
437
452
|
|
|
438
|
-
//
|
|
439
|
-
|
|
453
|
+
// Throw a ReExStateMismatchError error if state updates do not match.
|
|
454
|
+
// Note that we check the entire proposal payload here, since it could be inconsistent within itself,
|
|
455
|
+
// as in the archive root not being actually derived by its other tree roots.
|
|
456
|
+
if (!ConsensusPayload.fromBlock(block).equals(proposal.payload)) {
|
|
457
|
+
this.log.warn(`Re-execution state mismatch for slot ${proposal.slotNumber.toBigInt()}`, {
|
|
458
|
+
expected: ConsensusPayload.fromBlock(block).toInspect(),
|
|
459
|
+
actual: proposal.payload.toInspect(),
|
|
460
|
+
});
|
|
440
461
|
this.metrics.recordFailedReexecution(proposal);
|
|
441
462
|
throw new ReExStateMismatchError(
|
|
442
463
|
proposal.archive,
|