@aztec/p2p 0.67.1 → 0.68.1
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 +5 -3
- package/dest/bootstrap/bootstrap.d.ts.map +1 -1
- package/dest/bootstrap/bootstrap.js +17 -15
- package/dest/client/factory.d.ts +19 -0
- package/dest/client/factory.d.ts.map +1 -0
- package/dest/client/factory.js +40 -0
- package/dest/client/index.d.ts +1 -15
- package/dest/client/index.d.ts.map +1 -1
- package/dest/client/index.js +2 -37
- package/dest/client/p2p_client.d.ts +6 -6
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +12 -11
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +3 -3
- package/dest/errors/reqresp.error.d.ts +12 -1
- package/dest/errors/reqresp.error.d.ts.map +1 -1
- package/dest/errors/reqresp.error.js +15 -2
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +3 -3
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +9 -0
- 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 +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 +171 -0
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +29 -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 +114 -0
- package/dest/mem_pools/interface.d.ts +4 -3
- package/dest/mem_pools/interface.d.ts.map +1 -1
- package/dest/mocks/index.d.ts +7 -6
- package/dest/mocks/index.d.ts.map +1 -1
- package/dest/mocks/index.js +8 -8
- 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 +19 -0
- 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 +2 -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 +21 -0
- 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 +2 -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 +16 -0
- 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 +2 -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 +4 -0
- package/dest/{tx_validator → msg_validators/tx_validator}/aggregate_tx_validator.d.ts +1 -1
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +34 -0
- package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -0
- package/dest/{tx_validator → msg_validators/tx_validator}/data_validator.js +1 -1
- package/dest/{tx_validator → msg_validators/tx_validator}/double_spend_validator.d.ts +3 -2
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/double_spend_validator.js +56 -0
- package/dest/msg_validators/tx_validator/index.d.ts.map +1 -0
- package/dest/{tx_validator → msg_validators/tx_validator}/index.js +1 -1
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -0
- package/dest/{tx_validator → msg_validators/tx_validator}/metadata_validator.js +1 -1
- package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/tx_proof_validator.js +29 -0
- package/dest/services/data_store.d.ts.map +1 -0
- package/dest/services/data_store.js +188 -0
- package/dest/{service → services/discv5}/discV5_service.d.ts +3 -9
- package/dest/services/discv5/discV5_service.d.ts.map +1 -0
- package/dest/services/discv5/discV5_service.js +139 -0
- package/dest/services/dummy_service.d.ts.map +1 -0
- package/dest/{service → services}/dummy_service.js +1 -1
- package/dest/{service → services}/encoding.d.ts +5 -0
- package/dest/services/encoding.d.ts.map +1 -0
- package/dest/services/encoding.js +65 -0
- package/dest/services/index.d.ts +3 -0
- package/dest/services/index.d.ts.map +1 -0
- package/dest/services/index.js +3 -0
- package/dest/services/libp2p/libp2p_service.d.ts +222 -0
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -0
- package/dest/services/libp2p/libp2p_service.js +697 -0
- package/dest/services/peer-scoring/peer_scoring.d.ts +25 -0
- package/dest/services/peer-scoring/peer_scoring.d.ts.map +1 -0
- package/dest/services/peer-scoring/peer_scoring.js +75 -0
- package/dest/services/peer_manager.d.ts +60 -0
- package/dest/services/peer_manager.d.ts.map +1 -0
- package/dest/services/peer_manager.js +358 -0
- package/dest/services/reqresp/config.d.ts.map +1 -0
- package/dest/{service → services}/reqresp/config.js +1 -1
- package/dest/services/reqresp/handlers.d.ts.map +1 -0
- package/dest/{service → services}/reqresp/handlers.js +1 -1
- package/dest/services/reqresp/index.d.ts.map +1 -0
- package/dest/{service → services}/reqresp/index.js +1 -1
- package/dest/services/reqresp/interface.d.ts.map +1 -0
- package/dest/{service → services}/reqresp/interface.js +1 -1
- package/dest/services/reqresp/rate_limiter/index.d.ts.map +1 -0
- package/dest/{service → services}/reqresp/rate_limiter/index.js +1 -1
- package/dest/{service → services}/reqresp/rate_limiter/rate_limiter.d.ts +0 -5
- package/dest/services/reqresp/rate_limiter/rate_limiter.d.ts.map +1 -0
- package/dest/{service → services}/reqresp/rate_limiter/rate_limiter.js +7 -2
- package/dest/services/reqresp/rate_limiter/rate_limits.d.ts.map +1 -0
- package/dest/{service → services}/reqresp/rate_limiter/rate_limits.js +1 -1
- package/dest/{service → services}/reqresp/reqresp.d.ts +16 -0
- package/dest/services/reqresp/reqresp.d.ts.map +1 -0
- package/dest/services/reqresp/reqresp.js +279 -0
- package/dest/services/service.d.ts.map +1 -0
- package/dest/{service → services}/service.js +1 -1
- package/dest/services/types.d.ts +38 -0
- package/dest/services/types.d.ts.map +1 -0
- package/dest/services/types.js +43 -0
- package/package.json +14 -11
- package/src/bootstrap/bootstrap.ts +25 -20
- package/src/client/factory.ts +97 -0
- package/src/client/index.ts +1 -73
- package/src/client/p2p_client.ts +28 -15
- package/src/config.ts +2 -2
- package/src/errors/reqresp.error.ts +15 -1
- package/src/index.ts +2 -2
- package/src/mem_pools/attestation_pool/attestation_pool.ts +10 -0
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +237 -0
- package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +153 -0
- package/src/mem_pools/interface.ts +5 -3
- package/src/mocks/index.ts +16 -10
- package/src/msg_validators/attestation_validator/attestation_validator.ts +26 -0
- package/src/msg_validators/attestation_validator/index.ts +1 -0
- package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +29 -0
- package/src/msg_validators/block_proposal_validator/index.ts +1 -0
- package/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts +22 -0
- package/src/msg_validators/epoch_proof_quote_validator/index.ts +1 -0
- package/src/msg_validators/index.ts +3 -0
- package/src/{tx_validator → msg_validators/tx_validator}/aggregate_tx_validator.ts +5 -3
- package/src/{tx_validator → msg_validators/tx_validator}/double_spend_validator.ts +6 -8
- package/src/{service → services/discv5}/discV5_service.ts +19 -23
- package/src/{service → services}/encoding.ts +21 -3
- package/src/services/index.ts +2 -0
- package/src/{service → services/libp2p}/libp2p_service.ts +350 -90
- package/src/{service → services/peer-scoring}/peer_scoring.ts +27 -23
- package/src/services/peer_manager.ts +422 -0
- package/src/{service → services}/reqresp/rate_limiter/rate_limiter.ts +2 -1
- package/src/{service → services}/reqresp/reqresp.ts +86 -20
- package/src/services/types.ts +44 -0
- package/dest/service/data_store.d.ts.map +0 -1
- package/dest/service/data_store.js +0 -188
- package/dest/service/discV5_service.d.ts.map +0 -1
- package/dest/service/discV5_service.js +0 -144
- package/dest/service/dummy_service.d.ts.map +0 -1
- package/dest/service/encoding.d.ts.map +0 -1
- package/dest/service/encoding.js +0 -49
- package/dest/service/index.d.ts +0 -3
- package/dest/service/index.d.ts.map +0 -1
- package/dest/service/index.js +0 -3
- package/dest/service/libp2p_service.d.ts +0 -136
- package/dest/service/libp2p_service.d.ts.map +0 -1
- package/dest/service/libp2p_service.js +0 -500
- package/dest/service/peer_manager.d.ts +0 -33
- package/dest/service/peer_manager.d.ts.map +0 -1
- package/dest/service/peer_manager.js +0 -214
- package/dest/service/peer_scoring.d.ts +0 -35
- package/dest/service/peer_scoring.d.ts.map +0 -1
- package/dest/service/peer_scoring.js +0 -72
- package/dest/service/reqresp/config.d.ts.map +0 -1
- package/dest/service/reqresp/handlers.d.ts.map +0 -1
- package/dest/service/reqresp/index.d.ts.map +0 -1
- package/dest/service/reqresp/interface.d.ts.map +0 -1
- package/dest/service/reqresp/rate_limiter/index.d.ts.map +0 -1
- package/dest/service/reqresp/rate_limiter/rate_limiter.d.ts.map +0 -1
- package/dest/service/reqresp/rate_limiter/rate_limits.d.ts.map +0 -1
- package/dest/service/reqresp/reqresp.d.ts.map +0 -1
- package/dest/service/reqresp/reqresp.js +0 -230
- package/dest/service/service.d.ts.map +0 -1
- package/dest/tx_validator/aggregate_tx_validator.d.ts.map +0 -1
- package/dest/tx_validator/aggregate_tx_validator.js +0 -32
- package/dest/tx_validator/data_validator.d.ts.map +0 -1
- package/dest/tx_validator/double_spend_validator.d.ts.map +0 -1
- package/dest/tx_validator/double_spend_validator.js +0 -56
- package/dest/tx_validator/index.d.ts.map +0 -1
- package/dest/tx_validator/metadata_validator.d.ts.map +0 -1
- package/dest/tx_validator/tx_proof_validator.d.ts.map +0 -1
- package/dest/tx_validator/tx_proof_validator.js +0 -29
- package/src/service/index.ts +0 -2
- package/src/service/peer_manager.ts +0 -266
- /package/dest/{tx_validator → msg_validators/tx_validator}/data_validator.d.ts +0 -0
- /package/dest/{tx_validator → msg_validators/tx_validator}/index.d.ts +0 -0
- /package/dest/{tx_validator → msg_validators/tx_validator}/metadata_validator.d.ts +0 -0
- /package/dest/{tx_validator → msg_validators/tx_validator}/tx_proof_validator.d.ts +0 -0
- /package/dest/{service → services}/data_store.d.ts +0 -0
- /package/dest/{service → services}/dummy_service.d.ts +0 -0
- /package/dest/{service → services}/reqresp/config.d.ts +0 -0
- /package/dest/{service → services}/reqresp/handlers.d.ts +0 -0
- /package/dest/{service → services}/reqresp/index.d.ts +0 -0
- /package/dest/{service → services}/reqresp/interface.d.ts +0 -0
- /package/dest/{service → services}/reqresp/rate_limiter/index.d.ts +0 -0
- /package/dest/{service → services}/reqresp/rate_limiter/rate_limits.d.ts +0 -0
- /package/dest/{service → services}/service.d.ts +0 -0
- /package/src/{tx_validator → msg_validators/tx_validator}/data_validator.ts +0 -0
- /package/src/{tx_validator → msg_validators/tx_validator}/index.ts +0 -0
- /package/src/{tx_validator → msg_validators/tx_validator}/metadata_validator.ts +0 -0
- /package/src/{tx_validator → msg_validators/tx_validator}/tx_proof_validator.ts +0 -0
- /package/src/{service → services}/data_store.ts +0 -0
- /package/src/{service → services}/dummy_service.ts +0 -0
- /package/src/{service → services}/reqresp/config.ts +0 -0
- /package/src/{service → services}/reqresp/handlers.ts +0 -0
- /package/src/{service → services}/reqresp/index.ts +0 -0
- /package/src/{service → services}/reqresp/interface.ts +0 -0
- /package/src/{service → services}/reqresp/rate_limiter/index.ts +0 -0
- /package/src/{service → services}/reqresp/rate_limiter/rate_limits.ts +0 -0
- /package/src/{service → services}/service.ts +0 -0
|
@@ -1,31 +1,24 @@
|
|
|
1
|
+
import { PeerErrorSeverity } from '@aztec/circuit-types';
|
|
1
2
|
import { median } from '@aztec/foundation/collection';
|
|
2
3
|
|
|
3
|
-
import { type P2PConfig } from '
|
|
4
|
-
|
|
5
|
-
export enum PeerErrorSeverity {
|
|
6
|
-
/**
|
|
7
|
-
* Not malicious action, but it must not be tolerated
|
|
8
|
-
* ~2 occurrences will get the peer banned
|
|
9
|
-
*/
|
|
10
|
-
LowToleranceError = 'LowToleranceError',
|
|
11
|
-
/**
|
|
12
|
-
* Negative action that can be tolerated only sometimes
|
|
13
|
-
* ~10 occurrences will get the peer banned
|
|
14
|
-
*/
|
|
15
|
-
MidToleranceError = 'MidToleranceError',
|
|
16
|
-
/**
|
|
17
|
-
* Some error that can be tolerated multiple times
|
|
18
|
-
* ~50 occurrences will get the peer banned
|
|
19
|
-
*/
|
|
20
|
-
HighToleranceError = 'HighToleranceError',
|
|
21
|
-
}
|
|
4
|
+
import { type P2PConfig } from '../../config.js';
|
|
22
5
|
|
|
23
6
|
const DefaultPeerPenalties = {
|
|
24
|
-
[PeerErrorSeverity.LowToleranceError]:
|
|
7
|
+
[PeerErrorSeverity.LowToleranceError]: 50,
|
|
25
8
|
[PeerErrorSeverity.MidToleranceError]: 10,
|
|
26
|
-
[PeerErrorSeverity.HighToleranceError]:
|
|
9
|
+
[PeerErrorSeverity.HighToleranceError]: 2,
|
|
27
10
|
};
|
|
28
11
|
|
|
12
|
+
export enum PeerScoreState {
|
|
13
|
+
Banned,
|
|
14
|
+
Disconnect,
|
|
15
|
+
Healthy,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// TODO: move into config / constants
|
|
19
|
+
const MIN_SCORE_BEFORE_BAN = -100;
|
|
20
|
+
const MIN_SCORE_BEFORE_DISCONNECT = -50;
|
|
21
|
+
|
|
29
22
|
export class PeerScoring {
|
|
30
23
|
private scores: Map<string, number> = new Map();
|
|
31
24
|
private lastUpdateTime: Map<string, number> = new Map();
|
|
@@ -37,11 +30,11 @@ export class PeerScoring {
|
|
|
37
30
|
const orderedValues = config.peerPenaltyValues?.sort((a, b) => a - b);
|
|
38
31
|
this.peerPenalties = {
|
|
39
32
|
[PeerErrorSeverity.HighToleranceError]:
|
|
40
|
-
orderedValues?.[0] ?? DefaultPeerPenalties[PeerErrorSeverity.
|
|
33
|
+
orderedValues?.[0] ?? DefaultPeerPenalties[PeerErrorSeverity.HighToleranceError],
|
|
41
34
|
[PeerErrorSeverity.MidToleranceError]:
|
|
42
35
|
orderedValues?.[1] ?? DefaultPeerPenalties[PeerErrorSeverity.MidToleranceError],
|
|
43
36
|
[PeerErrorSeverity.LowToleranceError]:
|
|
44
|
-
orderedValues?.[2] ?? DefaultPeerPenalties[PeerErrorSeverity.
|
|
37
|
+
orderedValues?.[2] ?? DefaultPeerPenalties[PeerErrorSeverity.LowToleranceError],
|
|
45
38
|
};
|
|
46
39
|
}
|
|
47
40
|
|
|
@@ -82,6 +75,17 @@ export class PeerScoring {
|
|
|
82
75
|
return this.scores.get(peerId) || 0;
|
|
83
76
|
}
|
|
84
77
|
|
|
78
|
+
getScoreState(peerId: string) {
|
|
79
|
+
// TODO: permanently store banned peers???
|
|
80
|
+
const score = this.getScore(peerId);
|
|
81
|
+
if (score < MIN_SCORE_BEFORE_BAN) {
|
|
82
|
+
return PeerScoreState.Banned;
|
|
83
|
+
} else if (score < MIN_SCORE_BEFORE_DISCONNECT) {
|
|
84
|
+
return PeerScoreState.Disconnect;
|
|
85
|
+
}
|
|
86
|
+
return PeerScoreState.Healthy;
|
|
87
|
+
}
|
|
88
|
+
|
|
85
89
|
getStats(): { medianScore: number } {
|
|
86
90
|
return { medianScore: median(Array.from(this.scores.values())) ?? 0 };
|
|
87
91
|
}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { type PeerErrorSeverity, type PeerInfo } from '@aztec/circuit-types';
|
|
2
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import { type TelemetryClient, WithTracer, trackSpan } from '@aztec/telemetry-client';
|
|
4
|
+
|
|
5
|
+
import { type ENR } from '@chainsafe/enr';
|
|
6
|
+
import { type Connection, type PeerId } from '@libp2p/interface';
|
|
7
|
+
import { type Multiaddr } from '@multiformats/multiaddr';
|
|
8
|
+
import { inspect } from 'util';
|
|
9
|
+
|
|
10
|
+
import { type P2PConfig } from '../config.js';
|
|
11
|
+
import { type PubSubLibp2p } from '../util.js';
|
|
12
|
+
import { PeerScoreState, PeerScoring } from './peer-scoring/peer_scoring.js';
|
|
13
|
+
import { type PeerDiscoveryService } from './service.js';
|
|
14
|
+
import { PeerEvent } from './types.js';
|
|
15
|
+
|
|
16
|
+
const MAX_DIAL_ATTEMPTS = 3;
|
|
17
|
+
const MAX_CACHED_PEERS = 100;
|
|
18
|
+
const MAX_CACHED_PEER_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
19
|
+
const FAILED_PEER_BAN_TIME_MS = 5 * 60 * 1000; // 5 minutes timeout after failing MAX_DIAL_ATTEMPTS
|
|
20
|
+
|
|
21
|
+
type CachedPeer = {
|
|
22
|
+
peerId: PeerId;
|
|
23
|
+
enr: ENR;
|
|
24
|
+
multiaddrTcp: Multiaddr;
|
|
25
|
+
dialAttempts: number;
|
|
26
|
+
addedUnixMs: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type TimedOutPeer = {
|
|
30
|
+
peerId: string;
|
|
31
|
+
timeoutUntilMs: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class PeerManager extends WithTracer {
|
|
35
|
+
private cachedPeers: Map<string, CachedPeer> = new Map();
|
|
36
|
+
private peerScoring: PeerScoring;
|
|
37
|
+
private heartbeatCounter: number = 0;
|
|
38
|
+
private displayPeerCountsPeerHeartbeat: number = 0;
|
|
39
|
+
private timedOutPeers: Map<string, TimedOutPeer> = new Map();
|
|
40
|
+
|
|
41
|
+
constructor(
|
|
42
|
+
private libP2PNode: PubSubLibp2p,
|
|
43
|
+
private peerDiscoveryService: PeerDiscoveryService,
|
|
44
|
+
private config: P2PConfig,
|
|
45
|
+
telemetryClient: TelemetryClient,
|
|
46
|
+
private logger = createLogger('p2p:peer-manager'),
|
|
47
|
+
) {
|
|
48
|
+
super(telemetryClient, 'PeerManager');
|
|
49
|
+
|
|
50
|
+
this.peerScoring = new PeerScoring(config);
|
|
51
|
+
// Handle new established connections
|
|
52
|
+
this.libP2PNode.addEventListener(PeerEvent.CONNECTED, this.handleConnectedPeerEvent.bind(this));
|
|
53
|
+
// Handle lost connections
|
|
54
|
+
this.libP2PNode.addEventListener(PeerEvent.DISCONNECTED, this.handleDisconnectedPeerEvent.bind(this));
|
|
55
|
+
|
|
56
|
+
// Handle Discovered peers
|
|
57
|
+
this.peerDiscoveryService.on(PeerEvent.DISCOVERED, this.handleDiscoveredPeer.bind(this));
|
|
58
|
+
|
|
59
|
+
// Display peer counts every 60 seconds
|
|
60
|
+
this.displayPeerCountsPeerHeartbeat = Math.floor(60_000 / this.config.peerCheckIntervalMS);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@trackSpan('PeerManager.heartbeat')
|
|
64
|
+
public heartbeat() {
|
|
65
|
+
this.heartbeatCounter++;
|
|
66
|
+
this.peerScoring.decayAllScores();
|
|
67
|
+
|
|
68
|
+
this.cleanupExpiredTimeouts();
|
|
69
|
+
|
|
70
|
+
this.discover();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Cleans up expired timeouts.
|
|
75
|
+
*
|
|
76
|
+
* When peers fail to dial after a number of retries, they are temporarily timed out.
|
|
77
|
+
* This function removes any peers that have been in the timed out state for too long.
|
|
78
|
+
* To give them a chance to reconnect.
|
|
79
|
+
*/
|
|
80
|
+
private cleanupExpiredTimeouts() {
|
|
81
|
+
// Clean up expired timeouts
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
for (const [peerId, timedOutPeer] of this.timedOutPeers.entries()) {
|
|
84
|
+
if (now >= timedOutPeer.timeoutUntilMs) {
|
|
85
|
+
this.timedOutPeers.delete(peerId);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Simply logs the type of connected peer.
|
|
92
|
+
* @param e - The connected peer event.
|
|
93
|
+
*/
|
|
94
|
+
private handleConnectedPeerEvent(e: CustomEvent<PeerId>) {
|
|
95
|
+
const peerId = e.detail;
|
|
96
|
+
if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
|
|
97
|
+
this.logger.verbose(`Connected to bootstrap peer ${peerId.toString()}`);
|
|
98
|
+
} else {
|
|
99
|
+
this.logger.verbose(`Connected to transaction peer ${peerId.toString()}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Simply logs the type of disconnected peer.
|
|
105
|
+
* @param e - The disconnected peer event.
|
|
106
|
+
*/
|
|
107
|
+
private handleDisconnectedPeerEvent(e: CustomEvent<PeerId>) {
|
|
108
|
+
const peerId = e.detail;
|
|
109
|
+
if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
|
|
110
|
+
this.logger.verbose(`Disconnected from bootstrap peer ${peerId.toString()}`);
|
|
111
|
+
} else {
|
|
112
|
+
this.logger.verbose(`Disconnected from transaction peer ${peerId.toString()}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public penalizePeer(peerId: PeerId, penalty: PeerErrorSeverity) {
|
|
117
|
+
const id = peerId.toString();
|
|
118
|
+
const penaltyValue = this.peerScoring.peerPenalties[penalty];
|
|
119
|
+
const newScore = this.peerScoring.updateScore(id, -penaltyValue);
|
|
120
|
+
this.logger.verbose(`Penalizing peer ${id} with ${penalty} (new score is ${newScore})`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public getPeerScore(peerId: string): number {
|
|
124
|
+
return this.peerScoring.getScore(peerId);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public getPeers(includePending = false): PeerInfo[] {
|
|
128
|
+
const connected = this.libP2PNode
|
|
129
|
+
.getPeers()
|
|
130
|
+
.map(peer => ({ id: peer.toString(), score: this.getPeerScore(peer.toString()), status: 'connected' as const }));
|
|
131
|
+
|
|
132
|
+
if (!includePending) {
|
|
133
|
+
return connected;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const dialQueue = this.libP2PNode
|
|
137
|
+
.getDialQueue()
|
|
138
|
+
.filter(peer => !!peer.peerId)
|
|
139
|
+
.map(peer => ({
|
|
140
|
+
id: peer.peerId!.toString(),
|
|
141
|
+
status: 'dialing' as const,
|
|
142
|
+
dialStatus: peer.status,
|
|
143
|
+
addresses: peer.multiaddrs.map(m => m.toString()),
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
const cachedPeers = Array.from(this.cachedPeers.values())
|
|
147
|
+
.filter(peer => !dialQueue.some(dialPeer => dialPeer.id && peer.peerId.toString() === dialPeer.id.toString()))
|
|
148
|
+
.filter(peer => !connected.some(connPeer => connPeer.id.toString() === peer.peerId.toString()))
|
|
149
|
+
.map(peer => ({
|
|
150
|
+
status: 'cached' as const,
|
|
151
|
+
id: peer.peerId.toString(),
|
|
152
|
+
addresses: [peer.multiaddrTcp.toString()],
|
|
153
|
+
dialAttempts: peer.dialAttempts,
|
|
154
|
+
enr: peer.enr.encodeTxt(),
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
return [...connected, ...dialQueue, ...cachedPeers];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Discovers peers.
|
|
162
|
+
*/
|
|
163
|
+
private discover() {
|
|
164
|
+
const connections = this.libP2PNode.getConnections();
|
|
165
|
+
|
|
166
|
+
const healthyConnections = this.pruneUnhealthyPeers(connections);
|
|
167
|
+
|
|
168
|
+
// Calculate how many connections we're looking to make
|
|
169
|
+
const peersToConnect = this.config.maxPeerCount - healthyConnections.length;
|
|
170
|
+
|
|
171
|
+
const logLevel = this.heartbeatCounter % this.displayPeerCountsPeerHeartbeat === 0 ? 'info' : 'debug';
|
|
172
|
+
this.logger[logLevel](`Connected to ${connections.length} peers`, {
|
|
173
|
+
connections: connections.length,
|
|
174
|
+
maxPeerCount: this.config.maxPeerCount,
|
|
175
|
+
cachedPeers: this.cachedPeers.size,
|
|
176
|
+
...this.peerScoring.getStats(),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Exit if no peers to connect
|
|
180
|
+
if (peersToConnect <= 0) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const cachedPeersToDial: CachedPeer[] = [];
|
|
185
|
+
|
|
186
|
+
const pendingDials = new Set(
|
|
187
|
+
this.libP2PNode
|
|
188
|
+
.getDialQueue()
|
|
189
|
+
.map(pendingDial => pendingDial.peerId?.toString())
|
|
190
|
+
.filter(Boolean) as string[],
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
for (const [id, peerData] of this.cachedPeers.entries()) {
|
|
194
|
+
// if already dialling or connected to, remove from cache
|
|
195
|
+
if (
|
|
196
|
+
pendingDials.has(id) ||
|
|
197
|
+
healthyConnections.some(conn => conn.remotePeer.equals(peerData.peerId)) ||
|
|
198
|
+
// if peer has been in cache for the max cache age, remove from cache
|
|
199
|
+
Date.now() - peerData.addedUnixMs > MAX_CACHED_PEER_AGE_MS
|
|
200
|
+
) {
|
|
201
|
+
this.cachedPeers.delete(id);
|
|
202
|
+
} else {
|
|
203
|
+
// cachedPeersToDial.set(id, enr);
|
|
204
|
+
cachedPeersToDial.push(peerData);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// reverse to dial older entries first
|
|
209
|
+
cachedPeersToDial.reverse();
|
|
210
|
+
|
|
211
|
+
for (const peer of cachedPeersToDial) {
|
|
212
|
+
// We remove from the cache before, as dialling will add it back if it fails
|
|
213
|
+
this.cachedPeers.delete(peer.peerId.toString());
|
|
214
|
+
void this.dialPeer(peer);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// if we need more peers, start randomNodesQuery
|
|
218
|
+
if (peersToConnect > 0) {
|
|
219
|
+
this.logger.trace(`Running random nodes query to connect to ${peersToConnect} peers`);
|
|
220
|
+
void this.peerDiscoveryService.runRandomNodesQuery();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private pruneUnhealthyPeers(connections: Connection[]): Connection[] {
|
|
225
|
+
const connectedHealthyPeers: Connection[] = [];
|
|
226
|
+
|
|
227
|
+
for (const peer of connections) {
|
|
228
|
+
const score = this.peerScoring.getScoreState(peer.remotePeer.toString());
|
|
229
|
+
switch (score) {
|
|
230
|
+
// TODO: add goodbye and give reasons
|
|
231
|
+
case PeerScoreState.Banned:
|
|
232
|
+
case PeerScoreState.Disconnect:
|
|
233
|
+
void this.disconnectPeer(peer.remotePeer);
|
|
234
|
+
break;
|
|
235
|
+
case PeerScoreState.Healthy:
|
|
236
|
+
connectedHealthyPeers.push(peer);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return connectedHealthyPeers;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// TODO: send a goodbye with a reason to the peer
|
|
244
|
+
private async disconnectPeer(peer: PeerId) {
|
|
245
|
+
this.logger.debug(`Disconnecting peer ${peer.toString()}`);
|
|
246
|
+
await this.libP2PNode.hangUp(peer);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Handles a discovered peer.
|
|
251
|
+
* @param enr - The discovered peer's ENR.
|
|
252
|
+
*/
|
|
253
|
+
private async handleDiscoveredPeer(enr: ENR) {
|
|
254
|
+
// Check that the peer has not already been banned
|
|
255
|
+
const peerId = await enr.peerId();
|
|
256
|
+
const peerIdString = peerId.toString();
|
|
257
|
+
|
|
258
|
+
// Check if peer is temporarily timed out
|
|
259
|
+
const timedOutPeer = this.timedOutPeers.get(peerIdString);
|
|
260
|
+
if (timedOutPeer) {
|
|
261
|
+
if (Date.now() < timedOutPeer.timeoutUntilMs) {
|
|
262
|
+
this.logger.trace(`Skipping timed out peer ${peerId}`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// Timeout period expired, remove from timed out peers
|
|
266
|
+
this.timedOutPeers.delete(peerIdString);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (this.peerScoring.getScoreState(peerIdString) != PeerScoreState.Healthy) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const [multiaddrTcp] = await Promise.all([enr.getFullMultiaddr('tcp')]);
|
|
274
|
+
|
|
275
|
+
this.logger.trace(`Handling discovered peer ${peerId} at ${multiaddrTcp?.toString() ?? 'undefined address'}`);
|
|
276
|
+
|
|
277
|
+
// stop if no tcp addr in multiaddr
|
|
278
|
+
if (!multiaddrTcp) {
|
|
279
|
+
this.logger.debug(`No TCP address in discovered node's multiaddr ${enr.encodeTxt()}`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// check if peer is already connected
|
|
283
|
+
const connections = this.libP2PNode.getConnections();
|
|
284
|
+
if (connections.some(conn => conn.remotePeer.equals(peerId))) {
|
|
285
|
+
this.logger.trace(`Already connected to peer ${peerId}`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// check if peer is already in cache
|
|
290
|
+
if (this.cachedPeers.has(peerIdString)) {
|
|
291
|
+
this.logger.trace(`Peer already in cache ${peerIdString}`);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// create cached peer object
|
|
296
|
+
const cachedPeer: CachedPeer = {
|
|
297
|
+
peerId,
|
|
298
|
+
enr,
|
|
299
|
+
multiaddrTcp,
|
|
300
|
+
dialAttempts: 0,
|
|
301
|
+
addedUnixMs: Date.now(),
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// Determine if we should dial immediately or not
|
|
305
|
+
if (this.shouldDialPeer()) {
|
|
306
|
+
void this.dialPeer(cachedPeer);
|
|
307
|
+
} else {
|
|
308
|
+
this.logger.trace(`Caching peer ${peerIdString}`);
|
|
309
|
+
this.cachedPeers.set(peerIdString, cachedPeer);
|
|
310
|
+
// Prune set of cached peers
|
|
311
|
+
this.pruneCachedPeers();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private async dialPeer(peer: CachedPeer) {
|
|
316
|
+
const id = peer.peerId.toString();
|
|
317
|
+
|
|
318
|
+
// Add to the address book before dialing
|
|
319
|
+
await this.libP2PNode.peerStore.merge(peer.peerId, { multiaddrs: [peer.multiaddrTcp] });
|
|
320
|
+
|
|
321
|
+
this.logger.trace(`Dialing peer ${id}`);
|
|
322
|
+
try {
|
|
323
|
+
await this.libP2PNode.dial(peer.multiaddrTcp);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
peer.dialAttempts++;
|
|
326
|
+
if (peer.dialAttempts < MAX_DIAL_ATTEMPTS) {
|
|
327
|
+
this.logger.trace(`Failed to dial peer ${id} (attempt ${peer.dialAttempts})`, { error: inspect(error) });
|
|
328
|
+
this.cachedPeers.set(id, peer);
|
|
329
|
+
} else {
|
|
330
|
+
formatLibp2pDialError(error as Error);
|
|
331
|
+
this.logger.debug(`Failed to dial peer ${id} (dropping)`, { error: inspect(error) });
|
|
332
|
+
this.cachedPeers.delete(id);
|
|
333
|
+
// Add to timed out peers
|
|
334
|
+
this.timedOutPeers.set(id, {
|
|
335
|
+
peerId: id,
|
|
336
|
+
timeoutUntilMs: Date.now() + FAILED_PEER_BAN_TIME_MS,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private shouldDialPeer(): boolean {
|
|
343
|
+
const connections = this.libP2PNode.getConnections().length;
|
|
344
|
+
if (connections >= this.config.maxPeerCount) {
|
|
345
|
+
this.logger.trace(
|
|
346
|
+
`Not dialing peer due to max peer count of ${this.config.maxPeerCount} reached (${connections} current connections)`,
|
|
347
|
+
);
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private pruneCachedPeers() {
|
|
354
|
+
let peersToDelete = this.cachedPeers.size - MAX_CACHED_PEERS;
|
|
355
|
+
if (peersToDelete <= 0) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Remove the oldest peers
|
|
360
|
+
for (const key of this.cachedPeers.keys()) {
|
|
361
|
+
this.cachedPeers.delete(key);
|
|
362
|
+
this.logger.trace(`Pruning peer ${key} from cache`);
|
|
363
|
+
peersToDelete--;
|
|
364
|
+
if (peersToDelete <= 0) {
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Stops the peer manager.
|
|
372
|
+
* Removing all event listeners.
|
|
373
|
+
*/
|
|
374
|
+
public stop() {
|
|
375
|
+
this.libP2PNode.removeEventListener(PeerEvent.CONNECTED, this.handleConnectedPeerEvent);
|
|
376
|
+
this.libP2PNode.removeEventListener(PeerEvent.DISCONNECTED, this.handleDisconnectedPeerEvent);
|
|
377
|
+
this.peerDiscoveryService.off(PeerEvent.DISCOVERED, this.handleDiscoveredPeer);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* copied from github.com/ChainSafe/lodestar
|
|
383
|
+
* libp2p errors with extremely noisy errors here, which are deeply nested taking 30-50 lines.
|
|
384
|
+
* Some known errors:
|
|
385
|
+
* ```
|
|
386
|
+
* Error: The operation was aborted
|
|
387
|
+
* Error: stream ended before 1 bytes became available
|
|
388
|
+
* Error: Error occurred during XX handshake: Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key
|
|
389
|
+
* ```
|
|
390
|
+
*
|
|
391
|
+
* Also the error's message is not properly formatted, where the error message is indented and includes the full stack
|
|
392
|
+
* ```
|
|
393
|
+
* {
|
|
394
|
+
* emessage: '\n' +
|
|
395
|
+
* ' Error: stream ended before 1 bytes became available\n' +
|
|
396
|
+
* ' at /home/lion/Code/eth2.0/lodestar/node_modules/it-reader/index.js:37:9\n' +
|
|
397
|
+
* ' at runMicrotasks (<anonymous>)\n' +
|
|
398
|
+
* ' at decoder (/home/lion/Code/eth2.0/lodestar/node_modules/it-length-prefixed/src/decode.js:113:22)\n' +
|
|
399
|
+
* ' at first (/home/lion/Code/eth2.0/lodestar/node_modules/it-first/index.js:11:20)\n' +
|
|
400
|
+
* ' at Object.exports.read (/home/lion/Code/eth2.0/lodestar/node_modules/multistream-select/src/multistream.js:31:15)\n' +
|
|
401
|
+
* ' at module.exports (/home/lion/Code/eth2.0/lodestar/node_modules/multistream-select/src/select.js:21:19)\n' +
|
|
402
|
+
* ' at Upgrader._encryptOutbound (/home/lion/Code/eth2.0/lodestar/node_modules/libp2p/src/upgrader.js:397:36)\n' +
|
|
403
|
+
* ' at Upgrader.upgradeOutbound (/home/lion/Code/eth2.0/lodestar/node_modules/libp2p/src/upgrader.js:176:11)\n' +
|
|
404
|
+
* ' at ClassIsWrapper.dial (/home/lion/Code/eth2.0/lodestar/node_modules/libp2p-tcp/src/index.js:49:18)'
|
|
405
|
+
* }
|
|
406
|
+
* ```
|
|
407
|
+
*
|
|
408
|
+
* Tracking issue https://github.com/libp2p/js-libp2p/issues/996
|
|
409
|
+
*/
|
|
410
|
+
function formatLibp2pDialError(e: Error): void {
|
|
411
|
+
const errorMessage = e.message.trim();
|
|
412
|
+
const newlineIndex = errorMessage.indexOf('\n');
|
|
413
|
+
e.message = newlineIndex !== -1 ? errorMessage.slice(0, newlineIndex) : errorMessage;
|
|
414
|
+
|
|
415
|
+
if (
|
|
416
|
+
e.message.includes('The operation was aborted') ||
|
|
417
|
+
e.message.includes('stream ended before 1 bytes became available') ||
|
|
418
|
+
e.message.includes('The operation was aborted')
|
|
419
|
+
) {
|
|
420
|
+
e.stack = undefined;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Rationale is that if it was good enough for them, then it should be good enough for us.
|
|
4
4
|
* https://github.com/ChainSafe/lodestar
|
|
5
5
|
*/
|
|
6
|
+
import { PeerErrorSeverity } from '@aztec/circuit-types';
|
|
7
|
+
|
|
6
8
|
import { type PeerId } from '@libp2p/interface';
|
|
7
9
|
|
|
8
10
|
import { type PeerManager } from '../../peer_manager.js';
|
|
9
|
-
import { PeerErrorSeverity } from '../../peer_scoring.js';
|
|
10
11
|
import { type ReqRespSubProtocol, type ReqRespSubProtocolRateLimits } from '../interface.js';
|
|
11
12
|
import { DEFAULT_RATE_LIMITS } from './rate_limits.js';
|
|
12
13
|
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
// @attribution: lodestar impl for inspiration
|
|
2
|
+
import { PeerErrorSeverity } from '@aztec/circuit-types';
|
|
2
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
3
|
-
import {
|
|
4
|
+
import { executeTimeout } from '@aztec/foundation/timer';
|
|
4
5
|
|
|
5
6
|
import { type IncomingStreamData, type PeerId, type Stream } from '@libp2p/interface';
|
|
6
7
|
import { pipe } from 'it-pipe';
|
|
7
8
|
import { type Libp2p } from 'libp2p';
|
|
8
|
-
import { compressSync, uncompressSync } from 'snappy';
|
|
9
9
|
import { type Uint8ArrayList } from 'uint8arraylist';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
CollectiveReqRespTimeoutError,
|
|
13
|
+
IndividualReqRespTimeoutError,
|
|
14
|
+
InvalidResponseError,
|
|
15
|
+
} from '../../errors/reqresp.error.js';
|
|
16
|
+
import { SnappyTransform } from '../encoding.js';
|
|
12
17
|
import { type PeerManager } from '../peer_manager.js';
|
|
13
|
-
import { PeerErrorSeverity } from '../peer_scoring.js';
|
|
14
18
|
import { type P2PReqRespConfig } from './config.js';
|
|
15
19
|
import {
|
|
16
20
|
DEFAULT_SUB_PROTOCOL_HANDLERS,
|
|
@@ -49,6 +53,8 @@ export class ReqResp {
|
|
|
49
53
|
|
|
50
54
|
private rateLimiter: RequestResponseRateLimiter;
|
|
51
55
|
|
|
56
|
+
private snappyTransform: SnappyTransform;
|
|
57
|
+
|
|
52
58
|
constructor(config: P2PReqRespConfig, protected readonly libp2p: Libp2p, private peerManager: PeerManager) {
|
|
53
59
|
this.logger = createLogger('p2p:reqresp');
|
|
54
60
|
|
|
@@ -56,6 +62,7 @@ export class ReqResp {
|
|
|
56
62
|
this.individualRequestTimeoutMs = config.individualRequestTimeoutMs;
|
|
57
63
|
|
|
58
64
|
this.rateLimiter = new RequestResponseRateLimiter(peerManager);
|
|
65
|
+
this.snappyTransform = new SnappyTransform();
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
/**
|
|
@@ -143,8 +150,7 @@ export class ReqResp {
|
|
|
143
150
|
// The response validator handles peer punishment within
|
|
144
151
|
const isValid = await responseValidator(request, object, peer);
|
|
145
152
|
if (!isValid) {
|
|
146
|
-
|
|
147
|
-
return undefined;
|
|
153
|
+
throw new InvalidResponseError();
|
|
148
154
|
}
|
|
149
155
|
return object;
|
|
150
156
|
}
|
|
@@ -153,13 +159,13 @@ export class ReqResp {
|
|
|
153
159
|
};
|
|
154
160
|
|
|
155
161
|
try {
|
|
156
|
-
return await
|
|
162
|
+
return await executeTimeout<InstanceType<SubProtocolMap[SubProtocol]['response']> | undefined>(
|
|
157
163
|
requestFunction,
|
|
158
164
|
this.overallRequestTimeoutMs,
|
|
159
165
|
() => new CollectiveReqRespTimeoutError(),
|
|
160
166
|
);
|
|
161
167
|
} catch (e: any) {
|
|
162
|
-
this.logger.
|
|
168
|
+
this.logger.debug(`${e.message} | subProtocol: ${subProtocol}`);
|
|
163
169
|
return undefined;
|
|
164
170
|
}
|
|
165
171
|
}
|
|
@@ -199,19 +205,15 @@ export class ReqResp {
|
|
|
199
205
|
this.logger.trace(`Stream opened with ${peerId.toString()} for ${subProtocol}`);
|
|
200
206
|
|
|
201
207
|
// Open the stream with a timeout
|
|
202
|
-
const result = await
|
|
203
|
-
(): Promise<Buffer> => pipe([payload], stream!, this.readMessage),
|
|
208
|
+
const result = await executeTimeout<Buffer>(
|
|
209
|
+
(): Promise<Buffer> => pipe([payload], stream!, this.readMessage.bind(this)),
|
|
204
210
|
this.individualRequestTimeoutMs,
|
|
205
|
-
() => new
|
|
211
|
+
() => new IndividualReqRespTimeoutError(),
|
|
206
212
|
);
|
|
207
213
|
|
|
208
|
-
await stream.close();
|
|
209
|
-
this.logger.trace(`Stream closed with ${peerId.toString()} for ${subProtocol}`);
|
|
210
|
-
|
|
211
214
|
return result;
|
|
212
215
|
} catch (e: any) {
|
|
213
|
-
this.
|
|
214
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
|
|
216
|
+
this.handleResponseError(e, peerId, subProtocol);
|
|
215
217
|
} finally {
|
|
216
218
|
if (stream) {
|
|
217
219
|
try {
|
|
@@ -224,7 +226,70 @@ export class ReqResp {
|
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
228
|
}
|
|
227
|
-
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Handle a response error
|
|
233
|
+
*
|
|
234
|
+
* ReqResp errors are punished differently depending on the severity of the offense
|
|
235
|
+
*
|
|
236
|
+
* @param e - The error
|
|
237
|
+
* @param peerId - The peer id
|
|
238
|
+
* @param subProtocol - The sub protocol
|
|
239
|
+
* @returns If the error is non pubishable, then undefined is returned, otherwise the peer is penalized
|
|
240
|
+
*/
|
|
241
|
+
private handleResponseError(e: any, peerId: PeerId, subProtocol: ReqRespSubProtocol): void {
|
|
242
|
+
const severity = this.categorizeError(e, peerId, subProtocol);
|
|
243
|
+
if (severity) {
|
|
244
|
+
this.peerManager.penalizePeer(peerId, severity);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Categorize the error and log it.
|
|
250
|
+
*/
|
|
251
|
+
private categorizeError(e: any, peerId: PeerId, subProtocol: ReqRespSubProtocol): PeerErrorSeverity | undefined {
|
|
252
|
+
// Non pubishable errors
|
|
253
|
+
// We do not punish a collective timeout, as the node triggers this interupt, independent of the peer's behaviour
|
|
254
|
+
const logTags = {
|
|
255
|
+
peerId: peerId.toString(),
|
|
256
|
+
subProtocol,
|
|
257
|
+
};
|
|
258
|
+
if (e instanceof CollectiveReqRespTimeoutError || e instanceof InvalidResponseError) {
|
|
259
|
+
this.logger.debug(
|
|
260
|
+
`Non-punishable error: ${e.message} | peerId: ${peerId.toString()} | subProtocol: ${subProtocol}`,
|
|
261
|
+
logTags,
|
|
262
|
+
);
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Pubishable errors
|
|
267
|
+
// Connection reset errors in the networking stack are punished with high severity
|
|
268
|
+
// it just signals an unreliable peer
|
|
269
|
+
// We assume that the requesting node has a functioning networking stack.
|
|
270
|
+
if (e?.code === 'ECONNRESET' || e?.code === 'EPIPE') {
|
|
271
|
+
this.logger.debug(`Connection reset: ${peerId.toString()}`, logTags);
|
|
272
|
+
return PeerErrorSeverity.HighToleranceError;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (e?.code === 'ECONNREFUSED') {
|
|
276
|
+
this.logger.debug(`Connection refused: ${peerId.toString()}`, logTags);
|
|
277
|
+
return PeerErrorSeverity.HighToleranceError;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Timeout errors are punished with high tolerance, they can be due to a geogrpahically far away peer or an
|
|
281
|
+
// overloaded peer
|
|
282
|
+
if (e instanceof IndividualReqRespTimeoutError) {
|
|
283
|
+
this.logger.debug(
|
|
284
|
+
`Timeout error: ${e.message} | peerId: ${peerId.toString()} | subProtocol: ${subProtocol}`,
|
|
285
|
+
logTags,
|
|
286
|
+
);
|
|
287
|
+
return PeerErrorSeverity.HighToleranceError;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Catch all error
|
|
291
|
+
this.logger.error(`Unexpected error sending request to peer`, e, logTags);
|
|
292
|
+
return PeerErrorSeverity.HighToleranceError;
|
|
228
293
|
}
|
|
229
294
|
|
|
230
295
|
/**
|
|
@@ -235,8 +300,8 @@ export class ReqResp {
|
|
|
235
300
|
for await (const chunk of source) {
|
|
236
301
|
chunks.push(chunk.subarray());
|
|
237
302
|
}
|
|
238
|
-
const messageData =
|
|
239
|
-
return
|
|
303
|
+
const messageData = Buffer.concat(chunks);
|
|
304
|
+
return this.snappyTransform.inboundTransformNoTopic(messageData);
|
|
240
305
|
}
|
|
241
306
|
|
|
242
307
|
/**
|
|
@@ -266,6 +331,7 @@ export class ReqResp {
|
|
|
266
331
|
}
|
|
267
332
|
|
|
268
333
|
const handler = this.subProtocolHandlers[protocol];
|
|
334
|
+
const transform = this.snappyTransform;
|
|
269
335
|
|
|
270
336
|
try {
|
|
271
337
|
await pipe(
|
|
@@ -274,7 +340,7 @@ export class ReqResp {
|
|
|
274
340
|
for await (const chunkList of source) {
|
|
275
341
|
const msg = Buffer.from(chunkList.subarray());
|
|
276
342
|
const response = await handler(msg);
|
|
277
|
-
yield new Uint8Array(
|
|
343
|
+
yield new Uint8Array(transform.outboundTransformNoTopic(response));
|
|
278
344
|
}
|
|
279
345
|
},
|
|
280
346
|
stream,
|