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 }