@aztec/validator-client 0.86.0-starknet.1 → 0.87.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/duties/validation_service.d.ts +3 -3
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +4 -2
- package/dest/validator.d.ts +9 -9
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +68 -16
- package/package.json +11 -11
- package/src/duties/validation_service.ts +15 -5
- package/src/validator.ts +98 -25
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Fr } from '@aztec/foundation/fields';
|
|
2
|
-
import { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
|
|
3
|
-
import type { ProposedBlockHeader, StateReference,
|
|
2
|
+
import { BlockAttestation, BlockProposal, type BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
3
|
+
import type { ProposedBlockHeader, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
4
4
|
import type { ValidatorKeyStore } from '../key_store/interface.js';
|
|
5
5
|
export declare class ValidationService {
|
|
6
6
|
private keyStore;
|
|
@@ -15,7 +15,7 @@ export declare class ValidationService {
|
|
|
15
15
|
*
|
|
16
16
|
* @returns A block proposal signing the above information (not the current implementation!!!)
|
|
17
17
|
*/
|
|
18
|
-
createBlockProposal(blockNumber: Fr, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs:
|
|
18
|
+
createBlockProposal(blockNumber: Fr, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs: Tx[], options: BlockProposalOptions): Promise<BlockProposal>;
|
|
19
19
|
/**
|
|
20
20
|
* Attest to the given block proposal constructed by the current sequencer
|
|
21
21
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation_service.d.ts","sourceRoot":"","sources":["../../src/duties/validation_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,
|
|
1
|
+
{"version":3,"file":"validation_service.d.ts","sourceRoot":"","sources":["../../src/duties/validation_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,KAAK,oBAAoB,EAG1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnE,qBAAa,iBAAiB;IAChB,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,iBAAiB;IAE/C;;;;;;;;;OASG;IACG,mBAAmB,CACvB,WAAW,EAAE,EAAE,EACf,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,EAAE,EACX,cAAc,EAAE,cAAc,EAC9B,GAAG,EAAE,EAAE,EAAE,EACT,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC;IAazB;;;;;;;;OAQG;IACG,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAS3E"}
|
|
@@ -15,9 +15,11 @@ export class ValidationService {
|
|
|
15
15
|
* @param txs - TxHash[] ordered list of transactions
|
|
16
16
|
*
|
|
17
17
|
* @returns A block proposal signing the above information (not the current implementation!!!)
|
|
18
|
-
*/ createBlockProposal(blockNumber, header, archive, stateReference, txs) {
|
|
18
|
+
*/ async createBlockProposal(blockNumber, header, archive, stateReference, txs, options) {
|
|
19
19
|
const payloadSigner = (payload)=>this.keyStore.signMessage(payload);
|
|
20
|
-
|
|
20
|
+
// TODO: check if this is calculated earlier / can not be recomputed
|
|
21
|
+
const txHashes = await Promise.all(txs.map((tx)=>tx.getTxHash()));
|
|
22
|
+
return BlockProposal.createProposalFromSigner(blockNumber, new ConsensusPayload(header, archive, stateReference, txHashes), options.publishFullTxs ? txs : undefined, payloadSigner);
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* Attest to the given block proposal constructed by the current sequencer
|
package/dest/validator.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { Fr } from '@aztec/foundation/fields';
|
|
|
4
4
|
import { DateProvider, type Timer } from '@aztec/foundation/timer';
|
|
5
5
|
import type { P2P } from '@aztec/p2p';
|
|
6
6
|
import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
|
|
7
|
-
import type { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
|
|
8
|
-
import type { ProposedBlockHeader, StateReference, Tx
|
|
7
|
+
import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
8
|
+
import type { ProposedBlockHeader, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
9
9
|
import { type TelemetryClient, WithTracer } from '@aztec/telemetry-client';
|
|
10
10
|
import type { ValidatorClientConfig } from './config.js';
|
|
11
11
|
import type { ValidatorKeyStore } from './key_store/interface.js';
|
|
@@ -27,9 +27,9 @@ export interface Validator {
|
|
|
27
27
|
start(): Promise<void>;
|
|
28
28
|
registerBlockProposalHandler(): void;
|
|
29
29
|
registerBlockBuilder(blockBuilder: BlockBuilderCallback): void;
|
|
30
|
-
createBlockProposal(blockNumber: Fr, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs:
|
|
31
|
-
attestToProposal(proposal: BlockProposal):
|
|
32
|
-
broadcastBlockProposal(proposal: BlockProposal): void
|
|
30
|
+
createBlockProposal(blockNumber: Fr, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs: Tx[], options: BlockProposalOptions): Promise<BlockProposal | undefined>;
|
|
31
|
+
attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined>;
|
|
32
|
+
broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
|
|
33
33
|
collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
@@ -69,7 +69,7 @@ export declare class ValidatorClient extends WithTracer implements Validator {
|
|
|
69
69
|
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
70
70
|
* @param proposal - The proposal to re-execute
|
|
71
71
|
*/
|
|
72
|
-
reExecuteTransactions(proposal: BlockProposal): Promise<void>;
|
|
72
|
+
reExecuteTransactions(proposal: BlockProposal, txs: Tx[]): Promise<void>;
|
|
73
73
|
/**
|
|
74
74
|
* Ensure that all of the transactions in the proposal are available in the tx pool before attesting
|
|
75
75
|
*
|
|
@@ -78,9 +78,9 @@ export declare class ValidatorClient extends WithTracer implements Validator {
|
|
|
78
78
|
* 3. If we cannot retrieve them from the network, throw an error
|
|
79
79
|
* @param proposal - The proposal to attest to
|
|
80
80
|
*/
|
|
81
|
-
ensureTransactionsAreAvailable(proposal: BlockProposal): Promise<
|
|
82
|
-
createBlockProposal(blockNumber: Fr, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs:
|
|
83
|
-
broadcastBlockProposal(proposal: BlockProposal): void
|
|
81
|
+
ensureTransactionsAreAvailable(proposal: BlockProposal): Promise<Tx[]>;
|
|
82
|
+
createBlockProposal(blockNumber: Fr, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs: Tx[], options: BlockProposalOptions): Promise<BlockProposal | undefined>;
|
|
83
|
+
broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
|
|
84
84
|
collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
|
|
85
85
|
private doAttestToProposal;
|
|
86
86
|
}
|
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;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAI9C,OAAO,EAAE,YAAY,EAAE,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
|
package/dest/validator.js
CHANGED
|
@@ -147,10 +147,10 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
147
147
|
// Check that all of the transactions in the proposal are available in the tx pool before attesting
|
|
148
148
|
this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
|
|
149
149
|
try {
|
|
150
|
-
await this.ensureTransactionsAreAvailable(proposal);
|
|
150
|
+
const txs = await this.ensureTransactionsAreAvailable(proposal);
|
|
151
151
|
if (this.config.validatorReexecute) {
|
|
152
152
|
this.log.verbose(`Re-executing transactions in the proposal before attesting`);
|
|
153
|
-
await this.reExecuteTransactions(proposal);
|
|
153
|
+
await this.reExecuteTransactions(proposal, txs);
|
|
154
154
|
}
|
|
155
155
|
} catch (error) {
|
|
156
156
|
this.metrics.incFailedAttestations(error instanceof Error ? error.name : 'unknown');
|
|
@@ -173,10 +173,9 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
173
173
|
/**
|
|
174
174
|
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
175
175
|
* @param proposal - The proposal to re-execute
|
|
176
|
-
*/ async reExecuteTransactions(proposal) {
|
|
176
|
+
*/ async reExecuteTransactions(proposal, txs) {
|
|
177
177
|
const { header, txHashes } = proposal.payload;
|
|
178
|
-
|
|
179
|
-
// If we cannot request all of the transactions, then we should fail
|
|
178
|
+
// If we do not have all of the transactions, then we should fail
|
|
180
179
|
if (txs.length !== txHashes.length) {
|
|
181
180
|
const foundTxHashes = await Promise.all(txs.map(async (tx)=>await tx.getTxHash()));
|
|
182
181
|
const missingTxHashes = txHashes.filter((txHash)=>!foundTxHashes.includes(txHash));
|
|
@@ -215,29 +214,82 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
215
214
|
* 3. If we cannot retrieve them from the network, throw an error
|
|
216
215
|
* @param proposal - The proposal to attest to
|
|
217
216
|
*/ async ensureTransactionsAreAvailable(proposal) {
|
|
217
|
+
if (proposal.payload.txHashes.length === 0) {
|
|
218
|
+
this.log.verbose(`Received block proposal with no transactions, skipping transaction availability check`);
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
// Is this a new style proposal?
|
|
222
|
+
if (proposal.txs && proposal.txs.length > 0 && proposal.txs.length === proposal.payload.txHashes.length) {
|
|
223
|
+
// Yes, any txs that we already have we should use
|
|
224
|
+
this.log.info(`Using new style proposal with ${proposal.txs.length} transactions`);
|
|
225
|
+
// Request from the pool based on the signed hashes in the payload
|
|
226
|
+
const hashesFromPayload = proposal.payload.txHashes;
|
|
227
|
+
const txsToUse = await this.p2pClient.getTxsByHashFromPool(hashesFromPayload);
|
|
228
|
+
const missingTxs = txsToUse.filter((tx)=>tx === undefined).length;
|
|
229
|
+
if (missingTxs > 0) {
|
|
230
|
+
this.log.verbose(`Missing ${missingTxs}/${hashesFromPayload.length} transactions in the tx pool, will attempt to take from the proposal`);
|
|
231
|
+
}
|
|
232
|
+
let usedFromProposal = 0;
|
|
233
|
+
// Fill any holes with txs in the proposal, provided their hash matches the hash in the payload
|
|
234
|
+
for(let i = 0; i < txsToUse.length; i++){
|
|
235
|
+
if (txsToUse[i] === undefined) {
|
|
236
|
+
// We don't have the transaction, take from the proposal, provided the hash is the same
|
|
237
|
+
const hashOfTxInProposal = await proposal.txs[i].getTxHash();
|
|
238
|
+
if (hashOfTxInProposal.equals(hashesFromPayload[i])) {
|
|
239
|
+
// Hash is equal, we can use the tx from the proposal
|
|
240
|
+
txsToUse[i] = proposal.txs[i];
|
|
241
|
+
usedFromProposal++;
|
|
242
|
+
} else {
|
|
243
|
+
this.log.warn(`Unable to take tx: ${hashOfTxInProposal.toString()} from the proposal, it does not match payload hash: ${hashesFromPayload[i].toString()}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// See if we still have any holes, if there are then we were not successful and will try the old method
|
|
248
|
+
if (txsToUse.some((tx)=>tx === undefined)) {
|
|
249
|
+
this.log.warn(`Failed to use transactions from proposal. Falling back to old proposal logic`);
|
|
250
|
+
} else {
|
|
251
|
+
this.log.info(`Successfully used ${usedFromProposal}/${hashesFromPayload.length} transactions from the proposal`);
|
|
252
|
+
await this.p2pClient.validate(txsToUse);
|
|
253
|
+
return txsToUse;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
this.log.info(`Using old style proposal with ${proposal.payload.txHashes.length} transactions`);
|
|
257
|
+
// Old style proposal, we will perform a request by hash from pool
|
|
258
|
+
// This will request from network any txs that are missing
|
|
218
259
|
const txHashes = proposal.payload.txHashes;
|
|
260
|
+
// This part is just for logging that we are requesting from the network
|
|
219
261
|
const availability = await this.p2pClient.hasTxsInPool(txHashes);
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
222
|
-
|
|
262
|
+
const notAvailable = availability.filter((availability)=>availability === false);
|
|
263
|
+
if (notAvailable.length) {
|
|
264
|
+
this.log.verbose(`Missing ${notAvailable.length} transactions in the tx pool, will need to request from the network`);
|
|
223
265
|
}
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
266
|
+
// This will request from the network any txs that are missing
|
|
267
|
+
const retrievedTxs = await this.p2pClient.getTxsByHash(txHashes);
|
|
268
|
+
const missingTxs = retrievedTxs.map((tx, index)=>{
|
|
269
|
+
// Return the hash of any that we did not get
|
|
270
|
+
if (tx === undefined) {
|
|
271
|
+
return txHashes[index];
|
|
272
|
+
} else {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
}).filter((hash)=>hash !== undefined);
|
|
276
|
+
if (missingTxs.length > 0) {
|
|
227
277
|
throw new TransactionsNotAvailableError(missingTxs);
|
|
228
278
|
}
|
|
279
|
+
await this.p2pClient.validate(retrievedTxs);
|
|
280
|
+
return retrievedTxs;
|
|
229
281
|
}
|
|
230
|
-
async createBlockProposal(blockNumber, header, archive, stateReference, txs) {
|
|
282
|
+
async createBlockProposal(blockNumber, header, archive, stateReference, txs, options) {
|
|
231
283
|
if (this.previousProposal?.slotNumber.equals(header.slotNumber)) {
|
|
232
284
|
this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
|
|
233
285
|
return Promise.resolve(undefined);
|
|
234
286
|
}
|
|
235
|
-
const newProposal = await this.validationService.createBlockProposal(blockNumber, header, archive, stateReference, txs);
|
|
287
|
+
const newProposal = await this.validationService.createBlockProposal(blockNumber, header, archive, stateReference, txs, options);
|
|
236
288
|
this.previousProposal = newProposal;
|
|
237
289
|
return newProposal;
|
|
238
290
|
}
|
|
239
|
-
broadcastBlockProposal(proposal) {
|
|
240
|
-
this.p2pClient.broadcastProposal(proposal);
|
|
291
|
+
async broadcastBlockProposal(proposal) {
|
|
292
|
+
await this.p2pClient.broadcastProposal(proposal);
|
|
241
293
|
}
|
|
242
294
|
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962)
|
|
243
295
|
async collectAttestations(proposal, required, deadline) {
|
|
@@ -283,7 +335,7 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
283
335
|
function validatePrivateKey(privateKey) {
|
|
284
336
|
try {
|
|
285
337
|
return Buffer32.fromString(privateKey);
|
|
286
|
-
} catch
|
|
338
|
+
} catch {
|
|
287
339
|
throw new InvalidValidatorPrivateKeyError();
|
|
288
340
|
}
|
|
289
341
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
]
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@aztec/constants": "0.
|
|
64
|
-
"@aztec/epoch-cache": "0.
|
|
65
|
-
"@aztec/ethereum": "0.
|
|
66
|
-
"@aztec/foundation": "0.
|
|
67
|
-
"@aztec/p2p": "0.
|
|
68
|
-
"@aztec/stdlib": "0.
|
|
69
|
-
"@aztec/telemetry-client": "0.
|
|
63
|
+
"@aztec/constants": "0.87.0",
|
|
64
|
+
"@aztec/epoch-cache": "0.87.0",
|
|
65
|
+
"@aztec/ethereum": "0.87.0",
|
|
66
|
+
"@aztec/foundation": "0.87.0",
|
|
67
|
+
"@aztec/p2p": "0.87.0",
|
|
68
|
+
"@aztec/stdlib": "0.87.0",
|
|
69
|
+
"@aztec/telemetry-client": "0.87.0",
|
|
70
70
|
"koa": "^2.16.1",
|
|
71
71
|
"koa-router": "^12.0.0",
|
|
72
72
|
"tslib": "^2.4.0",
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@jest/globals": "^29.5.0",
|
|
77
77
|
"@types/jest": "^29.5.0",
|
|
78
|
-
"@types/node": "^
|
|
78
|
+
"@types/node": "^22.15.17",
|
|
79
79
|
"jest": "^29.5.0",
|
|
80
80
|
"jest-mock-extended": "^3.0.7",
|
|
81
81
|
"ts-node": "^10.9.1",
|
|
82
|
-
"typescript": "^5.
|
|
82
|
+
"typescript": "^5.3.3"
|
|
83
83
|
},
|
|
84
84
|
"files": [
|
|
85
85
|
"dest",
|
|
@@ -88,6 +88,6 @@
|
|
|
88
88
|
],
|
|
89
89
|
"types": "./dest/index.d.ts",
|
|
90
90
|
"engines": {
|
|
91
|
-
"node": ">=
|
|
91
|
+
"node": ">=20.10"
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
2
2
|
import { keccak256 } from '@aztec/foundation/crypto';
|
|
3
3
|
import type { Fr } from '@aztec/foundation/fields';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
BlockAttestation,
|
|
6
|
+
BlockProposal,
|
|
7
|
+
type BlockProposalOptions,
|
|
8
|
+
ConsensusPayload,
|
|
9
|
+
SignatureDomainSeparator,
|
|
10
|
+
} from '@aztec/stdlib/p2p';
|
|
11
|
+
import type { ProposedBlockHeader, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
6
12
|
|
|
7
13
|
import type { ValidatorKeyStore } from '../key_store/interface.js';
|
|
8
14
|
|
|
@@ -19,18 +25,22 @@ export class ValidationService {
|
|
|
19
25
|
*
|
|
20
26
|
* @returns A block proposal signing the above information (not the current implementation!!!)
|
|
21
27
|
*/
|
|
22
|
-
createBlockProposal(
|
|
28
|
+
async createBlockProposal(
|
|
23
29
|
blockNumber: Fr,
|
|
24
30
|
header: ProposedBlockHeader,
|
|
25
31
|
archive: Fr,
|
|
26
32
|
stateReference: StateReference,
|
|
27
|
-
txs:
|
|
33
|
+
txs: Tx[],
|
|
34
|
+
options: BlockProposalOptions,
|
|
28
35
|
): Promise<BlockProposal> {
|
|
29
36
|
const payloadSigner = (payload: Buffer32) => this.keyStore.signMessage(payload);
|
|
37
|
+
// TODO: check if this is calculated earlier / can not be recomputed
|
|
38
|
+
const txHashes = await Promise.all(txs.map(tx => tx.getTxHash()));
|
|
30
39
|
|
|
31
40
|
return BlockProposal.createProposalFromSigner(
|
|
32
41
|
blockNumber,
|
|
33
|
-
new ConsensusPayload(header, archive, stateReference,
|
|
42
|
+
new ConsensusPayload(header, archive, stateReference, txHashes),
|
|
43
|
+
options.publishFullTxs ? txs : undefined,
|
|
34
44
|
payloadSigner,
|
|
35
45
|
);
|
|
36
46
|
}
|
package/src/validator.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { DateProvider, type Timer } from '@aztec/foundation/timer';
|
|
|
10
10
|
import type { P2P } from '@aztec/p2p';
|
|
11
11
|
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
12
12
|
import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
|
|
13
|
-
import type { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
|
|
13
|
+
import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
14
14
|
import type { ProposedBlockHeader, StateReference, Tx, TxHash } from '@aztec/stdlib/tx';
|
|
15
15
|
import { type TelemetryClient, WithTracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
16
16
|
|
|
@@ -58,11 +58,12 @@ export interface Validator {
|
|
|
58
58
|
header: ProposedBlockHeader,
|
|
59
59
|
archive: Fr,
|
|
60
60
|
stateReference: StateReference,
|
|
61
|
-
txs:
|
|
61
|
+
txs: Tx[],
|
|
62
|
+
options: BlockProposalOptions,
|
|
62
63
|
): Promise<BlockProposal | undefined>;
|
|
63
|
-
attestToProposal(proposal: BlockProposal):
|
|
64
|
+
attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined>;
|
|
64
65
|
|
|
65
|
-
broadcastBlockProposal(proposal: BlockProposal): void
|
|
66
|
+
broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
|
|
66
67
|
collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -246,11 +247,11 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
246
247
|
// Check that all of the transactions in the proposal are available in the tx pool before attesting
|
|
247
248
|
this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
|
|
248
249
|
try {
|
|
249
|
-
await this.ensureTransactionsAreAvailable(proposal);
|
|
250
|
+
const txs = await this.ensureTransactionsAreAvailable(proposal);
|
|
250
251
|
|
|
251
252
|
if (this.config.validatorReexecute) {
|
|
252
253
|
this.log.verbose(`Re-executing transactions in the proposal before attesting`);
|
|
253
|
-
await this.reExecuteTransactions(proposal);
|
|
254
|
+
await this.reExecuteTransactions(proposal, txs);
|
|
254
255
|
}
|
|
255
256
|
} catch (error: any) {
|
|
256
257
|
this.metrics.incFailedAttestations(error instanceof Error ? error.name : 'unknown');
|
|
@@ -278,14 +279,10 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
278
279
|
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
279
280
|
* @param proposal - The proposal to re-execute
|
|
280
281
|
*/
|
|
281
|
-
async reExecuteTransactions(proposal: BlockProposal) {
|
|
282
|
+
async reExecuteTransactions(proposal: BlockProposal, txs: Tx[]) {
|
|
282
283
|
const { header, txHashes } = proposal.payload;
|
|
283
284
|
|
|
284
|
-
|
|
285
|
-
tx => tx !== undefined,
|
|
286
|
-
) as Tx[];
|
|
287
|
-
|
|
288
|
-
// If we cannot request all of the transactions, then we should fail
|
|
285
|
+
// If we do not have all of the transactions, then we should fail
|
|
289
286
|
if (txs.length !== txHashes.length) {
|
|
290
287
|
const foundTxHashes = await Promise.all(txs.map(async tx => await tx.getTxHash()));
|
|
291
288
|
const missingTxHashes = txHashes.filter(txHash => !foundTxHashes.includes(txHash));
|
|
@@ -331,21 +328,95 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
331
328
|
* 3. If we cannot retrieve them from the network, throw an error
|
|
332
329
|
* @param proposal - The proposal to attest to
|
|
333
330
|
*/
|
|
334
|
-
async ensureTransactionsAreAvailable(proposal: BlockProposal) {
|
|
331
|
+
async ensureTransactionsAreAvailable(proposal: BlockProposal): Promise<Tx[]> {
|
|
332
|
+
if (proposal.payload.txHashes.length === 0) {
|
|
333
|
+
this.log.verbose(`Received block proposal with no transactions, skipping transaction availability check`);
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
// Is this a new style proposal?
|
|
337
|
+
if (proposal.txs && proposal.txs.length > 0 && proposal.txs.length === proposal.payload.txHashes.length) {
|
|
338
|
+
// Yes, any txs that we already have we should use
|
|
339
|
+
this.log.info(`Using new style proposal with ${proposal.txs.length} transactions`);
|
|
340
|
+
|
|
341
|
+
// Request from the pool based on the signed hashes in the payload
|
|
342
|
+
const hashesFromPayload = proposal.payload.txHashes;
|
|
343
|
+
const txsToUse = await this.p2pClient.getTxsByHashFromPool(hashesFromPayload);
|
|
344
|
+
|
|
345
|
+
const missingTxs = txsToUse.filter(tx => tx === undefined).length;
|
|
346
|
+
if (missingTxs > 0) {
|
|
347
|
+
this.log.verbose(
|
|
348
|
+
`Missing ${missingTxs}/${hashesFromPayload.length} transactions in the tx pool, will attempt to take from the proposal`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
let usedFromProposal = 0;
|
|
353
|
+
|
|
354
|
+
// Fill any holes with txs in the proposal, provided their hash matches the hash in the payload
|
|
355
|
+
for (let i = 0; i < txsToUse.length; i++) {
|
|
356
|
+
if (txsToUse[i] === undefined) {
|
|
357
|
+
// We don't have the transaction, take from the proposal, provided the hash is the same
|
|
358
|
+
const hashOfTxInProposal = await proposal.txs[i].getTxHash();
|
|
359
|
+
if (hashOfTxInProposal.equals(hashesFromPayload[i])) {
|
|
360
|
+
// Hash is equal, we can use the tx from the proposal
|
|
361
|
+
txsToUse[i] = proposal.txs[i];
|
|
362
|
+
usedFromProposal++;
|
|
363
|
+
} else {
|
|
364
|
+
this.log.warn(
|
|
365
|
+
`Unable to take tx: ${hashOfTxInProposal.toString()} from the proposal, it does not match payload hash: ${hashesFromPayload[
|
|
366
|
+
i
|
|
367
|
+
].toString()}`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// See if we still have any holes, if there are then we were not successful and will try the old method
|
|
374
|
+
if (txsToUse.some(tx => tx === undefined)) {
|
|
375
|
+
this.log.warn(`Failed to use transactions from proposal. Falling back to old proposal logic`);
|
|
376
|
+
} else {
|
|
377
|
+
this.log.info(
|
|
378
|
+
`Successfully used ${usedFromProposal}/${hashesFromPayload.length} transactions from the proposal`,
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
await this.p2pClient.validate(txsToUse as Tx[]);
|
|
382
|
+
return txsToUse as Tx[];
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
this.log.info(`Using old style proposal with ${proposal.payload.txHashes.length} transactions`);
|
|
387
|
+
|
|
388
|
+
// Old style proposal, we will perform a request by hash from pool
|
|
389
|
+
// This will request from network any txs that are missing
|
|
335
390
|
const txHashes: TxHash[] = proposal.payload.txHashes;
|
|
391
|
+
|
|
392
|
+
// This part is just for logging that we are requesting from the network
|
|
336
393
|
const availability = await this.p2pClient.hasTxsInPool(txHashes);
|
|
337
|
-
const
|
|
394
|
+
const notAvailable = availability.filter(availability => availability === false);
|
|
395
|
+
if (notAvailable.length) {
|
|
396
|
+
this.log.verbose(
|
|
397
|
+
`Missing ${notAvailable.length} transactions in the tx pool, will need to request from the network`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
338
400
|
|
|
339
|
-
|
|
340
|
-
|
|
401
|
+
// This will request from the network any txs that are missing
|
|
402
|
+
const retrievedTxs = await this.p2pClient.getTxsByHash(txHashes);
|
|
403
|
+
const missingTxs = retrievedTxs
|
|
404
|
+
.map((tx, index) => {
|
|
405
|
+
// Return the hash of any that we did not get
|
|
406
|
+
if (tx === undefined) {
|
|
407
|
+
return txHashes[index];
|
|
408
|
+
} else {
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
.filter(hash => hash !== undefined);
|
|
413
|
+
if (missingTxs.length > 0) {
|
|
414
|
+
throw new TransactionsNotAvailableError(missingTxs as TxHash[]);
|
|
341
415
|
}
|
|
342
416
|
|
|
343
|
-
this.
|
|
417
|
+
await this.p2pClient.validate(retrievedTxs as Tx[]);
|
|
344
418
|
|
|
345
|
-
|
|
346
|
-
if (requestedTxs.some(tx => tx === undefined)) {
|
|
347
|
-
throw new TransactionsNotAvailableError(missingTxs);
|
|
348
|
-
}
|
|
419
|
+
return retrievedTxs as Tx[];
|
|
349
420
|
}
|
|
350
421
|
|
|
351
422
|
async createBlockProposal(
|
|
@@ -353,7 +424,8 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
353
424
|
header: ProposedBlockHeader,
|
|
354
425
|
archive: Fr,
|
|
355
426
|
stateReference: StateReference,
|
|
356
|
-
txs:
|
|
427
|
+
txs: Tx[],
|
|
428
|
+
options: BlockProposalOptions,
|
|
357
429
|
): Promise<BlockProposal | undefined> {
|
|
358
430
|
if (this.previousProposal?.slotNumber.equals(header.slotNumber)) {
|
|
359
431
|
this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
|
|
@@ -366,13 +438,14 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
366
438
|
archive,
|
|
367
439
|
stateReference,
|
|
368
440
|
txs,
|
|
441
|
+
options,
|
|
369
442
|
);
|
|
370
443
|
this.previousProposal = newProposal;
|
|
371
444
|
return newProposal;
|
|
372
445
|
}
|
|
373
446
|
|
|
374
|
-
broadcastBlockProposal(proposal: BlockProposal): void {
|
|
375
|
-
this.p2pClient.broadcastProposal(proposal);
|
|
447
|
+
async broadcastBlockProposal(proposal: BlockProposal): Promise<void> {
|
|
448
|
+
await this.p2pClient.broadcastProposal(proposal);
|
|
376
449
|
}
|
|
377
450
|
|
|
378
451
|
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962)
|
|
@@ -429,7 +502,7 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
429
502
|
function validatePrivateKey(privateKey: string): Buffer32 {
|
|
430
503
|
try {
|
|
431
504
|
return Buffer32.fromString(privateKey);
|
|
432
|
-
} catch
|
|
505
|
+
} catch {
|
|
433
506
|
throw new InvalidValidatorPrivateKeyError();
|
|
434
507
|
}
|
|
435
508
|
}
|