@aztec/validator-client 0.87.5 → 0.87.7
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 -12
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +25 -92
- package/package.json +8 -8
- package/src/validator.ts +32 -125
package/dest/validator.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { EpochCache } from '@aztec/epoch-cache';
|
|
|
2
2
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
3
|
import { Fr } from '@aztec/foundation/fields';
|
|
4
4
|
import { DateProvider, type Timer } from '@aztec/foundation/timer';
|
|
5
|
-
import type
|
|
5
|
+
import { type P2P, type PeerId } from '@aztec/p2p';
|
|
6
6
|
import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
|
|
7
7
|
import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
8
8
|
import type { ProposedBlockHeader, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
@@ -28,7 +28,7 @@ export interface Validator {
|
|
|
28
28
|
registerBlockProposalHandler(): void;
|
|
29
29
|
registerBlockBuilder(blockBuilder: BlockBuilderCallback): void;
|
|
30
30
|
createBlockProposal(blockNumber: Fr, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs: Tx[], options: BlockProposalOptions): Promise<BlockProposal | undefined>;
|
|
31
|
-
attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined>;
|
|
31
|
+
attestToProposal(proposal: BlockProposal, sender: PeerId): Promise<BlockAttestation | undefined>;
|
|
32
32
|
broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
|
|
33
33
|
collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
|
|
34
34
|
}
|
|
@@ -51,6 +51,7 @@ export declare class ValidatorClient extends WithTracer implements Validator {
|
|
|
51
51
|
private lastEpoch;
|
|
52
52
|
private epochCacheUpdateLoop;
|
|
53
53
|
private blockProposalValidator;
|
|
54
|
+
private txCollector;
|
|
54
55
|
constructor(keyStore: ValidatorKeyStore, epochCache: EpochCache, p2pClient: P2P, blockSource: L2BlockSource, config: ValidatorClientConfig, dateProvider?: DateProvider, telemetry?: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
|
|
55
56
|
private handleEpochCommitteeUpdate;
|
|
56
57
|
static new(config: ValidatorClientConfig, epochCache: EpochCache, p2pClient: P2P, blockSource: L2BlockSource, dateProvider?: DateProvider, telemetry?: TelemetryClient): ValidatorClient;
|
|
@@ -64,21 +65,12 @@ export declare class ValidatorClient extends WithTracer implements Validator {
|
|
|
64
65
|
* We reuse the sequencer's block building functionality for re-execution
|
|
65
66
|
*/
|
|
66
67
|
registerBlockBuilder(blockBuilder: BlockBuilderCallback): void;
|
|
67
|
-
attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined>;
|
|
68
|
+
attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation | undefined>;
|
|
68
69
|
/**
|
|
69
70
|
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
70
71
|
* @param proposal - The proposal to re-execute
|
|
71
72
|
*/
|
|
72
73
|
reExecuteTransactions(proposal: BlockProposal, txs: Tx[]): Promise<void>;
|
|
73
|
-
/**
|
|
74
|
-
* Ensure that all of the transactions in the proposal are available in the tx pool before attesting
|
|
75
|
-
*
|
|
76
|
-
* 1. Check if the local tx pool contains all of the transactions in the proposal
|
|
77
|
-
* 2. If any transactions are not in the local tx pool, request them from the network
|
|
78
|
-
* 3. If we cannot retrieve them from the network, throw an error
|
|
79
|
-
* @param proposal - The proposal to attest to
|
|
80
|
-
*/
|
|
81
|
-
ensureTransactionsAreAvailable(proposal: BlockProposal): Promise<Tx[]>;
|
|
82
74
|
createBlockProposal(blockNumber: Fr, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs: Tx[], options: BlockProposalOptions): Promise<BlockProposal | undefined>;
|
|
83
75
|
broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
|
|
84
76
|
collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
|
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,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
|
package/dest/validator.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createLogger } from '@aztec/foundation/log';
|
|
|
4
4
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
5
5
|
import { sleep } from '@aztec/foundation/sleep';
|
|
6
6
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
7
|
+
import { TxCollector } from '@aztec/p2p';
|
|
7
8
|
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
8
9
|
import { WithTracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
9
10
|
import { ValidationService } from './duties/validation_service.js';
|
|
@@ -30,12 +31,14 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
30
31
|
lastEpoch;
|
|
31
32
|
epochCacheUpdateLoop;
|
|
32
33
|
blockProposalValidator;
|
|
34
|
+
txCollector;
|
|
33
35
|
constructor(keyStore, epochCache, p2pClient, blockSource, config, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
34
36
|
// Instantiate tracer
|
|
35
37
|
super(telemetry, 'Validator'), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockSource = blockSource, this.config = config, this.dateProvider = dateProvider, this.log = log, this.blockBuilder = undefined;
|
|
36
38
|
this.metrics = new ValidatorMetrics(telemetry);
|
|
37
39
|
this.validationService = new ValidationService(keyStore);
|
|
38
40
|
this.blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
41
|
+
this.txCollector = new TxCollector(p2pClient, this.log);
|
|
39
42
|
// Refresh epoch cache every second to trigger alert if participation in committee changes
|
|
40
43
|
this.myAddress = this.keyStore.getAddress();
|
|
41
44
|
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
|
|
@@ -87,8 +90,8 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
87
90
|
await this.epochCacheUpdateLoop.stop();
|
|
88
91
|
}
|
|
89
92
|
registerBlockProposalHandler() {
|
|
90
|
-
const handler = (block)=>{
|
|
91
|
-
return this.attestToProposal(block);
|
|
93
|
+
const handler = (block, proposalSender)=>{
|
|
94
|
+
return this.attestToProposal(block, proposalSender);
|
|
92
95
|
};
|
|
93
96
|
this.p2pClient.registerBlockProposalHandler(handler);
|
|
94
97
|
}
|
|
@@ -99,7 +102,7 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
99
102
|
*/ registerBlockBuilder(blockBuilder) {
|
|
100
103
|
this.blockBuilder = blockBuilder;
|
|
101
104
|
}
|
|
102
|
-
async attestToProposal(proposal) {
|
|
105
|
+
async attestToProposal(proposal, proposalSender) {
|
|
103
106
|
const slotNumber = proposal.slotNumber.toNumber();
|
|
104
107
|
const blockNumber = proposal.blockNumber.toNumber();
|
|
105
108
|
const proposalInfo = {
|
|
@@ -110,12 +113,8 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
110
113
|
txHashes: proposal.payload.txHashes.map((txHash)=>txHash.toString())
|
|
111
114
|
};
|
|
112
115
|
this.log.verbose(`Received request to attest for slot ${slotNumber}`);
|
|
113
|
-
// Check that I am in the committee
|
|
114
|
-
if (!await this.epochCache.isInCommittee(this.keyStore.getAddress())) {
|
|
115
|
-
this.log.verbose(`Not in the committee, skipping attestation`);
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
116
|
// Check that the proposal is from the current proposer, or the next proposer.
|
|
117
|
+
// Q: Should this be moved to the block proposal validator, so we disregard proposals from anyone?
|
|
119
118
|
const invalidProposal = await this.blockProposalValidator.validate(proposal);
|
|
120
119
|
if (invalidProposal) {
|
|
121
120
|
this.log.verbose(`Proposal is not valid, skipping attestation`);
|
|
@@ -144,30 +143,37 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
144
143
|
return undefined;
|
|
145
144
|
}
|
|
146
145
|
}
|
|
146
|
+
// Collect txs from the proposal
|
|
147
|
+
const { missing, txs } = await this.txCollector.collectForBlockProposal(proposal, proposalSender);
|
|
148
|
+
// Check that I am in the committee before attesting
|
|
149
|
+
if (!await this.epochCache.isInCommittee(this.keyStore.getAddress())) {
|
|
150
|
+
this.log.verbose(`Not in the committee, skipping attestation`);
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
147
153
|
// Check that all of the transactions in the proposal are available in the tx pool before attesting
|
|
148
|
-
|
|
154
|
+
if (missing && missing.length > 0) {
|
|
155
|
+
this.log.error(`Missing ${missing.length}/${proposal.payload.txHashes.length} txs to attest to proposal`, undefined, {
|
|
156
|
+
proposalInfo,
|
|
157
|
+
missing
|
|
158
|
+
});
|
|
159
|
+
this.metrics.incFailedAttestations('TransactionsNotAvailableError');
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
// Try re-executing the transactions in the proposal
|
|
149
163
|
try {
|
|
150
|
-
|
|
164
|
+
this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
|
|
151
165
|
if (this.config.validatorReexecute) {
|
|
152
166
|
this.log.verbose(`Re-executing transactions in the proposal before attesting`);
|
|
153
167
|
await this.reExecuteTransactions(proposal, txs);
|
|
154
168
|
}
|
|
155
169
|
} catch (error) {
|
|
156
170
|
this.metrics.incFailedAttestations(error instanceof Error ? error.name : 'unknown');
|
|
157
|
-
|
|
158
|
-
if (error instanceof TransactionsNotAvailableError) {
|
|
159
|
-
this.log.error(`Transactions not available, skipping attestation`, error, proposalInfo);
|
|
160
|
-
} else {
|
|
161
|
-
// This branch most commonly be hit if the transactions are available, but the re-execution fails
|
|
162
|
-
// Catch all error handler
|
|
163
|
-
this.log.error(`Failed to attest to proposal`, error, proposalInfo);
|
|
164
|
-
}
|
|
171
|
+
this.log.error(`Failed to attest to proposal`, error, proposalInfo);
|
|
165
172
|
return undefined;
|
|
166
173
|
}
|
|
167
174
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
168
175
|
this.log.info(`Attesting to proposal for slot ${slotNumber}`, proposalInfo);
|
|
169
176
|
this.metrics.incAttestations();
|
|
170
|
-
// If the above function does not throw an error, then we can attest to the proposal
|
|
171
177
|
return this.doAttestToProposal(proposal);
|
|
172
178
|
}
|
|
173
179
|
/**
|
|
@@ -206,79 +212,6 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
206
212
|
throw new ReExStateMismatchError();
|
|
207
213
|
}
|
|
208
214
|
}
|
|
209
|
-
/**
|
|
210
|
-
* Ensure that all of the transactions in the proposal are available in the tx pool before attesting
|
|
211
|
-
*
|
|
212
|
-
* 1. Check if the local tx pool contains all of the transactions in the proposal
|
|
213
|
-
* 2. If any transactions are not in the local tx pool, request them from the network
|
|
214
|
-
* 3. If we cannot retrieve them from the network, throw an error
|
|
215
|
-
* @param proposal - The proposal to attest to
|
|
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
|
|
259
|
-
const txHashes = proposal.payload.txHashes;
|
|
260
|
-
// This part is just for logging that we are requesting from the network
|
|
261
|
-
const availability = await this.p2pClient.hasTxsInPool(txHashes);
|
|
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`);
|
|
265
|
-
}
|
|
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) {
|
|
277
|
-
throw new TransactionsNotAvailableError(missingTxs);
|
|
278
|
-
}
|
|
279
|
-
await this.p2pClient.validate(retrievedTxs);
|
|
280
|
-
return retrievedTxs;
|
|
281
|
-
}
|
|
282
215
|
async createBlockProposal(blockNumber, header, archive, stateReference, txs, options) {
|
|
283
216
|
if (this.previousProposal?.slotNumber.equals(header.slotNumber)) {
|
|
284
217
|
this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "0.87.
|
|
3
|
+
"version": "0.87.7",
|
|
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.87.
|
|
64
|
-
"@aztec/epoch-cache": "0.87.
|
|
65
|
-
"@aztec/ethereum": "0.87.
|
|
66
|
-
"@aztec/foundation": "0.87.
|
|
67
|
-
"@aztec/p2p": "0.87.
|
|
68
|
-
"@aztec/stdlib": "0.87.
|
|
69
|
-
"@aztec/telemetry-client": "0.87.
|
|
63
|
+
"@aztec/constants": "0.87.7",
|
|
64
|
+
"@aztec/epoch-cache": "0.87.7",
|
|
65
|
+
"@aztec/ethereum": "0.87.7",
|
|
66
|
+
"@aztec/foundation": "0.87.7",
|
|
67
|
+
"@aztec/p2p": "0.87.7",
|
|
68
|
+
"@aztec/stdlib": "0.87.7",
|
|
69
|
+
"@aztec/telemetry-client": "0.87.7",
|
|
70
70
|
"koa": "^2.16.1",
|
|
71
71
|
"koa-router": "^12.0.0",
|
|
72
72
|
"tslib": "^2.4.0",
|
package/src/validator.ts
CHANGED
|
@@ -7,11 +7,11 @@ import { createLogger } from '@aztec/foundation/log';
|
|
|
7
7
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
8
8
|
import { sleep } from '@aztec/foundation/sleep';
|
|
9
9
|
import { DateProvider, type Timer } from '@aztec/foundation/timer';
|
|
10
|
-
import type
|
|
10
|
+
import { type P2P, type PeerId, TxCollector } from '@aztec/p2p';
|
|
11
11
|
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
12
12
|
import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
|
|
13
13
|
import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
14
|
-
import type { ProposedBlockHeader, StateReference, Tx
|
|
14
|
+
import type { ProposedBlockHeader, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
15
15
|
import { type TelemetryClient, WithTracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
16
16
|
|
|
17
17
|
import type { ValidatorClientConfig } from './config.js';
|
|
@@ -61,7 +61,7 @@ export interface Validator {
|
|
|
61
61
|
txs: Tx[],
|
|
62
62
|
options: BlockProposalOptions,
|
|
63
63
|
): Promise<BlockProposal | undefined>;
|
|
64
|
-
attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined>;
|
|
64
|
+
attestToProposal(proposal: BlockProposal, sender: PeerId): Promise<BlockAttestation | undefined>;
|
|
65
65
|
|
|
66
66
|
broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
|
|
67
67
|
collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
|
|
@@ -85,6 +85,7 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
85
85
|
private epochCacheUpdateLoop: RunningPromise;
|
|
86
86
|
|
|
87
87
|
private blockProposalValidator: BlockProposalValidator;
|
|
88
|
+
private txCollector: TxCollector;
|
|
88
89
|
|
|
89
90
|
constructor(
|
|
90
91
|
private keyStore: ValidatorKeyStore,
|
|
@@ -104,6 +105,8 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
104
105
|
|
|
105
106
|
this.blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
106
107
|
|
|
108
|
+
this.txCollector = new TxCollector(p2pClient, this.log);
|
|
109
|
+
|
|
107
110
|
// Refresh epoch cache every second to trigger alert if participation in committee changes
|
|
108
111
|
this.myAddress = this.keyStore.getAddress();
|
|
109
112
|
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
|
|
@@ -180,8 +183,8 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
public registerBlockProposalHandler() {
|
|
183
|
-
const handler = (block: BlockProposal): Promise<BlockAttestation | undefined> => {
|
|
184
|
-
return this.attestToProposal(block);
|
|
186
|
+
const handler = (block: BlockProposal, proposalSender: any): Promise<BlockAttestation | undefined> => {
|
|
187
|
+
return this.attestToProposal(block, proposalSender);
|
|
185
188
|
};
|
|
186
189
|
this.p2pClient.registerBlockProposalHandler(handler);
|
|
187
190
|
}
|
|
@@ -195,7 +198,7 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
195
198
|
this.blockBuilder = blockBuilder;
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
async attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined> {
|
|
201
|
+
async attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation | undefined> {
|
|
199
202
|
const slotNumber = proposal.slotNumber.toNumber();
|
|
200
203
|
const blockNumber = proposal.blockNumber.toNumber();
|
|
201
204
|
const proposalInfo = {
|
|
@@ -207,13 +210,8 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
207
210
|
};
|
|
208
211
|
this.log.verbose(`Received request to attest for slot ${slotNumber}`);
|
|
209
212
|
|
|
210
|
-
// Check that I am in the committee
|
|
211
|
-
if (!(await this.epochCache.isInCommittee(this.keyStore.getAddress()))) {
|
|
212
|
-
this.log.verbose(`Not in the committee, skipping attestation`);
|
|
213
|
-
return undefined;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
213
|
// Check that the proposal is from the current proposer, or the next proposer.
|
|
214
|
+
// Q: Should this be moved to the block proposal validator, so we disregard proposals from anyone?
|
|
217
215
|
const invalidProposal = await this.blockProposalValidator.validate(proposal);
|
|
218
216
|
if (invalidProposal) {
|
|
219
217
|
this.log.verbose(`Proposal is not valid, skipping attestation`);
|
|
@@ -244,34 +242,42 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
244
242
|
}
|
|
245
243
|
}
|
|
246
244
|
|
|
245
|
+
// Collect txs from the proposal
|
|
246
|
+
const { missing, txs } = await this.txCollector.collectForBlockProposal(proposal, proposalSender);
|
|
247
|
+
|
|
248
|
+
// Check that I am in the committee before attesting
|
|
249
|
+
if (!(await this.epochCache.isInCommittee(this.keyStore.getAddress()))) {
|
|
250
|
+
this.log.verbose(`Not in the committee, skipping attestation`);
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
247
254
|
// Check that all of the transactions in the proposal are available in the tx pool before attesting
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
255
|
+
if (missing && missing.length > 0) {
|
|
256
|
+
this.log.error(
|
|
257
|
+
`Missing ${missing.length}/${proposal.payload.txHashes.length} txs to attest to proposal`,
|
|
258
|
+
undefined,
|
|
259
|
+
{ proposalInfo, missing },
|
|
260
|
+
);
|
|
261
|
+
this.metrics.incFailedAttestations('TransactionsNotAvailableError');
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
251
264
|
|
|
265
|
+
// Try re-executing the transactions in the proposal
|
|
266
|
+
try {
|
|
267
|
+
this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
|
|
252
268
|
if (this.config.validatorReexecute) {
|
|
253
269
|
this.log.verbose(`Re-executing transactions in the proposal before attesting`);
|
|
254
270
|
await this.reExecuteTransactions(proposal, txs);
|
|
255
271
|
}
|
|
256
272
|
} catch (error: any) {
|
|
257
273
|
this.metrics.incFailedAttestations(error instanceof Error ? error.name : 'unknown');
|
|
258
|
-
|
|
259
|
-
// If the transactions are not available, then we should not attempt to attest
|
|
260
|
-
if (error instanceof TransactionsNotAvailableError) {
|
|
261
|
-
this.log.error(`Transactions not available, skipping attestation`, error, proposalInfo);
|
|
262
|
-
} else {
|
|
263
|
-
// This branch most commonly be hit if the transactions are available, but the re-execution fails
|
|
264
|
-
// Catch all error handler
|
|
265
|
-
this.log.error(`Failed to attest to proposal`, error, proposalInfo);
|
|
266
|
-
}
|
|
274
|
+
this.log.error(`Failed to attest to proposal`, error, proposalInfo);
|
|
267
275
|
return undefined;
|
|
268
276
|
}
|
|
269
277
|
|
|
270
278
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
271
279
|
this.log.info(`Attesting to proposal for slot ${slotNumber}`, proposalInfo);
|
|
272
280
|
this.metrics.incAttestations();
|
|
273
|
-
|
|
274
|
-
// If the above function does not throw an error, then we can attest to the proposal
|
|
275
281
|
return this.doAttestToProposal(proposal);
|
|
276
282
|
}
|
|
277
283
|
|
|
@@ -320,105 +326,6 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
320
326
|
}
|
|
321
327
|
}
|
|
322
328
|
|
|
323
|
-
/**
|
|
324
|
-
* Ensure that all of the transactions in the proposal are available in the tx pool before attesting
|
|
325
|
-
*
|
|
326
|
-
* 1. Check if the local tx pool contains all of the transactions in the proposal
|
|
327
|
-
* 2. If any transactions are not in the local tx pool, request them from the network
|
|
328
|
-
* 3. If we cannot retrieve them from the network, throw an error
|
|
329
|
-
* @param proposal - The proposal to attest to
|
|
330
|
-
*/
|
|
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
|
|
390
|
-
const txHashes: TxHash[] = proposal.payload.txHashes;
|
|
391
|
-
|
|
392
|
-
// This part is just for logging that we are requesting from the network
|
|
393
|
-
const availability = await this.p2pClient.hasTxsInPool(txHashes);
|
|
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
|
-
}
|
|
400
|
-
|
|
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[]);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
await this.p2pClient.validate(retrievedTxs as Tx[]);
|
|
418
|
-
|
|
419
|
-
return retrievedTxs as Tx[];
|
|
420
|
-
}
|
|
421
|
-
|
|
422
329
|
async createBlockProposal(
|
|
423
330
|
blockNumber: Fr,
|
|
424
331
|
header: ProposedBlockHeader,
|