Coverage Summary for Class: ChannelManagerImpl (org.ethereum.net.server)
Class |
Class, %
|
Method, %
|
Line, %
|
ChannelManagerImpl |
0%
(0/1)
|
0%
(0/38)
|
0%
(0/136)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package org.ethereum.net.server;
21
22 import co.rsk.config.RskSystemProperties;
23 import co.rsk.net.Peer;
24 import co.rsk.net.NodeID;
25 import co.rsk.net.Status;
26 import co.rsk.net.messages.*;
27 import co.rsk.scoring.InetAddressBlock;
28 import com.google.common.annotations.VisibleForTesting;
29 import org.ethereum.config.NodeFilter;
30 import org.ethereum.core.Block;
31 import org.ethereum.core.BlockIdentifier;
32 import org.ethereum.core.Transaction;
33 import org.ethereum.net.message.ReasonCode;
34 import org.ethereum.sync.SyncPool;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import javax.annotation.Nonnull;
39 import java.net.InetAddress;
40 import java.time.Duration;
41 import java.time.Instant;
42 import java.util.*;
43 import java.util.concurrent.*;
44 import java.util.stream.Collectors;
45
46 /**
47 * @author Roman Mandeleil
48 * @since 11.11.2014
49 */
50 public class ChannelManagerImpl implements ChannelManager {
51
52 private static final Logger logger = LoggerFactory.getLogger("net");
53
54 // If the inbound peer connection was dropped by us with a reason message
55 // then we ban that peer IP on any connections for some time to protect from
56 // too active peers
57 private static final Duration INBOUND_CONNECTION_BAN_TIMEOUT = Duration.ofSeconds(10);
58 private final Object activePeersLock = new Object();
59 private final Map<NodeID, Channel> activePeers;
60
61 // Using a concurrent list
62 // (the add and remove methods copy an internal array,
63 // but the iterator directly use the internal array)
64 private final List<Channel> newPeers;
65
66 private final ScheduledExecutorService mainWorker;
67 private final Map<InetAddress, Instant> disconnectionsTimeouts;
68 private final Object disconnectionTimeoutsLock = new Object();
69
70 private final SyncPool syncPool;
71 private final NodeFilter trustedPeers;
72 private final int maxActivePeers;
73 private final int maxConnectionsAllowed;
74 private final int networkCIDR;
75
76 public ChannelManagerImpl(RskSystemProperties config, SyncPool syncPool) {
77 this.mainWorker = Executors.newSingleThreadScheduledExecutor(target -> new Thread(target, "newPeersProcessor"));
78 this.syncPool = syncPool;
79 this.maxActivePeers = config.maxActivePeers();
80 this.trustedPeers = config.trustedPeers();
81 this.disconnectionsTimeouts = new HashMap<>();
82 this.activePeers = new ConcurrentHashMap<>();
83 this.newPeers = new CopyOnWriteArrayList<>();
84 this.maxConnectionsAllowed = config.maxConnectionsAllowed();
85 this.networkCIDR = config.networkCIDR();
86 }
87
88 @Override
89 public void start() {
90 mainWorker.scheduleWithFixedDelay(this::handleNewPeersAndDisconnections, 0, 1, TimeUnit.SECONDS);
91 }
92
93 @Override
94 public void stop() {
95 mainWorker.shutdown();
96 }
97
98 private void handleNewPeersAndDisconnections() {
99 this.tryProcessNewPeers();
100 this.cleanDisconnections();
101 }
102
103 @VisibleForTesting
104 public void tryProcessNewPeers() {
105 if (newPeers.isEmpty()) {
106 return;
107 }
108
109 try {
110 processNewPeers();
111 } catch (Exception e) {
112 logger.error("Error", e);
113 }
114 }
115
116 private void cleanDisconnections() {
117 synchronized (disconnectionTimeoutsLock) {
118 Instant now = Instant.now();
119 disconnectionsTimeouts.values().removeIf(v -> !isRecent(v, now));
120 }
121 }
122
123 private void processNewPeers() {
124 synchronized (newPeers) {
125 List<Channel> processedChannels = new ArrayList<>();
126 newPeers.stream().filter(Channel::isProtocolsInitialized).forEach(c -> processNewPeer(c, processedChannels));
127 newPeers.removeAll(processedChannels);
128 }
129 }
130
131 private void processNewPeer(Channel channel, List<Channel> processedChannels) {
132 ReasonCode reason = getNewPeerDisconnectionReason(channel);
133 if (reason != null) {
134 disconnect(channel, reason);
135 } else {
136 addToActives(channel);
137 }
138 processedChannels.add(channel);
139 }
140
141 private ReasonCode getNewPeerDisconnectionReason(Channel channel) {
142 if (activePeers.containsKey(channel.getNodeId())) {
143 return ReasonCode.DUPLICATE_PEER;
144 }
145
146 if (!channel.isActive() && activePeers.size() >= maxActivePeers && !trustedPeers.accept(channel.getNode())) {
147 return ReasonCode.TOO_MANY_PEERS;
148 }
149
150 return null;
151 }
152
153 private void disconnect(Channel peer, ReasonCode reason) {
154 logger.debug("Disconnecting peer with reason {} : {}", reason, peer);
155 peer.disconnect(reason);
156 synchronized (disconnectionTimeoutsLock) {
157 disconnectionsTimeouts.put(peer.getInetSocketAddress().getAddress(),
158 Instant.now().plus(INBOUND_CONNECTION_BAN_TIMEOUT));
159 }
160 }
161
162 public boolean isRecentlyDisconnected(InetAddress peerAddress) {
163 synchronized (disconnectionTimeoutsLock) {
164 return isRecent(disconnectionsTimeouts.getOrDefault(peerAddress, Instant.EPOCH), Instant.now());
165 }
166 }
167
168 private boolean isRecent(Instant disconnectionTimeout, Instant currentTime) {
169 return currentTime.isBefore(disconnectionTimeout);
170 }
171
172 private void addToActives(Channel peer) {
173 if (peer.isUsingNewProtocol() || peer.hasEthStatusSucceeded()) {
174 syncPool.add(peer);
175 synchronized (activePeersLock) {
176 activePeers.put(peer.getNodeId(), peer);
177 }
178 }
179 }
180
181 /**
182 * broadcastBlock Propagates a block message across active peers
183 *
184 * @param block new Block to be sent
185 * @return a set containing the ids of the peers that received the block.
186 */
187 @Nonnull
188 public Set<NodeID> broadcastBlock(@Nonnull final Block block) {
189
190 final Set<NodeID> nodesIdsBroadcastedTo = new HashSet<>();
191 final BlockIdentifier bi = new BlockIdentifier(block.getHash().getBytes(), block.getNumber());
192 final Message newBlock = new BlockMessage(block);
193 final Message newBlockHashes = new NewBlockHashesMessage(Arrays.asList(bi));
194 synchronized (activePeersLock) {
195 // Get a randomized list with all the peers that don't have the block yet.
196 activePeers.values().forEach(c -> logger.trace("RSK activePeers: {}", c));
197 List<Channel> peers = new ArrayList<>(activePeers.values());
198 Collections.shuffle(peers);
199
200 int sqrt = (int) Math.floor(Math.sqrt(peers.size()));
201 for (int i = 0; i < sqrt; i++) {
202 Channel peer = peers.get(i);
203 nodesIdsBroadcastedTo.add(peer.getNodeId());
204 logger.trace("RSK propagate: {}", peer);
205 peer.sendMessage(newBlock);
206 }
207 for (int i = sqrt; i < peers.size(); i++) {
208 Channel peer = peers.get(i);
209 logger.trace("RSK announce: {}", peer);
210 peer.sendMessage(newBlockHashes);
211 }
212 }
213
214 return nodesIdsBroadcastedTo;
215 }
216
217 @Nonnull
218 public Set<NodeID> broadcastBlockHash(@Nonnull final List<BlockIdentifier> identifiers, final Set<NodeID> targets) {
219 final Set<NodeID> nodesIdsBroadcastedTo = new HashSet<>();
220 final Message newBlockHash = new NewBlockHashesMessage(identifiers);
221
222 synchronized (activePeersLock) {
223 activePeers.values().forEach(c -> logger.trace("RSK activePeers: {}", c));
224
225 activePeers.values().stream()
226 .filter(p -> targets.contains(p.getNodeId()))
227 .forEach(peer -> {
228 logger.trace("RSK announce hash: {}", peer);
229 peer.sendMessage(newBlockHash);
230 });
231 }
232
233 return nodesIdsBroadcastedTo;
234 }
235
236 @Override
237 public int broadcastStatus(Status status) {
238 final Message message = new StatusMessage(status);
239 synchronized (activePeersLock) {
240 if (activePeers.isEmpty()) {
241 return 0;
242 }
243
244 int numberOfPeersToSendStatusTo = getNumberOfPeersToSendStatusTo(activePeers.size());
245 List<Channel> shuffledPeers = new ArrayList<>(activePeers.values());
246 Collections.shuffle(shuffledPeers);
247 shuffledPeers.stream()
248 .limit(numberOfPeersToSendStatusTo)
249 .forEach(c -> c.sendMessage(message));
250 return numberOfPeersToSendStatusTo;
251 }
252 }
253
254 @VisibleForTesting
255 int getNumberOfPeersToSendStatusTo(int peerCount) {
256 // Send to the sqrt of number of peers.
257 // Make sure the number is between 3 and 10 (unless there are less than 3 peers).
258 int peerCountSqrt = (int) Math.sqrt(peerCount);
259 return Math.min(10, Math.min(Math.max(3, peerCountSqrt), peerCount));
260 }
261
262 public void add(Channel peer) {
263 newPeers.add(peer);
264 }
265
266 public void notifyDisconnect(Channel channel) {
267 logger.debug("Peer {}: notifies about disconnect", channel.getPeerId());
268 channel.onDisconnect();
269 synchronized (newPeers) {
270 if(newPeers.remove(channel)) {
271 logger.info("Peer removed from new peers list: {}", channel.getPeerId());
272 }
273 synchronized (activePeersLock){
274 if(activePeers.values().remove(channel)) {
275 logger.info("Peer removed from active peers list: {}", channel.getPeerId());
276 }
277 }
278 syncPool.onDisconnect(channel);
279 }
280 }
281
282 public Collection<Peer> getActivePeers() {
283 // from the docs: it is imperative to synchronize when iterating
284 synchronized (activePeersLock) {
285 return new ArrayList<>(activePeers.values());
286 }
287 }
288
289 public boolean isAddressBlockAvailable(InetAddress inetAddress) {
290 synchronized (activePeersLock) {
291 //TODO(lsebrie): save block address in a data structure and keep updated on each channel add/remove
292 //TODO(lsebrie): check if we need to use a different networkCIDR for ipv6
293 return activePeers.values().stream()
294 .map(ch -> new InetAddressBlock(ch.getInetSocketAddress().getAddress(), networkCIDR))
295 .filter(block -> block.contains(inetAddress))
296 .count() < maxConnectionsAllowed;
297 }
298 }
299
300 /**
301 * broadcastTransaction Propagates a transaction message across active peers with exclusion of
302 * the peers with an id belonging to the skip set.
303 *
304 * @param transaction new Transaction to be sent
305 * @param skip the set of peers to avoid sending the message.
306 * @return a set containing the ids of the peers that received the transaction.
307 */
308 @Nonnull
309 public Set<NodeID> broadcastTransaction(@Nonnull final Transaction transaction, @Nonnull final Set<NodeID> skip) {
310 List<Transaction> transactions = Collections.singletonList(transaction);
311
312 return internalBroadcastTransactions(skip, transactions);
313 }
314
315 /**
316 * broadcastTransaction Propagates a transaction message across active peers with exclusion of
317 * the peers with an id belonging to the skip set.
318 *
319 * @param transactions List of Transactions to be sent
320 * @param skip the set of peers to avoid sending the message.
321 * @return a set containing the ids of the peers that received the transaction.
322 */
323 @Override
324 public Set<NodeID> broadcastTransactions(@Nonnull final List<Transaction> transactions, @Nonnull final Set<NodeID> skip) {
325 return internalBroadcastTransactions(skip, transactions);
326 }
327
328 private Set<NodeID> internalBroadcastTransactions(Set<NodeID> skip, List<Transaction> transactions) {
329 final Set<NodeID> nodesIdsBroadcastedTo = new HashSet<>();
330 final Message newTransactions = new TransactionsMessage(transactions);
331 final List<Channel> peersToBroadcast = activePeers.values().stream().
332 filter(p -> !skip.contains(p.getNodeId())).collect(Collectors.toList());
333
334 peersToBroadcast.forEach(peer -> {
335 peer.sendMessage(newTransactions);
336 nodesIdsBroadcastedTo.add(peer.getNodeId());
337 });
338
339 return nodesIdsBroadcastedTo;
340 }
341
342 @VisibleForTesting
343 public void setActivePeers(Map<NodeID, Channel> newActivePeers) {
344 this.activePeers.clear();
345 this.activePeers.putAll(newActivePeers);
346 }
347
348 }