@aztec/p2p 0.0.1-commit.993d240 → 0.0.1-commit.9a89641
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/README.md +7 -6
- package/dest/bootstrap/bootstrap.d.ts +1 -1
- package/dest/bootstrap/bootstrap.d.ts.map +1 -1
- package/dest/bootstrap/bootstrap.js +20 -12
- package/dest/client/factory.d.ts +2 -2
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +10 -5
- package/dest/client/interface.d.ts +40 -9
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +23 -12
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +38 -35
- package/dest/config.d.ts +19 -13
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +27 -26
- package/dest/errors/reqresp.error.d.ts +13 -1
- package/dest/errors/reqresp.error.d.ts.map +1 -1
- package/dest/errors/reqresp.error.js +14 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -2
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +9 -2
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +43 -0
- package/dest/mem_pools/instrumentation.d.ts +9 -4
- package/dest/mem_pools/instrumentation.d.ts.map +1 -1
- package/dest/mem_pools/instrumentation.js +11 -8
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts +1 -5
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.js +1 -7
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +3 -3
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/deleted_pool.js +6 -6
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -3
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +3 -1
- package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.js +3 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +3 -4
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +4 -5
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +6 -5
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +30 -5
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +15 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +31 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +9 -3
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +19 -8
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +17 -4
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +133 -34
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +6 -5
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/attestation_validator.js +24 -26
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +4 -4
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +2 -2
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +4 -3
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/block_proposal_validator.js +2 -2
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +4 -3
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +8 -2
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +19 -5
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +66 -30
- package/dest/msg_validators/tx_validator/factory.d.ts +9 -11
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +15 -6
- package/dest/msg_validators/tx_validator/gas_validator.d.ts +12 -7
- package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/gas_validator.js +15 -15
- package/dest/msg_validators/tx_validator/phases_validator.js +2 -2
- package/dest/msg_validators/tx_validator/timestamp_validator.js +1 -1
- package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts +2 -2
- package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/tx_validation_cache.js +6 -7
- package/dest/services/discv5/discV5_service.d.ts +12 -2
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +84 -15
- package/dest/services/discv5/persisted_enr_store.d.ts +38 -0
- package/dest/services/discv5/persisted_enr_store.d.ts.map +1 -0
- package/dest/services/discv5/persisted_enr_store.js +145 -0
- package/dest/services/dummy_service.d.ts +6 -2
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +3 -0
- package/dest/services/encoding.d.ts +19 -6
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +25 -15
- package/dest/services/gossipsub/topic_score_params.d.ts +6 -23
- package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
- package/dest/services/gossipsub/topic_score_params.js +4 -31
- package/dest/services/libp2p/libp2p_service.d.ts +12 -3
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +204 -114
- package/dest/services/peer-manager/peer_manager.d.ts +15 -2
- package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_manager.js +46 -18
- package/dest/services/peer-manager/peer_scoring.d.ts +28 -1
- package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_scoring.js +70 -3
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -8
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +21 -39
- package/dest/services/reqresp/config.d.ts +13 -1
- package/dest/services/reqresp/config.d.ts.map +1 -1
- package/dest/services/reqresp/config.js +11 -0
- package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +4 -3
- package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/bitvector.js +3 -2
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +29 -28
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +12 -7
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +42 -24
- package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
- package/dest/services/reqresp/reqresp.d.ts +1 -1
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +34 -3
- package/dest/services/service.d.ts +21 -1
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_source.d.ts +1 -1
- package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_source.js +4 -1
- package/dest/test-helpers/testbench-utils.d.ts +4 -1
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +11 -3
- package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
- package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
- package/dest/testbench/p2p_client_testbench_worker.js +19 -2
- package/package.json +15 -17
- package/src/bootstrap/bootstrap.ts +15 -9
- package/src/client/factory.ts +9 -5
- package/src/client/interface.ts +33 -7
- package/src/client/p2p_client.ts +46 -48
- package/src/config.ts +50 -38
- package/src/errors/reqresp.error.ts +19 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +12 -2
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -1
- package/src/mem_pools/instrumentation.ts +13 -11
- package/src/mem_pools/tx_pool_v2/archive/tx_archive.ts +1 -9
- package/src/mem_pools/tx_pool_v2/deleted_pool.ts +7 -7
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +2 -4
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -2
- package/src/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.ts +3 -1
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +1 -3
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +2 -4
- package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +5 -6
- package/src/mem_pools/tx_pool_v2/interfaces.ts +28 -4
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +35 -0
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +22 -8
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +158 -33
- package/src/msg_validators/attestation_validator/attestation_validator.ts +26 -26
- package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +4 -3
- package/src/msg_validators/proposal_validator/block_proposal_validator.ts +4 -2
- package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +10 -2
- package/src/msg_validators/proposal_validator/proposal_validator.ts +69 -30
- package/src/msg_validators/tx_validator/README.md +2 -0
- package/src/msg_validators/tx_validator/factory.ts +19 -13
- package/src/msg_validators/tx_validator/gas_validator.ts +16 -20
- package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
- package/src/msg_validators/tx_validator/timestamp_validator.ts +1 -1
- package/src/msg_validators/tx_validator/tx_validation_cache.ts +6 -7
- package/src/services/discv5/discV5_service.ts +88 -9
- package/src/services/discv5/persisted_enr_store.ts +158 -0
- package/src/services/dummy_service.ts +6 -0
- package/src/services/encoding.ts +28 -20
- package/src/services/gossipsub/README.md +33 -11
- package/src/services/gossipsub/topic_score_params.ts +9 -50
- package/src/services/libp2p/libp2p_service.ts +234 -125
- package/src/services/peer-manager/peer_manager.ts +48 -17
- package/src/services/peer-manager/peer_scoring.ts +75 -3
- package/src/services/reqresp/README.md +2 -23
- package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +21 -45
- package/src/services/reqresp/config.ts +17 -0
- package/src/services/reqresp/protocols/block_txs/bitvector.ts +3 -2
- package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +33 -31
- package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +47 -20
- package/src/services/reqresp/rate-limiter/rate_limits.ts +3 -3
- package/src/services/reqresp/reqresp.ts +42 -2
- package/src/services/service.ts +23 -0
- package/src/services/tx_collection/tx_source.ts +2 -1
- package/src/test-helpers/testbench-utils.ts +13 -2
- package/src/testbench/p2p_client_testbench_worker.ts +22 -2
- package/dest/msg_validators/clock_tolerance.d.ts +0 -32
- package/dest/msg_validators/clock_tolerance.d.ts.map +0 -1
- package/dest/msg_validators/clock_tolerance.js +0 -95
- package/src/msg_validators/clock_tolerance.ts +0 -127
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
+
import { compactArray } from '@aztec/foundation/collection';
|
|
2
3
|
import { makeEthSignDigest, tryRecoverAddress } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
3
4
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
5
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -164,6 +165,7 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
164
165
|
public async heartbeat() {
|
|
165
166
|
this.heartbeatCounter++;
|
|
166
167
|
this.peerScoring.decayAllScores();
|
|
168
|
+
this.peerScoring.pruneExpiredBans();
|
|
167
169
|
this.cleanupExpiredTimeouts();
|
|
168
170
|
|
|
169
171
|
await this.setupDirectPeersIfValidator();
|
|
@@ -198,21 +200,27 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
198
200
|
.then(peerIds => peerIds.forEach(peerId => this.preferredPeers.add(peerId.toString())))
|
|
199
201
|
.catch(e => this.logger.error('Error initializing preferred peers', e));
|
|
200
202
|
|
|
201
|
-
const directPeers = (
|
|
203
|
+
const directPeers = compactArray(
|
|
202
204
|
await Promise.all(
|
|
203
205
|
preferredPeersEnrs.map(async enr => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
try {
|
|
207
|
+
const peerId = await enr.peerId();
|
|
208
|
+
const address = enr.getLocationMultiaddr('tcp');
|
|
209
|
+
if (address === undefined) {
|
|
210
|
+
throw new Error(`Direct peer ${peerId.toString()} has no TCP address, ENR: ${enr.encodeTxt()}`);
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
id: peerId,
|
|
214
|
+
addrs: [address],
|
|
215
|
+
};
|
|
216
|
+
} catch (err) {
|
|
217
|
+
// A malformed configured ENR shouldn't abort preferred-peer setup — skip it and log.
|
|
218
|
+
this.logger.warn(`Skipping preferred peer with invalid ENR`, { err });
|
|
219
|
+
return undefined;
|
|
208
220
|
}
|
|
209
|
-
return {
|
|
210
|
-
id: peerId,
|
|
211
|
-
addrs: [address],
|
|
212
|
-
};
|
|
213
221
|
}),
|
|
214
|
-
)
|
|
215
|
-
)
|
|
222
|
+
),
|
|
223
|
+
);
|
|
216
224
|
|
|
217
225
|
await Promise.all(
|
|
218
226
|
directPeers.map(peer => {
|
|
@@ -494,15 +502,38 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
494
502
|
*
|
|
495
503
|
* @returns: True if node is allowed to connect, otherwise false
|
|
496
504
|
* */
|
|
497
|
-
|
|
498
|
-
|
|
505
|
+
/**
|
|
506
|
+
* Whether a peer is allowed to connect, given its peer id. Rejects peers serving an active ban and
|
|
507
|
+
* peers that have exceeded the failed auth-handshake limit. Use this once the peer id is known —
|
|
508
|
+
* i.e. for the encrypted-inbound gater and when dialing.
|
|
509
|
+
*/
|
|
510
|
+
public isPeerAllowedToConnect(peerId: string | PeerId): boolean {
|
|
511
|
+
const id = peerId.toString();
|
|
512
|
+
if (this.peerScoring.getScoreState(id) === PeerScoreState.Banned) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
return this.isWithinFailedAuthLimit(id);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Whether a connection from an address is allowed. Bans are keyed by peer id, which isn't known at
|
|
520
|
+
* the raw-inbound layer, so only the failed auth-handshake limit (also tracked per address) is
|
|
521
|
+
* enforced here; the ban is applied once the peer id is known via {@link isPeerAllowedToConnect}.
|
|
522
|
+
*/
|
|
523
|
+
public isAddressAllowedToConnect(address: string): boolean {
|
|
524
|
+
return this.isWithinFailedAuthLimit(address);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Whether the failed auth-handshake count for a peer id or address is below the configured limit. */
|
|
528
|
+
private isWithinFailedAuthLimit(key: string): boolean {
|
|
529
|
+
const entry = this.failedAuthHandshakes.get(key);
|
|
499
530
|
if (!entry) {
|
|
500
531
|
return true;
|
|
501
532
|
}
|
|
502
533
|
|
|
503
534
|
// In case entry is too old, remove it and allow connection
|
|
504
535
|
if (this.dateProvider.now() - entry.lastFailureTimestamp > FAILED_AUTH_HANDSHAKE_EXPIRY_MS) {
|
|
505
|
-
this.failedAuthHandshakes.delete(
|
|
536
|
+
this.failedAuthHandshakes.delete(key);
|
|
506
537
|
return true;
|
|
507
538
|
}
|
|
508
539
|
|
|
@@ -729,9 +760,9 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
729
760
|
return;
|
|
730
761
|
}
|
|
731
762
|
|
|
732
|
-
// Don't dial peers that have exceeded the auth failure threshold
|
|
733
|
-
if (!this.
|
|
734
|
-
this.logger.trace(`Skipping peer ${peerId} due to failed auth handshake attempts`);
|
|
763
|
+
// Don't dial banned peers or those that have exceeded the auth failure threshold
|
|
764
|
+
if (!this.isPeerAllowedToConnect(peerId)) {
|
|
765
|
+
this.logger.trace(`Skipping peer ${peerId} due to ban or failed auth handshake attempts`);
|
|
735
766
|
return;
|
|
736
767
|
}
|
|
737
768
|
|
|
@@ -57,6 +57,9 @@ const MIN_SCORE_BEFORE_BAN = -100;
|
|
|
57
57
|
const MIN_SCORE_BEFORE_DISCONNECT = -50;
|
|
58
58
|
const SCORE_CLEANUP_THRESHOLD = 0.1;
|
|
59
59
|
|
|
60
|
+
/** An active ban: the score the peer held when banned, and the timestamp at which the ban lifts. */
|
|
61
|
+
type BanRecord = { score: number; expiry: number };
|
|
62
|
+
|
|
60
63
|
export class PeerScoring {
|
|
61
64
|
private logger = createLogger('p2p:peer-scoring');
|
|
62
65
|
private scores: Map<string, number> = new Map();
|
|
@@ -67,11 +70,21 @@ export class PeerScoring {
|
|
|
67
70
|
|
|
68
71
|
private peerStateCounter: UpDownCounter;
|
|
69
72
|
|
|
73
|
+
/** Active bans, keyed by peer id. Held in memory only, so they are cleared on restart. */
|
|
74
|
+
private bannedPeers: Map<string, BanRecord> = new Map();
|
|
75
|
+
/**
|
|
76
|
+
* How long a peer remains banned once its score crosses MIN_SCORE_BEFORE_BAN. While banned, the
|
|
77
|
+
* peer's ban score is returned by getScore regardless of decay, so it cannot recover its way out
|
|
78
|
+
* of the ban early. After the ban expires the live (decayed) score takes over again.
|
|
79
|
+
*/
|
|
80
|
+
private readonly banDurationMs: number;
|
|
81
|
+
|
|
70
82
|
constructor(
|
|
71
83
|
config: P2PConfig,
|
|
72
84
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
73
85
|
private readonly dateProvider: DateProvider = new DateProvider(),
|
|
74
86
|
) {
|
|
87
|
+
this.banDurationMs = config.peerBanDurationSeconds * 1000;
|
|
75
88
|
const orderedValues = config.peerPenaltyValues?.sort((a, b) => a - b);
|
|
76
89
|
this.peerPenalties = {
|
|
77
90
|
[PeerErrorSeverity.HighToleranceError]:
|
|
@@ -113,9 +126,31 @@ export class PeerScoring {
|
|
|
113
126
|
|
|
114
127
|
this.scores.set(peerId, currentScore);
|
|
115
128
|
this.lastUpdateTime.set(peerId, currentTime);
|
|
129
|
+
|
|
130
|
+
this.maybeBanPeer(peerId, currentScore);
|
|
131
|
+
|
|
116
132
|
return currentScore;
|
|
117
133
|
}
|
|
118
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Records a ban for a peer whose score has crossed the ban threshold, with an expiry banDurationMs
|
|
137
|
+
* in the future. No-op if the score is above the threshold or the peer is already serving an active
|
|
138
|
+
* ban (an existing ban is not extended; the original window stands). A previously expired ban does
|
|
139
|
+
* not block a fresh one — getActiveBanScore prunes it first.
|
|
140
|
+
*/
|
|
141
|
+
private maybeBanPeer(peerId: string, score: number): void {
|
|
142
|
+
if (score >= MIN_SCORE_BEFORE_BAN || this.getActiveBanScore(peerId) !== undefined) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const record: BanRecord = { score, expiry: this.dateProvider.now() + this.banDurationMs };
|
|
146
|
+
this.bannedPeers.set(peerId, record);
|
|
147
|
+
this.logger.verbose(`Banning peer ${peerId} until ${new Date(record.expiry).toISOString()}`, {
|
|
148
|
+
peerId,
|
|
149
|
+
score,
|
|
150
|
+
expiry: record.expiry,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
119
154
|
decayAllScores(): void {
|
|
120
155
|
const currentTime = this.dateProvider.now();
|
|
121
156
|
for (const [peerId, lastUpdate] of this.lastUpdateTime.entries()) {
|
|
@@ -135,10 +170,24 @@ export class PeerScoring {
|
|
|
135
170
|
}
|
|
136
171
|
}
|
|
137
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Removes bans whose window has elapsed. Expired bans are otherwise only pruned lazily when their
|
|
175
|
+
* peer's score is next queried, so a banned peer that disconnects and is never queried again would
|
|
176
|
+
* linger in the map. Called periodically (per heartbeat) to bound the ban map's size.
|
|
177
|
+
*/
|
|
178
|
+
pruneExpiredBans(): void {
|
|
179
|
+
const now = this.dateProvider.now();
|
|
180
|
+
const expired = [...this.bannedPeers.entries()].filter(([, ban]) => ban.expiry <= now).map(([peerId]) => peerId);
|
|
181
|
+
for (const peerId of expired) {
|
|
182
|
+
this.bannedPeers.delete(peerId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
138
186
|
/** Resets all peer scores. Useful for benchmarks to prevent cross-case contamination. */
|
|
139
187
|
resetAllScores(): void {
|
|
140
188
|
this.scores.clear();
|
|
141
189
|
this.lastUpdateTime.clear();
|
|
190
|
+
this.bannedPeers.clear();
|
|
142
191
|
}
|
|
143
192
|
|
|
144
193
|
removePeer(peerId: string): void {
|
|
@@ -146,12 +195,33 @@ export class PeerScoring {
|
|
|
146
195
|
this.lastUpdateTime.delete(peerId);
|
|
147
196
|
}
|
|
148
197
|
|
|
198
|
+
/**
|
|
199
|
+
* The single source of truth for whether a peer is banned. Returns the ban score while the ban is
|
|
200
|
+
* active, or undefined if the peer is not banned. A ban that has expired is lazily lifted before
|
|
201
|
+
* returning undefined, so callers never see a stale ban.
|
|
202
|
+
*/
|
|
203
|
+
private getActiveBanScore(peerId: string): number | undefined {
|
|
204
|
+
const ban = this.bannedPeers.get(peerId);
|
|
205
|
+
if (ban === undefined) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
if (ban.expiry > this.dateProvider.now()) {
|
|
209
|
+
return ban.score;
|
|
210
|
+
}
|
|
211
|
+
// Ban expired: lift it so the peer can recover.
|
|
212
|
+
this.bannedPeers.delete(peerId);
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
149
216
|
getScore(peerId: string): number {
|
|
150
|
-
|
|
217
|
+
// While a ban is active its ban score is returned regardless of how the live score has decayed,
|
|
218
|
+
// so the peer stays banned for the full duration.
|
|
219
|
+
return this.getActiveBanScore(peerId) ?? this.scores.get(peerId) ?? 0;
|
|
151
220
|
}
|
|
152
221
|
|
|
153
222
|
public getScoreState(peerId: string): PeerScoreState {
|
|
154
|
-
//
|
|
223
|
+
// A banned peer stays banned for the full ban duration regardless of score decay (see getScore /
|
|
224
|
+
// maybeBanPeer), rather than silently recovering once its decayed score is cleaned up.
|
|
155
225
|
const score = this.getScore(peerId);
|
|
156
226
|
if (score < MIN_SCORE_BEFORE_BAN) {
|
|
157
227
|
return PeerScoreState.Banned;
|
|
@@ -165,7 +235,9 @@ export class PeerScoring {
|
|
|
165
235
|
getStats(): { medianScore: number; healthyCount: number; disconnectCount: number; bannedCount: number } {
|
|
166
236
|
const stateCounts = { healthy: 0, disconnect: 0, banned: 0 };
|
|
167
237
|
|
|
168
|
-
|
|
238
|
+
// Include banned peers whose live score may have been decayed away but whose ban is still active.
|
|
239
|
+
const peerIds = new Set([...this.scores.keys(), ...this.bannedPeers.keys()]);
|
|
240
|
+
for (const peerId of peerIds) {
|
|
169
241
|
const state = this.getScoreState(peerId);
|
|
170
242
|
switch (state) {
|
|
171
243
|
case PeerScoreState.Healthy:
|
|
@@ -14,9 +14,8 @@ Applied before the protocol handler runs.
|
|
|
14
14
|
| STATUS | 5/s | 10/s | same |
|
|
15
15
|
| AUTH | 5/s | 10/s | same |
|
|
16
16
|
| GOODBYE | 5/s | 10/s | same |
|
|
17
|
-
| BLOCK | 2/s | 5/s | same |
|
|
18
17
|
| BLOCK_TXS | 10/s | 200/s | same |
|
|
19
|
-
| TX |
|
|
18
|
+
| TX | 10/s | 200/s | same |
|
|
20
19
|
|
|
21
20
|
- Per-peer limit exceeded: `HighToleranceError` penalty + `RATE_LIMIT_EXCEEDED` status. Penalty fires inside `RequestResponseRateLimiter.allow()`, not the stream handler.
|
|
22
21
|
- Global limit exceeded: `RATE_LIMIT_EXCEEDED` status only (no peer penalty).
|
|
@@ -149,26 +148,6 @@ Protected peers (private/trusted/preferred) are always considered "authenticated
|
|
|
149
148
|
|
|
150
149
|
## Block Data Protocols
|
|
151
150
|
|
|
152
|
-
### BLOCK Protocol (`/aztec/req/block/1.0.0`)
|
|
153
|
-
|
|
154
|
-
**Server side**:
|
|
155
|
-
|
|
156
|
-
| Rule | Consequence | File |
|
|
157
|
-
|------|-------------|------|
|
|
158
|
-
| Request must parse as `Fr` | `BADLY_FORMED_REQUEST` + LowToleranceError | `protocols/block.ts` |
|
|
159
|
-
| Block lookup throws | `INTERNAL_ERROR` status | same |
|
|
160
|
-
| Block not found | SUCCESS + empty buffer (design choice; no `NOT_FOUND` status used) | same |
|
|
161
|
-
|
|
162
|
-
**Requester side** (Snappy limit: 3 MB):
|
|
163
|
-
|
|
164
|
-
| Rule | Consequence | File |
|
|
165
|
-
|------|-------------|------|
|
|
166
|
-
| Response block number must match requested | LowToleranceError; rejected | `libp2p_service.ts` (`validateRequestedBlock`) |
|
|
167
|
-
| Local block must exist for hash verification | Rejected (no penalty) | same |
|
|
168
|
-
| Response block hash must equal local block hash | MidToleranceError; rejected | same |
|
|
169
|
-
|
|
170
|
-
**Limitation**: the local-block requirement means BLOCK req/resp is unusable for initial P2P-only sync (before L1 sync provides local copies for verification). A TODO in the code acknowledges this.
|
|
171
|
-
|
|
172
151
|
### BLOCK_TXS Protocol (`/aztec/req/block_txs/1.0.0`)
|
|
173
152
|
|
|
174
153
|
**Server side**:
|
|
@@ -178,7 +157,7 @@ Protected peers (private/trusted/preferred) are always considered "authenticated
|
|
|
178
157
|
| Request must parse as `BlockTxsRequest` (Fr + TxHashArray + BitVector) | `BADLY_FORMED_REQUEST` + LowToleranceError | `protocols/block_txs/block_txs_handler.ts` |
|
|
179
158
|
| BitVector length: non-negative and <= `MAX_TXS_PER_BLOCK` (65536) | Deserialization throws -> `BADLY_FORMED_REQUEST` | `protocols/block_txs/bitvector.ts` |
|
|
180
159
|
| Archive root not found and no explicit txHashes | `NOT_FOUND` status | handler |
|
|
181
|
-
| Internal error during lookup | Unhandled exception -> stream abort (no `INTERNAL_ERROR` status
|
|
160
|
+
| Internal error during lookup | Unhandled exception -> stream abort (no `INTERNAL_ERROR` status) | handler |
|
|
182
161
|
|
|
183
162
|
Conditional registration: BLOCK_TXS handler only registered when `config.disableTransactions` is false. Otherwise peers get `ERR_UNSUPPORTED_PROTOCOL`.
|
|
184
163
|
|
|
@@ -7,6 +7,7 @@ import { PeerErrorSeverity } from '@aztec/stdlib/p2p';
|
|
|
7
7
|
import { Tx, TxArray, TxHash, type TxValidator } from '@aztec/stdlib/tx';
|
|
8
8
|
|
|
9
9
|
import type { PeerId } from '@libp2p/interface';
|
|
10
|
+
import { strict as assert } from 'assert';
|
|
10
11
|
|
|
11
12
|
import type { IRequestTracker } from '../../tx_collection/request_tracker.js';
|
|
12
13
|
import { ReqRespSubProtocol } from '.././interface.js';
|
|
@@ -459,24 +460,19 @@ export class BatchTxRequester {
|
|
|
459
460
|
* this implies we will query these peers couple of more times and give them a chance to "redeem" themselves before completely ignoring them
|
|
460
461
|
*/
|
|
461
462
|
private handleFailResponseFromPeer(peerId: PeerId, responseStatus: ReqRespStatus) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (responseStatus === ReqRespStatus.RATE_LIMIT_EXCEEDED) {
|
|
479
|
-
this.peers.markPeerRateLimitExceeded(peerId);
|
|
463
|
+
switch (responseStatus) {
|
|
464
|
+
case ReqRespStatus.RATE_LIMIT_EXCEEDED:
|
|
465
|
+
this.peers.markPeerRateLimitExceeded(peerId);
|
|
466
|
+
return;
|
|
467
|
+
case ReqRespStatus.INTERNAL_ERROR:
|
|
468
|
+
this.peers.markPeerDumb(peerId);
|
|
469
|
+
this.txsMetadata.clearPeerData(peerId);
|
|
470
|
+
return;
|
|
471
|
+
default: // includes FAILURE, UNKNOWN
|
|
472
|
+
this.peers.penalisePeer(peerId, PeerErrorSeverity.HighToleranceError);
|
|
473
|
+
this.peers.markPeerDumb(peerId);
|
|
474
|
+
this.txsMetadata.clearPeerData(peerId);
|
|
475
|
+
return;
|
|
480
476
|
}
|
|
481
477
|
}
|
|
482
478
|
|
|
@@ -564,9 +560,10 @@ export class BatchTxRequester {
|
|
|
564
560
|
return;
|
|
565
561
|
}
|
|
566
562
|
|
|
567
|
-
|
|
568
|
-
if (
|
|
569
|
-
this.
|
|
563
|
+
// If the peer doesn't have the block, we mark them as dumb.
|
|
564
|
+
if (!response.peerHasBlock()) {
|
|
565
|
+
this.peers.markPeerDumb(peerId);
|
|
566
|
+
this.txsMetadata.clearPeerData(peerId);
|
|
570
567
|
return;
|
|
571
568
|
}
|
|
572
569
|
|
|
@@ -584,21 +581,6 @@ export class BatchTxRequester {
|
|
|
584
581
|
this.smartRequesterSemaphore.release();
|
|
585
582
|
}
|
|
586
583
|
|
|
587
|
-
/**
|
|
588
|
-
* Handles an archive root mismatch between local state and peer response.
|
|
589
|
-
*
|
|
590
|
-
* - Response archive is Fr.ZERO (peer pruned proposal, legitimate): marks peer dumb.
|
|
591
|
-
* - Non-zero archive mismatch (malicious response): penalises + marks dumb.
|
|
592
|
-
*/
|
|
593
|
-
private handleArchiveRootMismatch(peerId: PeerId, response: BlockTxsResponse): void {
|
|
594
|
-
if (!response.archiveRoot.isZero()) {
|
|
595
|
-
this.peers.penalisePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
this.peers.markPeerDumb(peerId);
|
|
599
|
-
this.txsMetadata.clearPeerData(peerId);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
584
|
private peerHasSomeTxsWeAreMissing(_peerId: PeerId, response: BlockTxsResponse): boolean {
|
|
603
585
|
if (response.txIndices.isEmpty()) {
|
|
604
586
|
return false;
|
|
@@ -614,15 +596,9 @@ export class BatchTxRequester {
|
|
|
614
596
|
}
|
|
615
597
|
|
|
616
598
|
private extractHashesPeerHasFromResponse(response: BlockTxsResponse): Array<TxHash> {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (indicesOfHashesPeerHas.has(idx)) {
|
|
621
|
-
hashes.push(hash);
|
|
622
|
-
}
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
return hashes;
|
|
599
|
+
// Should already have been validated, but just in case.
|
|
600
|
+
assert(response.txIndices.getLength() === this.blockTxsSource.txHashes.length);
|
|
601
|
+
return response.txIndices.getTrueIndices().map(idx => this.blockTxsSource.txHashes[idx]);
|
|
626
602
|
}
|
|
627
603
|
|
|
628
604
|
/*
|
|
@@ -5,6 +5,23 @@ export const DEFAULT_OVERALL_REQUEST_TIMEOUT_MS = 10_000; // Not currently used
|
|
|
5
5
|
export const DEFAULT_REQRESP_DIAL_TIMEOUT_MS = 5_000;
|
|
6
6
|
export const DEFAULT_OPTIMISTIC_NEGOTIATION = false;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Max size of a yamux frame, header included. This is the library default, pinned explicitly when configuring the
|
|
10
|
+
* muxer in the libp2p service so a dependency upgrade cannot silently change it.
|
|
11
|
+
*/
|
|
12
|
+
export const YAMUX_MAX_MESSAGE_SIZE_BYTES = 64 * 1024;
|
|
13
|
+
|
|
14
|
+
/** Size of a yamux data frame header. */
|
|
15
|
+
const YAMUX_HEADER_LENGTH_BYTES = 12;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Max size of a reqresp request payload. A request must fit in a single muxer frame: the responder never reassembles
|
|
19
|
+
* a request from multiple chunks (see `ReqResp.processStream`), and yamux splits writes larger than one frame into
|
|
20
|
+
* multiple frames, each of which arrives as a separate chunk. mplex only splits at 1 MiB, so yamux is the binding
|
|
21
|
+
* constraint.
|
|
22
|
+
*/
|
|
23
|
+
export const MAX_REQRESP_REQUEST_SIZE_BYTES = YAMUX_MAX_MESSAGE_SIZE_BYTES - YAMUX_HEADER_LENGTH_BYTES;
|
|
24
|
+
|
|
8
25
|
// For use in tests.
|
|
9
26
|
export const DEFAULT_P2P_REQRESP_CONFIG: P2PReqRespConfig = {
|
|
10
27
|
overallRequestTimeoutMs: DEFAULT_OVERALL_REQUEST_TIMEOUT_MS,
|
|
@@ -58,8 +58,9 @@ export class BitVector {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Returns all indices which are set to true
|
|
62
|
-
*
|
|
61
|
+
* Returns all indices which are set to true.
|
|
62
|
+
* Ordered by index. No duplicates.
|
|
63
|
+
*/
|
|
63
64
|
getTrueIndices(): number[] {
|
|
64
65
|
return Array.from({ length: this.length }, (_, i) => i).filter(i => this.isSet(i));
|
|
65
66
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
1
|
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
3
|
-
import { TxArray } from '@aztec/stdlib/tx';
|
|
2
|
+
import { TxArray, TxHash } from '@aztec/stdlib/tx';
|
|
4
3
|
|
|
5
4
|
import type { PeerId } from '@libp2p/interface';
|
|
6
5
|
|
|
@@ -36,42 +35,45 @@ export function reqRespBlockTxsHandler(
|
|
|
36
35
|
} catch (err: any) {
|
|
37
36
|
throw new ReqRespStatusError(ReqRespStatus.BADLY_FORMED_REQUEST, { cause: err });
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
|
|
39
|
+
// In principle assume we haven't found the block. This is how that is signaled to the requester.
|
|
40
|
+
let availableIndicesBitVector: BitVector = BitVector.init(0, []);
|
|
41
|
+
// We always try to service the explicitly requested tx hashes, even if we don't have the block.
|
|
42
|
+
const requestedTxsHashes: Set<string> = new Set(request.txHashes.map(h => h.toString()));
|
|
43
|
+
|
|
44
|
+
// First try attestation pool, then fall back to archiver.
|
|
45
|
+
let blockTxHashes = (await attestationPool.getBlockProposalByArchive(request.archiveRoot.toString()))?.txHashes;
|
|
46
|
+
if (!blockTxHashes) {
|
|
47
|
+
blockTxHashes = (await archiver.getBlock({ archive: request.archiveRoot }))?.body.txEffects.map(
|
|
43
48
|
effect => effect.txHash,
|
|
44
49
|
);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
// If we have found the block,
|
|
53
|
+
if (blockTxHashes) {
|
|
54
|
+
// First confirm that we are talking about the same block (up to tx hashes and order).
|
|
55
|
+
// If we are not, then we can't:
|
|
56
|
+
// 1. Use the indices from the request to get the txs from the pool.
|
|
57
|
+
// 2. Respond to the peer with the txs we have using indices.
|
|
58
|
+
const blockTxHashesCommitment = BlockTxsRequest.computeBlockTxHashesCommitment(blockTxHashes);
|
|
59
|
+
if (blockTxHashesCommitment.equals(request.blockTxHashesCommitment)) {
|
|
60
|
+
// In that case, we can also get the available indices from the pool.
|
|
61
|
+
const txsAvailableInPool = await txPool.hasTxs(blockTxHashes);
|
|
62
|
+
// Map txs in the pool to their indices in the block
|
|
63
|
+
const availableIndices = txsAvailableInPool.map((hasTx, idx) => (hasTx ? idx : -1)).filter(idx => idx !== -1);
|
|
64
|
+
availableIndicesBitVector = BitVector.init(blockTxHashes.length, availableIndices);
|
|
51
65
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const response = new BlockTxsResponse(Fr.zero(), new TxArray(...responseTxs), BitVector.init(0, []));
|
|
57
|
-
return response.toBuffer();
|
|
66
|
+
// We add the requested tx hashes (by index) to the list of tx hashes we are sending to the peer.
|
|
67
|
+
const requestedIndices = new Set(request.txIndices.getTrueIndices());
|
|
68
|
+
blockTxHashes.filter((_, idx) => requestedIndices.has(idx)).forEach(h => requestedTxsHashes.add(h.toString()));
|
|
69
|
+
}
|
|
58
70
|
}
|
|
59
71
|
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const txsAvailableInPool = await txPool.hasTxs(txHashes);
|
|
66
|
-
// Map txs in the pool to their indices in the block
|
|
67
|
-
const availableIndices = txsAvailableInPool.map((hasTx, idx) => (hasTx ? idx : -1)).filter(idx => idx !== -1);
|
|
68
|
-
const responseBitVector = BitVector.init(txHashes.length, availableIndices);
|
|
69
|
-
|
|
70
|
-
const requestedIndices = new Set(request.txIndices.getTrueIndices());
|
|
71
|
-
requestedTxsHashes = txHashes.filter((_, idx) => requestedIndices.has(idx));
|
|
72
|
-
|
|
73
|
-
const responseTxs = (await txPool.getTxsByHash(requestedTxsHashes)).filter(tx => !!tx);
|
|
74
|
-
const response = new BlockTxsResponse(request.archiveRoot, new TxArray(...responseTxs), responseBitVector);
|
|
72
|
+
// Finally, get the txs from the pool and create the response.
|
|
73
|
+
const responseTxs = (
|
|
74
|
+
await txPool.getTxsByHash(Array.from(requestedTxsHashes).map(h => TxHash.fromString(h)))
|
|
75
|
+
).filter(tx => !!tx);
|
|
76
|
+
const response = new BlockTxsResponse(new TxArray(...responseTxs), availableIndicesBitVector);
|
|
75
77
|
|
|
76
78
|
return response.toBuffer();
|
|
77
79
|
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
1
2
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
3
|
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
3
4
|
import { MAX_TX_SIZE_KB } from '@aztec/stdlib/p2p';
|
|
4
5
|
import { TxArray, type TxHash, TxHashArray } from '@aztec/stdlib/tx';
|
|
5
6
|
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
|
|
6
9
|
import { BitVector } from './bitvector.js';
|
|
7
10
|
|
|
8
11
|
/** Minimal interface for a block source that provides tx hashes and an archive root. */
|
|
@@ -16,17 +19,27 @@ export interface BlockTxsSource {
|
|
|
16
19
|
*/
|
|
17
20
|
export class BlockTxsRequest {
|
|
18
21
|
constructor(
|
|
19
|
-
// Archive root after the proposed block is applied (proposal identifier)
|
|
22
|
+
// Archive root after the proposed block is applied (proposal identifier).
|
|
20
23
|
readonly archiveRoot: Fr,
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
//
|
|
24
|
-
// 1 means we want the tx, 0 means we don't
|
|
25
|
-
// If we know peer has the Block Proposal then we can use this BitVector
|
|
26
|
-
// Otherwise we can use this optimization
|
|
24
|
+
// BitVector indicating which txs from the block (proposal) we are requesting
|
|
25
|
+
// 1 means we want the tx, 0 means we don't.
|
|
26
|
+
// This will only return txs in the response if the peer has the block (proposal).
|
|
27
27
|
readonly txIndices: BitVector,
|
|
28
|
+
// If we ask for txs via indices, the responder needs to be sure that we are talking about the same block.
|
|
29
|
+
// Unfortunately, the archive root is not enough, since it's possible for the peer to have seen a different
|
|
30
|
+
// block (proposal) with the same archive root but different txs. We include a commitment to the tx hashes
|
|
31
|
+
// so that the responder can verify this (for its own sake, and not be penalized).
|
|
32
|
+
readonly blockTxHashesCommitment: Buffer32,
|
|
33
|
+
// Explicit hashes of txs we are requesting (optional). The peer will try to serve these txs even if
|
|
34
|
+
// it doesn't have the block. If the requester lists txs from the block here, this serves as a fallback.
|
|
35
|
+
// Note: if a hash here is NOT part of the block (proposal), the peer can decide to return it or not.
|
|
36
|
+
readonly txHashes: TxHashArray,
|
|
28
37
|
) {}
|
|
29
38
|
|
|
39
|
+
static computeBlockTxHashesCommitment(blockTxHashes: TxHash[]): Buffer32 {
|
|
40
|
+
return Buffer32.fromBuffer(createHash('sha256').update(serializeToBuffer(blockTxHashes)).digest());
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
/**
|
|
31
44
|
* Creates new BlockTxsRequest given a block txs source and missing tx hashes.
|
|
32
45
|
*
|
|
@@ -59,8 +72,9 @@ export class BlockTxsRequest {
|
|
|
59
72
|
|
|
60
73
|
const requestBitVector = BitVector.init(blockTxsSource.txHashes.length, missingIndices);
|
|
61
74
|
const hashes = includeFullTxHashes ? new TxHashArray(...missingTxHashes) : new TxHashArray();
|
|
75
|
+
const commitment = BlockTxsRequest.computeBlockTxHashesCommitment(blockTxsSource.txHashes);
|
|
62
76
|
|
|
63
|
-
return new BlockTxsRequest(blockTxsSource.archive,
|
|
77
|
+
return new BlockTxsRequest(blockTxsSource.archive, requestBitVector, commitment, hashes);
|
|
64
78
|
}
|
|
65
79
|
|
|
66
80
|
/**
|
|
@@ -71,10 +85,11 @@ export class BlockTxsRequest {
|
|
|
71
85
|
static fromBuffer(buffer: Buffer | BufferReader): BlockTxsRequest {
|
|
72
86
|
const reader = BufferReader.asReader(buffer);
|
|
73
87
|
const archiveRoot = Fr.fromBuffer(reader);
|
|
74
|
-
const txHashes = TxHashArray.fromBuffer(reader);
|
|
75
88
|
const txIndices = BitVector.fromBuffer(reader);
|
|
89
|
+
const commitment = Buffer32.fromBuffer(reader);
|
|
90
|
+
const txHashes = TxHashArray.fromBuffer(reader);
|
|
76
91
|
|
|
77
|
-
return new BlockTxsRequest(archiveRoot,
|
|
92
|
+
return new BlockTxsRequest(archiveRoot, txIndices, commitment, txHashes);
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
/**
|
|
@@ -82,7 +97,12 @@ export class BlockTxsRequest {
|
|
|
82
97
|
* @returns Buffer representation of the BlockTxRequest object
|
|
83
98
|
*/
|
|
84
99
|
toBuffer(): Buffer {
|
|
85
|
-
return serializeToBuffer([
|
|
100
|
+
return serializeToBuffer([
|
|
101
|
+
this.archiveRoot,
|
|
102
|
+
this.txIndices.toBuffer(),
|
|
103
|
+
this.blockTxHashesCommitment.toBuffer(),
|
|
104
|
+
this.txHashes.toBuffer(),
|
|
105
|
+
]);
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
108
|
|
|
@@ -91,13 +111,23 @@ export class BlockTxsRequest {
|
|
|
91
111
|
*/
|
|
92
112
|
export class BlockTxsResponse {
|
|
93
113
|
constructor(
|
|
94
|
-
|
|
95
|
-
readonly txs: TxArray,
|
|
114
|
+
// List of transactions we requested and peer has.
|
|
115
|
+
readonly txs: TxArray,
|
|
96
116
|
// BitVector indicating which txs from the proposal are available at the peer
|
|
97
|
-
// 1 means the tx is available, 0 means it is not
|
|
117
|
+
// 1 means the tx is available, 0 means it is not.
|
|
118
|
+
// The vector should be empty to signal that the peer doesn't have the block.
|
|
119
|
+
// Otherwise, it should be a subset of the indices of the block tx hashes.
|
|
98
120
|
readonly txIndices: BitVector,
|
|
99
121
|
) {}
|
|
100
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Whether the response indicates that the peer has the block.
|
|
125
|
+
* @returns True if the peer has the block, false otherwise.
|
|
126
|
+
*/
|
|
127
|
+
peerHasBlock(): boolean {
|
|
128
|
+
return this.txIndices.getLength() > 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
101
131
|
/**
|
|
102
132
|
* Deserializes the BlockTxResponse object from a Buffer
|
|
103
133
|
* @param buffer - Buffer or BufferReader object to deserialize
|
|
@@ -105,25 +135,22 @@ export class BlockTxsResponse {
|
|
|
105
135
|
*/
|
|
106
136
|
static fromBuffer(buffer: Buffer | BufferReader): BlockTxsResponse {
|
|
107
137
|
const reader = BufferReader.asReader(buffer);
|
|
108
|
-
const archiveRoot = Fr.fromBuffer(reader);
|
|
109
138
|
const txs = TxArray.fromBuffer(reader);
|
|
110
139
|
const txIndices = BitVector.fromBuffer(reader);
|
|
111
140
|
|
|
112
|
-
return new BlockTxsResponse(
|
|
141
|
+
return new BlockTxsResponse(txs, txIndices);
|
|
113
142
|
}
|
|
114
143
|
|
|
115
144
|
/**
|
|
116
145
|
* Serializes the BlockTxResponse object into a Buffer
|
|
117
|
-
* @dev: In current implementation, txIndices is serialized as Buffer of unknown length,
|
|
118
|
-
* thus we serialize it last
|
|
119
146
|
* @returns Buffer representation of the BlockTxResponse object
|
|
120
147
|
*/
|
|
121
148
|
toBuffer(): Buffer {
|
|
122
|
-
return serializeToBuffer([this.
|
|
149
|
+
return serializeToBuffer([this.txs.toBuffer(), this.txIndices.toBuffer()]);
|
|
123
150
|
}
|
|
124
151
|
|
|
125
152
|
static empty(): BlockTxsResponse {
|
|
126
|
-
return new BlockTxsResponse(
|
|
153
|
+
return new BlockTxsResponse(new TxArray(), BitVector.init(0, []));
|
|
127
154
|
}
|
|
128
155
|
}
|
|
129
156
|
|