@aztec/validator-client 0.86.0 → 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.
@@ -1,6 +1,6 @@
1
1
  import type { Fr } from '@aztec/foundation/fields';
2
- import { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
3
- import type { BlockHeader, TxHash } from '@aztec/stdlib/tx';
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;
@@ -8,13 +8,14 @@ export declare class ValidationService {
8
8
  /**
9
9
  * Create a block proposal with the given header, archive, and transactions
10
10
  *
11
+ * @param blockNumber - The block number this proposal is for
11
12
  * @param header - The block header
12
13
  * @param archive - The archive of the current block
13
14
  * @param txs - TxHash[] ordered list of transactions
14
15
  *
15
16
  * @returns A block proposal signing the above information (not the current implementation!!!)
16
17
  */
17
- createBlockProposal(header: BlockHeader, archive: Fr, txs: TxHash[]): Promise<BlockProposal>;
18
+ createBlockProposal(blockNumber: Fr, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs: Tx[], options: BlockProposalOptions): Promise<BlockProposal>;
18
19
  /**
19
20
  * Attest to the given block proposal constructed by the current sequencer
20
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,EAAE,gBAAgB,EAAE,aAAa,EAA8C,MAAM,mBAAmB,CAAC;AAChH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnE,qBAAa,iBAAiB;IAChB,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,iBAAiB;IAE/C;;;;;;;;OAQG;IACH,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAM5F;;;;;;;;OAQG;IACG,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAS3E"}
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"}
@@ -9,14 +9,17 @@ export class ValidationService {
9
9
  /**
10
10
  * Create a block proposal with the given header, archive, and transactions
11
11
  *
12
+ * @param blockNumber - The block number this proposal is for
12
13
  * @param header - The block header
13
14
  * @param archive - The archive of the current block
14
15
  * @param txs - TxHash[] ordered list of transactions
15
16
  *
16
17
  * @returns A block proposal signing the above information (not the current implementation!!!)
17
- */ createBlockProposal(header, archive, txs) {
18
+ */ async createBlockProposal(blockNumber, header, archive, stateReference, txs, options) {
18
19
  const payloadSigner = (payload)=>this.keyStore.signMessage(payload);
19
- return BlockProposal.createProposalFromSigner(new ConsensusPayload(header, archive, txs), payloadSigner);
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);
20
23
  }
21
24
  /**
22
25
  * Attest to the given block proposal constructed by the current sequencer
@@ -30,6 +33,6 @@ export class ValidationService {
30
33
  // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7961): check that the current validator is correct
31
34
  const buf = Buffer32.fromBuffer(keccak256(proposal.payload.getPayloadToSign(SignatureDomainSeparator.blockAttestation)));
32
35
  const sig = await this.keyStore.signMessage(buf);
33
- return new BlockAttestation(proposal.payload, sig);
36
+ return new BlockAttestation(proposal.blockNumber, proposal.payload, sig);
34
37
  }
35
38
  }
package/dest/factory.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
2
  import type { DateProvider } from '@aztec/foundation/timer';
3
3
  import type { P2P } from '@aztec/p2p';
4
+ import type { L2BlockSource } from '@aztec/stdlib/block';
4
5
  import type { TelemetryClient } from '@aztec/telemetry-client';
5
6
  import type { ValidatorClientConfig } from './config.js';
6
7
  import { ValidatorClient } from './validator.js';
7
8
  export declare function createValidatorClient(config: ValidatorClientConfig, deps: {
8
9
  p2pClient: P2P;
10
+ blockSource: L2BlockSource;
9
11
  telemetry: TelemetryClient;
10
12
  dateProvider: DateProvider;
11
13
  epochCache: EpochCache;
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE;IACJ,SAAS,EAAE,GAAG,CAAC;IACf,SAAS,EAAE,eAAe,CAAC;IAC3B,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;CACxB,+BAUF"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE;IACJ,SAAS,EAAE,GAAG,CAAC;IACf,WAAW,EAAE,aAAa,CAAC;IAC3B,SAAS,EAAE,eAAe,CAAC;IAC3B,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;CACxB,+BAiBF"}
package/dest/factory.js CHANGED
@@ -7,5 +7,5 @@ export function createValidatorClient(config, deps) {
7
7
  if (config.validatorPrivateKey === undefined || config.validatorPrivateKey === '') {
8
8
  config.validatorPrivateKey = generatePrivateKey();
9
9
  }
10
- return ValidatorClient.new(config, deps.epochCache, deps.p2pClient, deps.dateProvider, deps.telemetry);
10
+ return ValidatorClient.new(config, deps.epochCache, deps.p2pClient, deps.blockSource, deps.dateProvider, deps.telemetry);
11
11
  }
@@ -1,11 +1,11 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
- import type { Fr } from '@aztec/foundation/fields';
3
+ 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
- import type { L2Block } from '@aztec/stdlib/block';
7
- import type { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
8
- import type { BlockHeader, GlobalVariables, Tx, TxHash } from '@aztec/stdlib/tx';
6
+ import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
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';
@@ -14,7 +14,7 @@ import type { ValidatorKeyStore } from './key_store/interface.js';
14
14
  *
15
15
  * We reuse the sequencer's block building functionality for re-execution
16
16
  */
17
- type BlockBuilderCallback = (txs: Iterable<Tx> | AsyncIterableIterator<Tx>, globalVariables: GlobalVariables, opts?: {
17
+ type BlockBuilderCallback = (blockNumber: Fr, header: ProposedBlockHeader, txs: Iterable<Tx> | AsyncIterableIterator<Tx>, opts?: {
18
18
  validateOnly?: boolean;
19
19
  }) => Promise<{
20
20
  block: L2Block;
@@ -27,9 +27,9 @@ export interface Validator {
27
27
  start(): Promise<void>;
28
28
  registerBlockProposalHandler(): void;
29
29
  registerBlockBuilder(blockBuilder: BlockBuilderCallback): void;
30
- createBlockProposal(header: BlockHeader, archive: Fr, txs: TxHash[]): Promise<BlockProposal | undefined>;
31
- attestToProposal(proposal: BlockProposal): void;
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
  /**
@@ -39,6 +39,7 @@ export declare class ValidatorClient extends WithTracer implements Validator {
39
39
  private keyStore;
40
40
  private epochCache;
41
41
  private p2pClient;
42
+ private blockSource;
42
43
  private config;
43
44
  private dateProvider;
44
45
  private log;
@@ -50,9 +51,9 @@ export declare class ValidatorClient extends WithTracer implements Validator {
50
51
  private lastEpoch;
51
52
  private epochCacheUpdateLoop;
52
53
  private blockProposalValidator;
53
- constructor(keyStore: ValidatorKeyStore, epochCache: EpochCache, p2pClient: P2P, config: ValidatorClientConfig, dateProvider?: DateProvider, telemetry?: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
54
- private handleEpochCommiteeUpdate;
55
- static new(config: ValidatorClientConfig, epochCache: EpochCache, p2pClient: P2P, dateProvider?: DateProvider, telemetry?: TelemetryClient): ValidatorClient;
54
+ constructor(keyStore: ValidatorKeyStore, epochCache: EpochCache, p2pClient: P2P, blockSource: L2BlockSource, config: ValidatorClientConfig, dateProvider?: DateProvider, telemetry?: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
55
+ private handleEpochCommitteeUpdate;
56
+ static new(config: ValidatorClientConfig, epochCache: EpochCache, p2pClient: P2P, blockSource: L2BlockSource, dateProvider?: DateProvider, telemetry?: TelemetryClient): ValidatorClient;
56
57
  getValidatorAddress(): EthAddress;
57
58
  start(): Promise<void>;
58
59
  stop(): Promise<void>;
@@ -68,7 +69,7 @@ export declare class ValidatorClient extends WithTracer implements Validator {
68
69
  * Re-execute the transactions in the proposal and check that the state updates match the header state
69
70
  * @param proposal - The proposal to re-execute
70
71
  */
71
- reExecuteTransactions(proposal: BlockProposal): Promise<void>;
72
+ reExecuteTransactions(proposal: BlockProposal, txs: Tx[]): Promise<void>;
72
73
  /**
73
74
  * Ensure that all of the transactions in the proposal are available in the tx pool before attesting
74
75
  *
@@ -77,9 +78,9 @@ export declare class ValidatorClient extends WithTracer implements Validator {
77
78
  * 3. If we cannot retrieve them from the network, throw an error
78
79
  * @param proposal - The proposal to attest to
79
80
  */
80
- ensureTransactionsAreAvailable(proposal: BlockProposal): Promise<void>;
81
- createBlockProposal(header: BlockHeader, archive: Fr, txs: TxHash[]): Promise<BlockProposal | undefined>;
82
- 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>;
83
84
  collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
84
85
  private doAttestToProposal;
85
86
  }
@@ -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
@@ -1,3 +1,4 @@
1
+ import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
1
2
  import { Buffer32 } from '@aztec/foundation/buffer';
2
3
  import { createLogger } from '@aztec/foundation/log';
3
4
  import { RunningPromise } from '@aztec/foundation/running-promise';
@@ -15,6 +16,7 @@ import { ValidatorMetrics } from './metrics.js';
15
16
  keyStore;
16
17
  epochCache;
17
18
  p2pClient;
19
+ blockSource;
18
20
  config;
19
21
  dateProvider;
20
22
  log;
@@ -28,18 +30,18 @@ import { ValidatorMetrics } from './metrics.js';
28
30
  lastEpoch;
29
31
  epochCacheUpdateLoop;
30
32
  blockProposalValidator;
31
- constructor(keyStore, epochCache, p2pClient, config, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
33
+ constructor(keyStore, epochCache, p2pClient, blockSource, config, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
32
34
  // Instantiate tracer
33
- super(telemetry, 'Validator'), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.config = config, this.dateProvider = dateProvider, this.log = log, this.blockBuilder = undefined;
35
+ 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;
34
36
  this.metrics = new ValidatorMetrics(telemetry);
35
37
  this.validationService = new ValidationService(keyStore);
36
38
  this.blockProposalValidator = new BlockProposalValidator(epochCache);
37
- // Refresh epoch cache every second to trigger alert if participation in commitee changes
39
+ // Refresh epoch cache every second to trigger alert if participation in committee changes
38
40
  this.myAddress = this.keyStore.getAddress();
39
- this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommiteeUpdate.bind(this), log, 1000);
41
+ this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
40
42
  this.log.verbose(`Initialized validator with address ${this.keyStore.getAddress().toString()}`);
41
43
  }
42
- async handleEpochCommiteeUpdate() {
44
+ async handleEpochCommitteeUpdate() {
43
45
  try {
44
46
  const { committee, epoch } = await this.epochCache.getCommittee('now');
45
47
  if (epoch !== this.lastEpoch) {
@@ -55,13 +57,13 @@ import { ValidatorMetrics } from './metrics.js';
55
57
  this.log.error(`Error updating epoch committee`, err);
56
58
  }
57
59
  }
58
- static new(config, epochCache, p2pClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient()) {
60
+ static new(config, epochCache, p2pClient, blockSource, dateProvider = new DateProvider(), telemetry = getTelemetryClient()) {
59
61
  if (!config.validatorPrivateKey) {
60
62
  throw new InvalidValidatorPrivateKeyError();
61
63
  }
62
64
  const privateKey = validatePrivateKey(config.validatorPrivateKey);
63
65
  const localKeyStore = new LocalKeyStore(privateKey);
64
- const validator = new ValidatorClient(localKeyStore, epochCache, p2pClient, config, dateProvider, telemetry);
66
+ const validator = new ValidatorClient(localKeyStore, epochCache, p2pClient, blockSource, config, dateProvider, telemetry);
65
67
  validator.registerBlockProposalHandler();
66
68
  return validator;
67
69
  }
@@ -99,9 +101,10 @@ import { ValidatorMetrics } from './metrics.js';
99
101
  }
100
102
  async attestToProposal(proposal) {
101
103
  const slotNumber = proposal.slotNumber.toNumber();
104
+ const blockNumber = proposal.blockNumber.toNumber();
102
105
  const proposalInfo = {
103
106
  slotNumber,
104
- blockNumber: proposal.payload.header.globalVariables.blockNumber.toNumber(),
107
+ blockNumber,
105
108
  archive: proposal.payload.archive.toString(),
106
109
  txCount: proposal.payload.txHashes.length,
107
110
  txHashes: proposal.payload.txHashes.map((txHash)=>txHash.toString())
@@ -119,20 +122,38 @@ import { ValidatorMetrics } from './metrics.js';
119
122
  this.metrics.incFailedAttestations('invalid_proposal');
120
123
  return undefined;
121
124
  }
125
+ // Check that the parent proposal is a block we know, otherwise reexecution would fail.
126
+ // Q: Should we move this to the block proposal validator? If there, then p2p would check it
127
+ // before re-broadcasting it. This means that proposals built on top of an L1-reorgd-out block
128
+ // would not be rebroadcasted. But it also means that nodes that have not fully synced would
129
+ // not rebroadcast the proposal.
130
+ if (blockNumber > INITIAL_L2_BLOCK_NUM) {
131
+ const parentBlock = await this.blockSource.getBlock(blockNumber - 1);
132
+ if (parentBlock === undefined) {
133
+ this.log.verbose(`Parent block for ${blockNumber} not found, skipping attestation`);
134
+ this.metrics.incFailedAttestations('parent_block_not_found');
135
+ return undefined;
136
+ }
137
+ if (!proposal.payload.header.lastArchiveRoot.equals(parentBlock.archive.root)) {
138
+ this.log.verbose(`Parent block archive root for proposal does not match, skipping attestation`, {
139
+ proposalLastArchiveRoot: proposal.payload.header.lastArchiveRoot.toString(),
140
+ parentBlockArchiveRoot: parentBlock.archive.root.toString(),
141
+ ...proposalInfo
142
+ });
143
+ this.metrics.incFailedAttestations('parent_block_does_not_match');
144
+ return undefined;
145
+ }
146
+ }
122
147
  // Check that all of the transactions in the proposal are available in the tx pool before attesting
123
148
  this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
124
149
  try {
125
- await this.ensureTransactionsAreAvailable(proposal);
150
+ const txs = await this.ensureTransactionsAreAvailable(proposal);
126
151
  if (this.config.validatorReexecute) {
127
152
  this.log.verbose(`Re-executing transactions in the proposal before attesting`);
128
- await this.reExecuteTransactions(proposal);
153
+ await this.reExecuteTransactions(proposal, txs);
129
154
  }
130
155
  } catch (error) {
131
- if (error instanceof Error) {
132
- this.metrics.incFailedAttestations(error.name);
133
- } else {
134
- this.metrics.incFailedAttestations('unknown');
135
- }
156
+ this.metrics.incFailedAttestations(error instanceof Error ? error.name : 'unknown');
136
157
  // If the transactions are not available, then we should not attempt to attest
137
158
  if (error instanceof TransactionsNotAvailableError) {
138
159
  this.log.error(`Transactions not available, skipping attestation`, error, proposalInfo);
@@ -152,12 +173,13 @@ import { ValidatorMetrics } from './metrics.js';
152
173
  /**
153
174
  * Re-execute the transactions in the proposal and check that the state updates match the header state
154
175
  * @param proposal - The proposal to re-execute
155
- */ async reExecuteTransactions(proposal) {
176
+ */ async reExecuteTransactions(proposal, txs) {
156
177
  const { header, txHashes } = proposal.payload;
157
- const txs = (await Promise.all(txHashes.map((tx)=>this.p2pClient.getTxByHash(tx)))).filter((tx)=>tx !== undefined);
158
- // 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
159
179
  if (txs.length !== txHashes.length) {
160
- throw new TransactionsNotAvailableError(txHashes);
180
+ const foundTxHashes = await Promise.all(txs.map(async (tx)=>await tx.getTxHash()));
181
+ const missingTxHashes = txHashes.filter((txHash)=>!foundTxHashes.includes(txHash));
182
+ throw new TransactionsNotAvailableError(missingTxHashes);
161
183
  }
162
184
  // Assertion: This check will fail if re-execution is not enabled
163
185
  if (this.blockBuilder === undefined) {
@@ -165,7 +187,7 @@ import { ValidatorMetrics } from './metrics.js';
165
187
  }
166
188
  // Use the sequencer's block building logic to re-execute the transactions
167
189
  const stopTimer = this.metrics.reExecutionTimer();
168
- const { block, numFailedTxs } = await this.blockBuilder(txs, header.globalVariables, {
190
+ const { block, numFailedTxs } = await this.blockBuilder(proposal.blockNumber, header, txs, {
169
191
  validateOnly: true
170
192
  });
171
193
  stopTimer();
@@ -192,37 +214,87 @@ import { ValidatorMetrics } from './metrics.js';
192
214
  * 3. If we cannot retrieve them from the network, throw an error
193
215
  * @param proposal - The proposal to attest to
194
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
195
259
  const txHashes = proposal.payload.txHashes;
196
- const transactionStatuses = await Promise.all(txHashes.map((txHash)=>this.p2pClient.getTxStatus(txHash)));
197
- const missingTxs = txHashes.filter((_, index)=>![
198
- 'pending',
199
- 'mined'
200
- ].includes(transactionStatuses[index] ?? ''));
201
- if (missingTxs.length === 0) {
202
- return; // All transactions are available
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`);
203
265
  }
204
- this.log.verbose(`Missing ${missingTxs.length} transactions in the tx pool, requesting from the network`);
205
- const requestedTxs = await this.p2pClient.requestTxs(missingTxs);
206
- if (requestedTxs.some((tx)=>tx === undefined)) {
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) {
207
277
  throw new TransactionsNotAvailableError(missingTxs);
208
278
  }
279
+ await this.p2pClient.validate(retrievedTxs);
280
+ return retrievedTxs;
209
281
  }
210
- async createBlockProposal(header, archive, txs) {
211
- if (this.previousProposal?.slotNumber.equals(header.globalVariables.slotNumber)) {
282
+ async createBlockProposal(blockNumber, header, archive, stateReference, txs, options) {
283
+ if (this.previousProposal?.slotNumber.equals(header.slotNumber)) {
212
284
  this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
213
285
  return Promise.resolve(undefined);
214
286
  }
215
- const newProposal = await this.validationService.createBlockProposal(header, archive, txs);
287
+ const newProposal = await this.validationService.createBlockProposal(blockNumber, header, archive, stateReference, txs, options);
216
288
  this.previousProposal = newProposal;
217
289
  return newProposal;
218
290
  }
219
- broadcastBlockProposal(proposal) {
220
- this.p2pClient.broadcastProposal(proposal);
291
+ async broadcastBlockProposal(proposal) {
292
+ await this.p2pClient.broadcastProposal(proposal);
221
293
  }
222
294
  // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962)
223
295
  async collectAttestations(proposal, required, deadline) {
224
296
  // Wait and poll the p2pClient's attestation pool for this block until we have enough attestations
225
- const slot = proposal.payload.header.globalVariables.slotNumber.toBigInt();
297
+ const slot = proposal.payload.header.slotNumber.toBigInt();
226
298
  this.log.debug(`Collecting ${required} attestations for slot ${slot} with deadline ${deadline.toISOString()}`);
227
299
  if (+deadline < this.dateProvider.now()) {
228
300
  this.log.error(`Deadline ${deadline.toISOString()} for collecting ${required} attestations for slot ${slot} is in the past`);
@@ -263,7 +335,7 @@ import { ValidatorMetrics } from './metrics.js';
263
335
  function validatePrivateKey(privateKey) {
264
336
  try {
265
337
  return Buffer32.fromString(privateKey);
266
- } catch (error) {
338
+ } catch {
267
339
  throw new InvalidValidatorPrivateKeyError();
268
340
  }
269
341
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/validator-client",
3
- "version": "0.86.0",
3
+ "version": "0.87.0",
4
4
  "main": "dest/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -60,12 +60,13 @@
60
60
  ]
61
61
  },
62
62
  "dependencies": {
63
- "@aztec/epoch-cache": "0.86.0",
64
- "@aztec/ethereum": "0.86.0",
65
- "@aztec/foundation": "0.86.0",
66
- "@aztec/p2p": "0.86.0",
67
- "@aztec/stdlib": "0.86.0",
68
- "@aztec/telemetry-client": "0.86.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",
69
70
  "koa": "^2.16.1",
70
71
  "koa-router": "^12.0.0",
71
72
  "tslib": "^2.4.0",
@@ -74,11 +75,11 @@
74
75
  "devDependencies": {
75
76
  "@jest/globals": "^29.5.0",
76
77
  "@types/jest": "^29.5.0",
77
- "@types/node": "^18.7.23",
78
+ "@types/node": "^22.15.17",
78
79
  "jest": "^29.5.0",
79
80
  "jest-mock-extended": "^3.0.7",
80
81
  "ts-node": "^10.9.1",
81
- "typescript": "^5.0.4"
82
+ "typescript": "^5.3.3"
82
83
  },
83
84
  "files": [
84
85
  "dest",
@@ -87,6 +88,6 @@
87
88
  ],
88
89
  "types": "./dest/index.d.ts",
89
90
  "engines": {
90
- "node": ">=18"
91
+ "node": ">=20.10"
91
92
  }
92
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 { BlockAttestation, BlockProposal, ConsensusPayload, SignatureDomainSeparator } from '@aztec/stdlib/p2p';
5
- import type { BlockHeader, TxHash } from '@aztec/stdlib/tx';
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
 
@@ -12,16 +18,31 @@ export class ValidationService {
12
18
  /**
13
19
  * Create a block proposal with the given header, archive, and transactions
14
20
  *
21
+ * @param blockNumber - The block number this proposal is for
15
22
  * @param header - The block header
16
23
  * @param archive - The archive of the current block
17
24
  * @param txs - TxHash[] ordered list of transactions
18
25
  *
19
26
  * @returns A block proposal signing the above information (not the current implementation!!!)
20
27
  */
21
- createBlockProposal(header: BlockHeader, archive: Fr, txs: TxHash[]): Promise<BlockProposal> {
28
+ async createBlockProposal(
29
+ blockNumber: Fr,
30
+ header: ProposedBlockHeader,
31
+ archive: Fr,
32
+ stateReference: StateReference,
33
+ txs: Tx[],
34
+ options: BlockProposalOptions,
35
+ ): Promise<BlockProposal> {
22
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()));
23
39
 
24
- return BlockProposal.createProposalFromSigner(new ConsensusPayload(header, archive, txs), payloadSigner);
40
+ return BlockProposal.createProposalFromSigner(
41
+ blockNumber,
42
+ new ConsensusPayload(header, archive, stateReference, txHashes),
43
+ options.publishFullTxs ? txs : undefined,
44
+ payloadSigner,
45
+ );
25
46
  }
26
47
 
27
48
  /**
@@ -40,6 +61,6 @@ export class ValidationService {
40
61
  keccak256(proposal.payload.getPayloadToSign(SignatureDomainSeparator.blockAttestation)),
41
62
  );
42
63
  const sig = await this.keyStore.signMessage(buf);
43
- return new BlockAttestation(proposal.payload, sig);
64
+ return new BlockAttestation(proposal.blockNumber, proposal.payload, sig);
44
65
  }
45
66
  }
package/src/factory.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
2
  import type { DateProvider } from '@aztec/foundation/timer';
3
3
  import type { P2P } from '@aztec/p2p';
4
+ import type { L2BlockSource } from '@aztec/stdlib/block';
4
5
  import type { TelemetryClient } from '@aztec/telemetry-client';
5
6
 
6
7
  import { generatePrivateKey } from 'viem/accounts';
@@ -12,6 +13,7 @@ export function createValidatorClient(
12
13
  config: ValidatorClientConfig,
13
14
  deps: {
14
15
  p2pClient: P2P;
16
+ blockSource: L2BlockSource;
15
17
  telemetry: TelemetryClient;
16
18
  dateProvider: DateProvider;
17
19
  epochCache: EpochCache;
@@ -24,5 +26,12 @@ export function createValidatorClient(
24
26
  config.validatorPrivateKey = generatePrivateKey();
25
27
  }
26
28
 
27
- return ValidatorClient.new(config, deps.epochCache, deps.p2pClient, deps.dateProvider, deps.telemetry);
29
+ return ValidatorClient.new(
30
+ config,
31
+ deps.epochCache,
32
+ deps.p2pClient,
33
+ deps.blockSource,
34
+ deps.dateProvider,
35
+ deps.telemetry,
36
+ );
28
37
  }
package/src/validator.ts CHANGED
@@ -1,16 +1,17 @@
1
+ import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
1
2
  import type { EpochCache } from '@aztec/epoch-cache';
2
3
  import { Buffer32 } from '@aztec/foundation/buffer';
3
4
  import type { EthAddress } from '@aztec/foundation/eth-address';
4
- import type { Fr } from '@aztec/foundation/fields';
5
+ import { Fr } from '@aztec/foundation/fields';
5
6
  import { createLogger } from '@aztec/foundation/log';
6
7
  import { RunningPromise } from '@aztec/foundation/running-promise';
7
8
  import { sleep } from '@aztec/foundation/sleep';
8
9
  import { DateProvider, type Timer } from '@aztec/foundation/timer';
9
10
  import type { P2P } from '@aztec/p2p';
10
11
  import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
11
- import type { L2Block } from '@aztec/stdlib/block';
12
- import type { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
13
- import type { BlockHeader, GlobalVariables, Tx, TxHash } from '@aztec/stdlib/tx';
12
+ import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
13
+ import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
14
+ import type { ProposedBlockHeader, StateReference, Tx, TxHash } from '@aztec/stdlib/tx';
14
15
  import { type TelemetryClient, WithTracer, getTelemetryClient } from '@aztec/telemetry-client';
15
16
 
16
17
  import type { ValidatorClientConfig } from './config.js';
@@ -34,8 +35,9 @@ import { ValidatorMetrics } from './metrics.js';
34
35
  * We reuse the sequencer's block building functionality for re-execution
35
36
  */
36
37
  type BlockBuilderCallback = (
38
+ blockNumber: Fr,
39
+ header: ProposedBlockHeader,
37
40
  txs: Iterable<Tx> | AsyncIterableIterator<Tx>,
38
- globalVariables: GlobalVariables,
39
41
  opts?: { validateOnly?: boolean },
40
42
  ) => Promise<{
41
43
  block: L2Block;
@@ -51,10 +53,17 @@ export interface Validator {
51
53
  registerBlockBuilder(blockBuilder: BlockBuilderCallback): void;
52
54
 
53
55
  // Block validation responsibilities
54
- createBlockProposal(header: BlockHeader, archive: Fr, txs: TxHash[]): Promise<BlockProposal | undefined>;
55
- attestToProposal(proposal: BlockProposal): void;
56
-
57
- broadcastBlockProposal(proposal: BlockProposal): void;
56
+ createBlockProposal(
57
+ blockNumber: Fr,
58
+ header: ProposedBlockHeader,
59
+ archive: Fr,
60
+ stateReference: StateReference,
61
+ txs: Tx[],
62
+ options: BlockProposalOptions,
63
+ ): Promise<BlockProposal | undefined>;
64
+ attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined>;
65
+
66
+ broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
58
67
  collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
59
68
  }
60
69
 
@@ -81,6 +90,7 @@ export class ValidatorClient extends WithTracer implements Validator {
81
90
  private keyStore: ValidatorKeyStore,
82
91
  private epochCache: EpochCache,
83
92
  private p2pClient: P2P,
93
+ private blockSource: L2BlockSource,
84
94
  private config: ValidatorClientConfig,
85
95
  private dateProvider: DateProvider = new DateProvider(),
86
96
  telemetry: TelemetryClient = getTelemetryClient(),
@@ -94,14 +104,14 @@ export class ValidatorClient extends WithTracer implements Validator {
94
104
 
95
105
  this.blockProposalValidator = new BlockProposalValidator(epochCache);
96
106
 
97
- // Refresh epoch cache every second to trigger alert if participation in commitee changes
107
+ // Refresh epoch cache every second to trigger alert if participation in committee changes
98
108
  this.myAddress = this.keyStore.getAddress();
99
- this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommiteeUpdate.bind(this), log, 1000);
109
+ this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
100
110
 
101
111
  this.log.verbose(`Initialized validator with address ${this.keyStore.getAddress().toString()}`);
102
112
  }
103
113
 
104
- private async handleEpochCommiteeUpdate() {
114
+ private async handleEpochCommitteeUpdate() {
105
115
  try {
106
116
  const { committee, epoch } = await this.epochCache.getCommittee('now');
107
117
  if (epoch !== this.lastEpoch) {
@@ -122,6 +132,7 @@ export class ValidatorClient extends WithTracer implements Validator {
122
132
  config: ValidatorClientConfig,
123
133
  epochCache: EpochCache,
124
134
  p2pClient: P2P,
135
+ blockSource: L2BlockSource,
125
136
  dateProvider: DateProvider = new DateProvider(),
126
137
  telemetry: TelemetryClient = getTelemetryClient(),
127
138
  ) {
@@ -132,7 +143,15 @@ export class ValidatorClient extends WithTracer implements Validator {
132
143
  const privateKey = validatePrivateKey(config.validatorPrivateKey);
133
144
  const localKeyStore = new LocalKeyStore(privateKey);
134
145
 
135
- const validator = new ValidatorClient(localKeyStore, epochCache, p2pClient, config, dateProvider, telemetry);
146
+ const validator = new ValidatorClient(
147
+ localKeyStore,
148
+ epochCache,
149
+ p2pClient,
150
+ blockSource,
151
+ config,
152
+ dateProvider,
153
+ telemetry,
154
+ );
136
155
  validator.registerBlockProposalHandler();
137
156
  return validator;
138
157
  }
@@ -178,9 +197,10 @@ export class ValidatorClient extends WithTracer implements Validator {
178
197
 
179
198
  async attestToProposal(proposal: BlockProposal): Promise<BlockAttestation | undefined> {
180
199
  const slotNumber = proposal.slotNumber.toNumber();
200
+ const blockNumber = proposal.blockNumber.toNumber();
181
201
  const proposalInfo = {
182
202
  slotNumber,
183
- blockNumber: proposal.payload.header.globalVariables.blockNumber.toNumber(),
203
+ blockNumber,
184
204
  archive: proposal.payload.archive.toString(),
185
205
  txCount: proposal.payload.txHashes.length,
186
206
  txHashes: proposal.payload.txHashes.map(txHash => txHash.toString()),
@@ -201,21 +221,40 @@ export class ValidatorClient extends WithTracer implements Validator {
201
221
  return undefined;
202
222
  }
203
223
 
224
+ // Check that the parent proposal is a block we know, otherwise reexecution would fail.
225
+ // Q: Should we move this to the block proposal validator? If there, then p2p would check it
226
+ // before re-broadcasting it. This means that proposals built on top of an L1-reorgd-out block
227
+ // would not be rebroadcasted. But it also means that nodes that have not fully synced would
228
+ // not rebroadcast the proposal.
229
+ if (blockNumber > INITIAL_L2_BLOCK_NUM) {
230
+ const parentBlock = await this.blockSource.getBlock(blockNumber - 1);
231
+ if (parentBlock === undefined) {
232
+ this.log.verbose(`Parent block for ${blockNumber} not found, skipping attestation`);
233
+ this.metrics.incFailedAttestations('parent_block_not_found');
234
+ return undefined;
235
+ }
236
+ if (!proposal.payload.header.lastArchiveRoot.equals(parentBlock.archive.root)) {
237
+ this.log.verbose(`Parent block archive root for proposal does not match, skipping attestation`, {
238
+ proposalLastArchiveRoot: proposal.payload.header.lastArchiveRoot.toString(),
239
+ parentBlockArchiveRoot: parentBlock.archive.root.toString(),
240
+ ...proposalInfo,
241
+ });
242
+ this.metrics.incFailedAttestations('parent_block_does_not_match');
243
+ return undefined;
244
+ }
245
+ }
246
+
204
247
  // Check that all of the transactions in the proposal are available in the tx pool before attesting
205
248
  this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
206
249
  try {
207
- await this.ensureTransactionsAreAvailable(proposal);
250
+ const txs = await this.ensureTransactionsAreAvailable(proposal);
208
251
 
209
252
  if (this.config.validatorReexecute) {
210
253
  this.log.verbose(`Re-executing transactions in the proposal before attesting`);
211
- await this.reExecuteTransactions(proposal);
254
+ await this.reExecuteTransactions(proposal, txs);
212
255
  }
213
256
  } catch (error: any) {
214
- if (error instanceof Error) {
215
- this.metrics.incFailedAttestations(error.name);
216
- } else {
217
- this.metrics.incFailedAttestations('unknown');
218
- }
257
+ this.metrics.incFailedAttestations(error instanceof Error ? error.name : 'unknown');
219
258
 
220
259
  // If the transactions are not available, then we should not attempt to attest
221
260
  if (error instanceof TransactionsNotAvailableError) {
@@ -240,16 +279,14 @@ export class ValidatorClient extends WithTracer implements Validator {
240
279
  * Re-execute the transactions in the proposal and check that the state updates match the header state
241
280
  * @param proposal - The proposal to re-execute
242
281
  */
243
- async reExecuteTransactions(proposal: BlockProposal) {
282
+ async reExecuteTransactions(proposal: BlockProposal, txs: Tx[]) {
244
283
  const { header, txHashes } = proposal.payload;
245
284
 
246
- const txs = (await Promise.all(txHashes.map(tx => this.p2pClient.getTxByHash(tx)))).filter(
247
- tx => tx !== undefined,
248
- ) as Tx[];
249
-
250
- // 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
251
286
  if (txs.length !== txHashes.length) {
252
- throw new TransactionsNotAvailableError(txHashes);
287
+ const foundTxHashes = await Promise.all(txs.map(async tx => await tx.getTxHash()));
288
+ const missingTxHashes = txHashes.filter(txHash => !foundTxHashes.includes(txHash));
289
+ throw new TransactionsNotAvailableError(missingTxHashes);
253
290
  }
254
291
 
255
292
  // Assertion: This check will fail if re-execution is not enabled
@@ -259,7 +296,7 @@ export class ValidatorClient extends WithTracer implements Validator {
259
296
 
260
297
  // Use the sequencer's block building logic to re-execute the transactions
261
298
  const stopTimer = this.metrics.reExecutionTimer();
262
- const { block, numFailedTxs } = await this.blockBuilder(txs, header.globalVariables, {
299
+ const { block, numFailedTxs } = await this.blockBuilder(proposal.blockNumber, header, txs, {
263
300
  validateOnly: true,
264
301
  });
265
302
  stopTimer();
@@ -291,43 +328,130 @@ export class ValidatorClient extends WithTracer implements Validator {
291
328
  * 3. If we cannot retrieve them from the network, throw an error
292
329
  * @param proposal - The proposal to attest to
293
330
  */
294
- async ensureTransactionsAreAvailable(proposal: BlockProposal) {
295
- const txHashes: TxHash[] = proposal.payload.txHashes;
296
- const transactionStatuses = await Promise.all(txHashes.map(txHash => this.p2pClient.getTxStatus(txHash)));
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
+ }
297
351
 
298
- const missingTxs = txHashes.filter((_, index) => !['pending', 'mined'].includes(transactionStatuses[index] ?? ''));
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
+ }
299
372
 
300
- if (missingTxs.length === 0) {
301
- return; // All transactions are available
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
+ }
302
384
  }
303
385
 
304
- this.log.verbose(`Missing ${missingTxs.length} transactions in the tx pool, requesting from the network`);
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
+ }
305
400
 
306
- const requestedTxs = await this.p2pClient.requestTxs(missingTxs);
307
- if (requestedTxs.some(tx => tx === undefined)) {
308
- throw new TransactionsNotAvailableError(missingTxs);
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[]);
309
415
  }
416
+
417
+ await this.p2pClient.validate(retrievedTxs as Tx[]);
418
+
419
+ return retrievedTxs as Tx[];
310
420
  }
311
421
 
312
- async createBlockProposal(header: BlockHeader, archive: Fr, txs: TxHash[]): Promise<BlockProposal | undefined> {
313
- if (this.previousProposal?.slotNumber.equals(header.globalVariables.slotNumber)) {
422
+ async createBlockProposal(
423
+ blockNumber: Fr,
424
+ header: ProposedBlockHeader,
425
+ archive: Fr,
426
+ stateReference: StateReference,
427
+ txs: Tx[],
428
+ options: BlockProposalOptions,
429
+ ): Promise<BlockProposal | undefined> {
430
+ if (this.previousProposal?.slotNumber.equals(header.slotNumber)) {
314
431
  this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
315
432
  return Promise.resolve(undefined);
316
433
  }
317
434
 
318
- const newProposal = await this.validationService.createBlockProposal(header, archive, txs);
435
+ const newProposal = await this.validationService.createBlockProposal(
436
+ blockNumber,
437
+ header,
438
+ archive,
439
+ stateReference,
440
+ txs,
441
+ options,
442
+ );
319
443
  this.previousProposal = newProposal;
320
444
  return newProposal;
321
445
  }
322
446
 
323
- broadcastBlockProposal(proposal: BlockProposal): void {
324
- this.p2pClient.broadcastProposal(proposal);
447
+ async broadcastBlockProposal(proposal: BlockProposal): Promise<void> {
448
+ await this.p2pClient.broadcastProposal(proposal);
325
449
  }
326
450
 
327
451
  // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962)
328
452
  async collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]> {
329
453
  // Wait and poll the p2pClient's attestation pool for this block until we have enough attestations
330
- const slot = proposal.payload.header.globalVariables.slotNumber.toBigInt();
454
+ const slot = proposal.payload.header.slotNumber.toBigInt();
331
455
  this.log.debug(`Collecting ${required} attestations for slot ${slot} with deadline ${deadline.toISOString()}`);
332
456
 
333
457
  if (+deadline < this.dateProvider.now()) {
@@ -378,7 +502,7 @@ export class ValidatorClient extends WithTracer implements Validator {
378
502
  function validatePrivateKey(privateKey: string): Buffer32 {
379
503
  try {
380
504
  return Buffer32.fromString(privateKey);
381
- } catch (error) {
505
+ } catch {
382
506
  throw new InvalidValidatorPrivateKeyError();
383
507
  }
384
508
  }