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 }