@aztec/p2p 0.75.0-commit.c03ba01a2a4122e43e90d5133ba017e54b90e9d2 → 0.76.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/bootstrap/bootstrap.d.ts +38 -0
- package/dest/bootstrap/bootstrap.d.ts.map +1 -0
- package/dest/bootstrap/bootstrap.js +31 -38
- package/dest/client/factory.d.ts +21 -0
- package/dest/client/factory.d.ts.map +1 -0
- package/dest/client/factory.js +13 -11
- package/dest/client/index.d.ts +3 -0
- package/dest/client/index.d.ts.map +1 -0
- package/dest/client/index.js +1 -0
- package/dest/client/p2p_client.d.ts +332 -0
- package/dest/client/p2p_client.d.ts.map +1 -0
- package/dest/client/p2p_client.js +535 -513
- package/dest/config.d.ts +187 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +64 -53
- package/dest/errors/reqresp.error.d.ts +28 -0
- package/dest/errors/reqresp.error.d.ts.map +1 -0
- package/dest/errors/reqresp.error.js +10 -6
- package/dest/index.d.ts +9 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +1 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +57 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.js +2 -6
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +3 -0
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -0
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +32 -64
- package/dest/mem_pools/attestation_pool/index.d.ts +3 -0
- package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -0
- package/dest/mem_pools/attestation_pool/index.js +1 -0
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +22 -0
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -0
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +19 -22
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +17 -0
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -0
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +26 -22
- package/dest/mem_pools/attestation_pool/mocks.d.ts +18 -0
- package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -0
- package/dest/mem_pools/attestation_pool/mocks.js +6 -10
- package/dest/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.d.ts +7 -0
- package/dest/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.d.ts.map +1 -0
- package/dest/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js +2 -1
- package/dest/mem_pools/epoch_proof_quote_pool/index.d.ts +4 -0
- package/dest/mem_pools/epoch_proof_quote_pool/index.d.ts.map +1 -0
- package/dest/mem_pools/epoch_proof_quote_pool/index.js +1 -0
- package/dest/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.d.ts +12 -0
- package/dest/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.d.ts.map +1 -0
- package/dest/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.js +4 -5
- package/dest/mem_pools/epoch_proof_quote_pool/test_utils.d.ts +8 -0
- package/dest/mem_pools/epoch_proof_quote_pool/test_utils.d.ts.map +1 -0
- package/dest/mem_pools/epoch_proof_quote_pool/test_utils.js +3 -2
- package/dest/mem_pools/index.d.ts +5 -0
- package/dest/mem_pools/index.d.ts.map +1 -0
- package/dest/mem_pools/index.js +2 -1
- package/dest/mem_pools/instrumentation.d.ts +31 -0
- package/dest/mem_pools/instrumentation.d.ts.map +1 -0
- package/dest/mem_pools/instrumentation.js +42 -37
- package/dest/mem_pools/interface.d.ts +13 -0
- package/dest/mem_pools/interface.d.ts.map +1 -0
- package/dest/mem_pools/interface.js +2 -3
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +66 -0
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +134 -127
- package/dest/mem_pools/tx_pool/index.d.ts +4 -0
- package/dest/mem_pools/tx_pool/index.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool/index.js +1 -0
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +56 -0
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool/memory_tx_pool.js +43 -45
- package/dest/mem_pools/tx_pool/priority.d.ts +8 -0
- package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool/priority.js +3 -1
- package/dest/mem_pools/tx_pool/tx_pool.d.ts +66 -0
- package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool/tx_pool.js +2 -3
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +7 -0
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +37 -107
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +8 -0
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -0
- package/dest/msg_validators/attestation_validator/attestation_validator.js +3 -3
- package/dest/msg_validators/attestation_validator/index.d.ts +2 -0
- package/dest/msg_validators/attestation_validator/index.d.ts.map +1 -0
- package/dest/msg_validators/attestation_validator/index.js +1 -0
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +8 -0
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +1 -0
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.js +2 -2
- package/dest/msg_validators/block_proposal_validator/index.d.ts +2 -0
- package/dest/msg_validators/block_proposal_validator/index.d.ts.map +1 -0
- package/dest/msg_validators/block_proposal_validator/index.js +1 -0
- package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.d.ts +8 -0
- package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.d.ts.map +1 -0
- package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.js +2 -2
- package/dest/msg_validators/epoch_proof_quote_validator/index.d.ts +2 -0
- package/dest/msg_validators/epoch_proof_quote_validator/index.d.ts.map +1 -0
- package/dest/msg_validators/epoch_proof_quote_validator/index.js +1 -0
- package/dest/msg_validators/index.d.ts +4 -0
- package/dest/msg_validators/index.d.ts.map +1 -0
- package/dest/msg_validators/index.js +1 -0
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +7 -0
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +11 -9
- package/dest/msg_validators/tx_validator/block_header_validator.d.ts +11 -0
- package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/block_header_validator.js +12 -17
- package/dest/msg_validators/tx_validator/data_validator.d.ts +6 -0
- package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/data_validator.js +32 -41
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +12 -0
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/double_spend_validator.js +14 -22
- package/dest/msg_validators/tx_validator/index.d.ts +7 -0
- package/dest/msg_validators/tx_validator/index.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/index.js +1 -0
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts +10 -0
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/metadata_validator.js +26 -29
- package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts +8 -0
- package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/tx_proof_validator.js +12 -17
- package/dest/services/data_store.d.ts +27 -0
- package/dest/services/data_store.d.ts.map +1 -0
- package/dest/services/data_store.js +57 -57
- package/dest/services/discv5/discV5_service.d.ts +40 -0
- package/dest/services/discv5/discV5_service.d.ts.map +1 -0
- package/dest/services/discv5/discV5_service.js +82 -43
- package/dest/services/dummy_service.d.ts +83 -0
- package/dest/services/dummy_service.d.ts.map +1 -0
- package/dest/services/dummy_service.js +59 -40
- package/dest/services/encoding.d.ts +31 -0
- package/dest/services/encoding.d.ts.map +1 -0
- package/dest/services/encoding.js +9 -10
- package/dest/services/index.d.ts +3 -0
- package/dest/services/index.d.ts.map +1 -0
- package/dest/services/index.js +1 -0
- package/dest/services/libp2p/libp2p_logger.d.ts +7 -0
- package/dest/services/libp2p/libp2p_logger.d.ts.map +1 -0
- package/dest/services/libp2p/libp2p_logger.js +67 -0
- package/dest/services/libp2p/libp2p_service.d.ts +225 -0
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -0
- package/dest/services/libp2p/libp2p_service.js +727 -709
- package/dest/services/peer-manager/metrics.d.ts +12 -0
- package/dest/services/peer-manager/metrics.d.ts.map +1 -0
- package/dest/services/peer-manager/metrics.js +7 -14
- package/dest/services/peer-manager/peer_manager.d.ts +76 -0
- package/dest/services/peer-manager/peer_manager.d.ts.map +1 -0
- package/dest/services/peer-manager/peer_manager.js +342 -340
- package/dest/services/peer-manager/peer_scoring.d.ts +28 -0
- package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -0
- package/dest/services/peer-manager/peer_scoring.js +18 -20
- package/dest/services/reqresp/config.d.ts +16 -0
- package/dest/services/reqresp/config.d.ts.map +1 -0
- package/dest/services/reqresp/config.js +5 -4
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts +45 -0
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts.map +1 -0
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.js +28 -35
- package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +61 -0
- package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -0
- package/dest/services/reqresp/connection-sampler/connection_sampler.js +59 -60
- package/dest/services/reqresp/index.d.ts +6 -0
- package/dest/services/reqresp/index.d.ts.map +1 -0
- package/dest/services/reqresp/index.js +3 -1
- package/dest/services/reqresp/interface.d.ts +116 -0
- package/dest/services/reqresp/interface.d.ts.map +1 -0
- package/dest/services/reqresp/interface.js +30 -25
- package/dest/services/reqresp/metrics.d.ts +15 -0
- package/dest/services/reqresp/metrics.d.ts.map +1 -0
- package/dest/services/reqresp/metrics.js +10 -23
- package/dest/services/reqresp/protocols/block.d.ts +4 -0
- package/dest/services/reqresp/protocols/block.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/block.js +2 -1
- package/dest/services/reqresp/protocols/goodbye.d.ts +51 -0
- package/dest/services/reqresp/protocols/goodbye.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/goodbye.js +41 -36
- package/dest/services/reqresp/protocols/index.d.ts +9 -0
- package/dest/services/reqresp/protocols/index.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/index.js +3 -1
- package/dest/services/reqresp/protocols/ping.d.ts +9 -0
- package/dest/services/reqresp/protocols/ping.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/ping.js +3 -1
- package/dest/services/reqresp/protocols/status.d.ts +9 -0
- package/dest/services/reqresp/protocols/status.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/status.js +3 -1
- package/dest/services/reqresp/protocols/tx.d.ts +13 -0
- package/dest/services/reqresp/protocols/tx.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/tx.js +8 -5
- package/dest/services/reqresp/rate-limiter/index.d.ts +2 -0
- package/dest/services/reqresp/rate-limiter/index.d.ts.map +1 -0
- package/dest/services/reqresp/rate-limiter/index.js +1 -0
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +102 -0
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -0
- package/dest/services/reqresp/rate-limiter/rate_limiter.js +35 -39
- package/dest/services/reqresp/rate-limiter/rate_limits.d.ts +3 -0
- package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -0
- package/dest/services/reqresp/rate-limiter/rate_limits.js +17 -16
- package/dest/services/reqresp/reqresp.d.ts +166 -0
- package/dest/services/reqresp/reqresp.d.ts.map +1 -0
- package/dest/services/reqresp/reqresp.js +463 -460
- package/dest/services/reqresp/status.d.ts +31 -0
- package/dest/services/reqresp/status.d.ts.map +1 -0
- package/dest/services/reqresp/status.js +17 -16
- package/dest/services/service.d.ts +86 -0
- package/dest/services/service.d.ts.map +1 -0
- package/dest/services/service.js +4 -3
- package/dest/services/types.d.ts +32 -0
- package/dest/services/types.d.ts.map +1 -0
- package/dest/services/types.js +19 -20
- package/dest/test-helpers/generate-peer-id-private-keys.d.ts +7 -0
- package/dest/test-helpers/generate-peer-id-private-keys.d.ts.map +1 -0
- package/dest/test-helpers/generate-peer-id-private-keys.js +15 -0
- package/dest/test-helpers/get-ports.d.ts +7 -0
- package/dest/test-helpers/get-ports.d.ts.map +1 -0
- package/dest/test-helpers/get-ports.js +8 -0
- package/dest/test-helpers/index.d.ts +6 -0
- package/dest/test-helpers/index.d.ts.map +1 -0
- package/dest/test-helpers/index.js +6 -0
- package/dest/test-helpers/make-enrs.d.ts +16 -0
- package/dest/test-helpers/make-enrs.d.ts.map +1 -0
- package/dest/test-helpers/make-enrs.js +35 -0
- package/dest/test-helpers/make-test-p2p-clients.d.ts +37 -0
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -0
- package/dest/test-helpers/make-test-p2p-clients.js +71 -0
- package/dest/test-helpers/reqresp-nodes.d.ts +55 -0
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -0
- package/dest/test-helpers/reqresp-nodes.js +183 -0
- package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -0
- package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -0
- package/dest/testbench/p2p_client_testbench_worker.js +125 -0
- package/dest/util.d.ts +53 -0
- package/dest/util.d.ts.map +1 -0
- package/dest/util.js +34 -23
- package/dest/versioning.d.ts +12 -0
- package/dest/versioning.d.ts.map +1 -0
- package/dest/versioning.js +38 -0
- package/package.json +11 -9
- package/src/bootstrap/bootstrap.ts +9 -3
- package/src/client/factory.ts +12 -5
- package/src/config.ts +56 -29
- package/src/mem_pools/index.ts +3 -3
- package/src/mem_pools/instrumentation.ts +2 -3
- package/src/msg_validators/attestation_validator/attestation_validator.ts +3 -3
- package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +3 -3
- package/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts +3 -3
- package/src/services/discv5/discV5_service.ts +67 -18
- package/src/services/dummy_service.ts +2 -0
- package/src/services/libp2p/libp2p_logger.ts +78 -0
- package/src/services/libp2p/libp2p_service.ts +47 -10
- package/src/services/reqresp/protocols/goodbye.ts +1 -1
- package/src/services/reqresp/reqresp.ts +9 -1
- package/src/services/service.ts +2 -0
- package/src/services/types.ts +2 -10
- package/src/test-helpers/generate-peer-id-private-keys.ts +15 -0
- package/src/test-helpers/get-ports.ts +8 -0
- package/src/test-helpers/index.ts +5 -0
- package/src/test-helpers/make-enrs.ts +44 -0
- package/src/test-helpers/make-test-p2p-clients.ts +124 -0
- package/src/{mocks/index.ts → test-helpers/reqresp-nodes.ts} +10 -5
- package/src/testbench/README.md +20 -0
- package/src/testbench/p2p_client_testbench_worker.ts +156 -0
- package/src/testbench/scripts/run_testbench.sh +7 -0
- package/src/versioning.ts +50 -0
- package/dest/mocks/index.js +0 -190
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
}
|
|
1
|
+
import { __esDecorate, __runInitializers } from "tslib";
|
|
7
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
8
3
|
import { trackSpan } from '@aztec/telemetry-client';
|
|
9
4
|
import { inspect } from 'util';
|
|
@@ -16,345 +11,348 @@ const MAX_DIAL_ATTEMPTS = 3;
|
|
|
16
11
|
const MAX_CACHED_PEERS = 100;
|
|
17
12
|
const MAX_CACHED_PEER_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
18
13
|
const FAILED_PEER_BAN_TIME_MS = 5 * 60 * 1000; // 5 minutes timeout after failing MAX_DIAL_ATTEMPTS
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Handle lost connections
|
|
47
|
-
this.libP2PNode.addEventListener(PeerEvent.DISCONNECTED, this.handleDisconnectedPeerEvent.bind(this));
|
|
48
|
-
// Handle Discovered peers
|
|
49
|
-
this.discoveredPeerHandler = (enr)=>this.handleDiscoveredPeer(enr).catch((e)=>this.logger.error('Error handling discovered peer', e));
|
|
50
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
51
|
-
this.peerDiscoveryService.on(PeerEvent.DISCOVERED, this.discoveredPeerHandler);
|
|
52
|
-
// Display peer counts every 60 seconds
|
|
53
|
-
this.displayPeerCountsPeerHeartbeat = Math.floor(60_000 / this.config.peerCheckIntervalMS);
|
|
54
|
-
}
|
|
55
|
-
get tracer() {
|
|
56
|
-
return this.metrics.tracer;
|
|
57
|
-
}
|
|
58
|
-
heartbeat() {
|
|
59
|
-
this.heartbeatCounter++;
|
|
60
|
-
this.peerScoring.decayAllScores();
|
|
61
|
-
this.cleanupExpiredTimeouts();
|
|
62
|
-
this.discover();
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Cleans up expired timeouts.
|
|
66
|
-
*
|
|
67
|
-
* When peers fail to dial after a number of retries, they are temporarily timed out.
|
|
68
|
-
* This function removes any peers that have been in the timed out state for too long.
|
|
69
|
-
* To give them a chance to reconnect.
|
|
70
|
-
*/ cleanupExpiredTimeouts() {
|
|
71
|
-
// Clean up expired timeouts
|
|
72
|
-
const now = Date.now();
|
|
73
|
-
for (const [peerId, timedOutPeer] of this.timedOutPeers.entries()){
|
|
74
|
-
if (now >= timedOutPeer.timeoutUntilMs) {
|
|
75
|
-
this.timedOutPeers.delete(peerId);
|
|
14
|
+
let PeerManager = (() => {
|
|
15
|
+
var _a;
|
|
16
|
+
let _instanceExtraInitializers = [];
|
|
17
|
+
let _heartbeat_decorators;
|
|
18
|
+
return _a = class PeerManager {
|
|
19
|
+
constructor(libP2PNode, peerDiscoveryService, config, telemetryClient, logger = createLogger('p2p:peer-manager'), peerScoring, reqresp) {
|
|
20
|
+
this.libP2PNode = (__runInitializers(this, _instanceExtraInitializers), libP2PNode);
|
|
21
|
+
this.peerDiscoveryService = peerDiscoveryService;
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
this.peerScoring = peerScoring;
|
|
25
|
+
this.reqresp = reqresp;
|
|
26
|
+
this.cachedPeers = new Map();
|
|
27
|
+
this.heartbeatCounter = 0;
|
|
28
|
+
this.displayPeerCountsPeerHeartbeat = 0;
|
|
29
|
+
this.timedOutPeers = new Map();
|
|
30
|
+
this.metrics = new PeerManagerMetrics(telemetryClient, 'PeerManager');
|
|
31
|
+
// Handle new established connections
|
|
32
|
+
this.libP2PNode.addEventListener(PeerEvent.CONNECTED, this.handleConnectedPeerEvent.bind(this));
|
|
33
|
+
// Handle lost connections
|
|
34
|
+
this.libP2PNode.addEventListener(PeerEvent.DISCONNECTED, this.handleDisconnectedPeerEvent.bind(this));
|
|
35
|
+
// Handle Discovered peers
|
|
36
|
+
this.discoveredPeerHandler = (enr) => this.handleDiscoveredPeer(enr).catch(e => this.logger.error('Error handling discovered peer', e));
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
38
|
+
this.peerDiscoveryService.on(PeerEvent.DISCOVERED, this.discoveredPeerHandler);
|
|
39
|
+
// Display peer counts every 60 seconds
|
|
40
|
+
this.displayPeerCountsPeerHeartbeat = Math.floor(60000 / this.config.peerCheckIntervalMS);
|
|
76
41
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Simply logs the type of connected peer.
|
|
81
|
-
* @param e - The connected peer event.
|
|
82
|
-
*/ handleConnectedPeerEvent(e) {
|
|
83
|
-
const peerId = e.detail;
|
|
84
|
-
if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
|
|
85
|
-
this.logger.verbose(`Connected to bootstrap peer ${peerId.toString()}`);
|
|
86
|
-
} else {
|
|
87
|
-
this.logger.verbose(`Connected to transaction peer ${peerId.toString()}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Simply logs the type of disconnected peer.
|
|
92
|
-
* @param e - The disconnected peer event.
|
|
93
|
-
*/ handleDisconnectedPeerEvent(e) {
|
|
94
|
-
const peerId = e.detail;
|
|
95
|
-
if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
|
|
96
|
-
this.logger.verbose(`Disconnected from bootstrap peer ${peerId.toString()}`);
|
|
97
|
-
} else {
|
|
98
|
-
this.logger.verbose(`Disconnected from transaction peer ${peerId.toString()}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Handles a goodbye received from a peer.
|
|
103
|
-
*
|
|
104
|
-
* Used as the reqresp handler when a peer sends us goodbye message.
|
|
105
|
-
* @param peerId - The peer ID.
|
|
106
|
-
* @param reason - The reason for the goodbye.
|
|
107
|
-
*/ goodbyeReceived(peerId, reason) {
|
|
108
|
-
this.logger.debug(`Goodbye received from peer ${peerId.toString()} with reason ${prettyGoodbyeReason(reason)}`);
|
|
109
|
-
this.metrics.recordGoodbyeReceived(reason);
|
|
110
|
-
void this.disconnectPeer(peerId);
|
|
111
|
-
}
|
|
112
|
-
penalizePeer(peerId, penalty) {
|
|
113
|
-
this.peerScoring.penalizePeer(peerId, penalty);
|
|
114
|
-
}
|
|
115
|
-
getPeerScore(peerId) {
|
|
116
|
-
return this.peerScoring.getScore(peerId);
|
|
117
|
-
}
|
|
118
|
-
getPeers(includePending = false) {
|
|
119
|
-
const connected = this.libP2PNode.getPeers().map((peer)=>({
|
|
120
|
-
id: peer.toString(),
|
|
121
|
-
score: this.getPeerScore(peer.toString()),
|
|
122
|
-
status: 'connected'
|
|
123
|
-
}));
|
|
124
|
-
if (!includePending) {
|
|
125
|
-
return connected;
|
|
126
|
-
}
|
|
127
|
-
const dialQueue = this.libP2PNode.getDialQueue().filter((peer)=>!!peer.peerId).map((peer)=>({
|
|
128
|
-
id: peer.peerId.toString(),
|
|
129
|
-
status: 'dialing',
|
|
130
|
-
dialStatus: peer.status,
|
|
131
|
-
addresses: peer.multiaddrs.map((m)=>m.toString())
|
|
132
|
-
}));
|
|
133
|
-
const cachedPeers = Array.from(this.cachedPeers.values()).filter((peer)=>!dialQueue.some((dialPeer)=>dialPeer.id && peer.peerId.toString() === dialPeer.id.toString())).filter((peer)=>!connected.some((connPeer)=>connPeer.id.toString() === peer.peerId.toString())).map((peer)=>({
|
|
134
|
-
status: 'cached',
|
|
135
|
-
id: peer.peerId.toString(),
|
|
136
|
-
addresses: [
|
|
137
|
-
peer.multiaddrTcp.toString()
|
|
138
|
-
],
|
|
139
|
-
dialAttempts: peer.dialAttempts,
|
|
140
|
-
enr: peer.enr.encodeTxt()
|
|
141
|
-
}));
|
|
142
|
-
return [
|
|
143
|
-
...connected,
|
|
144
|
-
...dialQueue,
|
|
145
|
-
...cachedPeers
|
|
146
|
-
];
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Discovers peers.
|
|
150
|
-
*/ discover() {
|
|
151
|
-
const connections = this.libP2PNode.getConnections();
|
|
152
|
-
const healthyConnections = this.pruneUnhealthyPeers(connections);
|
|
153
|
-
// Calculate how many connections we're looking to make
|
|
154
|
-
const peersToConnect = this.config.maxPeerCount - healthyConnections.length;
|
|
155
|
-
const logLevel = this.heartbeatCounter % this.displayPeerCountsPeerHeartbeat === 0 ? 'info' : 'debug';
|
|
156
|
-
this.logger[logLevel](`Connected to ${connections.length} peers`, {
|
|
157
|
-
connections: connections.length,
|
|
158
|
-
maxPeerCount: this.config.maxPeerCount,
|
|
159
|
-
cachedPeers: this.cachedPeers.size,
|
|
160
|
-
...this.peerScoring.getStats()
|
|
161
|
-
});
|
|
162
|
-
// Exit if no peers to connect
|
|
163
|
-
if (peersToConnect <= 0) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const cachedPeersToDial = [];
|
|
167
|
-
const pendingDials = new Set(this.libP2PNode.getDialQueue().map((pendingDial)=>pendingDial.peerId?.toString()).filter(Boolean));
|
|
168
|
-
for (const [id, peerData] of this.cachedPeers.entries()){
|
|
169
|
-
// if already dialling or connected to, remove from cache
|
|
170
|
-
if (pendingDials.has(id) || healthyConnections.some((conn)=>conn.remotePeer.equals(peerData.peerId)) || // if peer has been in cache for the max cache age, remove from cache
|
|
171
|
-
Date.now() - peerData.addedUnixMs > MAX_CACHED_PEER_AGE_MS) {
|
|
172
|
-
this.cachedPeers.delete(id);
|
|
173
|
-
} else {
|
|
174
|
-
// cachedPeersToDial.set(id, enr);
|
|
175
|
-
cachedPeersToDial.push(peerData);
|
|
42
|
+
get tracer() {
|
|
43
|
+
return this.metrics.tracer;
|
|
176
44
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
this.cachedPeers.delete(peer.peerId.toString());
|
|
183
|
-
void this.dialPeer(peer);
|
|
184
|
-
}
|
|
185
|
-
// if we need more peers, start randomNodesQuery
|
|
186
|
-
if (peersToConnect > 0) {
|
|
187
|
-
this.logger.trace(`Running random nodes query to connect to ${peersToConnect} peers`);
|
|
188
|
-
void this.peerDiscoveryService.runRandomNodesQuery();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
pruneUnhealthyPeers(connections) {
|
|
192
|
-
const connectedHealthyPeers = [];
|
|
193
|
-
for (const peer of connections){
|
|
194
|
-
const score = this.peerScoring.getScoreState(peer.remotePeer.toString());
|
|
195
|
-
switch(score){
|
|
196
|
-
case PeerScoreState.Banned:
|
|
197
|
-
void this.goodbyeAndDisconnectPeer(peer.remotePeer, GoodByeReason.BANNED);
|
|
198
|
-
break;
|
|
199
|
-
case PeerScoreState.Disconnect:
|
|
200
|
-
void this.goodbyeAndDisconnectPeer(peer.remotePeer, GoodByeReason.DISCONNECTED);
|
|
201
|
-
break;
|
|
202
|
-
case PeerScoreState.Healthy:
|
|
203
|
-
connectedHealthyPeers.push(peer);
|
|
45
|
+
heartbeat() {
|
|
46
|
+
this.heartbeatCounter++;
|
|
47
|
+
this.peerScoring.decayAllScores();
|
|
48
|
+
this.cleanupExpiredTimeouts();
|
|
49
|
+
this.discover();
|
|
204
50
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
async disconnectPeer(peer) {
|
|
222
|
-
try {
|
|
223
|
-
await this.libP2PNode.hangUp(peer);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
this.logger.debug(`Failed to disconnect peer ${peer.toString()}`, {
|
|
226
|
-
error: inspect(error)
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Handles a discovered peer.
|
|
232
|
-
* @param enr - The discovered peer's ENR.
|
|
233
|
-
*/ async handleDiscoveredPeer(enr) {
|
|
234
|
-
// Check that the peer has not already been banned
|
|
235
|
-
const peerId = await enr.peerId();
|
|
236
|
-
const peerIdString = peerId.toString();
|
|
237
|
-
// Check if peer is temporarily timed out
|
|
238
|
-
const timedOutPeer = this.timedOutPeers.get(peerIdString);
|
|
239
|
-
if (timedOutPeer) {
|
|
240
|
-
if (Date.now() < timedOutPeer.timeoutUntilMs) {
|
|
241
|
-
this.logger.trace(`Skipping timed out peer ${peerId}`);
|
|
242
|
-
return;
|
|
51
|
+
/**
|
|
52
|
+
* Cleans up expired timeouts.
|
|
53
|
+
*
|
|
54
|
+
* When peers fail to dial after a number of retries, they are temporarily timed out.
|
|
55
|
+
* This function removes any peers that have been in the timed out state for too long.
|
|
56
|
+
* To give them a chance to reconnect.
|
|
57
|
+
*/
|
|
58
|
+
cleanupExpiredTimeouts() {
|
|
59
|
+
// Clean up expired timeouts
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
for (const [peerId, timedOutPeer] of this.timedOutPeers.entries()) {
|
|
62
|
+
if (now >= timedOutPeer.timeoutUntilMs) {
|
|
63
|
+
this.timedOutPeers.delete(peerId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
243
66
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Simply logs the type of connected peer.
|
|
69
|
+
* @param e - The connected peer event.
|
|
70
|
+
*/
|
|
71
|
+
handleConnectedPeerEvent(e) {
|
|
72
|
+
const peerId = e.detail;
|
|
73
|
+
if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
|
|
74
|
+
this.logger.verbose(`Connected to bootstrap peer ${peerId.toString()}`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
this.logger.verbose(`Connected to transaction peer ${peerId.toString()}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Simply logs the type of disconnected peer.
|
|
82
|
+
* @param e - The disconnected peer event.
|
|
83
|
+
*/
|
|
84
|
+
handleDisconnectedPeerEvent(e) {
|
|
85
|
+
const peerId = e.detail;
|
|
86
|
+
if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
|
|
87
|
+
this.logger.verbose(`Disconnected from bootstrap peer ${peerId.toString()}`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.logger.verbose(`Disconnected from transaction peer ${peerId.toString()}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Handles a goodbye received from a peer.
|
|
95
|
+
*
|
|
96
|
+
* Used as the reqresp handler when a peer sends us goodbye message.
|
|
97
|
+
* @param peerId - The peer ID.
|
|
98
|
+
* @param reason - The reason for the goodbye.
|
|
99
|
+
*/
|
|
100
|
+
goodbyeReceived(peerId, reason) {
|
|
101
|
+
this.logger.debug(`Goodbye received from peer ${peerId.toString()} with reason ${prettyGoodbyeReason(reason)}`);
|
|
102
|
+
this.metrics.recordGoodbyeReceived(reason);
|
|
103
|
+
void this.disconnectPeer(peerId);
|
|
104
|
+
}
|
|
105
|
+
penalizePeer(peerId, penalty) {
|
|
106
|
+
this.peerScoring.penalizePeer(peerId, penalty);
|
|
107
|
+
}
|
|
108
|
+
getPeerScore(peerId) {
|
|
109
|
+
return this.peerScoring.getScore(peerId);
|
|
110
|
+
}
|
|
111
|
+
getPeers(includePending = false) {
|
|
112
|
+
const connected = this.libP2PNode
|
|
113
|
+
.getPeers()
|
|
114
|
+
.map(peer => ({ id: peer.toString(), score: this.getPeerScore(peer.toString()), status: 'connected' }));
|
|
115
|
+
if (!includePending) {
|
|
116
|
+
return connected;
|
|
117
|
+
}
|
|
118
|
+
const dialQueue = this.libP2PNode
|
|
119
|
+
.getDialQueue()
|
|
120
|
+
.filter(peer => !!peer.peerId)
|
|
121
|
+
.map(peer => ({
|
|
122
|
+
id: peer.peerId.toString(),
|
|
123
|
+
status: 'dialing',
|
|
124
|
+
dialStatus: peer.status,
|
|
125
|
+
addresses: peer.multiaddrs.map(m => m.toString()),
|
|
126
|
+
}));
|
|
127
|
+
const cachedPeers = Array.from(this.cachedPeers.values())
|
|
128
|
+
.filter(peer => !dialQueue.some(dialPeer => dialPeer.id && peer.peerId.toString() === dialPeer.id.toString()))
|
|
129
|
+
.filter(peer => !connected.some(connPeer => connPeer.id.toString() === peer.peerId.toString()))
|
|
130
|
+
.map(peer => ({
|
|
131
|
+
status: 'cached',
|
|
132
|
+
id: peer.peerId.toString(),
|
|
133
|
+
addresses: [peer.multiaddrTcp.toString()],
|
|
134
|
+
dialAttempts: peer.dialAttempts,
|
|
135
|
+
enr: peer.enr.encodeTxt(),
|
|
136
|
+
}));
|
|
137
|
+
return [...connected, ...dialQueue, ...cachedPeers];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Discovers peers.
|
|
141
|
+
*/
|
|
142
|
+
discover() {
|
|
143
|
+
const connections = this.libP2PNode.getConnections();
|
|
144
|
+
const healthyConnections = this.pruneUnhealthyPeers(connections);
|
|
145
|
+
// Calculate how many connections we're looking to make
|
|
146
|
+
const peersToConnect = this.config.maxPeerCount - healthyConnections.length;
|
|
147
|
+
const logLevel = this.heartbeatCounter % this.displayPeerCountsPeerHeartbeat === 0 ? 'info' : 'debug';
|
|
148
|
+
this.logger[logLevel](`Connected to ${connections.length} peers`, {
|
|
149
|
+
connections: connections.length,
|
|
150
|
+
maxPeerCount: this.config.maxPeerCount,
|
|
151
|
+
cachedPeers: this.cachedPeers.size,
|
|
152
|
+
...this.peerScoring.getStats(),
|
|
316
153
|
});
|
|
154
|
+
// Exit if no peers to connect
|
|
155
|
+
if (peersToConnect <= 0) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const cachedPeersToDial = [];
|
|
159
|
+
const pendingDials = new Set(this.libP2PNode
|
|
160
|
+
.getDialQueue()
|
|
161
|
+
.map(pendingDial => pendingDial.peerId?.toString())
|
|
162
|
+
.filter(Boolean));
|
|
163
|
+
for (const [id, peerData] of this.cachedPeers.entries()) {
|
|
164
|
+
// if already dialling or connected to, remove from cache
|
|
165
|
+
if (pendingDials.has(id) ||
|
|
166
|
+
healthyConnections.some(conn => conn.remotePeer.equals(peerData.peerId)) ||
|
|
167
|
+
// if peer has been in cache for the max cache age, remove from cache
|
|
168
|
+
Date.now() - peerData.addedUnixMs > MAX_CACHED_PEER_AGE_MS) {
|
|
169
|
+
this.cachedPeers.delete(id);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// cachedPeersToDial.set(id, enr);
|
|
173
|
+
cachedPeersToDial.push(peerData);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// reverse to dial older entries first
|
|
177
|
+
cachedPeersToDial.reverse();
|
|
178
|
+
for (const peer of cachedPeersToDial) {
|
|
179
|
+
// We remove from the cache before, as dialling will add it back if it fails
|
|
180
|
+
this.cachedPeers.delete(peer.peerId.toString());
|
|
181
|
+
void this.dialPeer(peer);
|
|
182
|
+
}
|
|
183
|
+
// if we need more peers, start randomNodesQuery
|
|
184
|
+
if (peersToConnect > 0) {
|
|
185
|
+
this.logger.trace(`Running random nodes query to connect to ${peersToConnect} peers`);
|
|
186
|
+
void this.peerDiscoveryService.runRandomNodesQuery();
|
|
187
|
+
}
|
|
317
188
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
for (const key of this.cachedPeers.keys()){
|
|
335
|
-
this.cachedPeers.delete(key);
|
|
336
|
-
this.logger.trace(`Pruning peer ${key} from cache`);
|
|
337
|
-
peersToDelete--;
|
|
338
|
-
if (peersToDelete <= 0) {
|
|
339
|
-
break;
|
|
189
|
+
pruneUnhealthyPeers(connections) {
|
|
190
|
+
const connectedHealthyPeers = [];
|
|
191
|
+
for (const peer of connections) {
|
|
192
|
+
const score = this.peerScoring.getScoreState(peer.remotePeer.toString());
|
|
193
|
+
switch (score) {
|
|
194
|
+
case PeerScoreState.Banned:
|
|
195
|
+
void this.goodbyeAndDisconnectPeer(peer.remotePeer, GoodByeReason.BANNED);
|
|
196
|
+
break;
|
|
197
|
+
case PeerScoreState.Disconnect:
|
|
198
|
+
void this.goodbyeAndDisconnectPeer(peer.remotePeer, GoodByeReason.DISCONNECTED);
|
|
199
|
+
break;
|
|
200
|
+
case PeerScoreState.Healthy:
|
|
201
|
+
connectedHealthyPeers.push(peer);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return connectedHealthyPeers;
|
|
340
205
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
206
|
+
async goodbyeAndDisconnectPeer(peer, reason) {
|
|
207
|
+
this.logger.debug(`Disconnecting peer ${peer.toString()} with reason ${prettyGoodbyeReason(reason)}`);
|
|
208
|
+
this.metrics.recordGoodbyeSent(reason);
|
|
209
|
+
try {
|
|
210
|
+
await this.reqresp.sendRequestToPeer(peer, ReqRespSubProtocol.GOODBYE, Buffer.from([reason]));
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
this.logger.debug(`Failed to send goodbye to peer ${peer.toString()}: ${error}`);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
await this.disconnectPeer(peer);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async disconnectPeer(peer) {
|
|
220
|
+
try {
|
|
221
|
+
await this.libP2PNode.hangUp(peer);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
this.logger.debug(`Failed to disconnect peer ${peer.toString()}`, { error: inspect(error) });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Handles a discovered peer.
|
|
229
|
+
* @param enr - The discovered peer's ENR.
|
|
230
|
+
*/
|
|
231
|
+
async handleDiscoveredPeer(enr) {
|
|
232
|
+
// Check that the peer has not already been banned
|
|
233
|
+
const peerId = await enr.peerId();
|
|
234
|
+
const peerIdString = peerId.toString();
|
|
235
|
+
// Check if peer is temporarily timed out
|
|
236
|
+
const timedOutPeer = this.timedOutPeers.get(peerIdString);
|
|
237
|
+
if (timedOutPeer) {
|
|
238
|
+
if (Date.now() < timedOutPeer.timeoutUntilMs) {
|
|
239
|
+
this.logger.trace(`Skipping timed out peer ${peerId}`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Timeout period expired, remove from timed out peers
|
|
243
|
+
this.timedOutPeers.delete(peerIdString);
|
|
244
|
+
}
|
|
245
|
+
if (this.peerScoring.getScoreState(peerIdString) != PeerScoreState.Healthy) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const [multiaddrTcp] = await Promise.all([enr.getFullMultiaddr('tcp')]);
|
|
249
|
+
this.logger.trace(`Handling discovered peer ${peerId} at ${multiaddrTcp?.toString() ?? 'undefined address'}`);
|
|
250
|
+
// stop if no tcp addr in multiaddr
|
|
251
|
+
if (!multiaddrTcp) {
|
|
252
|
+
this.logger.debug(`No TCP address in discovered node's multiaddr ${enr.encodeTxt()}`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// check if peer is already connected
|
|
256
|
+
const connections = this.libP2PNode.getConnections();
|
|
257
|
+
if (connections.some((conn) => conn.remotePeer.equals(peerId))) {
|
|
258
|
+
this.logger.trace(`Already connected to peer ${peerId}`);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// check if peer is already in cache
|
|
262
|
+
if (this.cachedPeers.has(peerIdString)) {
|
|
263
|
+
this.logger.trace(`Peer already in cache ${peerIdString}`);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// create cached peer object
|
|
267
|
+
const cachedPeer = {
|
|
268
|
+
peerId,
|
|
269
|
+
enr,
|
|
270
|
+
multiaddrTcp,
|
|
271
|
+
dialAttempts: 0,
|
|
272
|
+
addedUnixMs: Date.now(),
|
|
273
|
+
};
|
|
274
|
+
// Determine if we should dial immediately or not
|
|
275
|
+
if (this.shouldDialPeer()) {
|
|
276
|
+
void this.dialPeer(cachedPeer);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
this.logger.trace(`Caching peer ${peerIdString}`);
|
|
280
|
+
this.cachedPeers.set(peerIdString, cachedPeer);
|
|
281
|
+
// Prune set of cached peers
|
|
282
|
+
this.pruneCachedPeers();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async dialPeer(peer) {
|
|
286
|
+
const id = peer.peerId.toString();
|
|
287
|
+
// Add to the address book before dialing
|
|
288
|
+
await this.libP2PNode.peerStore.merge(peer.peerId, { multiaddrs: [peer.multiaddrTcp] });
|
|
289
|
+
this.logger.trace(`Dialing peer ${id}`);
|
|
290
|
+
try {
|
|
291
|
+
await this.libP2PNode.dial(peer.multiaddrTcp);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
peer.dialAttempts++;
|
|
295
|
+
if (peer.dialAttempts < MAX_DIAL_ATTEMPTS) {
|
|
296
|
+
this.logger.trace(`Failed to dial peer ${id} (attempt ${peer.dialAttempts})`, { error: inspect(error) });
|
|
297
|
+
this.cachedPeers.set(id, peer);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
formatLibp2pDialError(error);
|
|
301
|
+
this.logger.debug(`Failed to dial peer ${id} (dropping)`, { error: inspect(error) });
|
|
302
|
+
this.cachedPeers.delete(id);
|
|
303
|
+
// Add to timed out peers
|
|
304
|
+
this.timedOutPeers.set(id, {
|
|
305
|
+
peerId: id,
|
|
306
|
+
timeoutUntilMs: Date.now() + FAILED_PEER_BAN_TIME_MS,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
shouldDialPeer() {
|
|
312
|
+
const connections = this.libP2PNode.getConnections().length;
|
|
313
|
+
if (connections >= this.config.maxPeerCount) {
|
|
314
|
+
this.logger.trace(`Not dialing peer due to max peer count of ${this.config.maxPeerCount} reached (${connections} current connections)`);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
pruneCachedPeers() {
|
|
320
|
+
let peersToDelete = this.cachedPeers.size - MAX_CACHED_PEERS;
|
|
321
|
+
if (peersToDelete <= 0) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
// Remove the oldest peers
|
|
325
|
+
for (const key of this.cachedPeers.keys()) {
|
|
326
|
+
this.cachedPeers.delete(key);
|
|
327
|
+
this.logger.trace(`Pruning peer ${key} from cache`);
|
|
328
|
+
peersToDelete--;
|
|
329
|
+
if (peersToDelete <= 0) {
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Stops the peer manager.
|
|
336
|
+
* Removing all event listeners.
|
|
337
|
+
*/
|
|
338
|
+
async stop() {
|
|
339
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
340
|
+
this.peerDiscoveryService.off(PeerEvent.DISCOVERED, this.discoveredPeerHandler);
|
|
341
|
+
// Send goodbyes to all peers
|
|
342
|
+
await Promise.all(this.libP2PNode.getPeers().map(peer => this.goodbyeAndDisconnectPeer(peer, GoodByeReason.SHUTDOWN)));
|
|
343
|
+
this.libP2PNode.removeEventListener(PeerEvent.CONNECTED, this.handleConnectedPeerEvent);
|
|
344
|
+
this.libP2PNode.removeEventListener(PeerEvent.DISCONNECTED, this.handleDisconnectedPeerEvent);
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
(() => {
|
|
348
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
349
|
+
_heartbeat_decorators = [trackSpan('PeerManager.heartbeat')];
|
|
350
|
+
__esDecorate(_a, null, _heartbeat_decorators, { kind: "method", name: "heartbeat", static: false, private: false, access: { has: obj => "heartbeat" in obj, get: obj => obj.heartbeat }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
351
|
+
if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
352
|
+
})(),
|
|
353
|
+
_a;
|
|
354
|
+
})();
|
|
355
|
+
export { PeerManager };
|
|
358
356
|
/**
|
|
359
357
|
* copied from github.com/ChainSafe/lodestar
|
|
360
358
|
* libp2p errors with extremely noisy errors here, which are deeply nested taking 30-50 lines.
|
|
@@ -383,11 +381,15 @@ _ts_decorate([
|
|
|
383
381
|
* ```
|
|
384
382
|
*
|
|
385
383
|
* Tracking issue https://github.com/libp2p/js-libp2p/issues/996
|
|
386
|
-
*/
|
|
384
|
+
*/
|
|
385
|
+
function formatLibp2pDialError(e) {
|
|
387
386
|
const errorMessage = e.message.trim();
|
|
388
387
|
const newlineIndex = errorMessage.indexOf('\n');
|
|
389
388
|
e.message = newlineIndex !== -1 ? errorMessage.slice(0, newlineIndex) : errorMessage;
|
|
390
|
-
if (e.message.includes('The operation was aborted') ||
|
|
389
|
+
if (e.message.includes('The operation was aborted') ||
|
|
390
|
+
e.message.includes('stream ended before 1 bytes became available') ||
|
|
391
|
+
e.message.includes('The operation was aborted')) {
|
|
391
392
|
e.stack = undefined;
|
|
392
393
|
}
|
|
393
394
|
}
|
|
395
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVlcl9tYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3NlcnZpY2VzL3BlZXItbWFuYWdlci9wZWVyX21hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUNBLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUNyRCxPQUFPLEVBQXdCLFNBQVMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBSzFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFJL0IsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDN0QsT0FBTyxFQUFFLGFBQWEsRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBR3JGLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDeEMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ2xELE9BQU8sRUFBRSxjQUFjLEVBQW9CLE1BQU0sbUJBQW1CLENBQUM7QUFFckUsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLENBQUM7QUFDNUIsTUFBTSxnQkFBZ0IsR0FBRyxHQUFHLENBQUM7QUFDN0IsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDLFlBQVk7QUFDMUQsTUFBTSx1QkFBdUIsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDLG9EQUFvRDtJQWV0RixXQUFXOzs7O3NCQUFYLFdBQVc7WUFTdEIsWUFDVSxVQUF3QixFQUN4QixvQkFBMEMsRUFDMUMsTUFBaUIsRUFDekIsZUFBZ0MsRUFDeEIsU0FBUyxZQUFZLENBQUMsa0JBQWtCLENBQUMsRUFDekMsV0FBd0IsRUFDeEIsT0FBZ0I7Z0JBTmhCLGVBQVUsSUFWVCxtREFBVyxFQVVaLFVBQVUsRUFBYztnQkFDeEIseUJBQW9CLEdBQXBCLG9CQUFvQixDQUFzQjtn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
|