@aztec/validator-client 0.87.5 → 0.87.6

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.
@@ -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 { P2P } from '@aztec/p2p';
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[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
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
- this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
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
- const txs = await this.ensureTransactionsAreAvailable(proposal);
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
- // If the transactions are not available, then we should not attempt to attest
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.5",
3
+ "version": "0.87.6",
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.5",
64
- "@aztec/epoch-cache": "0.87.5",
65
- "@aztec/ethereum": "0.87.5",
66
- "@aztec/foundation": "0.87.5",
67
- "@aztec/p2p": "0.87.5",
68
- "@aztec/stdlib": "0.87.5",
69
- "@aztec/telemetry-client": "0.87.5",
63
+ "@aztec/constants": "0.87.6",
64
+ "@aztec/epoch-cache": "0.87.6",
65
+ "@aztec/ethereum": "0.87.6",
66
+ "@aztec/foundation": "0.87.6",
67
+ "@aztec/p2p": "0.87.6",
68
+ "@aztec/stdlib": "0.87.6",
69
+ "@aztec/telemetry-client": "0.87.6",
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 { P2P } from '@aztec/p2p';
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, TxHash } from '@aztec/stdlib/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
- this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
249
- try {
250
- const txs = await this.ensureTransactionsAreAvailable(proposal);
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,