Coverage Summary for Class: PeersInformation (co.rsk.net.sync)
Class |
Class, %
|
Method, %
|
Line, %
|
PeersInformation |
0%
(0/1)
|
0%
(0/28)
|
0%
(0/78)
|
1 package co.rsk.net.sync;
2
3 import co.rsk.core.BlockDifficulty;
4 import co.rsk.core.bc.BlockChainStatus;
5 import co.rsk.net.Peer;
6 import co.rsk.net.NodeID;
7 import co.rsk.net.Status;
8 import co.rsk.scoring.EventType;
9 import co.rsk.scoring.PeerScoringManager;
10 import co.rsk.util.MaxSizeHashMap;
11 import org.ethereum.core.Blockchain;
12 import org.ethereum.net.server.ChannelManager;
13 import org.slf4j.Logger;
14 import org.slf4j.LoggerFactory;
15
16 import java.time.Instant;
17 import java.util.*;
18 import java.util.function.Predicate;
19 import java.util.stream.Collectors;
20 import java.util.stream.Stream;
21
22 /**
23 * This is mostly a workaround because SyncProcessor needs to access Peer instances.
24 * TODO(mc) remove this after the logical node abstraction is created, since it will wrap
25 * things such as the underlying communication channel.
26 */
27 public class PeersInformation {
28
29 private static final int TIME_LIMIT_FAILURE_RECORD = 600;
30 private static final int MAX_SIZE_FAILURE_RECORDS = 10;
31 private static final Logger logger = LoggerFactory.getLogger(PeersInformation.class);
32
33 private final ChannelManager channelManager;
34 private final SyncConfiguration syncConfiguration;
35 private final Blockchain blockchain;
36 private final Map<NodeID, Instant> failedPeers;
37 private final PeerScoringManager peerScoringManager;
38 private final Comparator<Map.Entry<Peer, SyncPeerStatus>> peerComparator;
39 private Map<Peer, SyncPeerStatus> peerStatuses = new HashMap<>();
40
41 public PeersInformation(ChannelManager channelManager,
42 SyncConfiguration syncConfiguration,
43 Blockchain blockchain,
44 PeerScoringManager peerScoringManager){
45 this.channelManager = channelManager;
46 this.syncConfiguration = syncConfiguration;
47 this.blockchain = blockchain;
48 this.failedPeers = new MaxSizeHashMap<>(MAX_SIZE_FAILURE_RECORDS, true);
49 this.peerScoringManager = peerScoringManager;
50 this.peerComparator = ((Comparator<Map.Entry<Peer, SyncPeerStatus>>) this::comparePeerFailInstant)
51 // TODO reenable when unprocessable blocks stop being marked as invalid blocks
52 // .thenComparing(this::comparePeerScoring)
53 .thenComparing(this::comparePeerTotalDifficulty);
54 }
55
56 public void reportEventWithLog(String message, NodeID peerId, EventType eventType, Object... arguments) {
57 logger.trace(message, arguments);
58 peerScoringManager.recordEvent(peerId, null, eventType);
59 }
60
61 public void reportEvent(NodeID peerId, EventType eventType) {
62 peerScoringManager.recordEvent(peerId, null, eventType);
63 }
64
65 public void reportErrorEvent(NodeID peerId, String message, EventType eventType, Object... arguments) {
66 logger.trace(message, arguments);
67 failedPeers.put(peerId, Instant.now());
68 peerScoringManager.recordEvent(peerId, null, eventType);
69 }
70
71 private int getScore(NodeID peerId) {
72 return peerScoringManager.getPeerScoring(peerId).getScore();
73 }
74
75 public int count() {
76 return peerStatuses.size();
77 }
78
79 public int countIf(Predicate<SyncPeerStatus> predicate) {
80 long count = peerStatuses.values().stream()
81 .filter(predicate)
82 .count();
83 return Math.toIntExact(count);
84 }
85
86 public SyncPeerStatus getOrRegisterPeer(Peer peer) {
87 SyncPeerStatus peerStatus = this.peerStatuses.get(peer);
88
89 if (peerStatus != null && peerNotExpired(peerStatus)) {
90 return peerStatus;
91 }
92
93 return this.registerPeer(peer);
94 }
95
96 public SyncPeerStatus getPeer(Peer peer) {
97 return this.peerStatuses.get(peer);
98 }
99
100 public Optional<Peer> getBestPeer() {
101 return getCandidatesStream()
102 .max(this.peerComparator)
103 .map(Map.Entry::getKey);
104 }
105
106 private Stream<Map.Entry<Peer, SyncPeerStatus>> getCandidatesStream(){
107 Collection<Peer> activeNodes = channelManager.getActivePeers();
108
109 return peerStatuses.entrySet().stream()
110 .filter(e -> peerNotExpired(e.getValue()))
111 .filter(e -> activeNodes.contains(e.getKey()))
112 .filter(e -> peerScoringManager.hasGoodReputation(e.getKey().getPeerNodeID()))
113 .filter(e -> hasLowerDifficulty(e.getKey()));
114 }
115
116 private boolean hasLowerDifficulty(Peer peer) {
117 Status status = getPeer(peer).getStatus();
118 if (status == null) {
119 return false;
120 }
121
122 boolean hasTotalDifficulty = status.getTotalDifficulty() != null;
123 BlockChainStatus nodeStatus = blockchain.getStatus();
124 // this works only for testing purposes, real status without difficulty don't reach this far
125 return (hasTotalDifficulty && nodeStatus.hasLowerTotalDifficultyThan(status)) ||
126 (!hasTotalDifficulty && nodeStatus.getBestBlockNumber() < status.getBestBlockNumber());
127 }
128
129 public List<Peer> getPeerCandidates() {
130 return getCandidatesStream()
131 .map(Map.Entry::getKey)
132 .collect(Collectors.toList());
133 }
134
135 public Set<NodeID> knownNodeIds() {
136 return peerStatuses.keySet().stream()
137 .map(Peer::getPeerNodeID)
138 .collect(Collectors.toSet());
139 }
140
141 public SyncPeerStatus registerPeer(Peer peer) {
142 SyncPeerStatus peerStatus = new SyncPeerStatus();
143 peerStatuses.put(peer, peerStatus);
144 return peerStatus;
145 }
146
147 public void cleanExpired() {
148 peerStatuses = peerStatuses.entrySet().stream()
149 .filter(e -> peerNotExpired(e.getValue()))
150 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
151 }
152
153 private boolean peerNotExpired(SyncPeerStatus peer) {
154 return !peer.isExpired(syncConfiguration.getExpirationTimePeerStatus());
155 }
156
157 private int comparePeerFailInstant(
158 Map.Entry<Peer, SyncPeerStatus> entry,
159 Map.Entry<Peer, SyncPeerStatus> other) {
160 Instant failInstant = getFailInstant(entry.getKey());
161 Instant otherFailInstant = getFailInstant(other.getKey());
162 // note that this is in inverse order
163 return otherFailInstant.compareTo(failInstant);
164 }
165
166 private int comparePeerScoring(
167 Map.Entry<NodeID, SyncPeerStatus> entry,
168 Map.Entry<NodeID, SyncPeerStatus> other) {
169 int score = getScore(entry.getKey());
170 int scoreOther = getScore(other.getKey());
171 // Treats all non-negative scores the same for calculating the best peer
172 if (score >= 0 && scoreOther >= 0) {
173 return 0;
174 }
175
176 return Integer.compare(score, scoreOther);
177 }
178
179 private int comparePeerTotalDifficulty(
180 Map.Entry<Peer, SyncPeerStatus> entry,
181 Map.Entry<Peer, SyncPeerStatus> other) {
182 BlockDifficulty ttd = entry.getValue().getStatus().getTotalDifficulty();
183 BlockDifficulty otd = other.getValue().getStatus().getTotalDifficulty();
184
185 // status messages from outdated nodes might have null difficulties
186 if (ttd == null && otd == null) {
187 return 0;
188 }
189
190 if (ttd == null) {
191 return -1;
192 }
193
194 if (otd == null) {
195 return 1;
196 }
197
198 return ttd.compareTo(otd);
199 }
200
201 private Instant getFailInstant(Peer peer) {
202 Instant instant = failedPeers.get(peer.getPeerNodeID());
203 if (instant != null){
204 return instant;
205 }
206 return Instant.EPOCH;
207 }
208
209 public void clearOldFailedPeers() {
210 failedPeers.values().removeIf(Instant.now().minusSeconds(TIME_LIMIT_FAILURE_RECORD)::isAfter);
211 }
212 }