Coverage Summary for Class: PeerScoringManager (co.rsk.scoring)

Class Method, % Line, %
PeerScoringManager 5.3% (1/19) 1.1% (1/89)
PeerScoringManager$1 0% (0/2) 0% (0/2)
PeerScoringManager$MockitoMock$1606738340
PeerScoringManager$MockitoMock$1606738340$auxiliary$01pADsQI
PeerScoringManager$MockitoMock$1606738340$auxiliary$4Xdn68VQ
PeerScoringManager$MockitoMock$1606738340$auxiliary$7Za6Kljp
PeerScoringManager$MockitoMock$1606738340$auxiliary$afAFNYGn
PeerScoringManager$MockitoMock$1606738340$auxiliary$AjkYZfCP
PeerScoringManager$MockitoMock$1606738340$auxiliary$AYBPTEWM
PeerScoringManager$MockitoMock$1606738340$auxiliary$AZm8yE8j
PeerScoringManager$MockitoMock$1606738340$auxiliary$FZEPi9eY
PeerScoringManager$MockitoMock$1606738340$auxiliary$GO5ynVpe
PeerScoringManager$MockitoMock$1606738340$auxiliary$NjorDJ9b
PeerScoringManager$MockitoMock$1606738340$auxiliary$O52vMLuC
PeerScoringManager$MockitoMock$1606738340$auxiliary$R97kHC5i
PeerScoringManager$MockitoMock$1606738340$auxiliary$RZICydoe
PeerScoringManager$MockitoMock$1606738340$auxiliary$V9HICfxZ
PeerScoringManager$MockitoMock$1606738340$auxiliary$WIpVEhCT
PeerScoringManager$MockitoMock$1606738340$auxiliary$yFH9hNfs
Total 4.8% (1/21) 1.1% (1/91)


