@aztec/validator-client 0.85.0-alpha-testnet.5 → 0.85.0-alpha-testnet.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/duties/validation_service.d.ts +2 -2
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +4 -2
- package/dest/validator.d.ts +5 -5
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +66 -12
- package/package.json +7 -7
- package/src/duties/validation_service.ts +5 -3
- package/src/validator.ts +92 -20
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Fr } from '@aztec/foundation/fields';
|
|
2
2
|
import { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
|
|
3
|
-
import type { BlockHeader,
|
|
3
|
+
import type { BlockHeader, 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;
|
|
@@ -14,7 +14,7 @@ export declare class ValidationService {
|
|
|
14
14
|
*
|
|
15
15
|
* @returns A block proposal signing the above information (not the current implementation!!!)
|
|
16
16
|
*/
|
|
17
|
-
createBlockProposal(header: BlockHeader, archive: Fr, txs:
|
|
17
|
+
createBlockProposal(header: BlockHeader, archive: Fr, txs: Tx[]): Promise<BlockProposal>;
|
|
18
18
|
/**
|
|
19
19
|
* Attest to the given block proposal constructed by the current sequencer
|
|
20
20
|
*
|
|
@@ -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,
|
|
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,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnE,qBAAa,iBAAiB;IAChB,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,iBAAiB;IAE/C;;;;;;;;OAQG;IACG,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAQ9F;;;;;;;;OAQG;IACG,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAS3E"}
|
|
@@ -14,9 +14,11 @@ export class ValidationService {
|
|
|
14
14
|
* @param txs - TxHash[] ordered list of transactions
|
|
15
15
|
*
|
|
16
16
|
* @returns A block proposal signing the above information (not the current implementation!!!)
|
|
17
|
-
*/ createBlockProposal(header, archive, txs) {
|
|
17
|
+
*/ async createBlockProposal(header, archive, txs) {
|
|
18
18
|
const payloadSigner = (payload)=>this.keyStore.signMessage(payload);
|
|
19
|
-
|
|
19
|
+
// TODO: check if this is calculated earlier / can not be recomputed
|
|
20
|
+
const txHashes = await Promise.all(txs.map((tx)=>tx.getTxHash()));
|
|
21
|
+
return BlockProposal.createProposalFromSigner(new ConsensusPayload(header, archive, txHashes), txs, payloadSigner);
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
24
|
* Attest to the given block proposal constructed by the current sequencer
|
package/dest/validator.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { DateProvider, type Timer } from '@aztec/foundation/timer';
|
|
|
5
5
|
import type { P2P } from '@aztec/p2p';
|
|
6
6
|
import type { L2Block } from '@aztec/stdlib/block';
|
|
7
7
|
import type { BlockAttestation, BlockProposal } from '@aztec/stdlib/p2p';
|
|
8
|
-
import type { BlockHeader, GlobalVariables, Tx
|
|
8
|
+
import type { BlockHeader, GlobalVariables, Tx } from '@aztec/stdlib/tx';
|
|
9
9
|
import { type TelemetryClient, WithTracer } from '@aztec/telemetry-client';
|
|
10
10
|
import type { ValidatorClientConfig } from './config.js';
|
|
11
11
|
import type { ValidatorKeyStore } from './key_store/interface.js';
|
|
@@ -27,7 +27,7 @@ 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:
|
|
30
|
+
createBlockProposal(header: BlockHeader, archive: Fr, txs: Tx[]): Promise<BlockProposal | undefined>;
|
|
31
31
|
attestToProposal(proposal: BlockProposal): void;
|
|
32
32
|
broadcastBlockProposal(proposal: BlockProposal): void;
|
|
33
33
|
collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
|
|
@@ -68,7 +68,7 @@ export declare class ValidatorClient extends WithTracer implements Validator {
|
|
|
68
68
|
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
69
69
|
* @param proposal - The proposal to re-execute
|
|
70
70
|
*/
|
|
71
|
-
reExecuteTransactions(proposal: BlockProposal): Promise<void>;
|
|
71
|
+
reExecuteTransactions(proposal: BlockProposal, txs: Tx[]): Promise<void>;
|
|
72
72
|
/**
|
|
73
73
|
* Ensure that all of the transactions in the proposal are available in the tx pool before attesting
|
|
74
74
|
*
|
|
@@ -77,8 +77,8 @@ export declare class ValidatorClient extends WithTracer implements Validator {
|
|
|
77
77
|
* 3. If we cannot retrieve them from the network, throw an error
|
|
78
78
|
* @param proposal - The proposal to attest to
|
|
79
79
|
*/
|
|
80
|
-
ensureTransactionsAreAvailable(proposal: BlockProposal): Promise<
|
|
81
|
-
createBlockProposal(header: BlockHeader, archive: Fr, txs:
|
|
80
|
+
ensureTransactionsAreAvailable(proposal: BlockProposal): Promise<Tx[]>;
|
|
81
|
+
createBlockProposal(header: BlockHeader, archive: Fr, txs: Tx[]): Promise<BlockProposal | undefined>;
|
|
82
82
|
broadcastBlockProposal(proposal: BlockProposal): void;
|
|
83
83
|
collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
|
|
84
84
|
private doAttestToProposal;
|
package/dest/validator.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAInD,OAAO,EAAE,YAAY,EAAE,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
|
package/dest/validator.js
CHANGED
|
@@ -122,10 +122,10 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
122
122
|
// Check that all of the transactions in the proposal are available in the tx pool before attesting
|
|
123
123
|
this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
|
|
124
124
|
try {
|
|
125
|
-
await this.ensureTransactionsAreAvailable(proposal);
|
|
125
|
+
const txs = await this.ensureTransactionsAreAvailable(proposal);
|
|
126
126
|
if (this.config.validatorReexecute) {
|
|
127
127
|
this.log.verbose(`Re-executing transactions in the proposal before attesting`);
|
|
128
|
-
await this.reExecuteTransactions(proposal);
|
|
128
|
+
await this.reExecuteTransactions(proposal, txs);
|
|
129
129
|
}
|
|
130
130
|
} catch (error) {
|
|
131
131
|
if (error instanceof Error) {
|
|
@@ -152,12 +152,13 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
152
152
|
/**
|
|
153
153
|
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
154
154
|
* @param proposal - The proposal to re-execute
|
|
155
|
-
*/ async reExecuteTransactions(proposal) {
|
|
155
|
+
*/ async reExecuteTransactions(proposal, txs) {
|
|
156
156
|
const { header, txHashes } = proposal.payload;
|
|
157
|
-
|
|
158
|
-
// If we cannot request all of the transactions, then we should fail
|
|
157
|
+
// If we do not have all of the transactions, then we should fail
|
|
159
158
|
if (txs.length !== txHashes.length) {
|
|
160
|
-
|
|
159
|
+
const foundTxHashes = await Promise.all(txs.map(async (tx)=>(await tx.getTxHash()).toString()));
|
|
160
|
+
const missingTxHashes = txHashes.filter((txHash)=>!foundTxHashes.includes(txHash.toString()));
|
|
161
|
+
throw new TransactionsNotAvailableError(missingTxHashes);
|
|
161
162
|
}
|
|
162
163
|
// Assertion: This check will fail if re-execution is not enabled
|
|
163
164
|
if (this.blockBuilder === undefined) {
|
|
@@ -192,17 +193,70 @@ import { ValidatorMetrics } from './metrics.js';
|
|
|
192
193
|
* 3. If we cannot retrieve them from the network, throw an error
|
|
193
194
|
* @param proposal - The proposal to attest to
|
|
194
195
|
*/ async ensureTransactionsAreAvailable(proposal) {
|
|
196
|
+
if (proposal.payload.txHashes.length === 0) {
|
|
197
|
+
this.log.verbose(`Received block proposal with no transactions, skipping transaction availability check`);
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
// Is this a new style proposal?
|
|
201
|
+
if (proposal.txs && proposal.txs.length > 0 && proposal.txs.length === proposal.payload.txHashes.length) {
|
|
202
|
+
// Yes, any txs that we already have we should use
|
|
203
|
+
this.log.info(`Using new style proposal with ${proposal.txs.length} transactions`);
|
|
204
|
+
// Request from the pool based on the signed hashes in the payload
|
|
205
|
+
const hashesFromPayload = proposal.payload.txHashes;
|
|
206
|
+
const txsToUse = await this.p2pClient.getTxsByHashFromPool(hashesFromPayload);
|
|
207
|
+
const missingTxs = txsToUse.filter((tx)=>tx === undefined).length;
|
|
208
|
+
if (missingTxs > 0) {
|
|
209
|
+
this.log.verbose(`Missing ${missingTxs}/${hashesFromPayload.length} transactions in the tx pool, will attempt to take from the proposal`);
|
|
210
|
+
}
|
|
211
|
+
let usedFromProposal = 0;
|
|
212
|
+
// Fill any holes with txs in the proposal, provided their hash matches the hash in the payload
|
|
213
|
+
for(let i = 0; i < txsToUse.length; i++){
|
|
214
|
+
if (txsToUse[i] === undefined) {
|
|
215
|
+
// We don't have the transaction, take from the proposal, provided the hash is the same
|
|
216
|
+
const hashOfTxInProposal = await proposal.txs[i].getTxHash();
|
|
217
|
+
if (hashOfTxInProposal.equals(hashesFromPayload[i])) {
|
|
218
|
+
// Hash is equal, we can use the tx from the proposal
|
|
219
|
+
txsToUse[i] = proposal.txs[i];
|
|
220
|
+
usedFromProposal++;
|
|
221
|
+
} else {
|
|
222
|
+
this.log.warn(`Unable to take tx: ${hashOfTxInProposal.toString()} from the proposal, it does not match payload hash: ${hashesFromPayload[i].toString()}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// See if we still have any holes, if there are then we were not successful and will try the old method
|
|
227
|
+
if (txsToUse.some((tx)=>tx === undefined)) {
|
|
228
|
+
this.log.warn(`Failed to use transactions from proposal. Falling back to old proposal logic`);
|
|
229
|
+
} else {
|
|
230
|
+
this.log.info(`Successfully used ${usedFromProposal}/${hashesFromPayload.length} transactions from the proposal`);
|
|
231
|
+
await this.p2pClient.validate(txsToUse);
|
|
232
|
+
return txsToUse;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
this.log.info(`Using old style proposal with ${proposal.payload.txHashes.length} transactions`);
|
|
236
|
+
// Old style proposal, we will perform a request by hash from pool
|
|
237
|
+
// This will request from network any txs that are missing
|
|
195
238
|
const txHashes = proposal.payload.txHashes;
|
|
239
|
+
// This part is just for logging that we are requesting from the network
|
|
196
240
|
const availability = await this.p2pClient.hasTxsInPool(txHashes);
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
199
|
-
|
|
241
|
+
const notAvailable = availability.filter((availability)=>availability === false);
|
|
242
|
+
if (notAvailable.length) {
|
|
243
|
+
this.log.verbose(`Missing ${notAvailable.length} transactions in the tx pool, will need to request from the network`);
|
|
200
244
|
}
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
245
|
+
// This will request from the network any txs that are missing
|
|
246
|
+
const retrievedTxs = await this.p2pClient.getTxsByHash(txHashes);
|
|
247
|
+
const missingTxs = retrievedTxs.map((tx, index)=>{
|
|
248
|
+
// Return the hash of any that we did not get
|
|
249
|
+
if (tx === undefined) {
|
|
250
|
+
return txHashes[index];
|
|
251
|
+
} else {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
}).filter((hash)=>hash !== undefined);
|
|
255
|
+
if (missingTxs.length > 0) {
|
|
204
256
|
throw new TransactionsNotAvailableError(missingTxs);
|
|
205
257
|
}
|
|
258
|
+
await this.p2pClient.validate(retrievedTxs);
|
|
259
|
+
return retrievedTxs;
|
|
206
260
|
}
|
|
207
261
|
async createBlockProposal(header, archive, txs) {
|
|
208
262
|
if (this.previousProposal?.slotNumber.equals(header.globalVariables.slotNumber)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "0.85.0-alpha-testnet.
|
|
3
|
+
"version": "0.85.0-alpha-testnet.8",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
]
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@aztec/epoch-cache": "0.85.0-alpha-testnet.
|
|
66
|
-
"@aztec/ethereum": "0.85.0-alpha-testnet.
|
|
67
|
-
"@aztec/foundation": "0.85.0-alpha-testnet.
|
|
68
|
-
"@aztec/p2p": "0.85.0-alpha-testnet.
|
|
69
|
-
"@aztec/stdlib": "0.85.0-alpha-testnet.
|
|
70
|
-
"@aztec/telemetry-client": "0.85.0-alpha-testnet.
|
|
65
|
+
"@aztec/epoch-cache": "0.85.0-alpha-testnet.8",
|
|
66
|
+
"@aztec/ethereum": "0.85.0-alpha-testnet.8",
|
|
67
|
+
"@aztec/foundation": "0.85.0-alpha-testnet.8",
|
|
68
|
+
"@aztec/p2p": "0.85.0-alpha-testnet.8",
|
|
69
|
+
"@aztec/stdlib": "0.85.0-alpha-testnet.8",
|
|
70
|
+
"@aztec/telemetry-client": "0.85.0-alpha-testnet.8",
|
|
71
71
|
"koa": "^2.16.1",
|
|
72
72
|
"koa-router": "^12.0.0",
|
|
73
73
|
"tslib": "^2.4.0",
|
|
@@ -2,7 +2,7 @@ import { Buffer32 } from '@aztec/foundation/buffer';
|
|
|
2
2
|
import { keccak256 } from '@aztec/foundation/crypto';
|
|
3
3
|
import type { Fr } from '@aztec/foundation/fields';
|
|
4
4
|
import { BlockAttestation, BlockProposal, ConsensusPayload, SignatureDomainSeparator } from '@aztec/stdlib/p2p';
|
|
5
|
-
import type { BlockHeader,
|
|
5
|
+
import type { BlockHeader, Tx } from '@aztec/stdlib/tx';
|
|
6
6
|
|
|
7
7
|
import type { ValidatorKeyStore } from '../key_store/interface.js';
|
|
8
8
|
|
|
@@ -18,10 +18,12 @@ export class ValidationService {
|
|
|
18
18
|
*
|
|
19
19
|
* @returns A block proposal signing the above information (not the current implementation!!!)
|
|
20
20
|
*/
|
|
21
|
-
createBlockProposal(header: BlockHeader, archive: Fr, txs:
|
|
21
|
+
async createBlockProposal(header: BlockHeader, archive: Fr, txs: Tx[]): Promise<BlockProposal> {
|
|
22
22
|
const payloadSigner = (payload: Buffer32) => this.keyStore.signMessage(payload);
|
|
23
|
+
// TODO: check if this is calculated earlier / can not be recomputed
|
|
24
|
+
const txHashes = await Promise.all(txs.map(tx => tx.getTxHash()));
|
|
23
25
|
|
|
24
|
-
return BlockProposal.createProposalFromSigner(new ConsensusPayload(header, archive,
|
|
26
|
+
return BlockProposal.createProposalFromSigner(new ConsensusPayload(header, archive, txHashes), txs, payloadSigner);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
package/src/validator.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface Validator {
|
|
|
51
51
|
registerBlockBuilder(blockBuilder: BlockBuilderCallback): void;
|
|
52
52
|
|
|
53
53
|
// Block validation responsibilities
|
|
54
|
-
createBlockProposal(header: BlockHeader, archive: Fr, txs:
|
|
54
|
+
createBlockProposal(header: BlockHeader, archive: Fr, txs: Tx[]): Promise<BlockProposal | undefined>;
|
|
55
55
|
attestToProposal(proposal: BlockProposal): void;
|
|
56
56
|
|
|
57
57
|
broadcastBlockProposal(proposal: BlockProposal): void;
|
|
@@ -204,11 +204,11 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
204
204
|
// Check that all of the transactions in the proposal are available in the tx pool before attesting
|
|
205
205
|
this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
|
|
206
206
|
try {
|
|
207
|
-
await this.ensureTransactionsAreAvailable(proposal);
|
|
207
|
+
const txs = await this.ensureTransactionsAreAvailable(proposal);
|
|
208
208
|
|
|
209
209
|
if (this.config.validatorReexecute) {
|
|
210
210
|
this.log.verbose(`Re-executing transactions in the proposal before attesting`);
|
|
211
|
-
await this.reExecuteTransactions(proposal);
|
|
211
|
+
await this.reExecuteTransactions(proposal, txs);
|
|
212
212
|
}
|
|
213
213
|
} catch (error: any) {
|
|
214
214
|
if (error instanceof Error) {
|
|
@@ -240,16 +240,14 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
240
240
|
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
241
241
|
* @param proposal - The proposal to re-execute
|
|
242
242
|
*/
|
|
243
|
-
async reExecuteTransactions(proposal: BlockProposal) {
|
|
243
|
+
async reExecuteTransactions(proposal: BlockProposal, txs: Tx[]) {
|
|
244
244
|
const { header, txHashes } = proposal.payload;
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
tx => tx !== undefined,
|
|
248
|
-
) as Tx[];
|
|
249
|
-
|
|
250
|
-
// If we cannot request all of the transactions, then we should fail
|
|
246
|
+
// If we do not have all of the transactions, then we should fail
|
|
251
247
|
if (txs.length !== txHashes.length) {
|
|
252
|
-
|
|
248
|
+
const foundTxHashes = await Promise.all(txs.map(async tx => (await tx.getTxHash()).toString()));
|
|
249
|
+
const missingTxHashes = txHashes.filter(txHash => !foundTxHashes.includes(txHash.toString()));
|
|
250
|
+
throw new TransactionsNotAvailableError(missingTxHashes);
|
|
253
251
|
}
|
|
254
252
|
|
|
255
253
|
// Assertion: This check will fail if re-execution is not enabled
|
|
@@ -291,24 +289,98 @@ export class ValidatorClient extends WithTracer implements Validator {
|
|
|
291
289
|
* 3. If we cannot retrieve them from the network, throw an error
|
|
292
290
|
* @param proposal - The proposal to attest to
|
|
293
291
|
*/
|
|
294
|
-
async ensureTransactionsAreAvailable(proposal: BlockProposal) {
|
|
292
|
+
async ensureTransactionsAreAvailable(proposal: BlockProposal): Promise<Tx[]> {
|
|
293
|
+
if (proposal.payload.txHashes.length === 0) {
|
|
294
|
+
this.log.verbose(`Received block proposal with no transactions, skipping transaction availability check`);
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
// Is this a new style proposal?
|
|
298
|
+
if (proposal.txs && proposal.txs.length > 0 && proposal.txs.length === proposal.payload.txHashes.length) {
|
|
299
|
+
// Yes, any txs that we already have we should use
|
|
300
|
+
this.log.info(`Using new style proposal with ${proposal.txs.length} transactions`);
|
|
301
|
+
|
|
302
|
+
// Request from the pool based on the signed hashes in the payload
|
|
303
|
+
const hashesFromPayload = proposal.payload.txHashes;
|
|
304
|
+
const txsToUse = await this.p2pClient.getTxsByHashFromPool(hashesFromPayload);
|
|
305
|
+
|
|
306
|
+
const missingTxs = txsToUse.filter(tx => tx === undefined).length;
|
|
307
|
+
if (missingTxs > 0) {
|
|
308
|
+
this.log.verbose(
|
|
309
|
+
`Missing ${missingTxs}/${hashesFromPayload.length} transactions in the tx pool, will attempt to take from the proposal`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let usedFromProposal = 0;
|
|
314
|
+
|
|
315
|
+
// Fill any holes with txs in the proposal, provided their hash matches the hash in the payload
|
|
316
|
+
for (let i = 0; i < txsToUse.length; i++) {
|
|
317
|
+
if (txsToUse[i] === undefined) {
|
|
318
|
+
// We don't have the transaction, take from the proposal, provided the hash is the same
|
|
319
|
+
const hashOfTxInProposal = await proposal.txs[i].getTxHash();
|
|
320
|
+
if (hashOfTxInProposal.equals(hashesFromPayload[i])) {
|
|
321
|
+
// Hash is equal, we can use the tx from the proposal
|
|
322
|
+
txsToUse[i] = proposal.txs[i];
|
|
323
|
+
usedFromProposal++;
|
|
324
|
+
} else {
|
|
325
|
+
this.log.warn(
|
|
326
|
+
`Unable to take tx: ${hashOfTxInProposal.toString()} from the proposal, it does not match payload hash: ${hashesFromPayload[
|
|
327
|
+
i
|
|
328
|
+
].toString()}`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// See if we still have any holes, if there are then we were not successful and will try the old method
|
|
335
|
+
if (txsToUse.some(tx => tx === undefined)) {
|
|
336
|
+
this.log.warn(`Failed to use transactions from proposal. Falling back to old proposal logic`);
|
|
337
|
+
} else {
|
|
338
|
+
this.log.info(
|
|
339
|
+
`Successfully used ${usedFromProposal}/${hashesFromPayload.length} transactions from the proposal`,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
await this.p2pClient.validate(txsToUse as Tx[]);
|
|
343
|
+
return txsToUse as Tx[];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
this.log.info(`Using old style proposal with ${proposal.payload.txHashes.length} transactions`);
|
|
348
|
+
|
|
349
|
+
// Old style proposal, we will perform a request by hash from pool
|
|
350
|
+
// This will request from network any txs that are missing
|
|
295
351
|
const txHashes: TxHash[] = proposal.payload.txHashes;
|
|
352
|
+
|
|
353
|
+
// This part is just for logging that we are requesting from the network
|
|
296
354
|
const availability = await this.p2pClient.hasTxsInPool(txHashes);
|
|
297
|
-
const
|
|
355
|
+
const notAvailable = availability.filter(availability => availability === false);
|
|
356
|
+
if (notAvailable.length) {
|
|
357
|
+
this.log.verbose(
|
|
358
|
+
`Missing ${notAvailable.length} transactions in the tx pool, will need to request from the network`,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
298
361
|
|
|
299
|
-
|
|
300
|
-
|
|
362
|
+
// This will request from the network any txs that are missing
|
|
363
|
+
const retrievedTxs = await this.p2pClient.getTxsByHash(txHashes);
|
|
364
|
+
const missingTxs = retrievedTxs
|
|
365
|
+
.map((tx, index) => {
|
|
366
|
+
// Return the hash of any that we did not get
|
|
367
|
+
if (tx === undefined) {
|
|
368
|
+
return txHashes[index];
|
|
369
|
+
} else {
|
|
370
|
+
return undefined;
|
|
371
|
+
}
|
|
372
|
+
})
|
|
373
|
+
.filter(hash => hash !== undefined);
|
|
374
|
+
if (missingTxs.length > 0) {
|
|
375
|
+
throw new TransactionsNotAvailableError(missingTxs as TxHash[]);
|
|
301
376
|
}
|
|
302
377
|
|
|
303
|
-
this.
|
|
378
|
+
await this.p2pClient.validate(retrievedTxs as Tx[]);
|
|
304
379
|
|
|
305
|
-
|
|
306
|
-
if (requestedTxs.some(tx => tx === undefined)) {
|
|
307
|
-
throw new TransactionsNotAvailableError(missingTxs);
|
|
308
|
-
}
|
|
380
|
+
return retrievedTxs as Tx[];
|
|
309
381
|
}
|
|
310
382
|
|
|
311
|
-
async createBlockProposal(header: BlockHeader, archive: Fr, txs:
|
|
383
|
+
async createBlockProposal(header: BlockHeader, archive: Fr, txs: Tx[]): Promise<BlockProposal | undefined> {
|
|
312
384
|
if (this.previousProposal?.slotNumber.equals(header.globalVariables.slotNumber)) {
|
|
313
385
|
this.log.verbose(`Already made a proposal for the same slot, skipping proposal`);
|
|
314
386
|
return Promise.resolve(undefined);
|