@aztec/p2p 0.55.1 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/attestation_pool/attestation_pool.d.ts +2 -1
- package/dest/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/attestation_pool/memory_attestation_pool.d.ts +2 -1
- package/dest/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
- package/dest/attestation_pool/memory_attestation_pool.js +48 -15
- package/dest/attestation_pool/mocks.d.ts +2 -1
- package/dest/attestation_pool/mocks.d.ts.map +1 -1
- package/dest/attestation_pool/mocks.js +7 -7
- package/dest/client/index.d.ts +4 -1
- package/dest/client/index.d.ts.map +1 -1
- package/dest/client/index.js +7 -3
- package/dest/client/p2p_client.d.ts +25 -6
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +28 -15
- package/dest/epoch_proof_quote_pool/epoch_proof_quote_pool.d.ts +7 -0
- package/dest/epoch_proof_quote_pool/epoch_proof_quote_pool.d.ts.map +1 -0
- package/dest/epoch_proof_quote_pool/epoch_proof_quote_pool.js +2 -0
- package/dest/epoch_proof_quote_pool/index.d.ts +4 -0
- package/dest/epoch_proof_quote_pool/index.d.ts.map +1 -0
- package/dest/epoch_proof_quote_pool/index.js +4 -0
- package/dest/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.d.ts +10 -0
- package/dest/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.d.ts.map +1 -0
- package/dest/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.js +22 -0
- package/dest/epoch_proof_quote_pool/test_utils.d.ts +8 -0
- package/dest/epoch_proof_quote_pool/test_utils.d.ts.map +1 -0
- package/dest/epoch_proof_quote_pool/test_utils.js +21 -0
- package/dest/index.d.ts +4 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +5 -4
- package/dest/mocks/index.d.ts +13 -4
- package/dest/mocks/index.d.ts.map +1 -1
- package/dest/mocks/index.js +26 -9
- package/dest/service/libp2p_service.d.ts +16 -1
- package/dest/service/libp2p_service.d.ts.map +1 -1
- package/dest/service/libp2p_service.js +55 -25
- package/dest/service/reqresp/interface.d.ts +7 -0
- package/dest/service/reqresp/interface.d.ts.map +1 -1
- package/dest/service/reqresp/interface.js +7 -1
- package/dest/service/reqresp/rate_limiter/rate_limiter.d.ts +13 -3
- package/dest/service/reqresp/rate_limiter/rate_limiter.d.ts.map +1 -1
- package/dest/service/reqresp/rate_limiter/rate_limiter.js +29 -7
- package/dest/service/reqresp/reqresp.d.ts +56 -6
- package/dest/service/reqresp/reqresp.d.ts.map +1 -1
- package/dest/service/reqresp/reqresp.js +79 -12
- package/dest/tx_validator/aggregate_tx_validator.d.ts +1 -0
- package/dest/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
- package/dest/tx_validator/aggregate_tx_validator.js +10 -1
- package/dest/tx_validator/data_validator.d.ts +1 -0
- package/dest/tx_validator/data_validator.d.ts.map +1 -1
- package/dest/tx_validator/data_validator.js +4 -1
- package/dest/tx_validator/double_spend_validator.d.ts +1 -0
- package/dest/tx_validator/double_spend_validator.d.ts.map +1 -1
- package/dest/tx_validator/double_spend_validator.js +4 -1
- package/dest/tx_validator/metadata_validator.d.ts +1 -0
- package/dest/tx_validator/metadata_validator.d.ts.map +1 -1
- package/dest/tx_validator/metadata_validator.js +4 -1
- package/package.json +10 -6
- package/src/attestation_pool/attestation_pool.ts +2 -1
- package/src/attestation_pool/memory_attestation_pool.ts +56 -17
- package/src/attestation_pool/mocks.ts +11 -6
- package/src/client/index.ts +20 -3
- package/src/client/p2p_client.ts +46 -15
- package/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts +7 -0
- package/src/epoch_proof_quote_pool/index.ts +3 -0
- package/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts +26 -0
- package/src/epoch_proof_quote_pool/test_utils.ts +26 -0
- package/src/index.ts +4 -3
- package/src/mocks/index.ts +35 -7
- package/src/service/libp2p_service.ts +59 -26
- package/src/service/reqresp/interface.ts +20 -0
- package/src/service/reqresp/rate_limiter/rate_limiter.ts +30 -7
- package/src/service/reqresp/reqresp.ts +91 -13
- package/src/tx_validator/aggregate_tx_validator.ts +10 -0
- package/src/tx_validator/data_validator.ts +4 -0
- package/src/tx_validator/double_spend_validator.ts +4 -0
- package/src/tx_validator/metadata_validator.ts +4 -0
- package/dest/client/mocks.d.ts +0 -65
- package/dest/client/mocks.d.ts.map +0 -1
- package/dest/client/mocks.js +0 -106
- package/src/client/mocks.ts +0 -129
|
@@ -47,13 +47,13 @@ import { PeerErrorSeverity } from './peer_scoring.js';
|
|
|
47
47
|
import { pingHandler, statusHandler } from './reqresp/handlers.js';
|
|
48
48
|
import {
|
|
49
49
|
DEFAULT_SUB_PROTOCOL_HANDLERS,
|
|
50
|
+
DEFAULT_SUB_PROTOCOL_VALIDATORS,
|
|
50
51
|
PING_PROTOCOL,
|
|
51
52
|
type ReqRespSubProtocol,
|
|
52
53
|
type ReqRespSubProtocolHandlers,
|
|
53
54
|
STATUS_PROTOCOL,
|
|
54
55
|
type SubProtocolMap,
|
|
55
56
|
TX_REQ_PROTOCOL,
|
|
56
|
-
subProtocolMap,
|
|
57
57
|
} from './reqresp/interface.js';
|
|
58
58
|
import { ReqResp } from './reqresp/reqresp.js';
|
|
59
59
|
import type { P2PService, PeerDiscoveryService } from './service.js';
|
|
@@ -109,7 +109,7 @@ export class LibP2PService implements P2PService {
|
|
|
109
109
|
return this.peerManager.getPeerScore(peerId);
|
|
110
110
|
};
|
|
111
111
|
this.node.services.pubsub.score.params.appSpecificWeight = 10;
|
|
112
|
-
this.reqresp = new ReqResp(config, node);
|
|
112
|
+
this.reqresp = new ReqResp(config, node, this.peerManager);
|
|
113
113
|
|
|
114
114
|
this.blockReceivedCallback = (block: BlockProposal): Promise<BlockAttestation | undefined> => {
|
|
115
115
|
this.logger.verbose(
|
|
@@ -162,7 +162,13 @@ export class LibP2PService implements P2PService {
|
|
|
162
162
|
this.peerManager.heartbeat();
|
|
163
163
|
}, this.config.peerCheckIntervalMS);
|
|
164
164
|
this.discoveryRunningPromise.start();
|
|
165
|
-
|
|
165
|
+
|
|
166
|
+
// Define the sub protocol validators - This is done within this start() method to gain a callback to the existing validateTx function
|
|
167
|
+
const reqrespSubProtocolValidators = {
|
|
168
|
+
...DEFAULT_SUB_PROTOCOL_VALIDATORS,
|
|
169
|
+
[TX_REQ_PROTOCOL]: this.validateRequestedTx.bind(this),
|
|
170
|
+
};
|
|
171
|
+
await this.reqresp.start(this.requestResponseHandlers, reqrespSubProtocolValidators);
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
/**
|
|
@@ -176,12 +182,12 @@ export class LibP2PService implements P2PService {
|
|
|
176
182
|
await this.discoveryRunningPromise?.stop();
|
|
177
183
|
this.logger.debug('Stopping peer discovery service...');
|
|
178
184
|
await this.peerDiscoveryService.stop();
|
|
185
|
+
this.logger.debug('Request response service stopped...');
|
|
186
|
+
await this.reqresp.stop();
|
|
179
187
|
this.logger.debug('Stopping LibP2P...');
|
|
180
188
|
await this.stopLibP2P();
|
|
181
189
|
this.logger.info('LibP2P service stopped');
|
|
182
190
|
this.logger.debug('Stopping request response service...');
|
|
183
|
-
await this.reqresp.stop();
|
|
184
|
-
this.logger.debug('Request response service stopped...');
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
/**
|
|
@@ -302,18 +308,11 @@ export class LibP2PService implements P2PService {
|
|
|
302
308
|
* @param request The request type to send
|
|
303
309
|
* @returns
|
|
304
310
|
*/
|
|
305
|
-
|
|
311
|
+
sendRequest<SubProtocol extends ReqRespSubProtocol>(
|
|
306
312
|
protocol: SubProtocol,
|
|
307
313
|
request: InstanceType<SubProtocolMap[SubProtocol]['request']>,
|
|
308
314
|
): Promise<InstanceType<SubProtocolMap[SubProtocol]['response']> | undefined> {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const res = await this.reqresp.sendRequest(protocol, request.toBuffer());
|
|
312
|
-
if (!res) {
|
|
313
|
-
return undefined;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return pair.response.fromBuffer(res!);
|
|
315
|
+
return this.reqresp.sendRequest(protocol, request);
|
|
317
316
|
}
|
|
318
317
|
|
|
319
318
|
/**
|
|
@@ -418,19 +417,53 @@ export class LibP2PService implements P2PService {
|
|
|
418
417
|
const txHashString = txHash.toString();
|
|
419
418
|
this.logger.verbose(`Received tx ${txHashString} from external peer.`);
|
|
420
419
|
|
|
421
|
-
const isValidTx = await this.
|
|
420
|
+
const isValidTx = await this.validatePropagatedTx(tx, peerId);
|
|
422
421
|
|
|
423
422
|
if (isValidTx) {
|
|
424
423
|
await this.txPool.addTxs([tx]);
|
|
425
424
|
}
|
|
426
425
|
}
|
|
427
426
|
|
|
428
|
-
|
|
427
|
+
/**
|
|
428
|
+
* Validate a tx that has been requested from a peer.
|
|
429
|
+
*
|
|
430
|
+
* The core component of this validator is that the tx hash MUST match the requested tx hash,
|
|
431
|
+
* In order to perform this check, the tx proof must be verified.
|
|
432
|
+
*
|
|
433
|
+
* Note: This function is called from within `ReqResp.sendRequest` as part of the
|
|
434
|
+
* TX_REQ_PROTOCOL subprotocol validation.
|
|
435
|
+
*
|
|
436
|
+
* @param requestedTxHash - The hash of the tx that was requested.
|
|
437
|
+
* @param responseTx - The tx that was received as a response to the request.
|
|
438
|
+
* @param peerId - The peer ID of the peer that sent the tx.
|
|
439
|
+
* @returns True if the tx is valid, false otherwise.
|
|
440
|
+
*/
|
|
441
|
+
private async validateRequestedTx(requestedTxHash: TxHash, responseTx: Tx, peerId: PeerId): Promise<boolean> {
|
|
442
|
+
const proofValidator = new TxProofValidator(this.proofVerifier);
|
|
443
|
+
const validProof = await proofValidator.validateTx(responseTx);
|
|
444
|
+
|
|
445
|
+
// If the node returns the wrong data, we penalize it
|
|
446
|
+
if (!requestedTxHash.equals(responseTx.getTxHash())) {
|
|
447
|
+
// Returning the wrong data is a low tolerance error
|
|
448
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (!validProof) {
|
|
453
|
+
// If the proof is invalid, but the txHash is correct, then this is an active attack and we severly punish
|
|
454
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> {
|
|
429
462
|
const blockNumber = (await this.l2BlockSource.getBlockNumber()) + 1;
|
|
430
463
|
// basic data validation
|
|
431
464
|
const dataValidator = new DataTxValidator();
|
|
432
|
-
const
|
|
433
|
-
if (
|
|
465
|
+
const validData = await dataValidator.validateTx(tx);
|
|
466
|
+
if (!validData) {
|
|
434
467
|
// penalize
|
|
435
468
|
this.node.services.pubsub.score.markInvalidMessageDelivery(peerId.toString(), Tx.p2pTopic);
|
|
436
469
|
return false;
|
|
@@ -438,8 +471,8 @@ export class LibP2PService implements P2PService {
|
|
|
438
471
|
|
|
439
472
|
// metadata validation
|
|
440
473
|
const metadataValidator = new MetadataTxValidator(new Fr(this.config.l1ChainId), new Fr(blockNumber));
|
|
441
|
-
const
|
|
442
|
-
if (
|
|
474
|
+
const validMetadata = await metadataValidator.validateTx(tx);
|
|
475
|
+
if (!validMetadata) {
|
|
443
476
|
// penalize
|
|
444
477
|
this.node.services.pubsub.score.markInvalidMessageDelivery(peerId.toString(), Tx.p2pTopic);
|
|
445
478
|
return false;
|
|
@@ -453,8 +486,8 @@ export class LibP2PService implements P2PService {
|
|
|
453
486
|
return index;
|
|
454
487
|
},
|
|
455
488
|
});
|
|
456
|
-
const
|
|
457
|
-
if (
|
|
489
|
+
const validDoubleSpend = await doubleSpendValidator.validateTx(tx);
|
|
490
|
+
if (!validDoubleSpend) {
|
|
458
491
|
// check if nullifier is older than 20 blocks
|
|
459
492
|
if (blockNumber - this.config.severePeerPenaltyBlockLength > 0) {
|
|
460
493
|
const snapshotValidator = new DoubleSpendTxValidator({
|
|
@@ -467,9 +500,9 @@ export class LibP2PService implements P2PService {
|
|
|
467
500
|
},
|
|
468
501
|
});
|
|
469
502
|
|
|
470
|
-
const
|
|
503
|
+
const validSnapshot = await snapshotValidator.validateTx(tx);
|
|
471
504
|
// High penalty if nullifier is older than 20 blocks
|
|
472
|
-
if (
|
|
505
|
+
if (!validSnapshot) {
|
|
473
506
|
// penalize
|
|
474
507
|
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
475
508
|
return false;
|
|
@@ -482,8 +515,8 @@ export class LibP2PService implements P2PService {
|
|
|
482
515
|
|
|
483
516
|
// proof validation
|
|
484
517
|
const proofValidator = new TxProofValidator(this.proofVerifier);
|
|
485
|
-
const
|
|
486
|
-
if (
|
|
518
|
+
const validProof = await proofValidator.validateTx(tx);
|
|
519
|
+
if (!validProof) {
|
|
487
520
|
// penalize
|
|
488
521
|
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
489
522
|
return false;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Tx, TxHash } from '@aztec/circuit-types';
|
|
2
2
|
|
|
3
|
+
import { type PeerId } from '@libp2p/interface';
|
|
4
|
+
|
|
3
5
|
/*
|
|
4
6
|
* Request Response Sub Protocols
|
|
5
7
|
*/
|
|
@@ -46,11 +48,29 @@ export interface ProtocolRateLimitQuota {
|
|
|
46
48
|
globalLimit: RateLimitQuota;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
export const noopValidator = () => Promise.resolve(true);
|
|
52
|
+
|
|
49
53
|
/**
|
|
50
54
|
* A type mapping from supprotocol to it's handling funciton
|
|
51
55
|
*/
|
|
52
56
|
export type ReqRespSubProtocolHandlers = Record<ReqRespSubProtocol, ReqRespSubProtocolHandler>;
|
|
53
57
|
|
|
58
|
+
type ResponseValidator<RequestIdentifier, Response> = (
|
|
59
|
+
request: RequestIdentifier,
|
|
60
|
+
response: Response,
|
|
61
|
+
peerId: PeerId,
|
|
62
|
+
) => Promise<boolean>;
|
|
63
|
+
|
|
64
|
+
export type ReqRespSubProtocolValidators = {
|
|
65
|
+
[S in ReqRespSubProtocol]: ResponseValidator<any, any>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const DEFAULT_SUB_PROTOCOL_VALIDATORS: ReqRespSubProtocolValidators = {
|
|
69
|
+
[PING_PROTOCOL]: noopValidator,
|
|
70
|
+
[STATUS_PROTOCOL]: noopValidator,
|
|
71
|
+
[TX_REQ_PROTOCOL]: noopValidator,
|
|
72
|
+
};
|
|
73
|
+
|
|
54
74
|
/**
|
|
55
75
|
* Sub protocol map determines the request and response types for each
|
|
56
76
|
* Req Resp protocol
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type PeerId } from '@libp2p/interface';
|
|
7
7
|
|
|
8
|
+
import { type PeerManager } from '../../peer_manager.js';
|
|
9
|
+
import { PeerErrorSeverity } from '../../peer_scoring.js';
|
|
8
10
|
import { type ReqRespSubProtocol, type ReqRespSubProtocolRateLimits } from '../interface.js';
|
|
9
11
|
import { DEFAULT_RATE_LIMITS } from './rate_limits.js';
|
|
10
12
|
|
|
@@ -69,6 +71,12 @@ interface PeerRateLimiter {
|
|
|
69
71
|
lastAccess: number;
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
enum RateLimitStatus {
|
|
75
|
+
Allowed,
|
|
76
|
+
DeniedGlobal,
|
|
77
|
+
DeniedPeer,
|
|
78
|
+
}
|
|
79
|
+
|
|
72
80
|
/**
|
|
73
81
|
* SubProtocolRateLimiter: A rate limiter for managing request rates on a per-peer and global basis for a specific subprotocol.
|
|
74
82
|
*
|
|
@@ -98,9 +106,9 @@ export class SubProtocolRateLimiter {
|
|
|
98
106
|
this.peerQuotaTimeMs = peerQuotaTimeMs;
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
allow(peerId: PeerId):
|
|
109
|
+
allow(peerId: PeerId): RateLimitStatus {
|
|
102
110
|
if (!this.globalLimiter.allow()) {
|
|
103
|
-
return
|
|
111
|
+
return RateLimitStatus.DeniedGlobal;
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
const peerIdStr = peerId.toString();
|
|
@@ -115,7 +123,11 @@ export class SubProtocolRateLimiter {
|
|
|
115
123
|
} else {
|
|
116
124
|
peerLimiter.lastAccess = Date.now();
|
|
117
125
|
}
|
|
118
|
-
|
|
126
|
+
const peerLimitAllowed = peerLimiter.limiter.allow();
|
|
127
|
+
if (!peerLimitAllowed) {
|
|
128
|
+
return RateLimitStatus.DeniedPeer;
|
|
129
|
+
}
|
|
130
|
+
return RateLimitStatus.Allowed;
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
cleanupInactivePeers() {
|
|
@@ -138,14 +150,16 @@ export class SubProtocolRateLimiter {
|
|
|
138
150
|
* - Initializes with a set of rate limit configurations for different subprotocols.
|
|
139
151
|
* - Creates a separate SubProtocolRateLimiter for each configured subprotocol.
|
|
140
152
|
* - When a request comes in, it routes the rate limiting decision to the appropriate subprotocol limiter.
|
|
153
|
+
* - Peers who exceed their peer rate limits will be penalised by the peer manager.
|
|
141
154
|
*
|
|
142
155
|
* Usage:
|
|
143
156
|
* ```
|
|
157
|
+
* const peerManager = new PeerManager(...);
|
|
144
158
|
* const rateLimits = {
|
|
145
159
|
* subprotocol1: { peerLimit: { quotaCount: 10, quotaTimeMs: 1000 }, globalLimit: { quotaCount: 100, quotaTimeMs: 1000 } },
|
|
146
160
|
* subprotocol2: { peerLimit: { quotaCount: 5, quotaTimeMs: 1000 }, globalLimit: { quotaCount: 50, quotaTimeMs: 1000 } }
|
|
147
161
|
* };
|
|
148
|
-
* const limiter = new RequestResponseRateLimiter(rateLimits);
|
|
162
|
+
* const limiter = new RequestResponseRateLimiter(peerManager, rateLimits);
|
|
149
163
|
*
|
|
150
164
|
* Note: Ensure to call `stop()` when shutting down to properly clean up all subprotocol limiters.
|
|
151
165
|
*/
|
|
@@ -154,7 +168,7 @@ export class RequestResponseRateLimiter {
|
|
|
154
168
|
|
|
155
169
|
private cleanupInterval: NodeJS.Timeout | undefined = undefined;
|
|
156
170
|
|
|
157
|
-
constructor(rateLimits: ReqRespSubProtocolRateLimits = DEFAULT_RATE_LIMITS) {
|
|
171
|
+
constructor(private peerManager: PeerManager, rateLimits: ReqRespSubProtocolRateLimits = DEFAULT_RATE_LIMITS) {
|
|
158
172
|
this.subProtocolRateLimiters = new Map();
|
|
159
173
|
|
|
160
174
|
for (const [subProtocol, protocolLimits] of Object.entries(rateLimits)) {
|
|
@@ -179,10 +193,19 @@ export class RequestResponseRateLimiter {
|
|
|
179
193
|
allow(subProtocol: ReqRespSubProtocol, peerId: PeerId): boolean {
|
|
180
194
|
const limiter = this.subProtocolRateLimiters.get(subProtocol);
|
|
181
195
|
if (!limiter) {
|
|
182
|
-
// TODO: maybe throw an error here if no rate limiter is configured?
|
|
183
196
|
return true;
|
|
184
197
|
}
|
|
185
|
-
|
|
198
|
+
const rateLimitStatus = limiter.allow(peerId);
|
|
199
|
+
|
|
200
|
+
switch (rateLimitStatus) {
|
|
201
|
+
case RateLimitStatus.DeniedPeer:
|
|
202
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
203
|
+
return false;
|
|
204
|
+
case RateLimitStatus.DeniedGlobal:
|
|
205
|
+
return false;
|
|
206
|
+
default:
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
186
209
|
}
|
|
187
210
|
|
|
188
211
|
cleanupInactivePeers() {
|
|
@@ -8,11 +8,17 @@ import { type Libp2p } from 'libp2p';
|
|
|
8
8
|
import { type Uint8ArrayList } from 'uint8arraylist';
|
|
9
9
|
|
|
10
10
|
import { CollectiveReqRespTimeoutError, IndiviualReqRespTimeoutError } from '../../errors/reqresp.error.js';
|
|
11
|
+
import { type PeerManager } from '../peer_manager.js';
|
|
12
|
+
import { PeerErrorSeverity } from '../peer_scoring.js';
|
|
11
13
|
import { type P2PReqRespConfig } from './config.js';
|
|
12
14
|
import {
|
|
13
15
|
DEFAULT_SUB_PROTOCOL_HANDLERS,
|
|
16
|
+
DEFAULT_SUB_PROTOCOL_VALIDATORS,
|
|
14
17
|
type ReqRespSubProtocol,
|
|
15
18
|
type ReqRespSubProtocolHandlers,
|
|
19
|
+
type ReqRespSubProtocolValidators,
|
|
20
|
+
type SubProtocolMap,
|
|
21
|
+
subProtocolMap,
|
|
16
22
|
} from './interface.js';
|
|
17
23
|
import { RequestResponseRateLimiter } from './rate_limiter/rate_limiter.js';
|
|
18
24
|
|
|
@@ -30,28 +36,31 @@ import { RequestResponseRateLimiter } from './rate_limiter/rate_limiter.js';
|
|
|
30
36
|
export class ReqResp {
|
|
31
37
|
protected readonly logger: Logger;
|
|
32
38
|
|
|
33
|
-
private abortController: AbortController = new AbortController();
|
|
34
|
-
|
|
35
39
|
private overallRequestTimeoutMs: number;
|
|
36
40
|
private individualRequestTimeoutMs: number;
|
|
37
41
|
|
|
42
|
+
// Warning, if the `start` function is not called as the parent class constructor, then the default sub protocol handlers will be used ( not good )
|
|
38
43
|
private subProtocolHandlers: ReqRespSubProtocolHandlers = DEFAULT_SUB_PROTOCOL_HANDLERS;
|
|
44
|
+
private subProtocolValidators: ReqRespSubProtocolValidators = DEFAULT_SUB_PROTOCOL_VALIDATORS;
|
|
45
|
+
|
|
39
46
|
private rateLimiter: RequestResponseRateLimiter;
|
|
40
47
|
|
|
41
|
-
constructor(config: P2PReqRespConfig, protected readonly libp2p: Libp2p) {
|
|
48
|
+
constructor(config: P2PReqRespConfig, protected readonly libp2p: Libp2p, private peerManager: PeerManager) {
|
|
42
49
|
this.logger = createDebugLogger('aztec:p2p:reqresp');
|
|
43
50
|
|
|
44
51
|
this.overallRequestTimeoutMs = config.overallRequestTimeoutMs;
|
|
45
52
|
this.individualRequestTimeoutMs = config.individualRequestTimeoutMs;
|
|
46
53
|
|
|
47
|
-
this.rateLimiter = new RequestResponseRateLimiter();
|
|
54
|
+
this.rateLimiter = new RequestResponseRateLimiter(peerManager);
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
/**
|
|
51
58
|
* Start the reqresp service
|
|
52
59
|
*/
|
|
53
|
-
async start(subProtocolHandlers: ReqRespSubProtocolHandlers) {
|
|
60
|
+
async start(subProtocolHandlers: ReqRespSubProtocolHandlers, subProtocolValidators: ReqRespSubProtocolValidators) {
|
|
54
61
|
this.subProtocolHandlers = subProtocolHandlers;
|
|
62
|
+
this.subProtocolValidators = subProtocolValidators;
|
|
63
|
+
|
|
55
64
|
// Register all protocol handlers
|
|
56
65
|
for (const subProtocol of Object.keys(this.subProtocolHandlers)) {
|
|
57
66
|
await this.libp2p.handle(subProtocol, this.streamHandler.bind(this, subProtocol as ReqRespSubProtocol));
|
|
@@ -67,38 +76,80 @@ export class ReqResp {
|
|
|
67
76
|
for (const protocol of Object.keys(this.subProtocolHandlers)) {
|
|
68
77
|
await this.libp2p.unhandle(protocol);
|
|
69
78
|
}
|
|
79
|
+
|
|
80
|
+
// Close all active connections
|
|
81
|
+
const closeStreamPromises = this.libp2p.getConnections().map(connection => connection.close());
|
|
82
|
+
await Promise.all(closeStreamPromises);
|
|
83
|
+
this.logger.debug('ReqResp: All active streams closed');
|
|
84
|
+
|
|
70
85
|
this.rateLimiter.stop();
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
this.logger.debug('ReqResp: Rate limiter stopped');
|
|
87
|
+
|
|
88
|
+
// NOTE: We assume libp2p instance is managed by the caller
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
/**
|
|
76
92
|
* Send a request to peers, returns the first response
|
|
77
93
|
*
|
|
78
94
|
* @param subProtocol - The protocol being requested
|
|
79
|
-
* @param
|
|
95
|
+
* @param request - The request to send
|
|
80
96
|
* @returns - The response from the peer, otherwise undefined
|
|
97
|
+
*
|
|
98
|
+
* @description
|
|
99
|
+
* This method attempts to send a request to all active peers using the specified sub-protocol.
|
|
100
|
+
* It opens a stream with each peer, sends the request, and awaits a response.
|
|
101
|
+
* If a valid response is received, it returns the response; otherwise, it continues to the next peer.
|
|
102
|
+
* If no response is received from any peer, it returns undefined.
|
|
103
|
+
*
|
|
104
|
+
* The method performs the following steps:
|
|
105
|
+
* - Iterates over all active peers.
|
|
106
|
+
* - Opens a stream with each peer using the specified sub-protocol.
|
|
107
|
+
*
|
|
108
|
+
* When a response is received, it is validated using the given sub protocols response validator.
|
|
109
|
+
* To see the interface for the response validator - see `interface.ts`
|
|
110
|
+
*
|
|
111
|
+
* Failing a response validation requests in a severe peer penalty, and will
|
|
112
|
+
* prompt the node to continue to search to the next peer.
|
|
113
|
+
* For example, a transaction request validator will check that the payload returned does in fact
|
|
114
|
+
* match the txHash that was requested. A peer that fails this check an only be an extremely naughty peer.
|
|
115
|
+
*
|
|
116
|
+
* This entire operation is wrapped in an overall timeout, that is independent of the
|
|
117
|
+
* peer it is requesting data from.
|
|
118
|
+
*
|
|
81
119
|
*/
|
|
82
|
-
async sendRequest
|
|
120
|
+
async sendRequest<SubProtocol extends ReqRespSubProtocol>(
|
|
121
|
+
subProtocol: SubProtocol,
|
|
122
|
+
request: InstanceType<SubProtocolMap[SubProtocol]['request']>,
|
|
123
|
+
): Promise<InstanceType<SubProtocolMap[SubProtocol]['response']> | undefined> {
|
|
83
124
|
const requestFunction = async () => {
|
|
125
|
+
const responseValidator = this.subProtocolValidators[subProtocol];
|
|
126
|
+
const requestBuffer = request.toBuffer();
|
|
127
|
+
|
|
84
128
|
// Get active peers
|
|
85
129
|
const peers = this.libp2p.getPeers();
|
|
86
130
|
|
|
87
131
|
// Attempt to ask all of our peers
|
|
88
132
|
for (const peer of peers) {
|
|
89
|
-
const response = await this.sendRequestToPeer(peer, subProtocol,
|
|
133
|
+
const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffer);
|
|
90
134
|
|
|
91
135
|
// If we get a response, return it, otherwise we iterate onto the next peer
|
|
92
136
|
// We do not consider it a success if we have an empty buffer
|
|
93
137
|
if (response && response.length > 0) {
|
|
94
|
-
|
|
138
|
+
const object = subProtocolMap[subProtocol].response.fromBuffer(response);
|
|
139
|
+
// The response validator handles peer punishment within
|
|
140
|
+
const isValid = await responseValidator(request, object, peer);
|
|
141
|
+
if (!isValid) {
|
|
142
|
+
this.logger.error(`Invalid response for ${subProtocol} from ${peer.toString()}`);
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
return object;
|
|
95
146
|
}
|
|
96
147
|
}
|
|
97
148
|
return undefined;
|
|
98
149
|
};
|
|
99
150
|
|
|
100
151
|
try {
|
|
101
|
-
return await executeTimeoutWithCustomError<
|
|
152
|
+
return await executeTimeoutWithCustomError<InstanceType<SubProtocolMap[SubProtocol]['response']> | undefined>(
|
|
102
153
|
requestFunction,
|
|
103
154
|
this.overallRequestTimeoutMs,
|
|
104
155
|
() => new CollectiveReqRespTimeoutError(),
|
|
@@ -112,10 +163,26 @@ export class ReqResp {
|
|
|
112
163
|
/**
|
|
113
164
|
* Sends a request to a specific peer
|
|
114
165
|
*
|
|
166
|
+
* We first dial a particular protocol for the peer, this ensures that the peer knows
|
|
167
|
+
* what to respond with
|
|
168
|
+
*
|
|
169
|
+
*
|
|
115
170
|
* @param peerId - The peer to send the request to
|
|
116
171
|
* @param subProtocol - The protocol to use to request
|
|
117
172
|
* @param payload - The payload to send
|
|
118
173
|
* @returns If the request is successful, the response is returned, otherwise undefined
|
|
174
|
+
*
|
|
175
|
+
* @description
|
|
176
|
+
* This method attempts to open a stream with the specified peer, send the payload,
|
|
177
|
+
* and await a response.
|
|
178
|
+
* If an error occurs, it penalizes the peer and returns undefined.
|
|
179
|
+
*
|
|
180
|
+
* The method performs the following steps:
|
|
181
|
+
* - Opens a stream with the peer using the specified sub-protocol.
|
|
182
|
+
* - Sends the payload and awaits a response with a timeout.
|
|
183
|
+
*
|
|
184
|
+
* If the stream is not closed by the dialled peer, and a timeout occurs, then
|
|
185
|
+
* the stream is closed on the requester's end and sender (us) updates its peer score
|
|
119
186
|
*/
|
|
120
187
|
async sendRequestToPeer(
|
|
121
188
|
peerId: PeerId,
|
|
@@ -125,9 +192,9 @@ export class ReqResp {
|
|
|
125
192
|
let stream: Stream | undefined;
|
|
126
193
|
try {
|
|
127
194
|
stream = await this.libp2p.dialProtocol(peerId, subProtocol);
|
|
128
|
-
|
|
129
195
|
this.logger.debug(`Stream opened with ${peerId.toString()} for ${subProtocol}`);
|
|
130
196
|
|
|
197
|
+
// Open the stream with a timeout
|
|
131
198
|
const result = await executeTimeoutWithCustomError<Buffer>(
|
|
132
199
|
(): Promise<Buffer> => pipe([payload], stream!, this.readMessage),
|
|
133
200
|
this.individualRequestTimeoutMs,
|
|
@@ -140,6 +207,7 @@ export class ReqResp {
|
|
|
140
207
|
return result;
|
|
141
208
|
} catch (e: any) {
|
|
142
209
|
this.logger.error(`${e.message} | peerId: ${peerId.toString()} | subProtocol: ${subProtocol}`);
|
|
210
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
|
|
143
211
|
} finally {
|
|
144
212
|
if (stream) {
|
|
145
213
|
try {
|
|
@@ -172,6 +240,16 @@ export class ReqResp {
|
|
|
172
240
|
* Reads the incoming stream, determines the protocol, then triggers the appropriate handler
|
|
173
241
|
*
|
|
174
242
|
* @param param0 - The incoming stream data
|
|
243
|
+
*
|
|
244
|
+
* @description
|
|
245
|
+
* An individual stream handler will be bound to each sub protocol, and handles returning data back
|
|
246
|
+
* to the requesting peer.
|
|
247
|
+
*
|
|
248
|
+
* The sub protocol handler interface is defined within `interface.ts` and will be assigned to the
|
|
249
|
+
* req resp service on start up.
|
|
250
|
+
*
|
|
251
|
+
* We check rate limits for each peer, note the peer will be penalised within the rate limiter implementation
|
|
252
|
+
* if they exceed their peer specific limits.
|
|
175
253
|
*/
|
|
176
254
|
private async streamHandler(protocol: ReqRespSubProtocol, { stream, connection }: IncomingStreamData) {
|
|
177
255
|
// Store a reference to from this for the async generator
|
|
@@ -21,4 +21,14 @@ export class AggregateTxValidator<T extends Tx | ProcessedTx> implements TxValid
|
|
|
21
21
|
|
|
22
22
|
return [txPool, invalidTxs];
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
async validateTx(tx: T): Promise<boolean> {
|
|
26
|
+
for (const validator of this.#validators) {
|
|
27
|
+
const valid = await validator.validateTx(tx);
|
|
28
|
+
if (!valid) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
24
34
|
}
|
|
@@ -19,6 +19,10 @@ export class DataTxValidator implements TxValidator<Tx> {
|
|
|
19
19
|
return Promise.resolve([validTxs, invalidTxs]);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
validateTx(tx: Tx): Promise<boolean> {
|
|
23
|
+
return Promise.resolve(this.#hasCorrectExecutionRequests(tx));
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
#hasCorrectExecutionRequests(tx: Tx): boolean {
|
|
23
27
|
const callRequests = [
|
|
24
28
|
...tx.data.getRevertiblePublicCallRequests(),
|
|
@@ -31,6 +31,10 @@ export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
|
31
31
|
return [validTxs, invalidTxs];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
validateTx(tx: T): Promise<boolean> {
|
|
35
|
+
return this.#uniqueNullifiers(tx, new Set<bigint>());
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
async #uniqueNullifiers(tx: AnyTx, thisBlockNullifiers: Set<bigint>): Promise<boolean> {
|
|
35
39
|
const nullifiers = tx.data.getNonEmptyNullifiers().map(x => x.toBigInt());
|
|
36
40
|
|
|
@@ -27,6 +27,10 @@ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
|
27
27
|
return Promise.resolve([validTxs, invalidTxs]);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
validateTx(tx: T): Promise<boolean> {
|
|
31
|
+
return Promise.resolve(this.#hasCorrectChainId(tx) && this.#isValidForBlockNumber(tx));
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
#hasCorrectChainId(tx: T): boolean {
|
|
31
35
|
if (!tx.data.constants.txContext.chainId.equals(this.chainId)) {
|
|
32
36
|
this.#log.warn(
|
package/dest/client/mocks.d.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { L2Block, type L2BlockSource, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
|
|
2
|
-
import { EthAddress } from '@aztec/circuits.js';
|
|
3
|
-
/**
|
|
4
|
-
* A mocked implementation of L2BlockSource to be used in p2p tests.
|
|
5
|
-
*/
|
|
6
|
-
export declare class MockBlockSource implements L2BlockSource {
|
|
7
|
-
private provenBlockNumber?;
|
|
8
|
-
private l2Blocks;
|
|
9
|
-
private txEffects;
|
|
10
|
-
constructor(numBlocks?: number, provenBlockNumber?: number | undefined);
|
|
11
|
-
addBlocks(numBlocks: number): void;
|
|
12
|
-
setProvenBlockNumber(provenBlockNumber: number): void;
|
|
13
|
-
/**
|
|
14
|
-
* Method to fetch the rollup contract address at the base-layer.
|
|
15
|
-
* @returns The rollup address.
|
|
16
|
-
*/
|
|
17
|
-
getRollupAddress(): Promise<EthAddress>;
|
|
18
|
-
/**
|
|
19
|
-
* Method to fetch the registry contract address at the base-layer.
|
|
20
|
-
* @returns The registry address.
|
|
21
|
-
*/
|
|
22
|
-
getRegistryAddress(): Promise<EthAddress>;
|
|
23
|
-
/**
|
|
24
|
-
* Gets the number of the latest L2 block processed by the block source implementation.
|
|
25
|
-
* @returns In this mock instance, returns the number of L2 blocks that we've mocked.
|
|
26
|
-
*/
|
|
27
|
-
getBlockNumber(): Promise<number>;
|
|
28
|
-
getProvenBlockNumber(): Promise<number>;
|
|
29
|
-
/**
|
|
30
|
-
* Gets an l2 block.
|
|
31
|
-
* @param number - The block number to return (inclusive).
|
|
32
|
-
* @returns The requested L2 block.
|
|
33
|
-
*/
|
|
34
|
-
getBlock(number: number): Promise<L2Block>;
|
|
35
|
-
/**
|
|
36
|
-
* Gets up to `limit` amount of L2 blocks starting from `from`.
|
|
37
|
-
* @param from - Number of the first block to return (inclusive).
|
|
38
|
-
* @param limit - The maximum number of blocks to return.
|
|
39
|
-
* @returns The requested mocked L2 blocks.
|
|
40
|
-
*/
|
|
41
|
-
getBlocks(from: number, limit: number, proven?: boolean): Promise<L2Block[]>;
|
|
42
|
-
/**
|
|
43
|
-
* Gets a tx effect.
|
|
44
|
-
* @param txHash - The hash of a transaction which resulted in the returned tx effect.
|
|
45
|
-
* @returns The requested tx effect.
|
|
46
|
-
*/
|
|
47
|
-
getTxEffect(txHash: TxHash): Promise<TxEffect | undefined>;
|
|
48
|
-
/**
|
|
49
|
-
* Gets a receipt of a settled tx.
|
|
50
|
-
* @param txHash - The hash of a tx we try to get the receipt for.
|
|
51
|
-
* @returns The requested tx receipt (or undefined if not found).
|
|
52
|
-
*/
|
|
53
|
-
getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined>;
|
|
54
|
-
/**
|
|
55
|
-
* Starts the block source. In this mock implementation, this is a noop.
|
|
56
|
-
* @returns A promise that signals the initialization of the l2 block source on completion.
|
|
57
|
-
*/
|
|
58
|
-
start(): Promise<void>;
|
|
59
|
-
/**
|
|
60
|
-
* Stops the block source. In this mock implementation, this is a noop.
|
|
61
|
-
* @returns A promise that signals the l2 block source is now stopped.
|
|
62
|
-
*/
|
|
63
|
-
stop(): Promise<void>;
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=mocks.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mocks.d.ts","sourceRoot":"","sources":["../../src/client/mocks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,SAAS,EAAY,MAAM,sBAAsB,CAAC;AACpH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD;;GAEG;AACH,qBAAa,eAAgB,YAAW,aAAa;IAItB,OAAO,CAAC,iBAAiB,CAAC;IAHvD,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,SAAS,CAAkB;gBAEvB,SAAS,SAAM,EAAU,iBAAiB,CAAC,oBAAQ;IAIxD,SAAS,CAAC,SAAS,EAAE,MAAM;IAS3B,oBAAoB,CAAC,iBAAiB,EAAE,MAAM;IAIrD;;;OAGG;IACH,gBAAgB,IAAI,OAAO,CAAC,UAAU,CAAC;IAIvC;;;OAGG;IACH,kBAAkB,IAAI,OAAO,CAAC,UAAU,CAAC;IAIzC;;;OAGG;IACI,cAAc;IAIR,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;IAIpD;;;;OAIG;IACI,QAAQ,CAAC,MAAM,EAAE,MAAM;IAI9B;;;;;OAKG;IACI,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;IAQ9D;;;;OAIG;IACI,WAAW,CAAC,MAAM,EAAE,MAAM;IAKjC;;;;OAIG;IACI,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAoB1E;;;OAGG;IACI,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B;;;OAGG;IACI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|