1 package co.rsk.scoring; 2  3 import co.rsk.net.NodeID; 4 import com.google.common.annotations.VisibleForTesting; 5 import org.ethereum.util.ByteUtil; 6 import org.slf4j.Logger; 7 import org.slf4j.LoggerFactory; 8  9 import javax.annotation.concurrent.GuardedBy; 10 import java.net.InetAddress; 11 import java.util.*; 12 import java.util.stream.Collectors; 13  14 /** 15  * PeerScoringManager keeps list of nodes and addresses scoring 16  * Records events by node id and address 17  * Calculates good reputation by node id and address 18  * Starts punishments when the good reputation is lost 19  * Alsa keeps a list of banned addresses and blocks 20  * <p> 21  * Created by ajlopez on 28/06/2017. 22  */ 23 public class PeerScoringManager { 24  private final PeerScoring.Factory peerScoringFactory; 25  private final ScoringCalculator scoringCalculator; 26  private final PunishmentCalculator nodePunishmentCalculator; 27  private final PunishmentCalculator ipPunishmentCalculator; 28  29  private final Object accessLock = new Object(); 30  31  private final InetAddressTable addressTable = new InetAddressTable(); 32  33  private static final Logger logger = LoggerFactory.getLogger("peerScoring"); 34  35  @GuardedBy("accessLock") 36  private final Map<NodeID, PeerScoring> peersByNodeID; 37  38  @GuardedBy("accessLock") 39  private final Map<InetAddress, PeerScoring> peersByAddress; 40  41  /** 42  * Creates and initialize the scoring manager 43  * usually only one object per running node 44  * 45  * @param peerScoringFactory creates empty peer scorings 46  * @param nodePeersSize maximum number of nodes to keep 47  * @param nodeParameters nodes punishment parameters (@see PunishmentParameters) 48  * @param ipParameters address punishment parameters 49  */ 50  public PeerScoringManager( 51  PeerScoring.Factory peerScoringFactory, 52  int nodePeersSize, 53  PunishmentParameters nodeParameters, 54  PunishmentParameters ipParameters) { 55  this.peerScoringFactory = peerScoringFactory; 56  this.scoringCalculator = new ScoringCalculator(); 57  this.nodePunishmentCalculator = new PunishmentCalculator(nodeParameters); 58  this.ipPunishmentCalculator = new PunishmentCalculator(ipParameters); 59  60  this.peersByNodeID = new LinkedHashMap<NodeID, PeerScoring>(nodePeersSize, 0.75f, true) { 61  @Override 62  protected boolean removeEldestEntry(Map.Entry<NodeID, PeerScoring> eldest) { 63  return size() > nodePeersSize; 64  } 65  }; 66  67  this.peersByAddress = new HashMap<>(); 68  } 69  70  /** 71  * Record the event, given the node id and/or the network address 72  * 73  * Usually we collected the events TWICE, if possible: by node id and by address. 74  * In some events we don't have the node id, yet. The rationale to have both, is to collect events for the 75  * same node_id, but maybe with different address along the time. Or same address with different node id. 76  * 77  * @param id node id or null 78  * @param address address or null 79  * @param event event type (@see EventType) 80  */ 81  public void recordEvent(NodeID id, InetAddress address, EventType event) { 82  //todo(techdebt) this method encourages null params, this is not desirable 83  synchronized (accessLock) { 84  if (id != null) { 85  PeerScoring scoring = peersByNodeID.computeIfAbsent(id, k -> peerScoringFactory.newInstance()); 86  recordEventAndStartPunishment(scoring, event, this.nodePunishmentCalculator, id); 87  } 88  89  if (address != null) { 90  PeerScoring scoring = peersByAddress.computeIfAbsent(address, k -> peerScoringFactory.newInstance()); 91  recordEventAndStartPunishment(scoring, event, this.ipPunishmentCalculator, id); 92  } 93  94  logger.debug("Recorded {}. {}, Address: {}", event, nodeIdForLog(id), addressForLog(address)); 95  } 96  } 97  98  /** 99  * Returns if the given node id has good reputation 100  * 101  * @param id the node id 102  * @return <tt>true</tt> if the node has good reputation 103  */ 104  public boolean hasGoodReputation(NodeID id) { 105  synchronized (accessLock) { 106  return this.getPeerScoring(id).hasGoodReputation(); 107  } 108  } 109  110  /** 111  * Returns if the given networkaddress has good reputation 112  * 113  * @param address the network address 114  * @return <tt>true</tt> if the address has good reputation 115  */ 116  public boolean hasGoodReputation(InetAddress address) 117  { 118  if (this.addressTable.contains(address)) { 119  return false; 120  } 121  122  synchronized (accessLock) { 123  return this.getPeerScoring(address).hasGoodReputation(); 124  } 125  } 126  127  /** 128  * Adds a network address to the set of banned addresses 129  * 130  * @param address the address to be banned 131  */ 132  public void banAddress(InetAddress address) { 133  this.addressTable.addAddress(address); 134  } 135  136  /** 137  * Adds a network address to the set of banned addresses 138  * The address is represented in an string 139  * If it is a block, it has a mask 140  * 141  * @param address the address or address block to be banned 142  */ 143  public void banAddress(String address) throws InvalidInetAddressException { 144  boolean isAddressBlock = InetAddressUtils.hasMask(address); 145  if (isAddressBlock) { 146  this.banAddressBlock(InetAddressUtils.parse(address)); 147  } else { 148  this.banAddress(InetAddressUtils.getAddressForBan(address)); 149  } 150  151  String addressOrAddressBlock = isAddressBlock ? "block" : ""; 152  logger.debug("Banned address {} {}", addressOrAddressBlock ,address); 153  } 154  155  /** 156  * Removes a network address from the set of banned addresses 157  * 158  * @param address the address to be removed 159  */ 160  public void unbanAddress(InetAddress address) { 161  this.addressTable.removeAddress(address); 162  } 163  164  /** 165  * Removes a network address from the set of banned addresses 166  * The address is represented in an string 167  * If it is a block, it has a mask 168  * 169  * @param address the address or address block to be removed 170  */ 171  public void unbanAddress(String address) throws InvalidInetAddressException { 172  boolean isAddressBlock = InetAddressUtils.hasMask(address); 173  if (isAddressBlock) { 174  this.unbanAddressBlock(InetAddressUtils.parse(address)); 175  } else { 176  this.unbanAddress(InetAddressUtils.getAddressForBan(address)); 177  } 178  179  String addressOrAddressBlock = isAddressBlock ? "block" : ""; 180  logger.debug("Unbanned address {} {}", addressOrAddressBlock ,address); 181  } 182  183  /** 184  * Adds a network address block to the set of banned blocks 185  * 186  * @param addressBlock the address block to be banned 187  */ 188  public void banAddressBlock(InetAddressBlock addressBlock) { 189  this.addressTable.addAddressBlock(addressBlock); 190  } 191  192  /** 193  * Removes a network address block from the set of banned blocks 194  * 195  * @param addressBlock the address block to be removed 196  */ 197  public void unbanAddressBlock(InetAddressBlock addressBlock) { 198  this.addressTable.removeAddressBlock(addressBlock); 199  } 200  201  /** 202  * Returns the list of peer scoring information 203  * It contains the information recorded by node id and by address 204  * 205  * @return the list of peer scoring information 206  */ 207  public List<PeerScoringInformation> getPeersInformation() { 208  synchronized (accessLock) { 209  List<PeerScoringInformation> list = new ArrayList<>(this.peersByNodeID.size() + this.peersByAddress.size()); 210  211  list.addAll(this.peersByNodeID.entrySet().stream().map(entry -> PeerScoringInformation.buildByScoring(entry.getValue(), ByteUtil.toHexString(entry.getKey().getID()).substring(0, 8), "node")).collect(Collectors.toList())); 212  list.addAll(this.peersByAddress.entrySet().stream().map(entry -> PeerScoringInformation.buildByScoring(entry.getValue(), entry.getKey().getHostAddress(), "address")).collect(Collectors.toList())); 213  214  return list; 215  } 216  } 217  218  /** 219  * Returns the list of banned addresses, represented by a textual description 220  * The list includes the banned addresses and the banned blocks 221  * 222  * @return a list of strings describing the banned addresses and blocks 223  */ 224  public List<String> getBannedAddresses() { 225  List<String> list = new ArrayList<>(); 226  227  list.addAll(this.addressTable.getAddressList().stream().map(entry -> entry.getHostAddress()).collect(Collectors.toList())); 228  list.addAll(this.addressTable.getAddressBlockList().stream().map(entry -> entry.getDescription()).collect(Collectors.toList())); 229  230  return list; 231  } 232  233  @VisibleForTesting 234  public boolean isEmpty() { 235  synchronized (accessLock) { 236  return this.peersByAddress.isEmpty() && this.peersByNodeID.isEmpty(); 237  } 238  } 239  240  @VisibleForTesting 241  public PeerScoring getPeerScoring(NodeID id) { 242  synchronized (accessLock) { 243  if (peersByNodeID.containsKey(id)) { 244  return peersByNodeID.get(id); 245  } 246  247  return peerScoringFactory.newInstance(); 248  } 249  } 250  251  @VisibleForTesting 252  public PeerScoring getPeerScoring(InetAddress address) { 253  synchronized (accessLock) { 254  if (peersByAddress.containsKey(address)) { 255  return peersByAddress.get(address); 256  } 257  258  return peerScoringFactory.newInstance(); 259  } 260  } 261  262  /** 263  * Records an event and starts punishment if needed 264  * @param peerScoring the peer scoring 265  * @param event an event type 266  * @param punishmentCalculator calculator to use 267  * @param nodeID a node id 268  */ 269  private void recordEventAndStartPunishment(PeerScoring peerScoring, EventType event, PunishmentCalculator punishmentCalculator, NodeID nodeID) { 270  peerScoring.recordEvent(event); 271  272  boolean shouldStartPunishment = !scoringCalculator.hasGoodReputation(peerScoring) && peerScoring.hasGoodReputation(); 273  if (shouldStartPunishment) { 274  long punishmentTime = punishmentCalculator.calculate(peerScoring.getPunishmentCounter(), peerScoring.getScore()); 275  peerScoring.startPunishment(punishmentTime); 276  277  String nodeIDFormated = nodeIdForLog(nodeID); 278  logger.debug("NodeID {} has been punished for {} milliseconds. Last event {}", nodeIDFormated, punishmentTime, event); 279  logger.debug("{}", PeerScoringInformation.buildByScoring(peerScoring, nodeIDFormated, "")); 280  } 281  } 282  283  private String nodeIdForLog(NodeID id) { 284  if(id == null) { 285  return "NO_NODE_ID"; 286  } 287  return id.toString(); 288  } 289  290  private String addressForLog(InetAddress address) { 291  if(address == null) { 292  return "NO_ADDRESS"; 293  } 294  return address.getHostAddress(); 295  } 296 }