Coverage Summary for Class: PeerScoring (co.rsk.scoring)
Class |
Method, %
|
Line, %
|
PeerScoring |
0%
(0/13)
|
0%
(0/70)
|
PeerScoring$1 |
0%
(0/1)
|
0%
(0/1)
|
PeerScoring$Factory |
Total |
0%
(0/14)
|
0%
(0/71)
|
1 package co.rsk.scoring;
2
3 import com.google.common.annotations.VisibleForTesting;
4
5 import java.util.concurrent.locks.ReadWriteLock;
6 import java.util.concurrent.locks.ReentrantReadWriteLock;
7
8 /**
9 * PeerScoring records the events associated with a peer
10 * identified by node id or IP address (@see PeerScoringManager)
11 * An integer score value is calculated based on recorded events.
12 * Also, a good reputation flag is calculated.
13 * The number of punishment is recorded, as well the initial punishment time and its duration.
14 * When the punishment expires, the good reputation is restored and most counters are reset to zero
15 * <p>
16 * Created by ajlopez on 27/06/2017.
17 */
18 public class PeerScoring {
19 private final boolean punishmentEnabled;
20
21 private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
22 private int[] counters = new int[EventType.values().length];
23 private boolean goodReputation = true;
24 private long timeLostGoodReputation;
25 private long punishmentTime;
26 private int punishmentCounter;
27 private int score;
28
29 public PeerScoring() {
30 this(true);
31 }
32
33 public PeerScoring(boolean punishmentEnabled) {
34 this.punishmentEnabled = punishmentEnabled;
35 }
36
37 /**
38 * Records an event.
39 * Current implementation has a counter by event type.
40 * The score is incremented or decremented, acoording to the kind of the event.
41 * Some negative events alters the score to a negative level, without
42 * taking into account its previous positive value
43 *
44 * @param evt An event type @see EventType
45 */
46 public void recordEvent(EventType evt) {
47 try {
48 rwlock.writeLock().lock();
49
50 counters[evt.ordinal()]++;
51
52 switch (evt) {
53 case INVALID_NETWORK:
54 case INVALID_BLOCK:
55 case INVALID_TRANSACTION:
56 case INVALID_MESSAGE:
57 case INVALID_HEADER:
58 // TODO(lsebrie): review how to handle timeouts properly
59 //case TIMEOUT_MESSAGE:
60 if (score > 0) {
61 score = 0;
62 }
63 score--;
64 break;
65 case UNEXPECTED_MESSAGE:
66 case FAILED_HANDSHAKE:
67 case SUCCESSFUL_HANDSHAKE:
68 case REPEATED_MESSAGE:
69 break;
70
71 default:
72 if (score >= 0) {
73 score++;
74 }
75 break;
76 }
77 } finally {
78 rwlock.writeLock().unlock();
79 }
80 }
81
82 /**
83 * Returns the current computed score.
84 * The score is calculated based on previous event recording.
85 *
86 * @return An integer number, the level of score. Positive value is associated
87 * with a good reputation. Negative values indicates a possible punishment.
88 */
89 public int getScore() {
90 try {
91 rwlock.readLock().lock();
92 return score;
93 } finally {
94 rwlock.readLock().unlock();
95 }
96 }
97
98 /**
99 * Returns the count of events given a event type.
100 *
101 * @param evt Event Type (@see EventType)
102 *
103 * @return The count of events of the specefied type
104 */
105 public int getEventCounter(EventType evt) {
106 try {
107 rwlock.readLock().lock();
108 return counters[evt.ordinal()];
109 } finally {
110 rwlock.readLock().unlock();
111 }
112 }
113
114 /**
115 * Returns the count of all events
116 *
117 * @return The total count of events
118 */
119 public int getTotalEventCounter() {
120 try {
121 rwlock.readLock().lock();
122 int counter = 0;
123
124 for (int i = 0; i < counters.length; i++) {
125 counter += counters[i];
126 }
127
128 return counter;
129 } finally {
130 rwlock.readLock().unlock();
131 }
132 }
133
134 /**
135 * Returns <tt>true</tt> if there is no event recorded yet.
136 *
137 * @return <tt>true</tt> if there is no event
138 */
139 public boolean isEmpty() {
140 try {
141 rwlock.readLock().lock();
142 return getTotalEventCounter() == 0;
143 } finally {
144 rwlock.readLock().unlock();
145 }
146 }
147
148 /**
149 * Returns <tt>true</tt> if the peer has good reputation.
150 * Returns <tt>false</tt> if not.
151 *
152 * @return <tt>true</tt> or <tt>false</tt>
153 */
154 public boolean hasGoodReputation() {
155 try {
156 rwlock.writeLock().lock();
157 if (this.goodReputation) {
158 return true;
159 }
160
161 if (this.punishmentTime > 0 && this.timeLostGoodReputation > 0
162 && this.punishmentTime + this.timeLostGoodReputation <= System.currentTimeMillis()) {
163 this.endPunishment();
164 }
165
166 return this.goodReputation;
167 } finally {
168 rwlock.writeLock().unlock();
169 }
170 }
171
172 /**
173 * Starts the punishment, with specified duration
174 * Changes the reputation to not good
175 * Increments the punishment counter
176 *
177 * @param expirationTime punishment duration in milliseconds
178 */
179 public void startPunishment(long expirationTime) {
180 if (!punishmentEnabled) {
181 return;
182 }
183
184 try {
185 rwlock.writeLock().lock();
186 this.goodReputation = false;
187 this.punishmentTime = expirationTime;
188 this.punishmentCounter++;
189 this.timeLostGoodReputation = System.currentTimeMillis();
190 } finally {
191 rwlock.writeLock().unlock();
192 }
193 }
194
195 /**
196 * Ends the punishment
197 * Clear the event counters
198 *
199 */
200 private void endPunishment() {
201 if (!punishmentEnabled) {
202 return;
203 }
204
205 //Check locks before doing this function public
206 for (int i = 0; i < counters.length; i++) {
207 this.counters[i] = 0;
208 }
209 this.goodReputation = true;
210 this.timeLostGoodReputation = 0;
211 this.score = 0;
212 }
213
214 @VisibleForTesting
215 public long getPunishmentTime() {
216 return this.punishmentTime;
217 }
218
219 /**
220 * Returns the number of punishment suffered by this peer.
221 *
222 * @return the counter of punishments
223 */
224 public int getPunishmentCounter() {
225 return this.punishmentCounter;
226 }
227
228 @VisibleForTesting
229 public long getTimeLostGoodReputation() {
230 return this.timeLostGoodReputation;
231 }
232
233 public interface Factory {
234 PeerScoring newInstance();
235 }
236 }