@aztec/p2p 1.2.1 → 2.0.0-nightly.20250813
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/client/factory.d.ts +5 -1
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +29 -12
- package/dest/client/interface.d.ts +8 -13
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +18 -22
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +86 -83
- package/dest/config.d.ts +30 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +34 -1
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +13 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +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 +117 -10
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +4 -1
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +22 -1
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +4 -1
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +21 -1
- package/dest/mem_pools/attestation_pool/mocks.d.ts +1 -2
- package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/mocks.js +2 -10
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +8 -3
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +64 -37
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +8 -3
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/memory_tx_pool.js +18 -10
- package/dest/mem_pools/tx_pool/tx_pool.d.ts +11 -2
- package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +73 -44
- package/dest/msg_validators/attestation_validator/attestation_validator.js +1 -1
- package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/block_header_validator.js +2 -2
- package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/data_validator.js +35 -59
- package/dest/msg_validators/tx_validator/double_spend_validator.js +2 -2
- package/dest/msg_validators/tx_validator/gas_validator.js +4 -4
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/metadata_validator.js +21 -21
- package/dest/msg_validators/tx_validator/phases_validator.js +3 -3
- package/dest/msg_validators/tx_validator/tx_proof_validator.js +3 -3
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +4 -1
- package/dest/services/dummy_service.d.ts +13 -3
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +26 -3
- package/dest/services/index.d.ts +3 -1
- package/dest/services/index.d.ts.map +1 -1
- package/dest/services/index.js +3 -1
- package/dest/services/libp2p/libp2p_service.d.ts +13 -20
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +123 -46
- package/dest/services/peer-manager/interface.d.ts +8 -1
- package/dest/services/peer-manager/interface.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_manager.d.ts +70 -3
- package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_manager.js +369 -39
- package/dest/services/reqresp/config.d.ts +3 -3
- package/dest/services/reqresp/config.d.ts.map +1 -1
- package/dest/services/reqresp/config.js +3 -3
- package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +3 -4
- package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -1
- package/dest/services/reqresp/connection-sampler/connection_sampler.js +35 -52
- package/dest/services/reqresp/index.d.ts +2 -1
- package/dest/services/reqresp/index.d.ts.map +1 -1
- package/dest/services/reqresp/index.js +2 -1
- package/dest/services/reqresp/interface.d.ts +61 -10
- package/dest/services/reqresp/interface.d.ts.map +1 -1
- package/dest/services/reqresp/interface.js +41 -6
- package/dest/services/reqresp/protocols/auth.d.ts +43 -0
- package/dest/services/reqresp/protocols/auth.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/auth.js +71 -0
- package/dest/services/reqresp/protocols/block.d.ts +5 -0
- package/dest/services/reqresp/protocols/block.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block.js +28 -5
- package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +30 -0
- package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/block_txs/bitvector.js +75 -0
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +11 -0
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +39 -0
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +49 -0
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +75 -0
- package/dest/services/reqresp/protocols/block_txs/index.d.ts +4 -0
- package/dest/services/reqresp/protocols/block_txs/index.d.ts.map +1 -0
- package/dest/services/reqresp/protocols/block_txs/index.js +3 -0
- package/dest/services/reqresp/protocols/goodbye.js +3 -5
- package/dest/services/reqresp/protocols/index.d.ts +2 -0
- package/dest/services/reqresp/protocols/index.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/index.js +2 -0
- package/dest/services/reqresp/protocols/status.d.ts +2 -0
- package/dest/services/reqresp/protocols/status.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/status.js +7 -0
- package/dest/services/reqresp/protocols/tx.d.ts +12 -1
- package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/tx.js +34 -6
- package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -1
- package/dest/services/reqresp/rate-limiter/rate_limits.js +20 -0
- package/dest/services/reqresp/reqresp.d.ts +37 -39
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +220 -220
- package/dest/services/reqresp/status.d.ts +8 -3
- package/dest/services/reqresp/status.d.ts.map +1 -1
- package/dest/services/reqresp/status.js +6 -2
- package/dest/services/service.d.ts +10 -10
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/config.d.ts +25 -0
- package/dest/services/tx_collection/config.d.ts.map +1 -0
- package/dest/services/tx_collection/config.js +58 -0
- package/dest/services/tx_collection/fast_tx_collection.d.ts +56 -0
- package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -0
- package/dest/services/tx_collection/fast_tx_collection.js +295 -0
- package/dest/services/tx_collection/index.d.ts +3 -0
- package/dest/services/tx_collection/index.d.ts.map +1 -0
- package/dest/services/tx_collection/index.js +2 -0
- package/dest/services/tx_collection/instrumentation.d.ts +10 -0
- package/dest/services/tx_collection/instrumentation.d.ts.map +1 -0
- package/dest/services/tx_collection/instrumentation.js +34 -0
- package/dest/services/tx_collection/slow_tx_collection.d.ts +54 -0
- package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -0
- package/dest/services/tx_collection/slow_tx_collection.js +176 -0
- package/dest/services/tx_collection/tx_collection.d.ts +109 -0
- package/dest/services/tx_collection/tx_collection.d.ts.map +1 -0
- package/dest/services/tx_collection/tx_collection.js +127 -0
- package/dest/services/tx_collection/tx_collection_sink.d.ts +30 -0
- package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -0
- package/dest/services/tx_collection/tx_collection_sink.js +81 -0
- package/dest/services/tx_collection/tx_source.d.ts +18 -0
- package/dest/services/tx_collection/tx_source.d.ts.map +1 -0
- package/dest/services/tx_collection/tx_source.js +31 -0
- package/dest/services/tx_provider.d.ts +49 -0
- package/dest/services/tx_provider.d.ts.map +1 -0
- package/dest/services/tx_provider.js +206 -0
- package/dest/services/{tx_collect_instrumentation.d.ts → tx_provider_instrumentation.d.ts} +2 -2
- package/dest/services/tx_provider_instrumentation.d.ts.map +1 -0
- package/dest/services/{tx_collect_instrumentation.js → tx_provider_instrumentation.js} +5 -5
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/make-test-p2p-clients.js +4 -3
- package/dest/test-helpers/mock-pubsub.d.ts +2 -1
- package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.js +2 -1
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.js +8 -4
- package/dest/testbench/p2p_client_testbench_worker.js +11 -5
- package/dest/util.d.ts +1 -1
- package/dest/util.d.ts.map +1 -1
- package/package.json +14 -15
- package/src/client/factory.ts +87 -12
- package/src/client/interface.ts +19 -15
- package/src/client/p2p_client.ts +108 -92
- package/src/config.ts +52 -1
- package/src/index.ts +1 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +15 -1
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +155 -4
- package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +28 -1
- package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +28 -2
- package/src/mem_pools/attestation_pool/mocks.ts +1 -3
- package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +59 -41
- package/src/mem_pools/tx_pool/memory_tx_pool.ts +19 -9
- package/src/mem_pools/tx_pool/tx_pool.ts +7 -2
- package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +63 -40
- package/src/msg_validators/attestation_validator/attestation_validator.ts +1 -1
- package/src/msg_validators/tx_validator/block_header_validator.ts +2 -2
- package/src/msg_validators/tx_validator/data_validator.ts +36 -27
- package/src/msg_validators/tx_validator/double_spend_validator.ts +2 -2
- package/src/msg_validators/tx_validator/gas_validator.ts +4 -4
- package/src/msg_validators/tx_validator/metadata_validator.ts +22 -28
- package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
- package/src/msg_validators/tx_validator/tx_proof_validator.ts +2 -2
- package/src/services/discv5/discV5_service.ts +4 -1
- package/src/services/dummy_service.ts +44 -4
- package/src/services/index.ts +3 -1
- package/src/services/libp2p/libp2p_service.ts +147 -55
- package/src/services/peer-manager/interface.ts +10 -1
- package/src/services/peer-manager/peer_manager.ts +441 -41
- package/src/services/reqresp/config.ts +3 -3
- package/src/services/reqresp/connection-sampler/connection_sampler.ts +38 -63
- package/src/services/reqresp/index.ts +2 -0
- package/src/services/reqresp/interface.ts +63 -17
- package/src/services/reqresp/protocols/auth.ts +83 -0
- package/src/services/reqresp/protocols/block.ts +24 -3
- package/src/services/reqresp/protocols/block_txs/bitvector.ts +90 -0
- package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +53 -0
- package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +79 -0
- package/src/services/reqresp/protocols/block_txs/index.ts +3 -0
- package/src/services/reqresp/protocols/goodbye.ts +3 -3
- package/src/services/reqresp/protocols/index.ts +2 -0
- package/src/services/reqresp/protocols/status.ts +20 -0
- package/src/services/reqresp/protocols/tx.ts +35 -6
- package/src/services/reqresp/rate-limiter/rate_limits.ts +20 -0
- package/src/services/reqresp/reqresp.ts +294 -264
- package/src/services/reqresp/status.ts +9 -3
- package/src/services/service.ts +23 -14
- package/src/services/tx_collection/config.ts +84 -0
- package/src/services/tx_collection/fast_tx_collection.ts +338 -0
- package/src/services/tx_collection/index.ts +2 -0
- package/src/services/tx_collection/instrumentation.ts +43 -0
- package/src/services/tx_collection/slow_tx_collection.ts +232 -0
- package/src/services/tx_collection/tx_collection.ts +214 -0
- package/src/services/tx_collection/tx_collection_sink.ts +98 -0
- package/src/services/tx_collection/tx_source.ts +37 -0
- package/src/services/tx_provider.ts +215 -0
- package/src/services/{tx_collect_instrumentation.ts → tx_provider_instrumentation.ts} +5 -5
- package/src/test-helpers/make-test-p2p-clients.ts +4 -2
- package/src/test-helpers/mock-pubsub.ts +1 -0
- package/src/test-helpers/reqresp-nodes.ts +7 -1
- package/src/testbench/p2p_client_testbench_worker.ts +9 -2
- package/src/util.ts +1 -1
- package/dest/services/tx_collect_instrumentation.d.ts.map +0 -1
- package/dest/services/tx_collector.d.ts +0 -23
- package/dest/services/tx_collector.d.ts.map +0 -1
- package/dest/services/tx_collector.js +0 -95
- package/src/services/tx_collector.ts +0 -134
|
@@ -1,18 +1,25 @@
|
|
|
1
|
+
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
+
import { makeEthSignDigest, recoverAddress } from '@aztec/foundation/crypto';
|
|
3
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
1
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
6
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
7
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
3
8
|
import type { PeerInfo, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
4
9
|
import type { PeerErrorSeverity } from '@aztec/stdlib/p2p';
|
|
5
10
|
import { type TelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
6
11
|
|
|
7
12
|
import { ENR } from '@chainsafe/enr';
|
|
8
13
|
import type { Connection, PeerId } from '@libp2p/interface';
|
|
14
|
+
import { peerIdFromString } from '@libp2p/peer-id';
|
|
9
15
|
import type { Multiaddr } from '@multiformats/multiaddr';
|
|
10
|
-
import type { Libp2p } from 'libp2p';
|
|
11
16
|
import { inspect } from 'util';
|
|
12
17
|
|
|
13
18
|
import type { P2PConfig } from '../../config.js';
|
|
14
19
|
import { PeerEvent } from '../../types/index.js';
|
|
20
|
+
import type { FullLibp2p } from '../../util.js';
|
|
15
21
|
import { ReqRespSubProtocol } from '../reqresp/interface.js';
|
|
22
|
+
import { AuthRequest, AuthResponse } from '../reqresp/protocols/auth.js';
|
|
16
23
|
import { GoodByeReason, prettyGoodbyeReason } from '../reqresp/protocols/goodbye.js';
|
|
17
24
|
import { StatusMessage } from '../reqresp/protocols/status.js';
|
|
18
25
|
import type { ReqResp } from '../reqresp/reqresp.js';
|
|
@@ -27,6 +34,7 @@ const MAX_CACHED_PEERS = 100;
|
|
|
27
34
|
const MAX_CACHED_PEER_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
28
35
|
const FAILED_PEER_BAN_TIME_MS = 5 * 60 * 1000; // 5 minutes timeout after failing MAX_DIAL_ATTEMPTS
|
|
29
36
|
const GOODBYE_DIAL_TIMEOUT_MS = 1000;
|
|
37
|
+
const FAILED_AUTH_HANDSHAKE_EXPIRY_MS = 60 * 60 * 1000; // 1 hour
|
|
30
38
|
|
|
31
39
|
type CachedPeer = {
|
|
32
40
|
peerId: PeerId;
|
|
@@ -41,6 +49,11 @@ type TimedOutPeer = {
|
|
|
41
49
|
timeoutUntilMs: number;
|
|
42
50
|
};
|
|
43
51
|
|
|
52
|
+
type FailedAuthHandshakeEntry = {
|
|
53
|
+
count: number;
|
|
54
|
+
lastFailureTimestamp: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
44
57
|
export class PeerManager implements PeerManagerInterface {
|
|
45
58
|
private cachedPeers: Map<string, CachedPeer> = new Map();
|
|
46
59
|
private heartbeatCounter: number = 0;
|
|
@@ -50,6 +63,13 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
50
63
|
private trustedPeersInitialized: boolean = false;
|
|
51
64
|
private privatePeers: Set<string> = new Set();
|
|
52
65
|
private privatePeersInitialized: boolean = false;
|
|
66
|
+
private preferredPeers: Set<string> = new Set();
|
|
67
|
+
private authenticatedPeerIdToValidatorAddress: Map<string, EthAddress> = new Map();
|
|
68
|
+
private authenticatedValidatorAddressToPeerId: Map<string, PeerId> = new Map();
|
|
69
|
+
private peersToBeDisconnected: Set<string> = new Set();
|
|
70
|
+
private failedAuthHandshakes: Map<string, FailedAuthHandshakeEntry> = new Map();
|
|
71
|
+
private validatorAddresses: EthAddress[] = [];
|
|
72
|
+
private initializedPreferredPeers: boolean = false;
|
|
53
73
|
|
|
54
74
|
private metrics: PeerManagerMetrics;
|
|
55
75
|
private handlers: {
|
|
@@ -59,7 +79,7 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
59
79
|
};
|
|
60
80
|
|
|
61
81
|
constructor(
|
|
62
|
-
private libP2PNode:
|
|
82
|
+
private libP2PNode: FullLibp2p,
|
|
63
83
|
private peerDiscoveryService: PeerDiscoveryService,
|
|
64
84
|
private config: P2PConfig,
|
|
65
85
|
telemetryClient: TelemetryClient,
|
|
@@ -68,7 +88,12 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
68
88
|
private reqresp: ReqResp,
|
|
69
89
|
private readonly worldStateSynchronizer: WorldStateSynchronizer,
|
|
70
90
|
private readonly protocolVersion: string,
|
|
91
|
+
private readonly epochCache: EpochCacheInterface,
|
|
92
|
+
private readonly dateProvider: DateProvider = new DateProvider(),
|
|
71
93
|
) {
|
|
94
|
+
if (this.config.p2pDisableStatusHandshake && this.config.p2pAllowOnlyValidators) {
|
|
95
|
+
throw new Error('Status handshake disabled but is required to allow only validators to connect.');
|
|
96
|
+
}
|
|
72
97
|
this.metrics = new PeerManagerMetrics(telemetryClient, 'PeerManager');
|
|
73
98
|
|
|
74
99
|
// Handle Discovered peers
|
|
@@ -85,12 +110,11 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
85
110
|
this.libP2PNode.addEventListener(PeerEvent.DISCONNECTED, this.handlers.handleDisconnectedPeerEvent);
|
|
86
111
|
|
|
87
112
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
88
|
-
this.peerDiscoveryService
|
|
113
|
+
this.peerDiscoveryService?.on(PeerEvent.DISCOVERED, this.handlers.handleDiscoveredPeer);
|
|
89
114
|
|
|
90
115
|
// Display peer counts every 60 seconds
|
|
91
116
|
this.displayPeerCountsPeerHeartbeat = Math.floor(60_000 / this.config.peerCheckIntervalMS);
|
|
92
117
|
}
|
|
93
|
-
|
|
94
118
|
/**
|
|
95
119
|
* Initializes the trusted peers.
|
|
96
120
|
*
|
|
@@ -124,6 +148,13 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
124
148
|
})
|
|
125
149
|
.catch(e => this.logger.error('Error initializing private peers', e));
|
|
126
150
|
}
|
|
151
|
+
|
|
152
|
+
if (this.config.preferredPeers) {
|
|
153
|
+
const preferredPeersEnrs: ENR[] = this.config.preferredPeers.map(enr => ENR.decodeTxt(enr));
|
|
154
|
+
await Promise.all(preferredPeersEnrs.map(enr => enr.peerId()))
|
|
155
|
+
.then(peerIds => peerIds.forEach(peerId => this.preferredPeers.add(peerId.toString())))
|
|
156
|
+
.catch(e => this.logger.error('Error initializing preferred peers', e));
|
|
157
|
+
}
|
|
127
158
|
}
|
|
128
159
|
|
|
129
160
|
get tracer() {
|
|
@@ -131,15 +162,70 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
131
162
|
}
|
|
132
163
|
|
|
133
164
|
@trackSpan('PeerManager.heartbeat')
|
|
134
|
-
public heartbeat() {
|
|
165
|
+
public async heartbeat() {
|
|
135
166
|
this.heartbeatCounter++;
|
|
136
167
|
this.peerScoring.decayAllScores();
|
|
137
|
-
|
|
138
168
|
this.cleanupExpiredTimeouts();
|
|
139
169
|
|
|
170
|
+
await this.setupDirectPeersIfValidator();
|
|
171
|
+
await this.updateAuthenticatedPeers();
|
|
172
|
+
await this.processScheduledDisconnects();
|
|
173
|
+
|
|
140
174
|
this.discover();
|
|
141
175
|
}
|
|
142
176
|
|
|
177
|
+
/*
|
|
178
|
+
* If this node is connecting to preferred peers, make sure it is registered validator */
|
|
179
|
+
async setupDirectPeersIfValidator() {
|
|
180
|
+
if (!this.config.preferredPeers) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Already initialized preferred peers, don't wastefully repeat the same work
|
|
185
|
+
if (this.initializedPreferredPeers) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const registeredValidators = await this.epochCache.getRegisteredValidators();
|
|
190
|
+
const validatorSet = new Set(registeredValidators.map(v => v.toString()));
|
|
191
|
+
const isThisNodePartOfValidatorSet = this.validatorAddresses.some(v => validatorSet.has(v.toString()));
|
|
192
|
+
|
|
193
|
+
if (!isThisNodePartOfValidatorSet) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const preferredPeersEnrs: ENR[] = this.config.preferredPeers.map(enr => ENR.decodeTxt(enr));
|
|
198
|
+
await Promise.all(preferredPeersEnrs.map(enr => enr.peerId()))
|
|
199
|
+
.then(peerIds => peerIds.forEach(peerId => this.preferredPeers.add(peerId.toString())))
|
|
200
|
+
.catch(e => this.logger.error('Error initializing preferred peers', e));
|
|
201
|
+
|
|
202
|
+
const directPeers = (
|
|
203
|
+
await Promise.all(
|
|
204
|
+
preferredPeersEnrs.map(async enr => {
|
|
205
|
+
const peerId = await enr.peerId();
|
|
206
|
+
const address = enr.getLocationMultiaddr('tcp');
|
|
207
|
+
if (address === undefined) {
|
|
208
|
+
throw new Error(`Direct peer ${peerId.toString()} has no TCP address, ENR: ${enr.encodeTxt()}`);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
id: peerId,
|
|
212
|
+
addrs: [address],
|
|
213
|
+
};
|
|
214
|
+
}),
|
|
215
|
+
)
|
|
216
|
+
).filter(peer => peer !== undefined);
|
|
217
|
+
|
|
218
|
+
await Promise.all(
|
|
219
|
+
directPeers.map(peer => {
|
|
220
|
+
this.libP2PNode.services.pubsub.direct.add(peer.id.toString());
|
|
221
|
+
|
|
222
|
+
return this.libP2PNode.peerStore.merge(peer.id, { multiaddrs: peer.addrs });
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
this.initializedPreferredPeers = true;
|
|
227
|
+
}
|
|
228
|
+
|
|
143
229
|
/**
|
|
144
230
|
* Cleans up expired timeouts.
|
|
145
231
|
*
|
|
@@ -149,7 +235,7 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
149
235
|
*/
|
|
150
236
|
private cleanupExpiredTimeouts() {
|
|
151
237
|
// Clean up expired timeouts
|
|
152
|
-
const now =
|
|
238
|
+
const now = this.dateProvider.now();
|
|
153
239
|
for (const [peerId, timedOutPeer] of this.timedOutPeers.entries()) {
|
|
154
240
|
if (now >= timedOutPeer.timeoutUntilMs) {
|
|
155
241
|
this.timedOutPeers.delete(peerId);
|
|
@@ -157,20 +243,58 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
157
243
|
}
|
|
158
244
|
}
|
|
159
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Processes scheduled disconnects during heartbeat.
|
|
248
|
+
*
|
|
249
|
+
* This batch processes all peers that have been marked for disconnect.
|
|
250
|
+
* preventing immediate disconnects that could cause libp2p state corruption.
|
|
251
|
+
*/
|
|
252
|
+
private async processScheduledDisconnects() {
|
|
253
|
+
if (this.peersToBeDisconnected.size === 0) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const peersToDisconnect = Array.from(this.peersToBeDisconnected);
|
|
258
|
+
|
|
259
|
+
this.logger.debug(`Processing ${peersToDisconnect.length} scheduled disconnects`);
|
|
260
|
+
try {
|
|
261
|
+
await Promise.all(
|
|
262
|
+
peersToDisconnect.map(async peerIdStr => {
|
|
263
|
+
if (await this.disconnectPeer(peerIdFromString(peerIdStr))) {
|
|
264
|
+
this.peersToBeDisconnected.delete(peerIdStr);
|
|
265
|
+
}
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
this.logger.verbose(`Disconnected ${peersToDisconnect.length} peers`, { peersToDisconnect });
|
|
269
|
+
} catch (error) {
|
|
270
|
+
this.logger.error('Error when disconnecting from peers', error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
160
274
|
/**
|
|
161
275
|
* Performs Status Handshake with a connected peer.
|
|
162
276
|
* @param e - The connected peer event.
|
|
163
277
|
*/
|
|
164
278
|
private handleConnectedPeerEvent(e: CustomEvent<PeerId>) {
|
|
165
279
|
const peerId = e.detail;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.logger.verbose(`Connected to transaction peer ${peerId.toString()}`);
|
|
280
|
+
this.logger.verbose(`Connected to peer ${peerId.toString()}`);
|
|
281
|
+
if (this.config.p2pDisableStatusHandshake) {
|
|
282
|
+
return;
|
|
170
283
|
}
|
|
171
|
-
|
|
284
|
+
// If we are not configured to only allow validators then perform a status handshake
|
|
285
|
+
if (!this.config.p2pAllowOnlyValidators) {
|
|
172
286
|
void this.exchangeStatusHandshake(peerId);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// We are configured to only allow validators, but this doesn't apply to trusted, private peers or preferred peers
|
|
291
|
+
if (this.isProtectedPeer(peerId)) {
|
|
292
|
+
void this.exchangeStatusHandshake(peerId);
|
|
293
|
+
return;
|
|
173
294
|
}
|
|
295
|
+
|
|
296
|
+
// Initiate auth handshake
|
|
297
|
+
void this.exchangeAuthHandshake(peerId);
|
|
174
298
|
}
|
|
175
299
|
|
|
176
300
|
/**
|
|
@@ -179,13 +303,21 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
179
303
|
*/
|
|
180
304
|
private handleDisconnectedPeerEvent(e: CustomEvent<PeerId>) {
|
|
181
305
|
const peerId = e.detail;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
this.logger.
|
|
306
|
+
this.logger.verbose(`Disconnected from peer ${peerId.toString()}`);
|
|
307
|
+
const validatorAddress = this.authenticatedPeerIdToValidatorAddress.get(peerId.toString());
|
|
308
|
+
if (validatorAddress !== undefined) {
|
|
309
|
+
this.logger.info(
|
|
310
|
+
`Removing authentication for validator ${validatorAddress} at peer id ${peerId.toString()} due to disconnection`,
|
|
311
|
+
);
|
|
312
|
+
this.authenticatedValidatorAddressToPeerId.delete(validatorAddress.toString());
|
|
313
|
+
this.authenticatedPeerIdToValidatorAddress.delete(peerId.toString());
|
|
186
314
|
}
|
|
187
315
|
}
|
|
188
316
|
|
|
317
|
+
public registerThisValidatorAddresses(address: EthAddress[]): void {
|
|
318
|
+
this.validatorAddresses = [...address];
|
|
319
|
+
}
|
|
320
|
+
|
|
189
321
|
/**
|
|
190
322
|
* Checks if a peer is trusted.
|
|
191
323
|
* @param peerId - The peer ID.
|
|
@@ -239,13 +371,33 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
239
371
|
return this.privatePeers.has(peerId.toString());
|
|
240
372
|
}
|
|
241
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Adds a peer to the preferred peers set.
|
|
376
|
+
* @param peerId - The peer ID to add to preferred peers.
|
|
377
|
+
*/
|
|
378
|
+
public addPreferredPeer(peerId: PeerId): void {
|
|
379
|
+
const peerIdStr = peerId.toString();
|
|
380
|
+
|
|
381
|
+
this.preferredPeers.add(peerIdStr);
|
|
382
|
+
this.logger.verbose(`Added preferred peer ${peerIdStr}`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Checks if a peer is preferred.
|
|
387
|
+
* @param peerId - The peer ID.
|
|
388
|
+
* @returns True if the peer is preferred, false otherwise.
|
|
389
|
+
*/
|
|
390
|
+
private isPreferredPeer(peerId: PeerId): boolean {
|
|
391
|
+
return this.preferredPeers.has(peerId.toString());
|
|
392
|
+
}
|
|
393
|
+
|
|
242
394
|
/**
|
|
243
395
|
* Checks if a peer is protected (either trusted or private).
|
|
244
396
|
* @param peerId - The peer ID.
|
|
245
397
|
* @returns True if the peer is protected, false otherwise.
|
|
246
398
|
*/
|
|
247
399
|
private isProtectedPeer(peerId: PeerId): boolean {
|
|
248
|
-
return this.isTrustedPeer(peerId) || this.isPrivatePeer(peerId);
|
|
400
|
+
return this.isTrustedPeer(peerId) || this.isPrivatePeer(peerId) || this.isPreferredPeer(peerId);
|
|
249
401
|
}
|
|
250
402
|
|
|
251
403
|
/**
|
|
@@ -260,7 +412,7 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
260
412
|
|
|
261
413
|
this.metrics.recordGoodbyeReceived(reason);
|
|
262
414
|
|
|
263
|
-
|
|
415
|
+
this.markPeerForDisconnect(peerId);
|
|
264
416
|
}
|
|
265
417
|
|
|
266
418
|
public penalizePeer(peerId: PeerId, penalty: PeerErrorSeverity) {
|
|
@@ -271,6 +423,11 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
271
423
|
return this.peerScoring.getScore(peerId);
|
|
272
424
|
}
|
|
273
425
|
|
|
426
|
+
public shouldDisableP2PGossip(peerId: string): boolean {
|
|
427
|
+
const isAuthenticated = this.isAuthenticatedPeer(peerIdFromString(peerId));
|
|
428
|
+
return (this.config.p2pAllowOnlyValidators ?? false) && !isAuthenticated;
|
|
429
|
+
}
|
|
430
|
+
|
|
274
431
|
public getPeers(includePending = false): PeerInfo[] {
|
|
275
432
|
const connected = this.libP2PNode
|
|
276
433
|
.getPeers()
|
|
@@ -304,6 +461,38 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
304
461
|
return [...connected, ...dialQueue, ...cachedPeers];
|
|
305
462
|
}
|
|
306
463
|
|
|
464
|
+
public isAuthenticatedPeer(peerId: PeerId): boolean {
|
|
465
|
+
const peerIdAsString = peerId.toString();
|
|
466
|
+
return (
|
|
467
|
+
this.privatePeers.has(peerIdAsString) ||
|
|
468
|
+
this.trustedPeers.has(peerIdAsString) ||
|
|
469
|
+
this.preferredPeers.has(peerIdAsString) ||
|
|
470
|
+
this.authenticatedPeerIdToValidatorAddress.has(peerIdAsString)
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/*
|
|
475
|
+
* Checks whether peer is allowed to connect
|
|
476
|
+
*
|
|
477
|
+
* @param id: Address of the node or it's peerId
|
|
478
|
+
*
|
|
479
|
+
* @returns: True if node is allowed to connect, otherwise false
|
|
480
|
+
* */
|
|
481
|
+
public isNodeAllowedToConnect(id: string | PeerId): boolean {
|
|
482
|
+
const entry = this.failedAuthHandshakes.get(id.toString());
|
|
483
|
+
if (!entry) {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// In case entry is too old, remove it and allow connection
|
|
488
|
+
if (this.dateProvider.now() - entry.lastFailureTimestamp > FAILED_AUTH_HANDSHAKE_EXPIRY_MS) {
|
|
489
|
+
this.failedAuthHandshakes.delete(id.toString());
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return entry.count <= this.config.p2pMaxFailedAuthAttemptsAllowed;
|
|
494
|
+
}
|
|
495
|
+
|
|
307
496
|
/**
|
|
308
497
|
* Discovers peers.
|
|
309
498
|
*/
|
|
@@ -315,12 +504,13 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
315
504
|
);
|
|
316
505
|
|
|
317
506
|
// Calculate how many connections we're looking to make
|
|
318
|
-
const
|
|
507
|
+
const protectedPeerCount = this.getProtectedPeerCount();
|
|
508
|
+
const peersToConnect = this.config.maxPeerCount - healthyConnections.length - protectedPeerCount;
|
|
319
509
|
|
|
320
510
|
const logLevel = this.heartbeatCounter % this.displayPeerCountsPeerHeartbeat === 0 ? 'info' : 'debug';
|
|
321
511
|
this.logger[logLevel](`Connected to ${healthyConnections.length + this.trustedPeers.size} peers`, {
|
|
322
512
|
discoveredConnections: healthyConnections.length,
|
|
323
|
-
protectedConnections:
|
|
513
|
+
protectedConnections: protectedPeerCount,
|
|
324
514
|
maxPeerCount: this.config.maxPeerCount,
|
|
325
515
|
cachedPeers: this.cachedPeers.size,
|
|
326
516
|
...this.peerScoring.getStats(),
|
|
@@ -342,13 +532,14 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
342
532
|
.filter(Boolean) as string[],
|
|
343
533
|
);
|
|
344
534
|
|
|
535
|
+
const now = this.dateProvider.now();
|
|
345
536
|
for (const [id, peerData] of this.cachedPeers.entries()) {
|
|
346
537
|
// if already dialling or connected to, remove from cache
|
|
347
538
|
if (
|
|
348
539
|
pendingDials.has(id) ||
|
|
349
540
|
healthyConnections.some(conn => conn.remotePeer.equals(peerData.peerId)) ||
|
|
350
541
|
// if peer has been in cache for the max cache age, remove from cache
|
|
351
|
-
|
|
542
|
+
now - peerData.addedUnixMs > MAX_CACHED_PEER_AGE_MS
|
|
352
543
|
) {
|
|
353
544
|
this.cachedPeers.delete(id);
|
|
354
545
|
} else {
|
|
@@ -377,6 +568,10 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
377
568
|
return connections.filter(conn => !this.isProtectedPeer(conn.remotePeer));
|
|
378
569
|
}
|
|
379
570
|
|
|
571
|
+
private getProtectedPeerCount(): number {
|
|
572
|
+
return this.trustedPeers.size + this.privatePeers.size + this.preferredPeers.size;
|
|
573
|
+
}
|
|
574
|
+
|
|
380
575
|
private pruneUnhealthyPeers(connections: Connection[]): Connection[] {
|
|
381
576
|
const connectedHealthyPeers: Connection[] = [];
|
|
382
577
|
|
|
@@ -404,7 +599,8 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
404
599
|
* @returns The pruned list of connections.
|
|
405
600
|
*/
|
|
406
601
|
private prioritizePeers(connections: Connection[]): Connection[] {
|
|
407
|
-
|
|
602
|
+
const protectedPeerCount = this.getProtectedPeerCount();
|
|
603
|
+
if (connections.length > this.config.maxPeerCount - protectedPeerCount) {
|
|
408
604
|
// Sort the regular peer scores from highest to lowest
|
|
409
605
|
const prioritizedConnections = connections.sort((connectionA, connectionB) => {
|
|
410
606
|
const connectionScoreA = this.peerScoring.getScore(connectionA.remotePeer.toString());
|
|
@@ -413,7 +609,7 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
413
609
|
});
|
|
414
610
|
|
|
415
611
|
// Calculate how many regular peers we can keep
|
|
416
|
-
const peersToKeep = Math.max(0, this.config.maxPeerCount -
|
|
612
|
+
const peersToKeep = Math.max(0, this.config.maxPeerCount - protectedPeerCount);
|
|
417
613
|
|
|
418
614
|
// Disconnect from the lowest scoring regular connections that exceed our limit
|
|
419
615
|
for (const conn of prioritizedConnections.slice(peersToKeep)) {
|
|
@@ -484,15 +680,36 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
484
680
|
} catch (error) {
|
|
485
681
|
this.logger.debug(`Failed to send goodbye to peer ${peer.toString()}: ${error}`);
|
|
486
682
|
} finally {
|
|
487
|
-
|
|
683
|
+
this.markPeerForDisconnect(peer);
|
|
488
684
|
}
|
|
489
685
|
}
|
|
490
686
|
|
|
491
|
-
|
|
687
|
+
/*
|
|
688
|
+
* Marks peer to be disconnected on the next heartbeat
|
|
689
|
+
* */
|
|
690
|
+
private markPeerForDisconnect(peer: PeerId) {
|
|
691
|
+
const peerIdStr = peer.toString();
|
|
692
|
+
this.logger.debug(`Scheduling peer ${peerIdStr} for disconnection`);
|
|
693
|
+
this.peersToBeDisconnected.add(peerIdStr);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Performs the actual disconnection of a peer.
|
|
698
|
+
* This is called during heartbeat processing to avoid immediate disconnections.
|
|
699
|
+
*
|
|
700
|
+
* @returns True if peer was disconnect, otherwise false
|
|
701
|
+
*/
|
|
702
|
+
private async disconnectPeer(peer: PeerId): Promise<boolean> {
|
|
703
|
+
const peerIdStr = peer.toString();
|
|
704
|
+
|
|
492
705
|
try {
|
|
493
706
|
await this.libP2PNode.hangUp(peer);
|
|
707
|
+
|
|
708
|
+
this.logger.debug(`Successfully disconnected peer ${peerIdStr}`);
|
|
709
|
+
return true;
|
|
494
710
|
} catch (error) {
|
|
495
|
-
this.logger.
|
|
711
|
+
this.logger.warn(`Failed to disconnect peer ${peerIdStr}`, { error });
|
|
712
|
+
return false;
|
|
496
713
|
}
|
|
497
714
|
}
|
|
498
715
|
|
|
@@ -505,10 +722,16 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
505
722
|
const peerId = await enr.peerId();
|
|
506
723
|
const peerIdString = peerId.toString();
|
|
507
724
|
|
|
725
|
+
// Don't attempt to connect to peers scheduled for disconnection
|
|
726
|
+
if (this.peersToBeDisconnected.has(peerIdString)) {
|
|
727
|
+
this.logger.trace(`Skipping peer scheduled for disconnection ${peerId}`);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
|
|
508
731
|
// Check if peer is temporarily timed out
|
|
509
732
|
const timedOutPeer = this.timedOutPeers.get(peerIdString);
|
|
510
733
|
if (timedOutPeer) {
|
|
511
|
-
if (
|
|
734
|
+
if (this.dateProvider.now() < timedOutPeer.timeoutUntilMs) {
|
|
512
735
|
this.logger.trace(`Skipping timed out peer ${peerId}`);
|
|
513
736
|
return;
|
|
514
737
|
}
|
|
@@ -548,7 +771,7 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
548
771
|
enr,
|
|
549
772
|
multiaddrTcp,
|
|
550
773
|
dialAttempts: 0,
|
|
551
|
-
addedUnixMs:
|
|
774
|
+
addedUnixMs: this.dateProvider.now(),
|
|
552
775
|
};
|
|
553
776
|
|
|
554
777
|
// Determine if we should dial immediately or not
|
|
@@ -583,7 +806,7 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
583
806
|
// Add to timed out peers
|
|
584
807
|
this.timedOutPeers.set(id, {
|
|
585
808
|
peerId: id,
|
|
586
|
-
timeoutUntilMs:
|
|
809
|
+
timeoutUntilMs: this.dateProvider.now() + FAILED_PEER_BAN_TIME_MS,
|
|
587
810
|
});
|
|
588
811
|
}
|
|
589
812
|
}
|
|
@@ -622,6 +845,11 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
622
845
|
}
|
|
623
846
|
}
|
|
624
847
|
|
|
848
|
+
private async createStatusMessage() {
|
|
849
|
+
const syncSummary = (await this.worldStateSynchronizer.status()).syncSummary;
|
|
850
|
+
return StatusMessage.fromWorldStateSyncStatus(this.protocolVersion, syncSummary);
|
|
851
|
+
}
|
|
852
|
+
|
|
625
853
|
/**
|
|
626
854
|
* Performs status Handshake with the Peer
|
|
627
855
|
* The way the protocol is designed is that each peer will call this method on newly established p2p connection.
|
|
@@ -634,30 +862,30 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
634
862
|
* */
|
|
635
863
|
private async exchangeStatusHandshake(peerId: PeerId) {
|
|
636
864
|
try {
|
|
637
|
-
const
|
|
638
|
-
const ourStatus = StatusMessage.fromWorldStateSyncStatus(this.protocolVersion, syncSummary);
|
|
865
|
+
const ourStatus = await this.createStatusMessage();
|
|
639
866
|
//Note: Technically we don't have to send out status to peer as well, but we do.
|
|
640
867
|
//It will be easier to update protocol in the future this way if need be.
|
|
641
868
|
this.logger.trace(`Initiating status handshake with peer ${peerId}`);
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
ReqRespSubProtocol.STATUS,
|
|
645
|
-
ourStatus.toBuffer(),
|
|
646
|
-
);
|
|
647
|
-
const logData = { peerId, status: ReqRespStatus[status], data: data ? bufferToHex(data) : undefined };
|
|
869
|
+
const response = await this.reqresp.sendRequestToPeer(peerId, ReqRespSubProtocol.STATUS, ourStatus.toBuffer());
|
|
870
|
+
const { status } = response;
|
|
648
871
|
if (status !== ReqRespStatus.SUCCESS) {
|
|
649
872
|
//TODO: maybe hard ban these peers in the future.
|
|
650
873
|
//We could allow this to happen up to N times, and then hard ban?
|
|
651
874
|
//Hard ban: Disallow connection via e.g. libp2p's Gater
|
|
652
|
-
this.logger.debug(`Disconnecting peer ${peerId} who failed to respond status handshake`,
|
|
653
|
-
|
|
875
|
+
this.logger.debug(`Disconnecting peer ${peerId} who failed to respond status handshake`, {
|
|
876
|
+
peerId,
|
|
877
|
+
status: ReqRespStatus[status],
|
|
878
|
+
});
|
|
879
|
+
this.markPeerForDisconnect(peerId);
|
|
654
880
|
return;
|
|
655
881
|
}
|
|
656
882
|
|
|
883
|
+
const { data } = response;
|
|
884
|
+
const logData = { peerId, status: ReqRespStatus[status], data: data ? bufferToHex(data) : undefined };
|
|
657
885
|
const peerStatusMessage = StatusMessage.fromBuffer(data);
|
|
658
886
|
if (!ourStatus.validate(peerStatusMessage)) {
|
|
659
887
|
this.logger.debug(`Disconnecting peer ${peerId} due to failed status handshake.`, logData);
|
|
660
|
-
|
|
888
|
+
this.markPeerForDisconnect(peerId);
|
|
661
889
|
return;
|
|
662
890
|
}
|
|
663
891
|
this.logger.debug(`Successfully completed status handshake with peer ${peerId}`, logData);
|
|
@@ -666,10 +894,134 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
666
894
|
this.logger.debug(`Disconnecting peer ${peerId} due to error during status handshake: ${err.message ?? err}`, {
|
|
667
895
|
peerId,
|
|
668
896
|
});
|
|
669
|
-
|
|
897
|
+
this.markPeerForDisconnect(peerId);
|
|
670
898
|
}
|
|
671
899
|
}
|
|
672
900
|
|
|
901
|
+
/**
|
|
902
|
+
* Performs auth Handshake with the Peer
|
|
903
|
+
* A superset of the status handshake. Also includes a challenge that needs to be signed by the peer's validator key.
|
|
904
|
+
* @param: peerId The Id of the peer to request the Status from.
|
|
905
|
+
* */
|
|
906
|
+
private async exchangeAuthHandshake(peerId: PeerId) {
|
|
907
|
+
const peerIdString = peerId.toString();
|
|
908
|
+
|
|
909
|
+
try {
|
|
910
|
+
const ourStatus = await this.createStatusMessage();
|
|
911
|
+
const authRequest = new AuthRequest(ourStatus, Fr.random());
|
|
912
|
+
|
|
913
|
+
// Note: Technically we don't have to send our status to peer as well, but we do.
|
|
914
|
+
// It will be easier to update protocol in the future this way if need be.
|
|
915
|
+
// We also need to send the challenge at least, so that the peer can sign it.
|
|
916
|
+
this.logger.debug(`Initiating auth handshake with peer ${peerId}`);
|
|
917
|
+
const response = await this.reqresp.sendRequestToPeer(peerId, ReqRespSubProtocol.AUTH, authRequest.toBuffer());
|
|
918
|
+
const { status } = response;
|
|
919
|
+
if (status !== ReqRespStatus.SUCCESS) {
|
|
920
|
+
this.logger.debug(`Disconnecting peer ${peerId} who failed to respond auth handshake`, {
|
|
921
|
+
peerId,
|
|
922
|
+
status: ReqRespStatus[status],
|
|
923
|
+
});
|
|
924
|
+
this.markAuthHandshakeFailed(peerId);
|
|
925
|
+
this.markPeerForDisconnect(peerId);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const { data } = response;
|
|
930
|
+
const logData = { peerId, status: ReqRespStatus[status], data: data ? bufferToHex(data) : undefined };
|
|
931
|
+
|
|
932
|
+
const peerAuthResponse = AuthResponse.fromBuffer(data);
|
|
933
|
+
|
|
934
|
+
const peerStatusMessage = peerAuthResponse.status;
|
|
935
|
+
if (!ourStatus.validate(peerStatusMessage)) {
|
|
936
|
+
this.logger.debug(`Disconnecting peer ${peerId} due to failed status handshake as part of auth.`, logData);
|
|
937
|
+
this.markAuthHandshakeFailed(peerId);
|
|
938
|
+
this.markPeerForDisconnect(peerId);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const hashToRecover = authRequest.getPayloadToSign();
|
|
943
|
+
const ethSignedHash = makeEthSignDigest(hashToRecover);
|
|
944
|
+
const sender = recoverAddress(ethSignedHash, peerAuthResponse.signature);
|
|
945
|
+
const registeredValidators = await this.epochCache.getRegisteredValidators();
|
|
946
|
+
const found = registeredValidators.find(v => v.toString() === sender.toString()) !== undefined;
|
|
947
|
+
if (!found) {
|
|
948
|
+
this.logger.debug(
|
|
949
|
+
`Disconnecting peer ${peerId} due to failed auth handshake, peer is not a registered validator.`,
|
|
950
|
+
{
|
|
951
|
+
peerId,
|
|
952
|
+
address: sender.toString(),
|
|
953
|
+
},
|
|
954
|
+
);
|
|
955
|
+
this.markAuthHandshakeFailed(peerId);
|
|
956
|
+
this.markPeerForDisconnect(peerId);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Check to see that this validator address isn't already allocated to a different peer
|
|
961
|
+
const peerForAddress = this.authenticatedValidatorAddressToPeerId.get(sender.toString());
|
|
962
|
+
if (peerForAddress !== undefined && peerForAddress.toString() !== peerIdString) {
|
|
963
|
+
this.logger.debug(
|
|
964
|
+
`Received auth for validator ${sender.toString()} from peer ${peerIdString}, but this validator is already authenticated to peer ${peerForAddress.toString()}`,
|
|
965
|
+
);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
this.markAuthHandshakeSuccess(peerId);
|
|
970
|
+
this.authenticatedPeerIdToValidatorAddress.set(peerIdString, sender);
|
|
971
|
+
this.authenticatedValidatorAddressToPeerId.set(sender.toString(), peerId);
|
|
972
|
+
this.logger.info(
|
|
973
|
+
`Successfully completed auth handshake with peer ${peerId}, validator address ${sender.toString()}`,
|
|
974
|
+
logData,
|
|
975
|
+
);
|
|
976
|
+
} catch (err: any) {
|
|
977
|
+
//TODO: maybe hard ban these peers in the future
|
|
978
|
+
this.logger.debug(`Disconnecting peer ${peerId} due to error during auth handshake: ${err.message ?? err}`, {
|
|
979
|
+
peerId,
|
|
980
|
+
});
|
|
981
|
+
this.markAuthHandshakeFailed(peerId);
|
|
982
|
+
this.markPeerForDisconnect(peerId);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/*
|
|
987
|
+
* Marks when peer fails auth handshake
|
|
988
|
+
* */
|
|
989
|
+
private markAuthHandshakeFailed(peerId: PeerId) {
|
|
990
|
+
const now = this.dateProvider.now();
|
|
991
|
+
const peerIdStr = peerId.toString();
|
|
992
|
+
|
|
993
|
+
const existingEntry = this.failedAuthHandshakes.get(peerIdStr);
|
|
994
|
+
this.failedAuthHandshakes.set(peerIdStr, {
|
|
995
|
+
count: (existingEntry?.count || 0) + 1,
|
|
996
|
+
lastFailureTimestamp: now,
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
const connections = this.libP2PNode.getConnections(peerId);
|
|
1000
|
+
connections.forEach(conn => {
|
|
1001
|
+
// We mark the IP address
|
|
1002
|
+
const address = conn.remoteAddr.nodeAddress().address;
|
|
1003
|
+
const existingAddressEntry = this.failedAuthHandshakes.get(address);
|
|
1004
|
+
this.failedAuthHandshakes.set(address, {
|
|
1005
|
+
count: (existingAddressEntry?.count || 0) + 1,
|
|
1006
|
+
lastFailureTimestamp: now,
|
|
1007
|
+
});
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/*
|
|
1012
|
+
* Marks when peer exchanges auth handshake
|
|
1013
|
+
* Removes any failed previous attempts
|
|
1014
|
+
* */
|
|
1015
|
+
private markAuthHandshakeSuccess(peerId: PeerId) {
|
|
1016
|
+
this.failedAuthHandshakes.delete(peerId.toString());
|
|
1017
|
+
|
|
1018
|
+
const connections = this.libP2PNode.getConnections(peerId);
|
|
1019
|
+
connections.forEach(conn => {
|
|
1020
|
+
const address = conn.remoteAddr.nodeAddress().address;
|
|
1021
|
+
this.failedAuthHandshakes.delete(address);
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
|
|
673
1025
|
/**
|
|
674
1026
|
* Stops the peer manager.
|
|
675
1027
|
* Removing all event listeners.
|
|
@@ -686,6 +1038,54 @@ export class PeerManager implements PeerManagerInterface {
|
|
|
686
1038
|
this.libP2PNode.removeEventListener(PeerEvent.CONNECTED, this.handlers.handleConnectedPeerEvent);
|
|
687
1039
|
this.libP2PNode.removeEventListener(PeerEvent.DISCONNECTED, this.handlers.handleDisconnectedPeerEvent);
|
|
688
1040
|
}
|
|
1041
|
+
|
|
1042
|
+
private shouldTrustWithIdentity(peerId: PeerId): boolean {
|
|
1043
|
+
return this.isProtectedPeer(peerId);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Performs auth request verification from peer. An auth request is valid if requested by an authorized peer (a peer we trust).
|
|
1048
|
+
*
|
|
1049
|
+
* @param: _authRequest - Auth request (unused)
|
|
1050
|
+
* @param: peerId - The ID of the peer that requested the auth handshake
|
|
1051
|
+
*
|
|
1052
|
+
* @returns: StatusMessage if peer is trusted
|
|
1053
|
+
*
|
|
1054
|
+
* @throws: If peer is unauthorized
|
|
1055
|
+
* */
|
|
1056
|
+
public async handleAuthRequestFromPeer(_authRequest: AuthRequest, peerId: PeerId): Promise<StatusMessage> {
|
|
1057
|
+
if (!this.shouldTrustWithIdentity(peerId)) {
|
|
1058
|
+
this.logger.warn(`Received auth request from untrusted peer ${peerId.toString()}`);
|
|
1059
|
+
throw new Error('Unauthorised');
|
|
1060
|
+
}
|
|
1061
|
+
this.logger.debug(`Received auth request from trusted peer ${peerId.toString()}`);
|
|
1062
|
+
return await this.createStatusMessage();
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
private async updateAuthenticatedPeers(): Promise<void> {
|
|
1066
|
+
const registeredValidators = await this.epochCache.getRegisteredValidators();
|
|
1067
|
+
const validatorSet = new Set(registeredValidators.map(v => v.toString()));
|
|
1068
|
+
|
|
1069
|
+
const peersToDelete: Set<string> = new Set();
|
|
1070
|
+
const addressesToDelete: Set<string> = new Set();
|
|
1071
|
+
for (const [peer, address] of this.authenticatedPeerIdToValidatorAddress.entries()) {
|
|
1072
|
+
const addressString = address.toString();
|
|
1073
|
+
if (!validatorSet.has(addressString)) {
|
|
1074
|
+
peersToDelete.add(peer);
|
|
1075
|
+
addressesToDelete.add(addressString);
|
|
1076
|
+
this.logger.info(
|
|
1077
|
+
`Removing authentication for peer ${peer.toString()} at address ${addressString} due to no longer being a registered validator`,
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
for (const peer of peersToDelete) {
|
|
1083
|
+
this.authenticatedPeerIdToValidatorAddress.delete(peer);
|
|
1084
|
+
}
|
|
1085
|
+
for (const address of addressesToDelete) {
|
|
1086
|
+
this.authenticatedValidatorAddressToPeerId.delete(address);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
689
1089
|
}
|
|
690
1090
|
|
|
691
1091
|
/**
|