observer) {
- return controller.observePeerBondedEvents(observer);
- }
-
- /**
- * Adds an observer that will get called when a new peer is dropped from the peer table.
- *
- * No guarantees are made about the order in which observers are invoked.
- *
- * @param observer The observer to call.
- * @return A unique ID identifying this observer, to that it can be removed later.
- */
- public long observePeerDroppedEvents(final Consumer observer) {
- return controller.observePeerDroppedEvents(observer);
+ checkNotNull(observer);
+ return peerBondedObservers.subscribe(observer);
}
/**
@@ -363,17 +269,7 @@ public class PeerDiscoveryAgent implements DisconnectCallback {
* @return Whether the observer was located and removed.
*/
public boolean removePeerBondedObserver(final long observerId) {
- return controller.removePeerBondedObserver(observerId);
- }
-
- /**
- * Removes an previously added peer dropped observer.
- *
- * @param observerId The unique ID identifying the observer to remove.
- * @return Whether the observer was located and removed.
- */
- public boolean removePeerDroppedObserver(final long observerId) {
- return controller.removePeerDroppedObserver(observerId);
+ return peerBondedObservers.unsubscribe(observerId);
}
/**
@@ -383,7 +279,7 @@ public class PeerDiscoveryAgent implements DisconnectCallback {
*/
@VisibleForTesting
public int getObserverCount() {
- return controller.observerCount();
+ return peerBondedObservers.getSubscriberCount();
}
private static void validateConfiguration(final DiscoveryConfiguration config) {
diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java
new file mode 100644
index 0000000000..b9790fd117
--- /dev/null
+++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2019 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.ethereum.p2p.discovery;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
+import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerDiscoveryController;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerRequirement;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.TimerUtil;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.VertxTimerUtil;
+import tech.pegasys.pantheon.ethereum.p2p.peers.Endpoint;
+import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
+import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController;
+import tech.pegasys.pantheon.util.NetworkUtility;
+import tech.pegasys.pantheon.util.Preconditions;
+
+import java.io.IOException;
+import java.net.BindException;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.util.OptionalInt;
+import java.util.concurrent.CompletableFuture;
+
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Vertx;
+import io.vertx.core.datagram.DatagramPacket;
+import io.vertx.core.datagram.DatagramSocket;
+import io.vertx.core.datagram.DatagramSocketOptions;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class VertxPeerDiscoveryAgent extends PeerDiscoveryAgent {
+ private static final Logger LOG = LogManager.getLogger();
+
+ private final Vertx vertx;
+ /* The vert.x UDP socket. */
+ private DatagramSocket socket;
+
+ public VertxPeerDiscoveryAgent(
+ final Vertx vertx,
+ final KeyPair keyPair,
+ final DiscoveryConfiguration config,
+ final PeerRequirement peerRequirement,
+ final PeerBlacklist peerBlacklist,
+ final NodeWhitelistController nodeWhitelistController) {
+ super(keyPair, config, peerRequirement, peerBlacklist, nodeWhitelistController);
+ checkArgument(vertx != null, "vertx instance cannot be null");
+ this.vertx = vertx;
+ }
+
+ @Override
+ protected TimerUtil createTimer() {
+ return new VertxTimerUtil(vertx);
+ }
+
+ @Override
+ protected CompletableFuture listenForConnections() {
+ CompletableFuture future = new CompletableFuture<>();
+ vertx
+ .createDatagramSocket(new DatagramSocketOptions().setIpV6(NetworkUtility.isIPv6Available()))
+ .listen(
+ config.getBindPort(), config.getBindHost(), res -> handleListenerSetup(res, future));
+ return future;
+ }
+
+ protected void handleListenerSetup(
+ final AsyncResult listenResult,
+ final CompletableFuture addressFuture) {
+ if (listenResult.failed()) {
+ Throwable cause = listenResult.cause();
+ LOG.error("An exception occurred when starting the peer discovery agent", cause);
+
+ if (cause instanceof BindException || cause instanceof SocketException) {
+ cause =
+ new PeerDiscoveryServiceException(
+ String.format(
+ "Failed to bind Ethereum UDP discovery listener to %s:%d: %s",
+ config.getBindHost(), config.getBindPort(), cause.getMessage()));
+ }
+ addressFuture.completeExceptionally(cause);
+ return;
+ }
+
+ this.socket = listenResult.result();
+
+ // TODO: when using wildcard hosts (0.0.0.0), we need to handle multiple addresses by
+ // selecting
+ // the correct 'announce' address.
+ final String effectiveHost = socket.localAddress().host();
+ final int effectivePort = socket.localAddress().port();
+
+ LOG.info(
+ "Started peer discovery agent successfully, on effective host={} and port={}",
+ effectiveHost,
+ effectivePort);
+
+ socket.exceptionHandler(this::handleException);
+ socket.handler(this::handlePacket);
+
+ InetSocketAddress address =
+ new InetSocketAddress(socket.localAddress().host(), socket.localAddress().port());
+ addressFuture.complete(address);
+ }
+
+ @Override
+ protected CompletableFuture sendOutgoingPacket(
+ final DiscoveryPeer peer, final Packet packet) {
+ CompletableFuture result = new CompletableFuture<>();
+ socket.send(
+ packet.encode(),
+ peer.getEndpoint().getUdpPort(),
+ peer.getEndpoint().getHost(),
+ ar -> {
+ if (ar.failed()) {
+ result.completeExceptionally(ar.cause());
+ } else {
+ result.complete(null);
+ }
+ });
+ return result;
+ }
+
+ @Override
+ public CompletableFuture> stop() {
+ if (socket == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ final CompletableFuture> completion = new CompletableFuture<>();
+ socket.close(
+ ar -> {
+ if (ar.succeeded()) {
+ controller.ifPresent(PeerDiscoveryController::stop);
+ socket = null;
+ completion.complete(null);
+ } else {
+ completion.completeExceptionally(ar.cause());
+ }
+ });
+ return completion;
+ }
+
+ /**
+ * For uncontrolled exceptions occurring in the packet handlers.
+ *
+ * @param exception the exception that was raised
+ */
+ private void handleException(final Throwable exception) {
+ if (exception instanceof IOException) {
+ LOG.debug("Packet handler exception", exception);
+ } else {
+ LOG.error("Packet handler exception", exception);
+ }
+ }
+
+ /**
+ * The UDP packet handler. This is the entrypoint for all received datagrams.
+ *
+ * @param datagram the received datagram.
+ */
+ private void handlePacket(final DatagramPacket datagram) {
+ try {
+ final int length = datagram.data().length();
+ Preconditions.checkGuard(
+ validatePacketSize(length),
+ PeerDiscoveryPacketDecodingException::new,
+ "Packet too large. Actual size (bytes): %s",
+ length);
+
+ // We allow exceptions to bubble up, as they'll be picked up by the exception handler.
+ final Packet packet = Packet.decode(datagram.data());
+ // Acquire the senders coordinates to build a Peer representation from them.
+ final String host = datagram.sender().host();
+ final int port = datagram.sender().port();
+ final Endpoint endpoint = new Endpoint(host, port, OptionalInt.empty());
+ handleIncomingPacket(endpoint, packet);
+ } catch (final PeerDiscoveryPacketDecodingException e) {
+ LOG.debug("Discarding invalid peer discovery packet", e);
+ } catch (final Throwable t) {
+ LOG.error("Encountered error while handling packet", t);
+ }
+ }
+}
diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/OutboundMessageHandler.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/OutboundMessageHandler.java
new file mode 100644
index 0000000000..c04085c646
--- /dev/null
+++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/OutboundMessageHandler.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2019 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
+
+import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
+
+@FunctionalInterface
+public interface OutboundMessageHandler {
+ public static OutboundMessageHandler NOOP = (peer, packet) -> {};
+
+ void send(final DiscoveryPeer toPeer, final Packet packet);
+}
diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryController.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryController.java
index 6c3d194ab7..f36140bc37 100644
--- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryController.java
+++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryController.java
@@ -12,17 +12,16 @@
*/
package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
-import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Collections.emptyList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.AddResult.Outcome;
+import tech.pegasys.pantheon.crypto.SECP256K1;
+import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
-import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryAgent;
import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent;
import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerBondedEvent;
-import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerDroppedEvent;
import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryStatus;
import tech.pegasys.pantheon.ethereum.p2p.peers.Peer;
import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
@@ -42,7 +41,6 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import com.google.common.annotations.VisibleForTesting;
-import io.vertx.core.Vertx;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -94,7 +92,7 @@ public class PeerDiscoveryController {
private static final Logger LOG = LogManager.getLogger();
private static final long REFRESH_CHECK_INTERVAL_MILLIS = MILLISECONDS.convert(30, SECONDS);
- private final Vertx vertx;
+ protected final TimerUtil timerUtil;
private final PeerTable peerTable;
private final Collection bootstrapNodes;
@@ -105,7 +103,10 @@ public class PeerDiscoveryController {
private final AtomicBoolean started = new AtomicBoolean(false);
- private final PeerDiscoveryAgent agent;
+ private final SECP256K1.KeyPair keypair;
+ // The peer representation of this node
+ private final DiscoveryPeer localPeer;
+ private final OutboundMessageHandler outboundMessageHandler;
private final PeerBlacklist peerBlacklist;
private final NodeWhitelistController nodeWhitelist;
@@ -120,28 +121,31 @@ public class PeerDiscoveryController {
private OptionalLong tableRefreshTimerId = OptionalLong.empty();
// Observers for "peer bonded" discovery events.
- private final Subscribers> peerBondedObservers = new Subscribers<>();
-
- // Observers for "peer dropped" discovery events.
- private final Subscribers> peerDroppedObservers = new Subscribers<>();
+ private final Subscribers> peerBondedObservers;
public PeerDiscoveryController(
- final Vertx vertx,
- final PeerDiscoveryAgent agent,
+ final KeyPair keypair,
+ final DiscoveryPeer localPeer,
final PeerTable peerTable,
final Collection bootstrapNodes,
+ final OutboundMessageHandler outboundMessageHandler,
+ final TimerUtil timerUtil,
final long tableRefreshIntervalMs,
final PeerRequirement peerRequirement,
final PeerBlacklist peerBlacklist,
- final NodeWhitelistController nodeWhitelist) {
- this.vertx = vertx;
- this.agent = agent;
+ final NodeWhitelistController nodeWhitelist,
+ final Subscribers> peerBondedObservers) {
+ this.timerUtil = timerUtil;
+ this.keypair = keypair;
+ this.localPeer = localPeer;
this.bootstrapNodes = bootstrapNodes;
this.peerTable = peerTable;
this.tableRefreshIntervalMs = tableRefreshIntervalMs;
this.peerRequirement = peerRequirement;
this.peerBlacklist = peerBlacklist;
this.nodeWhitelist = nodeWhitelist;
+ this.outboundMessageHandler = outboundMessageHandler;
+ this.peerBondedObservers = peerBondedObservers;
}
public CompletableFuture> start() {
@@ -156,9 +160,9 @@ public class PeerDiscoveryController {
.forEach(node -> bond(node, true));
final long timerId =
- vertx.setPeriodic(
+ timerUtil.setPeriodic(
Math.min(REFRESH_CHECK_INTERVAL_MILLIS, tableRefreshIntervalMs),
- (l) -> refreshTableIfRequired());
+ () -> refreshTableIfRequired());
tableRefreshTimerId = OptionalLong.of(timerId);
return CompletableFuture.completedFuture(null);
@@ -169,7 +173,7 @@ public class PeerDiscoveryController {
return CompletableFuture.completedFuture(null);
}
- tableRefreshTimerId.ifPresent(vertx::cancelTimer);
+ tableRefreshTimerId.ifPresent(timerUtil::cancelTimer);
tableRefreshTimerId = OptionalLong.empty();
inflightInteractions.values().forEach(PeerInteractionState::cancelTimers);
inflightInteractions.clear();
@@ -196,7 +200,7 @@ public class PeerDiscoveryController {
packet);
// Message from self. This should not happen.
- if (sender.getId().equals(agent.getAdvertisedPeer().getId())) {
+ if (sender.getId().equals(localPeer.getId())) {
return;
}
@@ -230,7 +234,7 @@ public class PeerDiscoveryController {
// If this was a bootstrap peer, let's ask it for nodes near to us.
if (interaction.isBootstrap()) {
- findNodes(peer, agent.getAdvertisedPeer().getId());
+ findNodes(peer, localPeer.getId());
}
});
break;
@@ -247,9 +251,12 @@ public class PeerDiscoveryController {
.orElse(emptyList());
for (final DiscoveryPeer neighbor : neighbors) {
+ // If the peer is not whitelisted, is blacklisted, is already known, or
+ // represents this node, skip bonding
if (!nodeWhitelist.isPermitted(neighbor)
|| peerBlacklist.contains(neighbor)
- || peerTable.get(neighbor).isPresent()) {
+ || peerTable.get(neighbor).isPresent()
+ || neighbor.getId().equals(localPeer.getId())) {
continue;
}
bond(neighbor, false);
@@ -315,7 +322,7 @@ public class PeerDiscoveryController {
private void refreshTableIfRequired() {
final long now = System.currentTimeMillis();
- if (lastRefreshTime + tableRefreshIntervalMs < now) {
+ if (lastRefreshTime + tableRefreshIntervalMs <= now) {
LOG.info("Peer table refresh triggered by timer expiry");
refreshTable();
} else if (!peerRequirement.hasSufficientPeers()) {
@@ -348,10 +355,10 @@ public class PeerDiscoveryController {
final Consumer action =
interaction -> {
final PingPacketData data =
- PingPacketData.create(agent.getAdvertisedPeer().getEndpoint(), peer.getEndpoint());
- final Packet sentPacket = agent.sendPacket(peer, PacketType.PING, data);
+ PingPacketData.create(localPeer.getEndpoint(), peer.getEndpoint());
+ final Packet pingPacket = createPacket(PacketType.PING, data);
- final BytesValue pingHash = sentPacket.getHash();
+ final BytesValue pingHash = pingPacket.getHash();
// Update the matching filter to only accept the PONG if it echoes the hash of our PING.
final Predicate newFilter =
packet ->
@@ -360,6 +367,8 @@ public class PeerDiscoveryController {
.map(pong -> pong.getPingHash().equals(pingHash))
.orElse(false);
interaction.updateFilter(newFilter);
+
+ sendPacket(peer, pingPacket);
};
// The filter condition will be updated as soon as the action is performed.
@@ -368,6 +377,20 @@ public class PeerDiscoveryController {
dispatchInteraction(peer, ping);
}
+ private void sendPacket(final DiscoveryPeer peer, final PacketType type, final PacketData data) {
+ Packet packet = createPacket(type, data);
+ outboundMessageHandler.send(peer, packet);
+ }
+
+ private void sendPacket(final DiscoveryPeer peer, final Packet packet) {
+ outboundMessageHandler.send(peer, packet);
+ }
+
+ @VisibleForTesting
+ Packet createPacket(final PacketType type, final PacketData data) {
+ return Packet.create(type, data, keypair);
+ }
+
/**
* Sends a FIND_NEIGHBORS message to a {@link DiscoveryPeer}, in search of a target value.
*
@@ -378,7 +401,7 @@ public class PeerDiscoveryController {
final Consumer action =
(interaction) -> {
final FindNeighborsPacketData data = FindNeighborsPacketData.create(target);
- agent.sendPacket(peer, PacketType.FIND_NEIGHBORS, data);
+ sendPacket(peer, PacketType.FIND_NEIGHBORS, data);
};
final PeerInteractionState interaction =
new PeerInteractionState(action, PacketType.NEIGHBORS, packet -> true, true, false);
@@ -405,7 +428,7 @@ public class PeerDiscoveryController {
private void respondToPing(
final PingPacketData packetData, final BytesValue pingHash, final DiscoveryPeer sender) {
final PongPacketData data = PongPacketData.create(packetData.getFrom(), pingHash);
- agent.sendPacket(sender, PacketType.PONG, data);
+ sendPacket(sender, PacketType.PONG, data);
}
private void respondToFindNeighbors(
@@ -414,22 +437,13 @@ public class PeerDiscoveryController {
// peers they can fit in a 1280-byte payload.
final List peers = peerTable.nearestPeers(packetData.getTarget(), 16);
final PacketData data = NeighborsPacketData.create(peers);
- agent.sendPacket(sender, PacketType.NEIGHBORS, data);
+ sendPacket(sender, PacketType.NEIGHBORS, data);
}
- // Dispatches an event to a set of observers. Since we have no control over observer logic, we
- // take
- // precautions and we assume they are of blocking nature to protect our event loop.
+ // Dispatches an event to a set of observers.
private void dispatchEvent(
final Subscribers> observers, final T event) {
- observers.forEach(
- observer ->
- vertx.executeBlocking(
- future -> {
- observer.accept(event);
- future.complete();
- },
- x -> {}));
+ observers.forEach(observer -> observer.accept(event));
}
/**
@@ -446,63 +460,6 @@ public class PeerDiscoveryController {
this.retryDelayFunction = retryDelayFunction;
}
- /**
- * Adds an observer that will get called when a new peer is bonded with and added to the peer
- * table.
- *
- * No guarantees are made about the order in which observers are invoked.
- *
- * @param observer The observer to call.
- * @return A unique ID identifying this observer, to that it can be removed later.
- */
- public long observePeerBondedEvents(final Consumer observer) {
- checkNotNull(observer);
- return peerBondedObservers.subscribe(observer);
- }
-
- /**
- * Adds an observer that will get called when a new peer is dropped from the peer table.
- *
- * No guarantees are made about the order in which observers are invoked.
- *
- * @param observer The observer to call.
- * @return A unique ID identifying this observer, to that it can be removed later.
- */
- public long observePeerDroppedEvents(final Consumer observer) {
- checkNotNull(observer);
- return peerDroppedObservers.subscribe(observer);
- }
-
- /**
- * Removes an previously added peer bonded observer.
- *
- * @param observerId The unique ID identifying the observer to remove.
- * @return Whether the observer was located and removed.
- */
- public boolean removePeerBondedObserver(final long observerId) {
- return peerBondedObservers.unsubscribe(observerId);
- }
-
- /**
- * Removes an previously added peer dropped observer.
- *
- * @param observerId The unique ID identifying the observer to remove.
- * @return Whether the observer was located and removed.
- */
- public boolean removePeerDroppedObserver(final long observerId) {
- return peerDroppedObservers.unsubscribe(observerId);
- }
-
- /**
- * Returns the count of observers that are registered on this controller.
- *
- * @return The observer count.
- */
- @VisibleForTesting
- public int observerCount() {
- return peerBondedObservers.getSubscriberCount() + peerDroppedObservers.getSubscriberCount();
- }
-
/** Holds the state machine data for a peer interaction. */
private class PeerInteractionState implements Predicate {
/**
@@ -558,13 +515,13 @@ public class PeerDiscoveryController {
action.accept(this);
if (retryable) {
final long newTimeout = retryDelayFunction.apply(lastTimeout);
- timerId = OptionalLong.of(vertx.setTimer(newTimeout, id -> execute(newTimeout)));
+ timerId = OptionalLong.of(timerUtil.setTimer(newTimeout, () -> execute(newTimeout)));
}
}
/** Cancels any timers associated with this entry. */
void cancelTimers() {
- timerId.ifPresent(vertx::cancelTimer);
+ timerId.ifPresent(timerUtil::cancelTimer);
}
}
}
diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerRequirement.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerRequirement.java
index d839f49fcd..8213fd4cca 100644
--- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerRequirement.java
+++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerRequirement.java
@@ -14,6 +14,7 @@ package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
import java.util.Collection;
+@FunctionalInterface
public interface PeerRequirement {
boolean hasSufficientPeers();
diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/TimerUtil.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/TimerUtil.java
new file mode 100644
index 0000000000..c497c213ef
--- /dev/null
+++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/TimerUtil.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
+
+public interface TimerUtil {
+ long setPeriodic(long delay, TimerHandler handler);
+
+ long setTimer(long delay, TimerHandler handler);
+
+ void cancelTimer(long timerId);
+
+ @FunctionalInterface
+ interface TimerHandler {
+ void handle();
+ }
+}
diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/VertxTimerUtil.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/VertxTimerUtil.java
new file mode 100644
index 0000000000..b519bb1b2e
--- /dev/null
+++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/VertxTimerUtil.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
+
+import io.vertx.core.Vertx;
+
+public class VertxTimerUtil implements TimerUtil {
+
+ private final Vertx vertx;
+
+ public VertxTimerUtil(final Vertx vertx) {
+ this.vertx = vertx;
+ }
+
+ @Override
+ public long setPeriodic(final long delay, final TimerHandler handler) {
+ return vertx.setPeriodic(delay, (l) -> handler.handle());
+ }
+
+ @Override
+ public long setTimer(final long delay, final TimerHandler handler) {
+ return vertx.setTimer(delay, (l) -> handler.handle());
+ }
+
+ @Override
+ public void cancelTimer(final long timerId) {
+ vertx.cancelTimer(timerId);
+ }
+}
diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java
index 27a79635c9..13acb3fd8a 100644
--- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java
+++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java
@@ -22,6 +22,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection;
import tech.pegasys.pantheon.ethereum.p2p.config.NetworkingConfiguration;
import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryAgent;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.VertxPeerDiscoveryAgent;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerRequirement;
import tech.pegasys.pantheon.ethereum.p2p.peers.Endpoint;
import tech.pegasys.pantheon.ethereum.p2p.peers.Peer;
@@ -50,6 +51,7 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
+import com.google.common.annotations.VisibleForTesting;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
@@ -174,7 +176,7 @@ public final class NettyP2PNetwork implements P2PNetwork {
this.peerBlacklist = peerBlacklist;
this.nodeWhitelistController = nodeWhitelistController;
peerDiscoveryAgent =
- new PeerDiscoveryAgent(
+ new VertxPeerDiscoveryAgent(
vertx,
keyPair,
config.getDiscovery(),
@@ -380,7 +382,7 @@ public final class NettyP2PNetwork implements P2PNetwork {
@Override
public void run() {
try {
- peerDiscoveryAgent.start(ourPeerInfo.getPort()).join();
+ peerDiscoveryAgent.start().join();
final long observerId =
peerDiscoveryAgent.observePeerBondedEvents(
peerBondedEvent -> {
@@ -425,6 +427,7 @@ public final class NettyP2PNetwork implements P2PNetwork {
stop();
}
+ @VisibleForTesting
public Collection getDiscoveryPeers() {
return peerDiscoveryAgent.getPeers();
}
@@ -435,7 +438,7 @@ public final class NettyP2PNetwork implements P2PNetwork {
}
@Override
- public PeerInfo getSelf() {
+ public PeerInfo getLocalPeerInfo() {
return ourPeerInfo;
}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NettyP2PNetworkTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NettyP2PNetworkTest.java
index 9fd070fd04..623c03cc5c 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NettyP2PNetworkTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NettyP2PNetworkTest.java
@@ -93,7 +93,7 @@ public final class NettyP2PNetworkTest {
new NoOpMetricsSystem(),
new NodeWhitelistController(PermissioningConfiguration.createDefault()))) {
- final int listenPort = listener.getSelf().getPort();
+ final int listenPort = listener.getLocalPeerInfo().getPort();
listener.run();
connector.run();
final BytesValue listenId = listenKp.getPublicKey().getEncodedBytes();
@@ -146,7 +146,7 @@ public final class NettyP2PNetworkTest {
new PeerBlacklist(),
new NoOpMetricsSystem(),
new NodeWhitelistController(PermissioningConfiguration.createDefault()))) {
- final int listenPort = listener.getSelf().getPort();
+ final int listenPort = listener.getLocalPeerInfo().getPort();
listener.run();
connector.run();
final BytesValue listenId = listenKp.getPublicKey().getEncodedBytes();
@@ -229,7 +229,7 @@ public final class NettyP2PNetworkTest {
new NoOpMetricsSystem(),
new NodeWhitelistController(PermissioningConfiguration.createDefault()))) {
- final int listenPort = listener.getSelf().getPort();
+ final int listenPort = listener.getLocalPeerInfo().getPort();
// Setup listener and first connection
listener.run();
connector1.run();
@@ -296,7 +296,7 @@ public final class NettyP2PNetworkTest {
new PeerBlacklist(),
new NoOpMetricsSystem(),
new NodeWhitelistController(PermissioningConfiguration.createDefault()))) {
- final int listenPort = listener.getSelf().getPort();
+ final int listenPort = listener.getLocalPeerInfo().getPort();
listener.run();
connector.run();
final BytesValue listenId = listenKp.getPublicKey().getEncodedBytes();
@@ -351,8 +351,8 @@ public final class NettyP2PNetworkTest {
remoteBlacklist,
new NoOpMetricsSystem(),
new NodeWhitelistController(PermissioningConfiguration.createDefault()))) {
- final int localListenPort = localNetwork.getSelf().getPort();
- final int remoteListenPort = remoteNetwork.getSelf().getPort();
+ final int localListenPort = localNetwork.getLocalPeerInfo().getPort();
+ final int remoteListenPort = remoteNetwork.getLocalPeerInfo().getPort();
final Peer localPeer =
new DefaultPeer(
localId,
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/AbstractPeerDiscoveryTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/AbstractPeerDiscoveryTest.java
deleted file mode 100644
index c75c6f7da0..0000000000
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/AbstractPeerDiscoveryTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright 2018 ConsenSys AG.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package tech.pegasys.pantheon.ethereum.p2p.discovery;
-
-import static io.vertx.core.Vertx.vertx;
-
-import tech.pegasys.pantheon.crypto.SECP256K1;
-import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration;
-import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet;
-import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PacketType;
-import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PingPacketData;
-import tech.pegasys.pantheon.ethereum.p2p.peers.Endpoint;
-import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
-import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController;
-import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
-import tech.pegasys.pantheon.util.bytes.BytesValue;
-
-import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import io.vertx.core.Vertx;
-import io.vertx.core.datagram.DatagramSocket;
-import junit.framework.AssertionFailedError;
-import org.junit.After;
-
-/**
- * A test class you can extend to acquire the ability to easily start discovery agents with a
- * generated Peer and keypair, as well as test sockets to communicate with those discovery agents.
- *
- * Call {@link #startDiscoveryAgent(List)} and variants to start one or more discovery agents, or
- * {@link #startTestSocket()} or variants to start one or more test sockets. The lifecycle of those
- * objects is managed automatically for you via @Before and @After hooks, so you don't need to worry
- * about starting and stopping.
- */
-public abstract class AbstractPeerDiscoveryTest {
- private static final String LOOPBACK_IP_ADDR = "127.0.0.1";
- private static final int TEST_SOCKET_START_TIMEOUT_SECS = 5;
- private static final int BROADCAST_TCP_PORT = 12356;
- private final Vertx vertx = vertx();
-
- List discoveryTestSockets = new CopyOnWriteArrayList<>();
- List agents = new CopyOnWriteArrayList<>();
-
- @After
- public void stopServices() {
- // Close all sockets, will bubble up exceptions.
- final CompletableFuture>[] completions =
- discoveryTestSockets
- .stream()
- .filter(p -> p.getSocket() != null)
- .map(
- p -> {
- final CompletableFuture> completion = new CompletableFuture<>();
- p.getSocket()
- .close(
- ar -> {
- if (ar.succeeded()) {
- completion.complete(null);
- } else {
- completion.completeExceptionally(ar.cause());
- }
- });
- return completion;
- })
- .toArray(CompletableFuture>[]::new);
- try {
- CompletableFuture.allOf(completions).join();
- } finally {
- agents.forEach(PeerDiscoveryAgent::stop);
- vertx.close();
- }
- }
-
- /**
- * Starts multiple discovery agents with the provided boostrap peers.
- *
- * @param count the number of agents to start
- * @param bootstrapPeers the list of bootstrap peers
- * @return a list of discovery agents.
- */
- protected List startDiscoveryAgents(
- final int count, final List bootstrapPeers) {
- return Stream.generate(() -> startDiscoveryAgent(bootstrapPeers))
- .limit(count)
- .collect(Collectors.toList());
- }
-
- /**
- * Start a single discovery agent with the provided bootstrap peers.
- *
- * @param bootstrapPeers the list of bootstrap peers
- * @return a list of discovery agents.
- */
- protected PeerDiscoveryAgent startDiscoveryAgent(final List bootstrapPeers) {
- return startDiscoveryAgent(bootstrapPeers, new PeerBlacklist());
- }
-
- /**
- * Start a single discovery agent with the provided bootstrap peers.
- *
- * @param bootstrapPeers the list of bootstrap peers
- * @param blacklist the peer blacklist
- * @return a list of discovery agents.
- */
- protected PeerDiscoveryAgent startDiscoveryAgent(
- final List bootstrapPeers, final PeerBlacklist blacklist) {
- final DiscoveryConfiguration config = new DiscoveryConfiguration();
- config.setBootstrapPeers(bootstrapPeers);
- config.setBindPort(0);
-
- return startDiscoveryAgent(config, blacklist);
- }
-
- protected PeerDiscoveryAgent startDiscoveryAgent(
- final DiscoveryConfiguration config, final PeerBlacklist blacklist) {
- final PeerDiscoveryAgent agent =
- new PeerDiscoveryAgent(
- vertx,
- SECP256K1.KeyPair.generate(),
- config,
- () -> true,
- blacklist,
- new NodeWhitelistController(PermissioningConfiguration.createDefault()));
- try {
- agent.start(BROADCAST_TCP_PORT).get(5, TimeUnit.SECONDS);
- } catch (final Exception ex) {
- throw new AssertionError("Could not initialize discovery agent", ex);
- }
- agents.add(agent);
- return agent;
- }
-
- /**
- * Start multiple test sockets.
- *
- * A test socket allows you to send messages to a discovery agent, as well as to react to
- * received messages. A test socket encapsulates: (1) a {@link DiscoveryPeer} and its {@link
- * tech.pegasys.pantheon.crypto.SECP256K1.KeyPair}, (2) an {@link ArrayBlockingQueue} where
- * received messages are placed automatically, and (3) the socket itself.
- *
- * @param count the number of test sockets to start.
- * @return the test sockets.
- */
- protected List startTestSockets(final int count) {
- return Stream.generate(this::startTestSocket).limit(count).collect(Collectors.toList());
- }
-
- /**
- * Starts a single test socket.
- *
- * @return the test socket
- */
- protected DiscoveryTestSocket startTestSocket() {
- final ArrayBlockingQueue queue = new ArrayBlockingQueue<>(100);
-
- final SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.generate();
- final BytesValue peerId = keyPair.getPublicKey().getEncodedBytes();
-
- final CompletableFuture result = new CompletableFuture<>();
- // Test packet handler which feeds the received packet into a Future we later consume from.
- vertx
- .createDatagramSocket()
- .listen(
- 0,
- LOOPBACK_IP_ADDR,
- ar -> {
- if (!ar.succeeded()) {
- result.completeExceptionally(ar.cause());
- return;
- }
-
- final DatagramSocket socket = ar.result();
- socket.handler(p -> queue.add(Packet.decode(p.data())));
- final DiscoveryPeer peer =
- new DiscoveryPeer(
- peerId,
- LOOPBACK_IP_ADDR,
- socket.localAddress().port(),
- socket.localAddress().port());
- final DiscoveryTestSocket discoveryTestSocket =
- new DiscoveryTestSocket(peer, keyPair, queue, socket);
- result.complete(discoveryTestSocket);
- });
-
- final DiscoveryTestSocket discoveryTestSocket;
- try {
- discoveryTestSocket = result.get(TEST_SOCKET_START_TIMEOUT_SECS, TimeUnit.SECONDS);
- } catch (final Exception ex) {
- throw new AssertionError("Could not initialize test peer", ex);
- }
- discoveryTestSockets.add(discoveryTestSocket);
- return discoveryTestSocket;
- }
-
- protected void bondViaIncomingPing(
- final PeerDiscoveryAgent agent, final DiscoveryTestSocket peerSocket)
- throws InterruptedException {
- final DiscoveryPeer peer = peerSocket.getPeer();
-
- final PingPacketData ping =
- PingPacketData.create(peer.getEndpoint(), agent.getAdvertisedPeer().getEndpoint());
- final Packet pingPacket = Packet.create(PacketType.PING, ping, peerSocket.getKeyPair());
- peerSocket.sendToAgent(agent, pingPacket);
-
- // Wait for returned pong packet to finish bonding
- peerSocket.getIncomingPackets().poll(10, TimeUnit.SECONDS);
- }
-
- /**
- * Encapsulates a test socket representing a Peer, with an associated queue where all incoming
- * packets are placed.
- */
- protected static class DiscoveryTestSocket {
- private final DiscoveryPeer peer;
- private final SECP256K1.KeyPair keyPair;
- private final ArrayBlockingQueue queue;
- private final DatagramSocket socket;
-
- public DiscoveryTestSocket(
- final DiscoveryPeer peer,
- final SECP256K1.KeyPair keyPair,
- final ArrayBlockingQueue queue,
- final DatagramSocket socket) {
- this.peer = peer;
- this.keyPair = keyPair;
- this.queue = queue;
- this.socket = socket;
- }
-
- public DiscoveryPeer getPeer() {
- return peer;
- }
-
- public ArrayBlockingQueue getIncomingPackets() {
- return queue;
- }
-
- public DatagramSocket getSocket() {
- return socket;
- }
-
- public SECP256K1.KeyPair getKeyPair() {
- return keyPair;
- }
-
- /**
- * Sends a message to an agent.
- *
- * @param agent the recipient
- * @param packet the packet to send
- */
- public void sendToAgent(final PeerDiscoveryAgent agent, final Packet packet) {
- final Endpoint endpoint = agent.getAdvertisedPeer().getEndpoint();
- socket.send(packet.encode(), endpoint.getUdpPort(), endpoint.getHost(), ar -> {});
- }
-
- /**
- * Retrieves the head of the queue, compulsorily. If no message exists, or no message appears in
- * 5 seconds, it throws an assertion error.
- *
- * @return the head of the queue
- */
- public Packet compulsoryPoll() {
- final Packet packet;
- try {
- packet = queue.poll(5, TimeUnit.SECONDS);
- } catch (final Exception e) {
- throw new RuntimeException(e);
- }
-
- if (packet == null) {
- throw new AssertionFailedError(
- "Expected a message in the test peer queue, but found none.");
- }
- return packet;
- }
- }
-}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java
index beb0688f26..5065b25099 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java
@@ -13,152 +13,116 @@
package tech.pegasys.pantheon.ethereum.p2p.discovery;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
-import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection;
-import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper.AgentBuilder;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.FindNeighborsPacketData;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockPeerDiscoveryAgent;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockPeerDiscoveryAgent.IncomingPacket;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.NeighborsPacketData;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PacketType;
-import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PingPacketData;
-import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer;
import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
-import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.p2p.wire.PeerInfo;
import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason;
-import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.net.SocketAddress;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import com.google.common.collect.Lists;
-import io.vertx.core.Vertx;
-import org.junit.Ignore;
import org.junit.Test;
-public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
+public class PeerDiscoveryAgentTest {
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Test
- public void neighborsPacketFromUnbondedPeerIsDropped() throws Exception {
+ public void neighborsPacketFromUnbondedPeerIsDropped() {
// Start an agent with no bootstrap peers.
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList());
+ final MockPeerDiscoveryAgent agent = helper.startDiscoveryAgent(Collections.emptyList());
assertThat(agent.getPeers()).isEmpty();
- // Start a test peer and send a PING packet to the agent under test.
- final DiscoveryTestSocket discoveryTestSocket = startTestSocket();
-
- // Peer is unbonded, as it has not replied with a PONG.
+ // Start a test peer
+ final MockPeerDiscoveryAgent otherNode = helper.startDiscoveryAgent();
// Generate an out-of-band NEIGHBORS message.
- final DiscoveryPeer[] peers =
- PeerDiscoveryTestHelper.generatePeers(PeerDiscoveryTestHelper.generateKeyPairs(5));
- final NeighborsPacketData data = NeighborsPacketData.create(Arrays.asList(peers));
- final Packet packet =
- Packet.create(PacketType.NEIGHBORS, data, discoveryTestSocket.getKeyPair());
- discoveryTestSocket.sendToAgent(agent, packet);
-
- TimeUnit.SECONDS.sleep(1);
+ final List peers = helper.createDiscoveryPeers(5);
+ final NeighborsPacketData data = NeighborsPacketData.create(peers);
+ final Packet packet = Packet.create(PacketType.NEIGHBORS, data, otherNode.getKeyPair());
+ helper.sendMessageBetweenAgents(otherNode, agent, packet);
+
assertThat(agent.getPeers()).isEmpty();
}
@Test
- @Ignore("This test is failing intermittently - disabling while we investigate")
public void neighborsPacketLimited() {
// Start 20 agents with no bootstrap peers.
- final List agents = startDiscoveryAgents(20, Collections.emptyList());
- final List agentPeers =
- agents.stream().map(PeerDiscoveryAgent::getAdvertisedPeer).collect(Collectors.toList());
-
- // Start another bootstrap peer pointing to those 20 agents.
- final PeerDiscoveryAgent agent = startDiscoveryAgent(agentPeers);
- await()
- .atMost(10, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- assertThat(agent.getPeers()).hasSize(20);
- assertThat(agent.getPeers())
- .allMatch(p -> p.getStatus() == PeerDiscoveryStatus.BONDED);
- });
+ final List otherAgents =
+ helper.startDiscoveryAgents(20, Collections.emptyList());
+ final List otherPeers =
+ otherAgents
+ .stream()
+ .map(MockPeerDiscoveryAgent::getAdvertisedPeer)
+ .collect(Collectors.toList());
+
+ // Start another peer pointing to those 20 agents.
+ final MockPeerDiscoveryAgent agent = helper.startDiscoveryAgent(otherPeers);
+ assertThat(agent.getPeers()).hasSize(20);
+ assertThat(agent.getPeers()).allMatch(p -> p.getStatus() == PeerDiscoveryStatus.BONDED);
+
+ // Use additional agent to exchange messages with agent
+ final MockPeerDiscoveryAgent testAgent = helper.startDiscoveryAgent();
// Send a PING so we can exchange messages with the latter agent.
- final DiscoveryTestSocket testSocket = startTestSocket();
- Packet packet =
- Packet.create(
- PacketType.PING,
- PingPacketData.create(
- testSocket.getPeer().getEndpoint(), testSocket.getPeer().getEndpoint()),
- testSocket.getKeyPair());
- testSocket.sendToAgent(agent, packet);
-
- // Wait until PONG is received.
- final Packet pong = testSocket.compulsoryPoll();
- assertThat(pong.getType()).isEqualTo(PacketType.PONG);
+ Packet packet = helper.createPingPacket(testAgent, agent);
+ helper.sendMessageBetweenAgents(testAgent, agent, packet);
// Send a FIND_NEIGHBORS message.
packet =
Packet.create(
PacketType.FIND_NEIGHBORS,
- FindNeighborsPacketData.create(agents.get(0).getAdvertisedPeer().getId()),
- testSocket.getKeyPair());
- testSocket.sendToAgent(agent, packet);
-
- // Wait until NEIGHBORS is received.
- packet = testSocket.compulsoryPoll();
- assertThat(packet.getType()).isEqualTo(PacketType.NEIGHBORS);
+ FindNeighborsPacketData.create(otherAgents.get(0).getAdvertisedPeer().getId()),
+ testAgent.getKeyPair());
+ helper.sendMessageBetweenAgents(testAgent, agent, packet);
+
+ // Check response packet
+ List incomingPackets =
+ testAgent
+ .getIncomingPackets()
+ .stream()
+ .filter(p -> p.packet.getType().equals(PacketType.NEIGHBORS))
+ .collect(Collectors.toList());
+ assertThat(incomingPackets.size()).isEqualTo(1);
+ IncomingPacket neighborsPacket = incomingPackets.get(0);
+ assertThat(neighborsPacket.fromAgent).isEqualTo(agent);
// Assert that we only received 16 items.
- final NeighborsPacketData neighbors = packet.getPacketData(NeighborsPacketData.class).get();
+ final NeighborsPacketData neighbors =
+ neighborsPacket.packet.getPacketData(NeighborsPacketData.class).get();
assertThat(neighbors).isNotNull();
assertThat(neighbors.getNodes()).hasSize(16);
// Assert that after removing those 16 items we're left with either 4 or 5.
// If we are left with 5, the test peer was returned as an item, assert that this is the case.
- agentPeers.removeAll(neighbors.getNodes());
- assertThat(agentPeers.size()).isBetween(4, 5);
- if (agentPeers.size() == 5) {
- assertThat(neighbors.getNodes()).contains(testSocket.getPeer());
+ otherPeers.removeAll(neighbors.getNodes());
+ assertThat(otherPeers.size()).isBetween(4, 5);
+ if (otherPeers.size() == 5) {
+ assertThat(neighbors.getNodes()).contains(testAgent.getAdvertisedPeer());
}
}
@Test
public void shouldEvictPeerOnDisconnect() {
- final Vertx vertx = Vertx.vertx();
-
- final SECP256K1.KeyPair keyPair1 = SECP256K1.KeyPair.generate();
- final PeerDiscoveryAgent peerDiscoveryAgent1 =
- new PeerDiscoveryAgent(
- vertx,
- keyPair1,
- DiscoveryConfiguration.create().setBindHost("127.0.0.1").setBindPort(0),
- () -> true,
- new PeerBlacklist(),
- new NodeWhitelistController(PermissioningConfiguration.createDefault()));
- peerDiscoveryAgent1.start(0).join();
- final DefaultPeer peer = peerDiscoveryAgent1.getAdvertisedPeer();
-
- final SECP256K1.KeyPair keyPair2 = SECP256K1.KeyPair.generate();
- final PeerDiscoveryAgent peerDiscoveryAgent2 =
- new PeerDiscoveryAgent(
- vertx,
- keyPair2,
- DiscoveryConfiguration.create()
- .setBindHost("127.0.0.1")
- .setBindPort(0)
- .setBootstrapPeers(Lists.newArrayList(peer)),
- () -> true,
- new PeerBlacklist(),
- new NodeWhitelistController(PermissioningConfiguration.createDefault()));
- peerDiscoveryAgent2.start(0).join();
+ final MockPeerDiscoveryAgent peerDiscoveryAgent1 = helper.startDiscoveryAgent();
+ peerDiscoveryAgent1.start().join();
+ final DiscoveryPeer peer = peerDiscoveryAgent1.getAdvertisedPeer();
+
+ final MockPeerDiscoveryAgent peerDiscoveryAgent2 = helper.startDiscoveryAgent(peer);
+ peerDiscoveryAgent2.start().join();
assertThat(peerDiscoveryAgent2.getPeers().size()).isEqualTo(1);
@@ -169,16 +133,17 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
}
@Test
- public void doesNotBlacklistPeerForNormalDisconnect() throws Exception {
+ public void doesNotBlacklistPeerForNormalDisconnect() {
// Start an agent with no bootstrap peers.
final PeerBlacklist blacklist = new PeerBlacklist();
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList(), blacklist);
+ final MockPeerDiscoveryAgent agent =
+ helper.startDiscoveryAgent(Collections.emptyList(), blacklist);
// Setup peer
- final DiscoveryTestSocket peerSocket = startTestSocket();
- final PeerConnection wirePeer = createAnonymousPeerConnection(peerSocket.getPeer().getId());
+ final MockPeerDiscoveryAgent otherNode = helper.startDiscoveryAgent();
+ final PeerConnection wirePeer = createAnonymousPeerConnection(otherNode.getId());
// Bond to peer
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
assertThat(agent.getPeers()).hasSize(1);
// Disconnect with innocuous reason
@@ -188,23 +153,30 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
assertThat(agent.getPeers()).hasSize(0);
// Bond again
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
// Check peer was allowed to connect
assertThat(agent.getPeers()).hasSize(1);
}
+ protected void bondViaIncomingPing(
+ final MockPeerDiscoveryAgent agent, final MockPeerDiscoveryAgent otherNode) {
+ Packet pingPacket = helper.createPingPacket(otherNode, agent);
+ helper.sendMessageBetweenAgents(otherNode, agent, pingPacket);
+ }
+
@Test
- public void blacklistPeerForBadBehavior() throws Exception {
+ public void blacklistPeerForBadBehavior() {
// Start an agent with no bootstrap peers.
final PeerBlacklist blacklist = new PeerBlacklist();
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList(), blacklist);
+ final MockPeerDiscoveryAgent agent =
+ helper.startDiscoveryAgent(Collections.emptyList(), blacklist);
// Setup peer
- final DiscoveryTestSocket peerSocket = startTestSocket();
- final PeerConnection wirePeer = createAnonymousPeerConnection(peerSocket.getPeer().getId());
+ final MockPeerDiscoveryAgent otherNode = helper.startDiscoveryAgent();
+ final PeerConnection wirePeer = createAnonymousPeerConnection(otherNode.getId());
// Bond to peer
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
assertThat(agent.getPeers()).hasSize(1);
// Disconnect with problematic reason
@@ -214,7 +186,7 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
assertThat(agent.getPeers()).hasSize(0);
// Bond again
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
// Check peer was not allowed to connect
assertThat(agent.getPeers()).hasSize(0);
@@ -224,13 +196,14 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
public void doesNotBlacklistPeerForOurBadBehavior() throws Exception {
// Start an agent with no bootstrap peers.
final PeerBlacklist blacklist = new PeerBlacklist();
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList(), blacklist);
+ final MockPeerDiscoveryAgent agent =
+ helper.startDiscoveryAgent(Collections.emptyList(), blacklist);
// Setup peer
- final DiscoveryTestSocket peerSocket = startTestSocket();
- final PeerConnection wirePeer = createAnonymousPeerConnection(peerSocket.getPeer().getId());
+ final MockPeerDiscoveryAgent otherNode = helper.startDiscoveryAgent();
+ final PeerConnection wirePeer = createAnonymousPeerConnection(otherNode.getId());
// Bond to peer
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
assertThat(agent.getPeers()).hasSize(1);
// Disconnect with problematic reason
@@ -240,7 +213,7 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
assertThat(agent.getPeers()).hasSize(0);
// Bond again
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
// Check peer was allowed to connect
assertThat(agent.getPeers()).hasSize(1);
@@ -250,13 +223,14 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
public void blacklistIncompatiblePeer() throws Exception {
// Start an agent with no bootstrap peers.
final PeerBlacklist blacklist = new PeerBlacklist();
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList(), blacklist);
+ final MockPeerDiscoveryAgent agent =
+ helper.startDiscoveryAgent(Collections.emptyList(), blacklist);
// Setup peer
- final DiscoveryTestSocket peerSocket = startTestSocket();
- final PeerConnection wirePeer = createAnonymousPeerConnection(peerSocket.getPeer().getId());
+ final MockPeerDiscoveryAgent otherNode = helper.startDiscoveryAgent();
+ final PeerConnection wirePeer = createAnonymousPeerConnection(otherNode.getId());
// Bond to peer
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
assertThat(agent.getPeers()).hasSize(1);
// Disconnect
@@ -266,7 +240,7 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
assertThat(agent.getPeers()).hasSize(0);
// Bond again
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
// Check peer was not allowed to connect
assertThat(agent.getPeers()).hasSize(0);
@@ -276,13 +250,14 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
public void blacklistIncompatiblePeerWhoIssuesDisconnect() throws Exception {
// Start an agent with no bootstrap peers.
final PeerBlacklist blacklist = new PeerBlacklist();
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList(), blacklist);
+ final MockPeerDiscoveryAgent agent =
+ helper.startDiscoveryAgent(Collections.emptyList(), blacklist);
// Setup peer
- final DiscoveryTestSocket peerSocket = startTestSocket();
- final PeerConnection wirePeer = createAnonymousPeerConnection(peerSocket.getPeer().getId());
+ final MockPeerDiscoveryAgent otherNode = helper.startDiscoveryAgent();
+ final PeerConnection wirePeer = createAnonymousPeerConnection(otherNode.getId());
// Bond to peer
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
assertThat(agent.getPeers()).hasSize(1);
// Disconnect
@@ -292,7 +267,7 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
assertThat(agent.getPeers()).hasSize(0);
// Bond again
- bondViaIncomingPing(agent, peerSocket);
+ bondViaIncomingPing(agent, otherNode);
// Check peer was not allowed to connect
assertThat(agent.getPeers()).hasSize(0);
@@ -300,20 +275,16 @@ public class PeerDiscoveryAgentTest extends AbstractPeerDiscoveryTest {
@Test
public void shouldBeActiveWhenConfigIsTrue() {
- final DiscoveryConfiguration config = new DiscoveryConfiguration();
- config.setActive(true).setBindPort(0);
-
- final PeerDiscoveryAgent agent = startDiscoveryAgent(config, new PeerBlacklist());
+ AgentBuilder agentBuilder = helper.agentBuilder().active(true);
+ final MockPeerDiscoveryAgent agent = helper.startDiscoveryAgent(agentBuilder);
assertThat(agent.isActive()).isTrue();
}
@Test
public void shouldNotBeActiveWhenConfigIsFalse() {
- final DiscoveryConfiguration config = new DiscoveryConfiguration();
- config.setActive(false).setBindPort(0);
-
- final PeerDiscoveryAgent agent = startDiscoveryAgent(config, new PeerBlacklist());
+ AgentBuilder agentBuilder = helper.agentBuilder().active(false);
+ final MockPeerDiscoveryAgent agent = helper.startDiscoveryAgent(agentBuilder);
assertThat(agent.isActive()).isFalse();
}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryBondingTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryBondingTest.java
index 06342d8cba..f1f9f82e69 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryBondingTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryBondingTest.java
@@ -16,38 +16,43 @@ import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.FindNeighborsPacketData;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockPeerDiscoveryAgent;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockPeerDiscoveryAgent.IncomingPacket;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PacketType;
-import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PingPacketData;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PongPacketData;
import java.util.Collections;
-import java.util.concurrent.TimeUnit;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
import org.junit.Test;
-public class PeerDiscoveryBondingTest extends AbstractPeerDiscoveryTest {
+public class PeerDiscoveryBondingTest {
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Test
- public void pongSentUponPing() throws Exception {
+ public void pongSentUponPing() {
// Start an agent with no bootstrap peers.
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList());
+ final MockPeerDiscoveryAgent agent = helper.startDiscoveryAgent(Collections.emptyList());
// Start a test peer and send a PING packet to the agent under test.
- final DiscoveryTestSocket discoveryTestSocket = startTestSocket();
-
- final PingPacketData ping =
- PingPacketData.create(
- discoveryTestSocket.getPeer().getEndpoint(), agent.getAdvertisedPeer().getEndpoint());
- final Packet packet = Packet.create(PacketType.PING, ping, discoveryTestSocket.getKeyPair());
- discoveryTestSocket.sendToAgent(agent, packet);
-
- final Packet pongPacket = discoveryTestSocket.getIncomingPackets().poll(10, TimeUnit.SECONDS);
- assertThat(pongPacket.getType()).isEqualTo(PacketType.PONG);
- assertThat(pongPacket.getPacketData(PongPacketData.class)).isPresent();
-
- final PongPacketData pong = pongPacket.getPacketData(PongPacketData.class).get();
- assertThat(pong.getTo()).isEqualTo(discoveryTestSocket.getPeer().getEndpoint());
+ final MockPeerDiscoveryAgent otherAgent = helper.startDiscoveryAgent();
+ final Packet ping = helper.createPingPacket(otherAgent, agent);
+ helper.sendMessageBetweenAgents(otherAgent, agent, ping);
+
+ final List otherAgentIncomingPongs =
+ otherAgent
+ .getIncomingPackets()
+ .stream()
+ .filter(p -> p.packet.getType().equals(PacketType.PONG))
+ .collect(Collectors.toList());
+ assertThat(otherAgentIncomingPongs.size()).isEqualTo(1);
+
+ final PongPacketData pong =
+ otherAgentIncomingPongs.get(0).packet.getPacketData(PongPacketData.class).get();
+ assertThat(pong.getTo()).isEqualTo(otherAgent.getAdvertisedPeer().getEndpoint());
// The agent considers the test peer BONDED.
assertThat(agent.getPeers()).hasSize(1);
@@ -57,38 +62,38 @@ public class PeerDiscoveryBondingTest extends AbstractPeerDiscoveryTest {
@Test
public void neighborsPacketNotSentUnlessBonded() throws InterruptedException {
// Start an agent.
- final PeerDiscoveryAgent agent = startDiscoveryAgent(emptyList());
+ final MockPeerDiscoveryAgent agent = helper.startDiscoveryAgent(emptyList());
// Start a test peer that will send a FIND_NEIGHBORS to the agent under test. It should be
// ignored because
// we haven't bonded.
- final DiscoveryTestSocket discoveryTestSocket = startTestSocket();
- final FindNeighborsPacketData data =
- FindNeighborsPacketData.create(discoveryTestSocket.getPeer().getId());
- Packet packet =
- Packet.create(PacketType.FIND_NEIGHBORS, data, discoveryTestSocket.getKeyPair());
- discoveryTestSocket.sendToAgent(agent, packet);
+ final MockPeerDiscoveryAgent otherNode = helper.startDiscoveryAgent();
+ final FindNeighborsPacketData data = FindNeighborsPacketData.create(otherNode.getId());
+ Packet packet = Packet.create(PacketType.FIND_NEIGHBORS, data, otherNode.getKeyPair());
+ helper.sendMessageBetweenAgents(otherNode, agent, packet);
- // No responses received in 2 seconds.
- final Packet incoming = discoveryTestSocket.getIncomingPackets().poll(2, TimeUnit.SECONDS);
- assertThat(incoming).isNull();
+ // No responses received
+ final List incoming = otherNode.getIncomingPackets();
+ assertThat(incoming.size()).isEqualTo(0);
// Create and dispatch a PING packet.
- final PingPacketData ping =
- PingPacketData.create(
- discoveryTestSocket.getPeer().getEndpoint(), agent.getAdvertisedPeer().getEndpoint());
- packet = Packet.create(PacketType.PING, ping, discoveryTestSocket.getKeyPair());
- discoveryTestSocket.sendToAgent(agent, packet);
+ final Packet ping = helper.createPingPacket(otherNode, agent);
+ helper.sendMessageBetweenAgents(otherNode, agent, ping);
// Now we received a PONG.
- final Packet pongPacket = discoveryTestSocket.getIncomingPackets().poll(2, TimeUnit.SECONDS);
- assertThat(pongPacket.getType()).isEqualTo(PacketType.PONG);
- assertThat(pongPacket.getPacketData(PongPacketData.class)).isPresent();
-
- final PongPacketData pong = pongPacket.getPacketData(PongPacketData.class).get();
- assertThat(pong.getTo()).isEqualTo(discoveryTestSocket.getPeer().getEndpoint());
+ final List incomingPongs =
+ otherNode
+ .getIncomingPackets()
+ .stream()
+ .filter(p -> p.packet.getType().equals(PacketType.PONG))
+ .collect(Collectors.toList());
+ assertThat(incomingPongs.size()).isEqualTo(1);
+ Optional maybePongData =
+ incomingPongs.get(0).packet.getPacketData(PongPacketData.class);
+ assertThat(maybePongData).isPresent();
+ assertThat(maybePongData.get().getTo()).isEqualTo(otherNode.getAdvertisedPeer().getEndpoint());
// No more packets.
- assertThat(discoveryTestSocket.getIncomingPackets()).hasSize(0);
+ assertThat(otherNode.getIncomingPackets()).hasSize(0);
}
}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryBootstrappingTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryBootstrappingTest.java
index 92d1152800..51befa6bba 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryBootstrappingTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryBootstrappingTest.java
@@ -16,8 +16,9 @@ import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockPeerDiscoveryAgent;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockPeerDiscoveryAgent.IncomingPacket;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PacketType;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PingPacketData;
@@ -25,67 +26,71 @@ import tech.pegasys.pantheon.ethereum.p2p.peers.Peer;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
import org.junit.Test;
-public class PeerDiscoveryBootstrappingTest extends AbstractPeerDiscoveryTest {
+public class PeerDiscoveryBootstrappingTest {
+
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Test
- public void bootstrappingPingsSentSingleBootstrapPeer() throws Exception {
+ public void bootstrappingPingsSentSingleBootstrapPeer() {
// Start one test peer and use it as a bootstrap peer.
- final DiscoveryTestSocket discoveryTestSocket = startTestSocket();
- final List bootstrapPeers = singletonList(discoveryTestSocket.getPeer());
+ final MockPeerDiscoveryAgent testAgent = helper.startDiscoveryAgent();
// Start an agent.
- final PeerDiscoveryAgent agent = startDiscoveryAgent(bootstrapPeers);
-
- final Packet packet = discoveryTestSocket.getIncomingPackets().poll(2, TimeUnit.SECONDS);
+ final PeerDiscoveryAgent agent = helper.startDiscoveryAgent(testAgent.getAdvertisedPeer());
- assertThat(packet.getType()).isEqualTo(PacketType.PING);
- assertThat(packet.getNodeId()).isEqualTo(agent.getAdvertisedPeer().getId());
+ final List incomingPackets =
+ testAgent
+ .getIncomingPackets()
+ .stream()
+ .filter(p -> p.packet.getType().equals(PacketType.PING))
+ .collect(toList());
+ assertThat(incomingPackets.size()).isEqualTo(1);
+ Packet pingPacket = incomingPackets.get(0).packet;
+ assertThat(pingPacket.getNodeId()).isEqualTo(agent.getAdvertisedPeer().getId());
- final PingPacketData pingData = packet.getPacketData(PingPacketData.class).get();
+ final PingPacketData pingData = pingPacket.getPacketData(PingPacketData.class).get();
assertThat(pingData.getExpiration())
.isGreaterThanOrEqualTo(System.currentTimeMillis() / 1000 - 10000);
assertThat(pingData.getFrom()).isEqualTo(agent.getAdvertisedPeer().getEndpoint());
- assertThat(pingData.getTo()).isEqualTo(discoveryTestSocket.getPeer().getEndpoint());
+ assertThat(pingData.getTo()).isEqualTo(testAgent.getAdvertisedPeer().getEndpoint());
}
@Test
public void bootstrappingPingsSentMultipleBootstrapPeers() {
- // Start three test peers.
- startTestSockets(3);
-
// Use these peers as bootstrap peers.
+ final List bootstrapAgents = helper.startDiscoveryAgents(3);
final List bootstrapPeers =
- discoveryTestSockets.stream().map(DiscoveryTestSocket::getPeer).collect(toList());
+ bootstrapAgents.stream().map(PeerDiscoveryAgent::getAdvertisedPeer).collect(toList());
// Start five agents.
- startDiscoveryAgents(5, bootstrapPeers);
+ List agents = helper.startDiscoveryAgents(5, bootstrapPeers);
// Assert that all test peers received a Find Neighbors packet.
- for (final DiscoveryTestSocket peer : discoveryTestSockets) {
+ for (final MockPeerDiscoveryAgent bootstrapAgent : bootstrapAgents) {
// Five messages per peer (sent by each of the five agents).
- final List packets = Stream.generate(peer::compulsoryPoll).limit(5).collect(toList());
-
- // No more messages left.
- assertThat(peer.getIncomingPackets().size()).isEqualTo(0);
+ final List packets =
+ bootstrapAgent.getIncomingPackets().stream().map(p -> p.packet).collect(toList());
// Assert that the node IDs we received belong to the test agents.
- final List peerIds = packets.stream().map(Packet::getNodeId).collect(toList());
- final List nodeIds =
+ final List senderIds =
+ packets.stream().map(Packet::getNodeId).distinct().collect(toList());
+ final List agentIds =
agents
.stream()
.map(PeerDiscoveryAgent::getAdvertisedPeer)
.map(Peer::getId)
+ .distinct()
.collect(toList());
- assertThat(peerIds).containsExactlyInAnyOrderElementsOf(nodeIds);
+ assertThat(senderIds).containsExactlyInAnyOrderElementsOf(agentIds);
- // Traverse all received packets.
- for (final Packet packet : packets) {
+ // Traverse all received pings.
+ List pingPackets =
+ packets.stream().filter(p -> p.getType().equals(PacketType.PING)).collect(toList());
+ for (final Packet packet : pingPackets) {
// Assert that the packet was a Find Neighbors one.
assertThat(packet.getType()).isEqualTo(PacketType.PING);
@@ -93,7 +98,7 @@ public class PeerDiscoveryBootstrappingTest extends AbstractPeerDiscoveryTest {
final PingPacketData ping = packet.getPacketData(PingPacketData.class).get();
assertThat(ping.getExpiration())
.isGreaterThanOrEqualTo(System.currentTimeMillis() / 1000 - 10000);
- assertThat(ping.getTo()).isEqualTo(peer.getPeer().getEndpoint());
+ assertThat(ping.getTo()).isEqualTo(bootstrapAgent.getAdvertisedPeer().getEndpoint());
}
}
}
@@ -101,25 +106,18 @@ public class PeerDiscoveryBootstrappingTest extends AbstractPeerDiscoveryTest {
@Test
public void bootstrappingPeersListUpdated() {
// Start an agent.
- final PeerDiscoveryAgent bootstrapAgent = startDiscoveryAgent(emptyList());
+ final PeerDiscoveryAgent bootstrapAgent = helper.startDiscoveryAgent(emptyList());
// Start other five agents, pointing to the one above as a bootstrap peer.
- final List otherAgents =
- startDiscoveryAgents(5, singletonList(bootstrapAgent.getAdvertisedPeer()));
+ final List otherAgents =
+ helper.startDiscoveryAgents(5, singletonList(bootstrapAgent.getAdvertisedPeer()));
final BytesValue[] otherPeersIds =
- otherAgents
- .stream()
- .map(PeerDiscoveryAgent::getAdvertisedPeer)
- .map(Peer::getId)
- .toArray(BytesValue[]::new);
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(
- () ->
- assertThat(bootstrapAgent.getPeers())
- .extracting(Peer::getId)
- .containsExactlyInAnyOrder(otherPeersIds));
+ otherAgents.stream().map(PeerDiscoveryAgent::getId).toArray(BytesValue[]::new);
+
+ assertThat(bootstrapAgent.getPeers())
+ .extracting(Peer::getId)
+ .containsExactlyInAnyOrder(otherPeersIds);
assertThat(bootstrapAgent.getPeers())
.allMatch(p -> p.getStatus() == PeerDiscoveryStatus.BONDED);
@@ -128,9 +126,7 @@ public class PeerDiscoveryBootstrappingTest extends AbstractPeerDiscoveryTest {
// and will
// bond with them, ultimately adding all 7 nodes in the network to its table.
final PeerDiscoveryAgent newAgent =
- startDiscoveryAgent(singletonList(bootstrapAgent.getAdvertisedPeer()));
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(() -> assertThat(newAgent.getPeers()).hasSize(6));
+ helper.startDiscoveryAgent(bootstrapAgent.getAdvertisedPeer());
+ assertThat(newAgent.getPeers()).hasSize(6);
}
}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryObserversTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryObserversTest.java
index a6fd0080f0..81b5b734c4 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryObserversTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryObserversTest.java
@@ -12,48 +12,41 @@
*/
package tech.pegasys.pantheon.ethereum.p2p.discovery;
-import static io.vertx.core.Vertx.vertx;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static org.awaitility.Awaitility.await;
-import static tech.pegasys.pantheon.ethereum.p2p.NetworkingTestHelper.configWithRandomPorts;
-import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerBondedEvent;
-import tech.pegasys.pantheon.ethereum.p2p.peers.Peer;
-import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
-import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController;
-import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockPeerDiscoveryAgent;
+import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.awaitility.core.ConditionTimeoutException;
import org.junit.Test;
-public class PeerDiscoveryObserversTest extends AbstractPeerDiscoveryTest {
+public class PeerDiscoveryObserversTest {
private static final Logger LOG = LogManager.getLogger();
private static final int BROADCAST_TCP_PORT = 26422;
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Test
public void addAndRemoveObservers() {
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList());
+ final MockPeerDiscoveryAgent agent = helper.startDiscoveryAgent(Collections.emptyList());
assertThat(agent.getObserverCount()).isEqualTo(0);
final long id1 = agent.observePeerBondedEvents((event) -> {});
final long id2 = agent.observePeerBondedEvents((event) -> {});
final long id3 = agent.observePeerBondedEvents((event) -> {});
- final long id4 = agent.observePeerDroppedEvents((event) -> {});
- final long id5 = agent.observePeerDroppedEvents((event) -> {});
- final long id6 = agent.observePeerDroppedEvents((event) -> {});
+ final long id4 = agent.observePeerBondedEvents((event) -> {});
+ final long id5 = agent.observePeerBondedEvents((event) -> {});
+ final long id6 = agent.observePeerBondedEvents((event) -> {});
assertThat(agent.getObserverCount()).isEqualTo(6);
agent.removePeerBondedObserver(id1);
@@ -61,25 +54,25 @@ public class PeerDiscoveryObserversTest extends AbstractPeerDiscoveryTest {
assertThat(agent.getObserverCount()).isEqualTo(4);
agent.removePeerBondedObserver(id3);
- agent.removePeerDroppedObserver(id4);
+ agent.removePeerBondedObserver(id4);
assertThat(agent.getObserverCount()).isEqualTo(2);
- agent.removePeerDroppedObserver(id5);
- agent.removePeerDroppedObserver(id6);
+ agent.removePeerBondedObserver(id5);
+ agent.removePeerBondedObserver(id6);
assertThat(agent.getObserverCount()).isEqualTo(0);
final long id7 = agent.observePeerBondedEvents((event) -> {});
- final long id8 = agent.observePeerDroppedEvents((event) -> {});
+ final long id8 = agent.observePeerBondedEvents((event) -> {});
assertThat(agent.getObserverCount()).isEqualTo(2);
agent.removePeerBondedObserver(id7);
- agent.removePeerDroppedObserver(id8);
+ agent.removePeerBondedObserver(id8);
assertThat(agent.getObserverCount()).isEqualTo(0);
}
@Test
public void removeInexistingObserver() {
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList());
+ final MockPeerDiscoveryAgent agent = helper.startDiscoveryAgent(Collections.emptyList());
assertThat(agent.getObserverCount()).isEqualTo(0);
agent.observePeerBondedEvents((event) -> {});
@@ -89,14 +82,21 @@ public class PeerDiscoveryObserversTest extends AbstractPeerDiscoveryTest {
@Test
public void peerBondedObserverTriggered() throws TimeoutException, InterruptedException {
// Create 3 discovery agents with no bootstrap peers.
- final List others1 = startDiscoveryAgents(3, Collections.emptyList());
+ final List others1 =
+ helper.startDiscoveryAgents(3, Collections.emptyList());
final List peers1 =
- others1.stream().map(PeerDiscoveryAgent::getAdvertisedPeer).collect(Collectors.toList());
+ others1
+ .stream()
+ .map(MockPeerDiscoveryAgent::getAdvertisedPeer)
+ .collect(Collectors.toList());
// Create two discovery agents pointing to the above as bootstrap peers.
- final List others2 = startDiscoveryAgents(2, peers1);
+ final List others2 = helper.startDiscoveryAgents(2, peers1);
final List peers2 =
- others2.stream().map(PeerDiscoveryAgent::getAdvertisedPeer).collect(Collectors.toList());
+ others2
+ .stream()
+ .map(MockPeerDiscoveryAgent::getAdvertisedPeer)
+ .collect(Collectors.toList());
// A list of all peers.
final List allPeers = new ArrayList<>(peers1);
@@ -104,42 +104,26 @@ public class PeerDiscoveryObserversTest extends AbstractPeerDiscoveryTest {
// Create a discovery agent (which we'll assert on), using the above two peers as bootstrap
// peers.
- final PeerDiscoveryAgent agent =
- new PeerDiscoveryAgent(
- vertx(),
- SECP256K1.KeyPair.generate(),
- configWithRandomPorts().getDiscovery().setBootstrapPeers(peers2),
- () -> true,
- new PeerBlacklist(),
- new NodeWhitelistController(PermissioningConfiguration.createDefault()));
-
+ final MockPeerDiscoveryAgent agent = helper.createDiscoveryAgent(peers2);
// A queue for storing peer bonded events.
- final ArrayBlockingQueue queue = new ArrayBlockingQueue<>(10);
- agent.observePeerBondedEvents(queue::add);
- assertThatCode(() -> agent.start(BROADCAST_TCP_PORT).get(5, TimeUnit.SECONDS))
- .doesNotThrowAnyException();
-
- // Wait until we've received 5 events.
- try {
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(() -> assertThat(queue.size()).isEqualTo(5));
- } catch (final ConditionTimeoutException | AssertionError e) {
- final List events = new ArrayList<>();
- queue.forEach(evt -> events.add(evt.toString()));
- LOG.error("Queue:\n" + String.join("\n", events), e);
- throw e;
- }
- // Wait one second and check we've received no more events.
- Thread.sleep(1000);
- assertThat(queue.size()).isEqualTo(5);
-
- // Extract all events and perform asserts on them.
- final List events = new ArrayList<>(5);
- queue.drainTo(events, 5);
+ final List events = new ArrayList<>(10);
+ agent.observePeerBondedEvents(events::add);
+ agent.start();
+
+ final HashSet seenPeers = new HashSet<>();
+ List discoveredPeers =
+ events
+ .stream()
+ .map(PeerDiscoveryEvent::getPeer)
+ // We emit some duplicate events when the tcp port differs (in terms of presence) for a
+ // peer,
+ // filter peers by id to remove duplicates (See: DefaultPeer::equals).
+ // TODO: Should we evaluate peer equality based on id??
+ .filter((p) -> seenPeers.add(p.getId()))
+ .collect(Collectors.toList());
+ assertThat(discoveredPeers.size()).isEqualTo(allPeers.size());
- assertThat(events)
- .extracting(PeerDiscoveryEvent::getPeer)
+ assertThat(discoveredPeers)
.extracting(DiscoveryPeer::getId)
.containsExactlyInAnyOrderElementsOf(
allPeers.stream().map(DiscoveryPeer::getId).collect(Collectors.toList()));
@@ -149,38 +133,33 @@ public class PeerDiscoveryObserversTest extends AbstractPeerDiscoveryTest {
@Test
public void multiplePeerBondedObserversTriggered() {
// Create 3 discovery agents with no bootstrap peers.
- final List others = startDiscoveryAgents(3, Collections.emptyList());
- final Peer peer = others.stream().map(PeerDiscoveryAgent::getAdvertisedPeer).findFirst().get();
+ final List others =
+ helper.startDiscoveryAgents(3, Collections.emptyList());
+ final DiscoveryPeer peer = others.get(0).getAdvertisedPeer();
// Create a discovery agent (which we'll assert on), using the above two peers as bootstrap
// peers.
- final PeerDiscoveryAgent agent =
- new PeerDiscoveryAgent(
- vertx(),
- SECP256K1.KeyPair.generate(),
- configWithRandomPorts()
- .getDiscovery()
- .setBootstrapPeers(Collections.singletonList(peer)),
- () -> true,
- new PeerBlacklist(),
- new NodeWhitelistController(PermissioningConfiguration.createDefault()));
+ final MockPeerDiscoveryAgent agent = helper.createDiscoveryAgent(peer);
// Create 5 queues and subscribe them to peer bonded events.
- final List> queues =
- Stream.generate(() -> new ArrayBlockingQueue(10))
+ final List> queues =
+ Stream.generate(() -> new ArrayList(10))
.limit(5)
.collect(Collectors.toList());
queues.forEach(q -> agent.observePeerBondedEvents(q::add));
// Start the agent and wait until each queue receives one event.
- agent.start(BROADCAST_TCP_PORT);
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(() -> assertThat(queues).allMatch(q -> q.size() == 1));
+ agent.start();
+ for (List eventQueue : queues) {
+ assertThat(eventQueue.size()).isEqualTo(1);
+ }
// All events are for the same peer.
final List events =
- queues.stream().map(ArrayBlockingQueue::poll).collect(Collectors.toList());
+ Stream.of(queues)
+ .flatMap(Collection::stream)
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
assertThat(events).extracting(PeerDiscoveryEvent::getPeer).allMatch(p -> p.equals(peer));
// We can event check that the event instance is the same across all queues.
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryPacketSedesTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryPacketSedesTest.java
index 934f9889a3..7e830443d8 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryPacketSedesTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryPacketSedesTest.java
@@ -15,8 +15,6 @@ package tech.pegasys.pantheon.ethereum.p2p.discovery;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;
import static org.junit.Assert.assertNotNull;
-import static tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper.generateKeyPairs;
-import static tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper.generatePeers;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.FindNeighborsPacketData;
@@ -29,7 +27,6 @@ import tech.pegasys.pantheon.ethereum.rlp.RLPException;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.MutableBytesValue;
-import java.util.Arrays;
import java.util.List;
import java.util.Random;
@@ -37,6 +34,7 @@ import io.vertx.core.buffer.Buffer;
import org.junit.Test;
public class PeerDiscoveryPacketSedesTest {
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Test
public void serializeDeserializeEntirePacket() {
@@ -79,7 +77,7 @@ public class PeerDiscoveryPacketSedesTest {
@Test
public void neighborsPacketData() {
- final List peers = Arrays.asList(generatePeers(generateKeyPairs(5)));
+ final List peers = helper.createDiscoveryPeers(5);
final NeighborsPacketData packet = NeighborsPacketData.create(peers);
final BytesValue serialized = RLP.encode(packet::writeTo);
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTestHelper.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTestHelper.java
index 0f1ab660a5..0951655df6 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTestHelper.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTestHelper.java
@@ -13,27 +13,206 @@
package tech.pegasys.pantheon.ethereum.p2p.discovery;
import tech.pegasys.pantheon.crypto.SECP256K1;
-import tech.pegasys.pantheon.ethereum.p2p.peers.Endpoint;
+import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
+import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockPeerDiscoveryAgent;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PacketType;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PingPacketData;
+import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
+import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController;
+import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
+import tech.pegasys.pantheon.util.bytes.BytesValue;
-import java.util.OptionalInt;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
public class PeerDiscoveryTestHelper {
+ private static final String LOOPBACK_IP_ADDR = "127.0.0.1";
- public static SECP256K1.KeyPair[] generateKeyPairs(final int count) {
- return Stream.generate(SECP256K1.KeyPair::generate)
+ private final AtomicInteger nextAvailablePort = new AtomicInteger(1);
+ Map agents = new HashMap<>();
+
+ public static List generateKeyPairs(final int count) {
+ return Stream.generate(SECP256K1.KeyPair::generate).limit(count).collect(Collectors.toList());
+ }
+
+ /**
+ * Starts multiple discovery agents from generated peers.
+ *
+ * @param count the number of agents to start
+ * @return a list of discovery agents.
+ */
+ public List createDiscoveryPeers(final int count) {
+ return Stream.generate(this::createDiscoveryPeer).limit(count).collect(Collectors.toList());
+ }
+
+ public List createDiscoveryPeers(final List keyPairs) {
+ return keyPairs.stream().map(this::createDiscoveryPeer).collect(Collectors.toList());
+ }
+
+ public DiscoveryPeer createDiscoveryPeer() {
+ return createDiscoveryPeer(KeyPair.generate());
+ }
+
+ public DiscoveryPeer createDiscoveryPeer(final KeyPair keyPair) {
+ final BytesValue peerId = keyPair.getPublicKey().getEncodedBytes();
+ final int port = nextAvailablePort.incrementAndGet();
+ return new DiscoveryPeer(peerId, LOOPBACK_IP_ADDR, port, port);
+ }
+
+ public Packet createPingPacket(
+ final MockPeerDiscoveryAgent fromAgent, final MockPeerDiscoveryAgent toAgent) {
+ return Packet.create(
+ PacketType.PING,
+ PingPacketData.create(
+ fromAgent.getAdvertisedPeer().getEndpoint(), toAgent.getAdvertisedPeer().getEndpoint()),
+ fromAgent.getKeyPair());
+ }
+
+ public AgentBuilder agentBuilder() {
+ return new AgentBuilder(agents, nextAvailablePort);
+ }
+
+ public void sendMessageBetweenAgents(
+ final MockPeerDiscoveryAgent fromAgent,
+ final MockPeerDiscoveryAgent toAgent,
+ final Packet packet) {
+ toAgent.processIncomingPacket(fromAgent, packet);
+ }
+
+ /**
+ * Starts multiple discovery agents with the provided boostrap peers.
+ *
+ * @param count the number of agents to start
+ * @param bootstrapPeers the list of bootstrap peers
+ * @return a list of discovery agents.
+ */
+ public List startDiscoveryAgents(
+ final int count, final List bootstrapPeers) {
+ return Stream.generate(() -> startDiscoveryAgent(bootstrapPeers))
.limit(count)
- .toArray(SECP256K1.KeyPair[]::new);
+ .collect(Collectors.toList());
+ }
+
+ public List startDiscoveryAgents(final int count) {
+ return Stream.generate(() -> startDiscoveryAgent(Collections.emptyList()))
+ .limit(count)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Start a single discovery agent with the provided bootstrap peers.
+ *
+ * @param bootstrapPeers the list of bootstrap peers
+ * @return a list of discovery agents.
+ */
+ public MockPeerDiscoveryAgent startDiscoveryAgent(final List bootstrapPeers) {
+ AgentBuilder agentBuilder = agentBuilder().bootstrapPeers(bootstrapPeers);
+
+ return startDiscoveryAgent(agentBuilder);
}
- public static DiscoveryPeer[] generatePeers(final SECP256K1.KeyPair... keypairs) {
- return Stream.of(keypairs)
- .map(kp -> kp.getPublicKey().getEncodedBytes())
- .map(bytes -> new DiscoveryPeer(bytes, new Endpoint("127.0.0.1", 1, OptionalInt.empty())))
- .toArray(DiscoveryPeer[]::new);
+ public MockPeerDiscoveryAgent startDiscoveryAgent(final DiscoveryPeer... bootstrapPeers) {
+ AgentBuilder agentBuilder = agentBuilder().bootstrapPeers(bootstrapPeers);
+
+ return startDiscoveryAgent(agentBuilder);
+ }
+
+ /**
+ * Start a single discovery agent with the provided bootstrap peers.
+ *
+ * @param bootstrapPeers the list of bootstrap peers
+ * @param blacklist the peer blacklist
+ * @return a list of discovery agents.
+ */
+ public MockPeerDiscoveryAgent startDiscoveryAgent(
+ final List bootstrapPeers, final PeerBlacklist blacklist) {
+ AgentBuilder agentBuilder = agentBuilder().bootstrapPeers(bootstrapPeers).blacklist(blacklist);
+
+ return startDiscoveryAgent(agentBuilder);
+ }
+
+ public MockPeerDiscoveryAgent startDiscoveryAgent(final AgentBuilder agentBuilder) {
+ final MockPeerDiscoveryAgent agent = createDiscoveryAgent(agentBuilder);
+ agent.start();
+ return agent;
+ }
+
+ public MockPeerDiscoveryAgent createDiscoveryAgent(final List bootstrapPeers) {
+ AgentBuilder agentBuilder = agentBuilder().bootstrapPeers(bootstrapPeers);
+
+ return createDiscoveryAgent(agentBuilder);
+ }
+
+ public MockPeerDiscoveryAgent createDiscoveryAgent(final DiscoveryPeer... bootstrapPeers) {
+ AgentBuilder agentBuilder = agentBuilder().bootstrapPeers(bootstrapPeers);
+
+ return createDiscoveryAgent(agentBuilder);
}
- public static DiscoveryPeer[] generateDiscoveryPeers(final SECP256K1.KeyPair... keypairs) {
- return Stream.of(generatePeers(keypairs)).map(DiscoveryPeer::new).toArray(DiscoveryPeer[]::new);
+ public MockPeerDiscoveryAgent createDiscoveryAgent(final AgentBuilder agentBuilder) {
+ final MockPeerDiscoveryAgent agent = agentBuilder.build();
+ agents.put(agent.getId(), agent);
+ return agent;
+ }
+
+ public static class AgentBuilder {
+ private final Map agents;
+ private final AtomicInteger nextAvailablePort;
+
+ private PeerBlacklist blacklist = new PeerBlacklist();
+ private NodeWhitelistController whitelist =
+ new NodeWhitelistController(PermissioningConfiguration.createDefault());
+ private List bootstrapPeers = Collections.emptyList();
+ private boolean active = true;
+
+ public AgentBuilder(
+ final Map agents,
+ final AtomicInteger nextAvailablePort) {
+ this.agents = agents;
+ this.nextAvailablePort = nextAvailablePort;
+ }
+
+ public AgentBuilder bootstrapPeers(final List peers) {
+ this.bootstrapPeers = peers;
+ return this;
+ }
+
+ public AgentBuilder bootstrapPeers(final DiscoveryPeer... peers) {
+ this.bootstrapPeers = Arrays.asList(peers);
+ return this;
+ }
+
+ public AgentBuilder whiteList(final NodeWhitelistController whitelist) {
+ this.whitelist = whitelist;
+ return this;
+ }
+
+ public AgentBuilder blacklist(final PeerBlacklist blacklist) {
+ this.blacklist = blacklist;
+ return this;
+ }
+
+ public AgentBuilder active(final boolean active) {
+ this.active = active;
+ return this;
+ }
+
+ public MockPeerDiscoveryAgent build() {
+ final DiscoveryConfiguration config = new DiscoveryConfiguration();
+ config.setBootstrapPeers(bootstrapPeers);
+ config.setBindPort(nextAvailablePort.incrementAndGet());
+ config.setActive(active);
+
+ return new MockPeerDiscoveryAgent(
+ SECP256K1.KeyPair.generate(), config, () -> true, blacklist, whitelist, agents);
+ }
}
}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java
index 98f34ec3c9..b1a4c6eb12 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java
@@ -13,11 +13,13 @@
package tech.pegasys.pantheon.ethereum.p2p.discovery;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import tech.pegasys.pantheon.crypto.SECP256K1;
+import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockPeerDiscoveryAgent;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.MockTimerUtil;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.OutboundMessageHandler;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PacketType;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerDiscoveryController;
@@ -26,126 +28,107 @@ import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PingPacketData;
import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
+import tech.pegasys.pantheon.util.Subscribers;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
-import io.vertx.core.Vertx;
import org.junit.Test;
-public class PeerDiscoveryTimestampsTest extends AbstractPeerDiscoveryTest {
+public class PeerDiscoveryTimestampsTest {
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Test
public void lastSeenAndFirstDiscoveredTimestampsUpdatedOnMessage() {
// peer[0] => controller // peer[1] => sender
- final SECP256K1.KeyPair[] keypairs = PeerDiscoveryTestHelper.generateKeyPairs(2);
- final DiscoveryPeer[] peers = PeerDiscoveryTestHelper.generateDiscoveryPeers(keypairs);
+ final List keypairs = PeerDiscoveryTestHelper.generateKeyPairs(2);
+ final List peers = helper.createDiscoveryPeers(keypairs);
- final PeerDiscoveryAgent agent = mock(PeerDiscoveryAgent.class);
- when(agent.getAdvertisedPeer()).thenReturn(peers[0]);
+ final MockPeerDiscoveryAgent agent = mock(MockPeerDiscoveryAgent.class);
+ when(agent.getAdvertisedPeer()).thenReturn(peers.get(0));
+ DiscoveryPeer localPeer = peers.get(0);
+ KeyPair localKeyPair = keypairs.get(0);
final PeerDiscoveryController controller =
new PeerDiscoveryController(
- mock(Vertx.class),
- agent,
+ localKeyPair,
+ localPeer,
new PeerTable(agent.getAdvertisedPeer().getId()),
Collections.emptyList(),
+ OutboundMessageHandler.NOOP,
+ new MockTimerUtil(),
TimeUnit.HOURS.toMillis(1),
() -> true,
new PeerBlacklist(),
- new NodeWhitelistController(PermissioningConfiguration.createDefault()));
+ new NodeWhitelistController(PermissioningConfiguration.createDefault()),
+ new Subscribers<>());
controller.start();
final PingPacketData ping =
- PingPacketData.create(peers[1].getEndpoint(), peers[0].getEndpoint());
- final Packet packet = Packet.create(PacketType.PING, ping, keypairs[1]);
+ PingPacketData.create(peers.get(1).getEndpoint(), peers.get(0).getEndpoint());
+ final Packet packet = Packet.create(PacketType.PING, ping, keypairs.get(1));
- controller.onMessage(packet, peers[1]);
+ controller.onMessage(packet, peers.get(1));
final AtomicLong lastSeen = new AtomicLong();
final AtomicLong firstDiscovered = new AtomicLong();
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- assertThat(controller.getPeers()).hasSize(1);
+ assertThat(controller.getPeers()).hasSize(1);
- final DiscoveryPeer p = controller.getPeers().iterator().next();
- assertThat(p.getLastSeen()).isGreaterThan(0);
- assertThat(p.getFirstDiscovered()).isGreaterThan(0);
+ DiscoveryPeer p = controller.getPeers().iterator().next();
+ assertThat(p.getLastSeen()).isGreaterThan(0);
+ assertThat(p.getFirstDiscovered()).isGreaterThan(0);
- lastSeen.set(p.getLastSeen());
- firstDiscovered.set(p.getFirstDiscovered());
- });
+ lastSeen.set(p.getLastSeen());
+ firstDiscovered.set(p.getFirstDiscovered());
- controller.onMessage(packet, peers[1]);
+ controller.onMessage(packet, peers.get(1));
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- assertThat(controller.getPeers()).hasSize(1);
+ assertThat(controller.getPeers()).hasSize(1);
- final DiscoveryPeer p = controller.getPeers().iterator().next();
- assertThat(p.getLastSeen()).isGreaterThan(lastSeen.get());
- assertThat(p.getFirstDiscovered()).isEqualTo(firstDiscovered.get());
- });
+ p = controller.getPeers().iterator().next();
+ assertThat(p.getLastSeen()).isGreaterThan(lastSeen.get());
+ assertThat(p.getFirstDiscovered()).isEqualTo(firstDiscovered.get());
}
@Test
public void lastContactedTimestampUpdatedOnOutboundMessage() {
- final PeerDiscoveryAgent agent = startDiscoveryAgent(Collections.emptyList());
+ final MockPeerDiscoveryAgent agent = helper.startDiscoveryAgent(Collections.emptyList());
assertThat(agent.getPeers()).hasSize(0);
// Start a test peer and send a PING packet to the agent under test.
- final DiscoveryTestSocket discoveryTestSocket = startTestSocket();
+ final MockPeerDiscoveryAgent testAgent = helper.startDiscoveryAgent();
+ final Packet ping = helper.createPingPacket(testAgent, agent);
+ helper.sendMessageBetweenAgents(testAgent, agent, ping);
- final PingPacketData ping =
- PingPacketData.create(
- discoveryTestSocket.getPeer().getEndpoint(), agent.getAdvertisedPeer().getEndpoint());
- final Packet packet = Packet.create(PacketType.PING, ping, discoveryTestSocket.getKeyPair());
- discoveryTestSocket.sendToAgent(agent, packet);
-
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(() -> assertThat(agent.getPeers()).hasSize(1));
+ assertThat(agent.getPeers()).hasSize(1);
final AtomicLong lastContacted = new AtomicLong();
final AtomicLong lastSeen = new AtomicLong();
final AtomicLong firstDiscovered = new AtomicLong();
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- final DiscoveryPeer peer = agent.getPeers().iterator().next();
- final long lc = peer.getLastContacted();
- final long ls = peer.getLastSeen();
- final long fd = peer.getFirstDiscovered();
+ DiscoveryPeer peer = agent.getPeers().iterator().next();
+ final long lc = peer.getLastContacted();
+ final long ls = peer.getLastSeen();
+ final long fd = peer.getFirstDiscovered();
- assertThat(lc).isGreaterThan(0);
- assertThat(ls).isGreaterThan(0);
- assertThat(fd).isGreaterThan(0);
+ assertThat(lc).isGreaterThan(0);
+ assertThat(ls).isGreaterThan(0);
+ assertThat(fd).isGreaterThan(0);
- lastContacted.set(lc);
- lastSeen.set(ls);
- firstDiscovered.set(fd);
- });
+ lastContacted.set(lc);
+ lastSeen.set(ls);
+ firstDiscovered.set(fd);
// Send another packet and ensure that timestamps are updated accordingly.
- discoveryTestSocket.sendToAgent(agent, packet);
-
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- final DiscoveryPeer peer = agent.getPeers().iterator().next();
-
- assertThat(peer.getLastContacted()).isGreaterThan(lastContacted.get());
- assertThat(peer.getLastSeen()).isGreaterThan(lastSeen.get());
- assertThat(peer.getFirstDiscovered()).isEqualTo(firstDiscovered.get());
- });
+ helper.sendMessageBetweenAgents(testAgent, agent, ping);
+
+ peer = agent.getPeers().iterator().next();
+
+ assertThat(peer.getLastContacted()).isGreaterThan(lastContacted.get());
+ assertThat(peer.getLastSeen()).isGreaterThan(lastSeen.get());
+ assertThat(peer.getFirstDiscovered()).isEqualTo(firstDiscovered.get());
}
}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/BucketTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/BucketTest.java
index 73a809142b..8a2421fd3d 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/BucketTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/BucketTest.java
@@ -15,26 +15,26 @@ package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
import static junit.framework.TestCase.assertFalse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper.generateDiscoveryPeers;
-import static tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper.generateKeyPairs;
import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper;
+import java.util.List;
import java.util.Optional;
-import java.util.stream.Stream;
import org.junit.Test;
public class BucketTest {
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Test
public void successfulAddAndGet() {
final Bucket kBucket = new Bucket(16);
- final DiscoveryPeer[] peers = generateDiscoveryPeers(generateKeyPairs(10));
- for (int i = 0; i < peers.length - 1; i++) {
- kBucket.add(peers[i]);
+ final List peers = helper.createDiscoveryPeers(10);
+ for (int i = 0; i < peers.size() - 1; i++) {
+ kBucket.add(peers.get(i));
}
- final DiscoveryPeer testPeer = peers[peers.length - 1];
+ final DiscoveryPeer testPeer = peers.get(peers.size() - 1);
kBucket.add(testPeer);
assertThat(testPeer).isEqualTo(kBucket.getAndTouch(testPeer.getId()).get());
}
@@ -42,48 +42,48 @@ public class BucketTest {
@Test
public void unsuccessfulAdd() {
final Bucket kBucket = new Bucket(16);
- final DiscoveryPeer[] peers = generateDiscoveryPeers(generateKeyPairs(17));
- for (int i = 0; i < peers.length - 1; i++) {
- kBucket.add(peers[i]);
+ final List peers = helper.createDiscoveryPeers(17);
+ for (int i = 0; i < peers.size() - 1; i++) {
+ kBucket.add(peers.get(i));
}
- final DiscoveryPeer testPeer = peers[peers.length - 1];
+ final DiscoveryPeer testPeer = peers.get(peers.size() - 1);
final Optional evictionCandidate = kBucket.add(testPeer);
- assertThat(evictionCandidate.get()).isEqualTo(kBucket.getAndTouch(peers[0].getId()).get());
+ assertThat(evictionCandidate.get()).isEqualTo(kBucket.getAndTouch(peers.get(0).getId()).get());
}
@Test
public void movedToHead() {
final Bucket kBucket = new Bucket(16);
- final DiscoveryPeer[] peers = generateDiscoveryPeers(generateKeyPairs(5));
+ final List peers = helper.createDiscoveryPeers(5);
for (final DiscoveryPeer peer : peers) {
kBucket.add(peer);
}
- kBucket.getAndTouch(peers[0].getId());
- assertThat(kBucket.peers().indexOf(peers[0])).isEqualTo(0);
+ kBucket.getAndTouch(peers.get(0).getId());
+ assertThat(kBucket.peers().indexOf(peers.get(0))).isEqualTo(0);
}
@Test
public void evictPeer() {
final Bucket kBucket = new Bucket(16);
- final DiscoveryPeer[] peers = generateDiscoveryPeers(generateKeyPairs(5));
+ final List peers = helper.createDiscoveryPeers(5);
for (final DiscoveryPeer p : peers) {
kBucket.add(p);
}
- kBucket.evict(peers[4]);
- assertFalse(kBucket.peers().contains(peers[4]));
+ kBucket.evict(peers.get(4));
+ assertFalse(kBucket.peers().contains(peers.get(4)));
}
@Test
public void allActionsOnBucket() {
final Bucket kBucket = new Bucket(16);
- final DiscoveryPeer[] peers = generateDiscoveryPeers(generateKeyPairs(30));
+ final List peers = helper.createDiscoveryPeers(30);
// Try to evict a peer on an empty bucket.
- assertThat(kBucket.evict(peers[29])).isFalse();
+ assertThat(kBucket.evict(peers.get(29))).isFalse();
// Add the first 16 peers to the bucket.
- Stream.of(peers)
- .limit(16)
+ peers
+ .subList(0, 16)
.forEach(
p -> {
assertThat(kBucket.getAndTouch(p.getId())).isNotPresent();
@@ -93,42 +93,57 @@ public class BucketTest {
});
// Ensure the peer is not there already.
- assertThat(kBucket.getAndTouch(peers[16].getId())).isNotPresent();
+ assertThat(kBucket.getAndTouch(peers.get(16).getId())).isNotPresent();
// Try to add a 17th peer and check that the eviction candidate matches the first peer.
- final Optional evictionCandidate = kBucket.add(peers[16]);
- assertThat(evictionCandidate).isPresent().get().isEqualTo(peers[0]);
+ final Optional evictionCandidate = kBucket.add(peers.get(16));
+ assertThat(evictionCandidate).isPresent().get().isEqualTo(peers.get(0));
// Try to add a peer that already exists, and check that the bucket size still remains capped at
// 16.
- assertThatThrownBy(() -> kBucket.add(peers[0])).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> kBucket.add(peers.get(0)))
+ .isInstanceOf(IllegalArgumentException.class);
assertThat(kBucket.peers()).hasSize(16);
// Try to evict a peer that doesn't exist, and check the result is false.
- assertThat(kBucket.evict(peers[17])).isFalse();
+ assertThat(kBucket.evict(peers.get(17))).isFalse();
assertThat(kBucket.peers()).hasSize(16);
// Evict a peer from head, another from the middle, and the tail.
- assertThat(kBucket.evict(peers[0])).isTrue();
+ assertThat(kBucket.evict(peers.get(0))).isTrue();
assertThat(kBucket.peers()).hasSize(15);
- assertThat(kBucket.evict(peers[7])).isTrue();
+ assertThat(kBucket.evict(peers.get(7))).isTrue();
assertThat(kBucket.peers()).hasSize(14);
- assertThat(kBucket.evict(peers[15])).isTrue();
+ assertThat(kBucket.evict(peers.get(15))).isTrue();
assertThat(kBucket.peers()).hasSize(13);
// Check that we can now add peers again.
- assertThat(kBucket.add(peers[0])).isNotPresent();
- assertThat(kBucket.add(peers[7])).isNotPresent();
- assertThat(kBucket.add(peers[15])).isNotPresent();
- assertThat(kBucket.add(peers[17])).isPresent().get().isEqualTo(peers[1]);
+ assertThat(kBucket.add(peers.get(0))).isNotPresent();
+ assertThat(kBucket.add(peers.get(7))).isNotPresent();
+ assertThat(kBucket.add(peers.get(15))).isNotPresent();
+ assertThat(kBucket.add(peers.get(17))).isPresent().get().isEqualTo(peers.get(1));
// Test the touch behaviour.
- assertThat(kBucket.getAndTouch(peers[6].getId())).isPresent().get().isEqualTo(peers[6]);
- assertThat(kBucket.getAndTouch(peers[9].getId())).isPresent().get().isEqualTo(peers[9]);
+ assertThat(kBucket.getAndTouch(peers.get(6).getId())).isPresent().get().isEqualTo(peers.get(6));
+ assertThat(kBucket.getAndTouch(peers.get(9).getId())).isPresent().get().isEqualTo(peers.get(9));
assertThat(kBucket.peers())
.containsSequence(
- peers[9], peers[6], peers[15], peers[7], peers[0], peers[14], peers[13], peers[12],
- peers[11], peers[10], peers[8], peers[5], peers[4], peers[3], peers[2], peers[1]);
+ peers.get(9),
+ peers.get(6),
+ peers.get(15),
+ peers.get(7),
+ peers.get(0),
+ peers.get(14),
+ peers.get(13),
+ peers.get(12),
+ peers.get(11),
+ peers.get(10),
+ peers.get(8),
+ peers.get(5),
+ peers.get(4),
+ peers.get(3),
+ peers.get(2),
+ peers.get(1));
}
}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/MockPeerDiscoveryAgent.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/MockPeerDiscoveryAgent.java
new file mode 100644
index 0000000000..73f1af667d
--- /dev/null
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/MockPeerDiscoveryAgent.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2019 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
+
+import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
+import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
+import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryAgent;
+import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
+import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController;
+import tech.pegasys.pantheon.util.bytes.BytesValue;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+public class MockPeerDiscoveryAgent extends PeerDiscoveryAgent {
+ // The set of known agents operating on the network
+ private final Map agentNetwork;
+ private final Deque incomingPackets = new ArrayDeque<>();
+
+ public MockPeerDiscoveryAgent(
+ final KeyPair keyPair,
+ final DiscoveryConfiguration config,
+ final PeerRequirement peerRequirement,
+ final PeerBlacklist peerBlacklist,
+ final NodeWhitelistController nodeWhitelistController,
+ final Map agentNetwork) {
+ super(keyPair, config, peerRequirement, peerBlacklist, nodeWhitelistController);
+ this.agentNetwork = agentNetwork;
+ }
+
+ public void processIncomingPacket(final MockPeerDiscoveryAgent fromAgent, final Packet packet) {
+ // Cycle packet through encode / decode to make clone of any data
+ // This ensures that any data passed between agents is not shared
+ final Packet packetClone = Packet.decode(packet.encode());
+ incomingPackets.add(new IncomingPacket(fromAgent, packetClone));
+ handleIncomingPacket(fromAgent.getAdvertisedPeer().getEndpoint(), packetClone);
+ }
+
+ /**
+ * Get and clear the list of any incoming packets to this agent.
+ *
+ * @return A list of packets received by this agent
+ */
+ public List getIncomingPackets() {
+ List packets = Arrays.asList(incomingPackets.toArray(new IncomingPacket[0]));
+ incomingPackets.clear();
+ return packets;
+ }
+
+ @Override
+ protected CompletableFuture listenForConnections() {
+ // Skip network setup for tests
+ InetSocketAddress address =
+ new InetSocketAddress(config.getAdvertisedHost(), config.getBindPort());
+ return CompletableFuture.completedFuture(address);
+ }
+
+ @Override
+ protected CompletableFuture sendOutgoingPacket(
+ final DiscoveryPeer toPeer, final Packet packet) {
+ CompletableFuture result = new CompletableFuture<>();
+ MockPeerDiscoveryAgent toAgent = agentNetwork.get(toPeer.getId());
+ if (toAgent == null) {
+ result.completeExceptionally(
+ new Exception(
+ "Attempt to send to unknown peer. Agents must be constructed through PeerDiscoveryTestHelper."));
+ } else {
+ toAgent.processIncomingPacket(this, packet);
+ result.complete(null);
+ }
+ return result;
+ }
+
+ @Override
+ protected TimerUtil createTimer() {
+ return new MockTimerUtil();
+ }
+
+ @Override
+ public CompletableFuture> stop() {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ public KeyPair getKeyPair() {
+ return keyPair;
+ }
+
+ public static class IncomingPacket {
+ public final MockPeerDiscoveryAgent fromAgent;
+ public final Packet packet;
+
+ public IncomingPacket(final MockPeerDiscoveryAgent fromAgent, final Packet packet) {
+ this.fromAgent = fromAgent;
+ this.packet = packet;
+ }
+ }
+}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/MockTimerUtil.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/MockTimerUtil.java
new file mode 100644
index 0000000000..a7a03e016d
--- /dev/null
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/MockTimerUtil.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MockTimerUtil implements TimerUtil {
+ private final AtomicLong nextId = new AtomicLong(0);
+ private final Map timerHandlers = new HashMap<>();
+ private final Map periodicHandlers = new HashMap<>();
+
+ @Override
+ public long setPeriodic(final long delay, final TimerHandler handler) {
+ long id = nextId.incrementAndGet();
+ periodicHandlers.put(id, handler);
+ return id;
+ }
+
+ @Override
+ public long setTimer(final long delay, final TimerHandler handler) {
+ long id = nextId.incrementAndGet();
+ timerHandlers.put(id, handler);
+ return id;
+ }
+
+ @Override
+ public void cancelTimer(final long timerId) {
+ timerHandlers.remove(timerId);
+ periodicHandlers.remove(timerId);
+ }
+
+ public void runHandlers() {
+ runTimerHandlers();
+ runPeriodicHandlers();
+ }
+
+ public void runTimerHandlers() {
+ // Create a copy of the handlers to avoid concurrent modification as handlers run
+ List handlers = new ArrayList<>();
+ timerHandlers.forEach((id, handler) -> handlers.add(handler));
+ timerHandlers.clear();
+
+ handlers.forEach(TimerHandler::handle);
+ }
+
+ public void runPeriodicHandlers() {
+ // Create a copy of the handlers to avoid concurrent modification as handlers run
+ List handlers = new ArrayList<>();
+ periodicHandlers.forEach((id, handler) -> handlers.add(handler));
+
+ handlers.forEach(TimerHandler::handle);
+ }
+}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java
index 033ccdf0eb..602f11d305 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java
@@ -12,24 +12,24 @@
*/
package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
+import static com.google.common.base.Preconditions.checkNotNull;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.crypto.SECP256K1;
+import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
-import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryAgent;
import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryStatus;
import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper;
import tech.pegasys.pantheon.ethereum.p2p.peers.Endpoint;
@@ -37,6 +37,7 @@ import tech.pegasys.pantheon.ethereum.p2p.peers.Peer;
import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
+import tech.pegasys.pantheon.util.Subscribers;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.MutableBytesValue;
@@ -45,14 +46,17 @@ import tech.pegasys.pantheon.util.uint.UInt256Value;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.stream.Stream;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
-import io.vertx.core.Vertx;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -65,24 +69,19 @@ public class PeerDiscoveryControllerTest {
private static final RetryDelayFunction SHORT_DELAY_FUNCTION = (prev) -> Math.max(100, prev * 2);
private static final PeerRequirement PEER_REQUIREMENT = () -> true;
private static final long TABLE_REFRESH_INTERVAL_MS = TimeUnit.HOURS.toMillis(1);
- private final Vertx vertx = spy(Vertx.vertx());
- private PeerDiscoveryAgent agent;
private PeerDiscoveryController controller;
- private DiscoveryPeer peer;
+ private DiscoveryPeer localPeer;
private PeerTable peerTable;
- private NodeWhitelistController defaultNodeWhitelistController;
+ private KeyPair localKeyPair;
+ private final AtomicInteger counter = new AtomicInteger(1);
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Before
public void initializeMocks() {
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- peer = PeerDiscoveryTestHelper.generatePeers(keyPairs)[0];
-
- agent = mock(PeerDiscoveryAgent.class);
- when(agent.getAdvertisedPeer()).thenReturn(peer);
- peerTable = new PeerTable(peer.getId());
-
- defaultNodeWhitelistController =
- new NodeWhitelistController(PermissioningConfiguration.createDefault());
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ localKeyPair = keyPairs.get(0);
+ localPeer = helper.createDiscoveryPeer(localKeyPair);
+ peerTable = new PeerTable(localPeer.getId());
}
@After
@@ -95,33 +94,48 @@ public class PeerDiscoveryControllerTest {
@Test
public void bootstrapPeersRetriesSent() {
// Create peers.
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(3);
- final DiscoveryPeer[] peers = PeerDiscoveryTestHelper.generateDiscoveryPeers(keyPairs);
+ int peerCount = 3;
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(peerCount);
+ final List peers = helper.createDiscoveryPeers(keyPairs);
+
+ MockTimerUtil timer = spy(new MockTimerUtil());
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
+ controller =
+ getControllerBuilder()
+ .peers(peers)
+ .timerUtil(timer)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+ controller.setRetryDelayFunction(SHORT_DELAY_FUNCTION);
// Mock the creation of the PING packet, so that we can control the hash,
// which gets validated when receiving the PONG.
final PingPacketData mockPing =
- PingPacketData.create(peer.getEndpoint(), peers[0].getEndpoint());
- final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs[0]);
- when(agent.sendPacket(any(), eq(PacketType.PING), any())).then((invocation) -> mockPacket);
+ PingPacketData.create(localPeer.getEndpoint(), peers.get(0).getEndpoint());
+ final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs.get(0));
+ doReturn(mockPacket).when(controller).createPacket(eq(PacketType.PING), any());
- startPeerDiscoveryController(SHORT_DELAY_FUNCTION, peers);
+ controller.start();
- // Wait at most 4 seconds until all PING packets have been sent.
- await()
- .atMost(4, TimeUnit.SECONDS)
- .untilAsserted(() -> verify(vertx, times(15)).setTimer(anyLong(), any()));
+ int timeouts = 4;
+ for (int i = 0; i < timeouts; i++) {
+ timer.runTimerHandlers();
+ }
+ int expectedTimerEvents = (timeouts + 1) * peerCount;
+ verify(timer, times(expectedTimerEvents)).setTimer(anyLong(), any());
// Within this time period, 4 timers should be placed with these timeouts.
final long[] expectedTimeouts = {100, 200, 400, 800};
for (final long timeout : expectedTimeouts) {
- verify(vertx, times(3)).setTimer(eq(timeout), any());
+ verify(timer, times(peerCount)).setTimer(eq(timeout), any());
}
// Check that 5 PING packets were sent for each peer (the initial + 4 attempts following
// timeouts).
- Stream.of(peers)
- .forEach(p -> verify(agent, times(5)).sendPacket(eq(p), eq(PacketType.PING), any()));
+ peers.forEach(
+ p ->
+ verify(outboundMessageHandler, times(timeouts + 1))
+ .send(eq(p), matchPacketOfType(PacketType.PING)));
controller
.getPeers()
@@ -131,50 +145,81 @@ public class PeerDiscoveryControllerTest {
@Test
public void bootstrapPeersRetriesStoppedUponResponse() {
// Create peers.
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(3);
- final DiscoveryPeer[] peers = PeerDiscoveryTestHelper.generateDiscoveryPeers(keyPairs);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(3);
+ final List peers = helper.createDiscoveryPeers(keyPairs);
+
+ MockTimerUtil timer = new MockTimerUtil();
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
+ controller =
+ getControllerBuilder()
+ .peers(peers)
+ .timerUtil(timer)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
// Mock the creation of the PING packet, so that we can control the hash,
// which gets validated when receiving the PONG.
final PingPacketData mockPing =
- PingPacketData.create(peer.getEndpoint(), peers[0].getEndpoint());
- final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs[0]);
- when(agent.sendPacket(any(), eq(PacketType.PING), any())).then((invocation) -> mockPacket);
+ PingPacketData.create(localPeer.getEndpoint(), peers.get(0).getEndpoint());
+ final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs.get(0));
+ doReturn(mockPacket).when(controller).createPacket(eq(PacketType.PING), any());
- startPeerDiscoveryController(SHORT_DELAY_FUNCTION, peers);
+ controller.start();
- // Wait at most 3 seconds until many PING packets attempts have been sent.
- // Assert timer was invoked several times.
- verify(vertx, timeout(3000).times(12)).setTimer(anyLong(), any());
+ // Invoke timers several times so that ping to peers should be resent
+ for (int i = 0; i < 3; i++) {
+ timer.runTimerHandlers();
+ }
// Assert PING packet was sent for peer[0] 4 times.
- verify(agent, timeout(1000).times(4)).sendPacket(eq(peers[0]), eq(PacketType.PING), any());
+ for (DiscoveryPeer peer : peers) {
+ verify(outboundMessageHandler, times(4)).send(eq(peer), matchPacketOfType(PacketType.PING));
+ }
// Simulate a PONG message from peer 0.
final PongPacketData packetData =
- PongPacketData.create(peer.getEndpoint(), mockPacket.getHash());
- final Packet packet = Packet.create(PacketType.PONG, packetData, keyPairs[0]);
- controller.onMessage(packet, peers[0]);
+ PongPacketData.create(localPeer.getEndpoint(), mockPacket.getHash());
+ final Packet packet = Packet.create(PacketType.PONG, packetData, keyPairs.get(0));
+ controller.onMessage(packet, peers.get(0));
+
+ // Invoke timers again
+ for (int i = 0; i < 4; i++) {
+ timer.runTimerHandlers();
+ }
// Ensure we receive no more PING packets for peer[0].
- verify(agent, timeout(1000).times(4)).sendPacket(eq(peers[0]), eq(PacketType.PING), any());
+ // Assert PING packet was sent for peer[0] 4 times.
+ for (DiscoveryPeer peer : peers) {
+ int expectedCount = peer.equals(peers.get(0)) ? 4 : 8;
+ verify(outboundMessageHandler, times(expectedCount))
+ .send(eq(peer), matchPacketOfType(PacketType.PING));
+ }
}
@Test
public void bootstrapPeersPongReceived_HashMatched() {
// Create peers.
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(3);
- final DiscoveryPeer[] peers = PeerDiscoveryTestHelper.generateDiscoveryPeers(keyPairs);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(3);
+ final List peers = helper.createDiscoveryPeers(keyPairs);
+
+ MockTimerUtil timer = new MockTimerUtil();
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
+ controller =
+ getControllerBuilder()
+ .peers(peers)
+ .timerUtil(timer)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
// Mock the creation of the PING packet, so that we can control the hash, which gets validated
- // when receiving
- // the PONG.
+ // when receiving the PONG.
final PingPacketData mockPing =
- PingPacketData.create(peer.getEndpoint(), peers[0].getEndpoint());
- final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs[0]);
- when(agent.sendPacket(any(), eq(PacketType.PING), any())).then((invocation) -> mockPacket);
+ PingPacketData.create(localPeer.getEndpoint(), peers.get(0).getEndpoint());
+ final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs.get(0));
+ doReturn(mockPacket).when(controller).createPacket(eq(PacketType.PING), any());
+
+ controller.start();
- startPeerDiscoveryController(SHORT_DELAY_FUNCTION, peers);
assertThat(
controller
.getPeers()
@@ -184,17 +229,17 @@ public class PeerDiscoveryControllerTest {
// Simulate a PONG message from peer 0.
final PongPacketData packetData =
- PongPacketData.create(peer.getEndpoint(), mockPacket.getHash());
- final Packet packet = Packet.create(PacketType.PONG, packetData, keyPairs[0]);
- controller.onMessage(packet, peers[0]);
+ PongPacketData.create(localPeer.getEndpoint(), mockPacket.getHash());
+ final Packet packet = Packet.create(PacketType.PONG, packetData, keyPairs.get(0));
+ controller.onMessage(packet, peers.get(0));
// Ensure that the peer controller is now sending FIND_NEIGHBORS messages for this peer.
- await()
- .atMost(3, TimeUnit.SECONDS)
- .untilAsserted(
- () ->
- verify(agent, atLeast(3))
- .sendPacket(eq(peers[0]), eq(PacketType.FIND_NEIGHBORS), any()));
+ verify(outboundMessageHandler, times(1))
+ .send(eq(peers.get(0)), matchPacketOfType(PacketType.FIND_NEIGHBORS));
+ // Invoke timeouts and check that we resent our neighbors request
+ timer.runTimerHandlers();
+ verify(outboundMessageHandler, times(2))
+ .send(eq(peers.get(0)), matchPacketOfType(PacketType.FIND_NEIGHBORS));
assertThat(
controller
@@ -210,18 +255,23 @@ public class PeerDiscoveryControllerTest {
@Test
public void bootstrapPeersPongReceived_HashUnmatched() {
// Create peers.
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(3);
- final DiscoveryPeer[] peers = PeerDiscoveryTestHelper.generateDiscoveryPeers(keyPairs);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(3);
+ final List peers = helper.createDiscoveryPeers(keyPairs);
+
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
+ controller =
+ getControllerBuilder().peers(peers).outboundMessageHandler(outboundMessageHandler).build();
+ controller.setRetryDelayFunction(LONG_DELAY_FUNCTION);
// Mock the creation of the PING packet, so that we can control the hash, which gets validated
// when
// processing the PONG.
final PingPacketData mockPing =
- PingPacketData.create(peer.getEndpoint(), peers[0].getEndpoint());
- final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs[0]);
- when(agent.sendPacket(any(), eq(PacketType.PING), any())).then((invocation) -> mockPacket);
+ PingPacketData.create(localPeer.getEndpoint(), peers.get(0).getEndpoint());
+ final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs.get(0));
+ doReturn(mockPacket).when(controller).createPacket(eq(PacketType.PING), any());
- startPeerDiscoveryController(peers);
+ controller.start();
assertThat(
controller
@@ -232,17 +282,13 @@ public class PeerDiscoveryControllerTest {
// Send a PONG packet from peer 1, with an incorrect hash.
final PongPacketData packetData =
- PongPacketData.create(peer.getEndpoint(), BytesValue.fromHexString("1212"));
- final Packet packet = Packet.create(PacketType.PONG, packetData, keyPairs[1]);
- controller.onMessage(packet, peers[1]);
+ PongPacketData.create(localPeer.getEndpoint(), BytesValue.fromHexString("1212"));
+ final Packet packet = Packet.create(PacketType.PONG, packetData, keyPairs.get(1));
+ controller.onMessage(packet, peers.get(1));
// No FIND_NEIGHBORS packet was sent for peer 1.
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () ->
- verify(agent, never())
- .sendPacket(eq(peers[1]), eq(PacketType.FIND_NEIGHBORS), any()));
+ verify(outboundMessageHandler, never())
+ .send(eq(peers.get(1)), matchPacketOfType(PacketType.FIND_NEIGHBORS));
assertThat(
controller
@@ -255,153 +301,140 @@ public class PeerDiscoveryControllerTest {
@Test
public void findNeighborsSentAfterBondingFinished() {
// Create three peers, out of which the first two are bootstrap peers.
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- final DiscoveryPeer[] peers = PeerDiscoveryTestHelper.generateDiscoveryPeers(keyPairs);
-
- // Mock the creation of the PING packet, so that we can control the hash, which gets validated
- // when
- // processing the PONG.
- final PingPacketData mockPing =
- PingPacketData.create(peer.getEndpoint(), peers[0].getEndpoint());
- final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs[0]);
- when(agent.sendPacket(any(), eq(PacketType.PING), any())).then((invocation) -> mockPacket);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ final List peers = helper.createDiscoveryPeers(keyPairs);
// Initialize the peer controller, setting a high controller refresh interval and a high timeout
// threshold,
// to avoid retries getting in the way of this test.
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
controller =
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Collections.singletonList(peers[0]),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- new PeerBlacklist(),
- defaultNodeWhitelistController);
+ getControllerBuilder()
+ .peers(peers.get(0))
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+
+ // Mock the creation of the PING packet, so that we can control the hash, which gets validated
+ // when
+ // processing the PONG.
+ final PingPacketData mockPing =
+ PingPacketData.create(localPeer.getEndpoint(), peers.get(0).getEndpoint());
+ final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs.get(0));
+ doReturn(mockPacket).when(controller).createPacket(eq(PacketType.PING), any());
controller.setRetryDelayFunction((prev) -> 999999999L);
controller.start();
// Verify that the PING was sent.
- await()
- .atMost(2, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, times(1)).sendPacket(eq(peers[0]), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(1))
+ .send(eq(peers.get(0)), matchPacketOfType(PacketType.PING));
// Simulate a PONG message from peer[0].
final PongPacketData packetData =
- PongPacketData.create(peer.getEndpoint(), mockPacket.getHash());
- final Packet pongPacket = Packet.create(PacketType.PONG, packetData, keyPairs[0]);
- controller.onMessage(pongPacket, peers[0]);
-
- // Verify that the FIND_NEIGHBORS packet was sent with target == self.
- final ArgumentCaptor captor = ArgumentCaptor.forClass(PacketData.class);
- await()
- .atMost(2, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, times(1))
- .sendPacket(eq(peers[0]), eq(PacketType.FIND_NEIGHBORS), captor.capture());
- });
-
- assertThat(captor.getValue()).isInstanceOf(FindNeighborsPacketData.class);
- final FindNeighborsPacketData data = (FindNeighborsPacketData) captor.getValue();
- assertThat(data.getTarget()).isEqualTo(peer.getId());
+ PongPacketData.create(localPeer.getEndpoint(), mockPacket.getHash());
+ final Packet pongPacket = Packet.create(PacketType.PONG, packetData, keyPairs.get(0));
+ controller.onMessage(pongPacket, peers.get(0));
+
+ // Verify that the FIND_NEIGHBORS packet was sent with target == localPeer.
+ final ArgumentCaptor captor = ArgumentCaptor.forClass(Packet.class);
+ verify(outboundMessageHandler, atLeast(1)).send(eq(peers.get(0)), captor.capture());
+ List neighborsPackets =
+ captor
+ .getAllValues()
+ .stream()
+ .filter(p -> p.getType().equals(PacketType.FIND_NEIGHBORS))
+ .collect(Collectors.toList());
+ assertThat(neighborsPackets.size()).isEqualTo(1);
+ Packet nieghborsPacket = neighborsPackets.get(0);
+ final Optional maybeData =
+ nieghborsPacket.getPacketData(FindNeighborsPacketData.class);
+ assertThat(maybeData).isPresent();
+ final FindNeighborsPacketData data = maybeData.get();
+ assertThat(data.getTarget()).isEqualTo(localPeer.getId());
+
assertThat(controller.getPeers()).hasSize(1);
assertThat(controller.getPeers().stream().findFirst().get().getStatus())
.isEqualTo(PeerDiscoveryStatus.BONDED);
}
+ private ControllerBuilder getControllerBuilder() {
+ return ControllerBuilder.create()
+ .keyPair(localKeyPair)
+ .localPeer(localPeer)
+ .peerTable(peerTable);
+ }
+
@Test
public void peerSeenTwice() throws InterruptedException {
// Create three peers, out of which the first two are bootstrap peers.
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(3);
- final DiscoveryPeer[] peers = PeerDiscoveryTestHelper.generateDiscoveryPeers(keyPairs);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(3);
+ final List peers = helper.createDiscoveryPeers(keyPairs);
// Mock the creation of the PING packet, so that we can control the hash, which gets validated
// when
// processing the PONG.
final PingPacketData mockPing =
- PingPacketData.create(peer.getEndpoint(), peers[0].getEndpoint());
- final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs[0]);
- when(agent.sendPacket(any(), eq(PacketType.PING), any())).then((invocation) -> mockPacket);
+ PingPacketData.create(localPeer.getEndpoint(), peers.get(0).getEndpoint());
+ final Packet mockPacket = Packet.create(PacketType.PING, mockPing, keyPairs.get(0));
- // Initialize the peer controller, setting a high controller refresh interval and a high timeout
- // threshold, to avoid retries
- // getting in the way of this test.
+ // Initialize the peer controller
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
controller =
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Arrays.asList(peers[0], peers[1]),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- new PeerBlacklist(),
- defaultNodeWhitelistController);
+ getControllerBuilder()
+ .peers(peers.get(0), peers.get(1))
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+
+ doReturn(mockPacket).when(controller).createPacket(eq(PacketType.PING), any());
+
controller.setRetryDelayFunction((prev) -> 999999999L);
controller.start();
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1)).sendPacket(eq(peers[0]), eq(PacketType.PING), any());
- verify(agent, atLeast(1)).sendPacket(eq(peers[1]), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(1))
+ .send(eq(peers.get(0)), matchPacketOfType(PacketType.PING));
+ verify(outboundMessageHandler, times(1))
+ .send(eq(peers.get(1)), matchPacketOfType(PacketType.PING));
// Simulate a PONG message from peer[0].
final PongPacketData packetData =
- PongPacketData.create(peer.getEndpoint(), mockPacket.getHash());
- Packet pongPacket = Packet.create(PacketType.PONG, packetData, keyPairs[0]);
- controller.onMessage(pongPacket, peers[0]);
+ PongPacketData.create(localPeer.getEndpoint(), mockPacket.getHash());
+ Packet pongPacket = Packet.create(PacketType.PONG, packetData, keyPairs.get(0));
+ controller.onMessage(pongPacket, peers.get(0));
// Simulate a NEIGHBORS message from peer[0] listing peer[2].
final NeighborsPacketData neighbors =
- NeighborsPacketData.create(Collections.singletonList(peers[2]));
- Packet neighborsPacket = Packet.create(PacketType.NEIGHBORS, neighbors, keyPairs[0]);
- controller.onMessage(neighborsPacket, peers[0]);
+ NeighborsPacketData.create(Collections.singletonList(peers.get(2)));
+ Packet neighborsPacket = Packet.create(PacketType.NEIGHBORS, neighbors, keyPairs.get(0));
+ controller.onMessage(neighborsPacket, peers.get(0));
// Assert that we're bonding with the third peer.
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- assertThat(controller.getPeers()).hasSize(2);
- assertThat(controller.getPeers())
- .filteredOn(p -> p.getStatus() == PeerDiscoveryStatus.BONDING)
- .hasSize(1);
- assertThat(controller.getPeers())
- .filteredOn(p -> p.getStatus() == PeerDiscoveryStatus.BONDED)
- .hasSize(1);
- });
+ assertThat(controller.getPeers()).hasSize(2);
+ assertThat(controller.getPeers())
+ .filteredOn(p -> p.getStatus() == PeerDiscoveryStatus.BONDING)
+ .hasSize(1);
+ assertThat(controller.getPeers())
+ .filteredOn(p -> p.getStatus() == PeerDiscoveryStatus.BONDED)
+ .hasSize(1);
// Send a PONG packet from peer[2], to transition it to the BONDED state.
- pongPacket = Packet.create(PacketType.PONG, packetData, keyPairs[2]);
- controller.onMessage(pongPacket, peers[2]);
+ pongPacket = Packet.create(PacketType.PONG, packetData, keyPairs.get(2));
+ controller.onMessage(pongPacket, peers.get(2));
// Assert we're now bonded with peer[2].
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () ->
- assertThat(controller.getPeers())
- .filteredOn(
- p -> p.equals(peers[2]) && p.getStatus() == PeerDiscoveryStatus.BONDED)
- .hasSize(1));
+ assertThat(controller.getPeers())
+ .filteredOn(p -> p.equals(peers.get(2)) && p.getStatus() == PeerDiscoveryStatus.BONDED)
+ .hasSize(1);
// Simulate bonding and neighbors packet from the second boostrap peer, with peer[2] reported in
// the peer list.
- pongPacket = Packet.create(PacketType.PONG, packetData, keyPairs[1]);
- controller.onMessage(pongPacket, peers[1]);
- neighborsPacket = Packet.create(PacketType.NEIGHBORS, neighbors, keyPairs[1]);
- controller.onMessage(neighborsPacket, peers[1]);
+ pongPacket = Packet.create(PacketType.PONG, packetData, keyPairs.get(1));
+ controller.onMessage(pongPacket, peers.get(1));
+ neighborsPacket = Packet.create(PacketType.NEIGHBORS, neighbors, keyPairs.get(1));
+ controller.onMessage(neighborsPacket, peers.get(1));
// Wait for 1 second and ensure that only 1 PING was ever sent to peer[2].
Thread.sleep(1000);
- verify(agent, times(1)).sendPacket(eq(peers[2]), eq(PacketType.PING), any());
+ verify(outboundMessageHandler, times(1))
+ .send(eq(peers.get(2)), matchPacketOfType(PacketType.PING));
}
@Test(expected = IllegalStateException.class)
@@ -420,201 +453,192 @@ public class PeerDiscoveryControllerTest {
@Test
public void shouldAddNewPeerWhenReceivedPingAndPeerTableBucketIsNotFull() {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 1);
+ final List peers = createPeersInLastBucket(localPeer, 1);
startPeerDiscoveryController();
- final Packet pingPacket = mockPingPacket(peers[0], peer);
- controller.onMessage(pingPacket, peers[0]);
- assertThat(controller.getPeers()).contains(peers[0]);
+ final Packet pingPacket = mockPingPacket(peers.get(0), localPeer);
+ controller.onMessage(pingPacket, peers.get(0));
+ assertThat(controller.getPeers()).contains(peers.get(0));
}
@Test
public void shouldNotAddSelfWhenReceivedPingFromSelf() {
startPeerDiscoveryController();
- final DiscoveryPeer self = new DiscoveryPeer(peer.getId(), peer.getEndpoint());
+ final DiscoveryPeer localPeer =
+ new DiscoveryPeer(this.localPeer.getId(), this.localPeer.getEndpoint());
- final Packet pingPacket = mockPingPacket(peer, peer);
- controller.onMessage(pingPacket, self);
+ final Packet pingPacket = mockPingPacket(this.localPeer, this.localPeer);
+ controller.onMessage(pingPacket, localPeer);
- assertThat(controller.getPeers()).doesNotContain(self);
+ assertThat(controller.getPeers()).doesNotContain(localPeer);
}
@Test
public void shouldAddNewPeerWhenReceivedPingAndPeerTableBucketIsFull() {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 17);
+ final List peers = createPeersInLastBucket(localPeer, 17);
startPeerDiscoveryController();
// Fill the last bucket.
for (int i = 0; i < 16; i++) {
- peerTable.tryAdd(peers[i]);
+ peerTable.tryAdd(peers.get(i));
}
- final Packet pingPacket = mockPingPacket(peers[16], peer);
- controller.onMessage(pingPacket, peers[16]);
+ final Packet pingPacket = mockPingPacket(peers.get(16), localPeer);
+ controller.onMessage(pingPacket, peers.get(16));
- assertThat(controller.getPeers()).contains(peers[16]);
+ assertThat(controller.getPeers()).contains(peers.get(16));
// The first peer added should have been evicted.
- assertThat(controller.getPeers()).doesNotContain(peers[0]);
+ assertThat(controller.getPeers()).doesNotContain(peers.get(0));
}
@Test
public void shouldNotRemoveExistingPeerWhenReceivedPing() {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 1);
+ final List peers = createPeersInLastBucket(localPeer, 1);
startPeerDiscoveryController();
- peerTable.tryAdd(peers[0]);
- assertThat(controller.getPeers()).contains(peers[0]);
+ peerTable.tryAdd(peers.get(0));
+ assertThat(controller.getPeers()).contains(peers.get(0));
- final Packet pingPacket = mockPingPacket(peers[0], peer);
- controller.onMessage(pingPacket, peers[0]);
+ final Packet pingPacket = mockPingPacket(peers.get(0), localPeer);
+ controller.onMessage(pingPacket, peers.get(0));
- assertThat(controller.getPeers()).contains(peers[0]);
+ assertThat(controller.getPeers()).contains(peers.get(0));
}
@Test
- public void shouldNotAddNewPeerWhenReceivedPongFromBlacklistedPeer()
- throws InterruptedException, ExecutionException, TimeoutException {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 3);
- final DiscoveryPeer discoPeer = peers[0];
- final DiscoveryPeer otherPeer = peers[1];
- final DiscoveryPeer otherPeer2 = peers[2];
+ public void shouldNotAddNewPeerWhenReceivedPongFromBlacklistedPeer() {
+ final List peers = createPeersInLastBucket(localPeer, 3);
+ final DiscoveryPeer discoPeer = peers.get(0);
+ final DiscoveryPeer otherPeer = peers.get(1);
+ final DiscoveryPeer otherPeer2 = peers.get(2);
final PeerBlacklist blacklist = new PeerBlacklist();
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
controller =
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Collections.singletonList(discoPeer),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- blacklist,
- defaultNodeWhitelistController);
-
- final Endpoint agentEndpoint = agent.getAdvertisedPeer().getEndpoint();
+ getControllerBuilder()
+ .peers(discoPeer)
+ .blacklist(blacklist)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+
+ final Endpoint localEndpoint = localPeer.getEndpoint();
// Setup ping to be sent to discoPeer
- SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- PingPacketData pingPacketData = PingPacketData.create(agentEndpoint, discoPeer.getEndpoint());
- final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(discoPeerPing).when(agent).sendPacket(eq(discoPeer), eq(PacketType.PING), any());
+ List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ PingPacketData pingPacketData = PingPacketData.create(localEndpoint, discoPeer.getEndpoint());
+ final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(discoPeerPing)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(discoPeer));
controller.start();
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1)).sendPacket(any(), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(1))
+ .send(eq(peers.get(0)), matchPacketOfType(PacketType.PING));
final Packet pongFromDiscoPeer =
MockPacketDataFactory.mockPongPacket(discoPeer, discoPeerPing.getHash());
controller.onMessage(pongFromDiscoPeer, discoPeer);
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1))
- .sendPacket(eq(discoPeer), eq(PacketType.FIND_NEIGHBORS), any());
- });
+ verify(outboundMessageHandler, times(1))
+ .send(eq(discoPeer), matchPacketOfType(PacketType.FIND_NEIGHBORS));
// Setup ping to be sent to otherPeer after neighbors packet is received
keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- pingPacketData = PingPacketData.create(agentEndpoint, otherPeer.getEndpoint());
- final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(pingPacket).when(agent).sendPacket(eq(otherPeer), eq(PacketType.PING), any());
+ pingPacketData = PingPacketData.create(localEndpoint, otherPeer.getEndpoint());
+ final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(pingPacket)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(otherPeer));
// Setup ping to be sent to otherPeer2 after neighbors packet is received
keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- pingPacketData = PingPacketData.create(agentEndpoint, otherPeer2.getEndpoint());
- final Packet pingPacket2 = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(pingPacket2).when(agent).sendPacket(eq(otherPeer2), eq(PacketType.PING), any());
+ pingPacketData = PingPacketData.create(localEndpoint, otherPeer2.getEndpoint());
+ final Packet pingPacket2 = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(pingPacket2)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(otherPeer2));
final Packet neighborsPacket =
MockPacketDataFactory.mockNeighborsPacket(discoPeer, otherPeer, otherPeer2);
controller.onMessage(neighborsPacket, discoPeer);
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(2)).sendPacket(any(), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(peers.size()))
+ .send(any(), matchPacketOfType(PacketType.PING));
final Packet pongPacket = MockPacketDataFactory.mockPongPacket(otherPeer, pingPacket.getHash());
controller.onMessage(pongPacket, otherPeer);
- // Blaclist otherPeer2 before sending return pong
+ // Blacklist otherPeer2 before sending return pong
blacklist.add(otherPeer2);
final Packet pongPacket2 =
MockPacketDataFactory.mockPongPacket(otherPeer2, pingPacket2.getHash());
controller.onMessage(pongPacket2, otherPeer2);
assertThat(controller.getPeers()).hasSize(2);
+ assertThat(controller.getPeers()).contains(discoPeer);
assertThat(controller.getPeers()).contains(otherPeer);
assertThat(controller.getPeers()).doesNotContain(otherPeer2);
}
+ private PacketData matchPingDataForPeer(final DiscoveryPeer peer) {
+ return argThat((PacketData data) -> ((PingPacketData) data).getTo().equals(peer.getEndpoint()));
+ }
+
+ private Packet matchPacketOfType(final PacketType type) {
+ return argThat((Packet packet) -> packet.getType().equals(type));
+ }
+
@Test
public void shouldNotBondWithBlacklistedPeer()
throws InterruptedException, ExecutionException, TimeoutException {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 3);
- final DiscoveryPeer discoPeer = peers[0];
- final DiscoveryPeer otherPeer = peers[1];
- final DiscoveryPeer otherPeer2 = peers[2];
+ final List peers = createPeersInLastBucket(localPeer, 3);
+ final DiscoveryPeer discoPeer = peers.get(0);
+ final DiscoveryPeer otherPeer = peers.get(1);
+ final DiscoveryPeer otherPeer2 = peers.get(2);
final PeerBlacklist blacklist = new PeerBlacklist();
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
controller =
- spy(
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Collections.singletonList(discoPeer),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- blacklist,
- defaultNodeWhitelistController));
-
- final Endpoint agentEndpoint = agent.getAdvertisedPeer().getEndpoint();
+ getControllerBuilder()
+ .peers(discoPeer)
+ .blacklist(blacklist)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+
+ final Endpoint localEndpoint = localPeer.getEndpoint();
// Setup ping to be sent to discoPeer
- SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- PingPacketData pingPacketData = PingPacketData.create(agentEndpoint, discoPeer.getEndpoint());
- final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(discoPeerPing).when(agent).sendPacket(eq(discoPeer), eq(PacketType.PING), any());
+ List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ PingPacketData pingPacketData = PingPacketData.create(localEndpoint, discoPeer.getEndpoint());
+ final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(discoPeerPing)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(discoPeer));
controller.start();
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1)).sendPacket(any(), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(1)).send(any(), matchPacketOfType(PacketType.PING));
final Packet pongFromDiscoPeer =
MockPacketDataFactory.mockPongPacket(discoPeer, discoPeerPing.getHash());
controller.onMessage(pongFromDiscoPeer, discoPeer);
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1))
- .sendPacket(eq(discoPeer), eq(PacketType.FIND_NEIGHBORS), any());
- });
+ verify(outboundMessageHandler, times(1))
+ .send(eq(discoPeer), matchPacketOfType(PacketType.FIND_NEIGHBORS));
// Setup ping to be sent to otherPeer after neighbors packet is received
keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- pingPacketData = PingPacketData.create(agentEndpoint, otherPeer.getEndpoint());
- final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(pingPacket).when(agent).sendPacket(eq(otherPeer), eq(PacketType.PING), any());
+ pingPacketData = PingPacketData.create(localEndpoint, otherPeer.getEndpoint());
+ final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(pingPacket)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(otherPeer));
// Setup ping to be sent to otherPeer2 after neighbors packet is received
keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- pingPacketData = PingPacketData.create(agentEndpoint, otherPeer2.getEndpoint());
- final Packet pingPacket2 = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(pingPacket2).when(agent).sendPacket(eq(otherPeer2), eq(PacketType.PING), any());
+ pingPacketData = PingPacketData.create(localEndpoint, otherPeer2.getEndpoint());
+ final Packet pingPacket2 = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(pingPacket2)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(otherPeer2));
// Blacklist peer
blacklist.add(otherPeer);
@@ -630,280 +654,243 @@ public class PeerDiscoveryControllerTest {
@Test
public void shouldRespondToNeighborsRequestFromKnownPeer()
throws InterruptedException, ExecutionException, TimeoutException {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 1);
- final DiscoveryPeer discoPeer = peers[0];
+ final List peers = createPeersInLastBucket(localPeer, 1);
+ final DiscoveryPeer discoPeer = peers.get(0);
final PeerBlacklist blacklist = new PeerBlacklist();
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
controller =
- spy(
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Collections.singletonList(discoPeer),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- blacklist,
- defaultNodeWhitelistController));
-
- final Endpoint agentEndpoint = agent.getAdvertisedPeer().getEndpoint();
+ getControllerBuilder()
+ .peers(discoPeer)
+ .blacklist(blacklist)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+
+ final Endpoint localEndpoint = localPeer.getEndpoint();
// Setup ping to be sent to discoPeer
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
final PingPacketData pingPacketData =
- PingPacketData.create(agentEndpoint, discoPeer.getEndpoint());
- final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(discoPeerPing).when(agent).sendPacket(eq(discoPeer), eq(PacketType.PING), any());
+ PingPacketData.create(localEndpoint, discoPeer.getEndpoint());
+ final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(discoPeerPing)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(discoPeer));
controller.start();
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1)).sendPacket(any(), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(1)).send(any(), matchPacketOfType(PacketType.PING));
final Packet pongFromDiscoPeer =
MockPacketDataFactory.mockPongPacket(discoPeer, discoPeerPing.getHash());
controller.onMessage(pongFromDiscoPeer, discoPeer);
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1))
- .sendPacket(eq(discoPeer), eq(PacketType.FIND_NEIGHBORS), any());
- });
+ verify(outboundMessageHandler, times(1))
+ .send(eq(discoPeer), matchPacketOfType(PacketType.FIND_NEIGHBORS));
final Packet findNeighborsPacket = MockPacketDataFactory.mockFindNeighborsPacket(discoPeer);
controller.onMessage(findNeighborsPacket, discoPeer);
- verify(agent, times(1)).sendPacket(eq(discoPeer), eq(PacketType.NEIGHBORS), any());
+ verify(outboundMessageHandler, times(1))
+ .send(eq(discoPeer), matchPacketOfType(PacketType.NEIGHBORS));
}
@Test
public void shouldNotRespondToNeighborsRequestFromUnknownPeer()
throws InterruptedException, ExecutionException, TimeoutException {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 2);
- final DiscoveryPeer discoPeer = peers[0];
- final DiscoveryPeer otherPeer = peers[1];
+ final List peers = createPeersInLastBucket(localPeer, 2);
+ final DiscoveryPeer discoPeer = peers.get(0);
+ final DiscoveryPeer otherPeer = peers.get(1);
final PeerBlacklist blacklist = new PeerBlacklist();
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
controller =
- spy(
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Collections.singletonList(discoPeer),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- blacklist,
- defaultNodeWhitelistController));
-
- final Endpoint agentEndpoint = agent.getAdvertisedPeer().getEndpoint();
+ getControllerBuilder()
+ .peers(discoPeer)
+ .blacklist(blacklist)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+
+ final Endpoint localEndpoint = localPeer.getEndpoint();
// Setup ping to be sent to discoPeer
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
final PingPacketData pingPacketData =
- PingPacketData.create(agentEndpoint, discoPeer.getEndpoint());
- final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(discoPeerPing).when(agent).sendPacket(eq(discoPeer), eq(PacketType.PING), any());
+ PingPacketData.create(localEndpoint, discoPeer.getEndpoint());
+ final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(discoPeerPing)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(discoPeer));
controller.start();
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1)).sendPacket(any(), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(1)).send(any(), matchPacketOfType(PacketType.PING));
final Packet pongFromDiscoPeer =
MockPacketDataFactory.mockPongPacket(discoPeer, discoPeerPing.getHash());
controller.onMessage(pongFromDiscoPeer, discoPeer);
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1))
- .sendPacket(eq(discoPeer), eq(PacketType.FIND_NEIGHBORS), any());
- });
+ verify(outboundMessageHandler, times(1))
+ .send(eq(discoPeer), matchPacketOfType(PacketType.FIND_NEIGHBORS));
final Packet findNeighborsPacket = MockPacketDataFactory.mockFindNeighborsPacket(discoPeer);
controller.onMessage(findNeighborsPacket, otherPeer);
- verify(agent, times(0)).sendPacket(eq(otherPeer), eq(PacketType.NEIGHBORS), any());
+ verify(outboundMessageHandler, times(0))
+ .send(eq(otherPeer), matchPacketOfType(PacketType.NEIGHBORS));
}
@Test
- public void shouldNotRespondToNeighborsRequestFromBlacklistedPeer()
- throws InterruptedException, ExecutionException, TimeoutException {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 1);
- final DiscoveryPeer discoPeer = peers[0];
+ public void shouldNotRespondToNeighborsRequestFromBlacklistedPeer() {
+ final List peers = createPeersInLastBucket(localPeer, 1);
+ final DiscoveryPeer discoPeer = peers.get(0);
final PeerBlacklist blacklist = new PeerBlacklist();
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
controller =
- spy(
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Collections.singletonList(discoPeer),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- blacklist,
- defaultNodeWhitelistController));
-
- final Endpoint agentEndpoint = agent.getAdvertisedPeer().getEndpoint();
+ getControllerBuilder()
+ .peers(discoPeer)
+ .blacklist(blacklist)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+
+ final Endpoint localEndpoint = localPeer.getEndpoint();
// Setup ping to be sent to discoPeer
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
final PingPacketData pingPacketData =
- PingPacketData.create(agentEndpoint, discoPeer.getEndpoint());
- final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(discoPeerPing).when(agent).sendPacket(eq(discoPeer), eq(PacketType.PING), any());
+ PingPacketData.create(localEndpoint, discoPeer.getEndpoint());
+ final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(discoPeerPing)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(discoPeer));
controller.start();
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1)).sendPacket(any(), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(1)).send(any(), matchPacketOfType(PacketType.PING));
final Packet pongFromDiscoPeer =
MockPacketDataFactory.mockPongPacket(discoPeer, discoPeerPing.getHash());
controller.onMessage(pongFromDiscoPeer, discoPeer);
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1))
- .sendPacket(eq(discoPeer), eq(PacketType.FIND_NEIGHBORS), any());
- });
+ verify(outboundMessageHandler, times(1))
+ .send(eq(discoPeer), matchPacketOfType(PacketType.FIND_NEIGHBORS));
blacklist.add(discoPeer);
final Packet findNeighborsPacket = MockPacketDataFactory.mockFindNeighborsPacket(discoPeer);
controller.onMessage(findNeighborsPacket, discoPeer);
- verify(agent, times(0)).sendPacket(eq(discoPeer), eq(PacketType.NEIGHBORS), any());
+ verify(outboundMessageHandler, times(0))
+ .send(eq(discoPeer), matchPacketOfType(PacketType.NEIGHBORS));
}
@Test
public void shouldAddNewPeerWhenReceivedPongAndPeerTableBucketIsNotFull() {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 1);
+ final List peers = createPeersInLastBucket(localPeer, 1);
// Mock the creation of the PING packet to control hash for PONG.
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
final PingPacketData pingPacketData =
- PingPacketData.create(peer.getEndpoint(), peers[0].getEndpoint());
- final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- when(agent.sendPacket(any(), eq(PacketType.PING), any())).then((invocation) -> pingPacket);
+ PingPacketData.create(localPeer.getEndpoint(), peers.get(0).getEndpoint());
+ final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
controller =
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Arrays.asList(peers[0]),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- new PeerBlacklist(),
- defaultNodeWhitelistController);
+ getControllerBuilder()
+ .peers(peers.get(0))
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+ doReturn(pingPacket).when(controller).createPacket(eq(PacketType.PING), any());
+
controller.setRetryDelayFunction((prev) -> 999999999L);
controller.start();
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1)).sendPacket(any(), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(1)).send(any(), matchPacketOfType(PacketType.PING));
- final Packet pongPacket = MockPacketDataFactory.mockPongPacket(peers[0], pingPacket.getHash());
- controller.onMessage(pongPacket, peers[0]);
+ final Packet pongPacket =
+ MockPacketDataFactory.mockPongPacket(peers.get(0), pingPacket.getHash());
+ controller.onMessage(pongPacket, peers.get(0));
- assertThat(controller.getPeers()).contains(peers[0]);
+ assertThat(controller.getPeers()).contains(peers.get(0));
}
@Test
public void shouldAddNewPeerWhenReceivedPongAndPeerTableBucketIsFull() {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 17);
+ final List peers = createPeersInLastBucket(localPeer, 17);
+
+ final List bootstrapPeers = peers.subList(0, 16);
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
+ controller =
+ getControllerBuilder()
+ .peers(bootstrapPeers)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+ controller.setRetryDelayFunction(LONG_DELAY_FUNCTION);
// Mock the creation of PING packets to control hash PONG packets.
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
final PingPacketData pingPacketData =
- PingPacketData.create(peer.getEndpoint(), peers[0].getEndpoint());
- final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- when(agent.sendPacket(any(), eq(PacketType.PING), any())).then((invocation) -> pingPacket);
+ PingPacketData.create(localPeer.getEndpoint(), peers.get(0).getEndpoint());
+ final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(pingPacket).when(controller).createPacket(eq(PacketType.PING), any());
- final DiscoveryPeer[] bootstrapPeers = Arrays.copyOfRange(peers, 0, 16);
- startPeerDiscoveryController(bootstrapPeers);
+ controller.start();
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(16)).sendPacket(any(), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(16)).send(any(), matchPacketOfType(PacketType.PING));
- final Packet pongPacket = MockPacketDataFactory.mockPongPacket(peers[0], pingPacket.getHash());
- controller.onMessage(pongPacket, peers[0]);
+ final Packet pongPacket =
+ MockPacketDataFactory.mockPongPacket(peers.get(0), pingPacket.getHash());
+ controller.onMessage(pongPacket, peers.get(0));
- final Packet neighborsPacket = MockPacketDataFactory.mockNeighborsPacket(peers[0], peers[16]);
- controller.onMessage(neighborsPacket, peers[0]);
+ final Packet neighborsPacket =
+ MockPacketDataFactory.mockNeighborsPacket(peers.get(0), peers.get(16));
+ controller.onMessage(neighborsPacket, peers.get(0));
final Packet pongPacket2 =
- MockPacketDataFactory.mockPongPacket(peers[16], pingPacket.getHash());
- controller.onMessage(pongPacket2, peers[16]);
+ MockPacketDataFactory.mockPongPacket(peers.get(16), pingPacket.getHash());
+ controller.onMessage(pongPacket2, peers.get(16));
- assertThat(controller.getPeers()).contains(peers[16]);
+ assertThat(controller.getPeers()).contains(peers.get(16));
// Explain
- assertThat(controller.getPeers()).doesNotContain(peers[1]);
+ assertThat(controller.getPeers()).doesNotContain(peers.get(1));
}
@Test
public void shouldNotAddPeerInNeighborsPacketWithoutBonding() {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 2);
+ final List peers = createPeersInLastBucket(localPeer, 2);
// Mock the creation of the PING packet to control hash for PONG.
- final SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ final List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
final PingPacketData pingPacketData =
- PingPacketData.create(peer.getEndpoint(), peers[0].getEndpoint());
- final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- when(agent.sendPacket(any(), eq(PacketType.PING), any())).then((invocation) -> pingPacket);
-
- startPeerDiscoveryController(peers[0]);
-
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1)).sendPacket(eq(peers[0]), eq(PacketType.PING), any());
- });
-
- final Packet pongPacket = MockPacketDataFactory.mockPongPacket(peers[0], pingPacket.getHash());
- controller.onMessage(pongPacket, peers[0]);
-
- await()
- .atMost(3, TimeUnit.SECONDS)
- .untilAsserted(
- () ->
- verify(agent, atLeast(1))
- .sendPacket(eq(peers[0]), eq(PacketType.FIND_NEIGHBORS), any()));
-
- assertThat(controller.getPeers()).doesNotContain(peers[1]);
+ PingPacketData.create(localPeer.getEndpoint(), peers.get(0).getEndpoint());
+ final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
+ controller =
+ getControllerBuilder()
+ .peers(peers.get(0))
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+ doReturn(pingPacket).when(controller).createPacket(eq(PacketType.PING), any());
+ controller.start();
+
+ verify(outboundMessageHandler, times(1))
+ .send(eq(peers.get(0)), matchPacketOfType(PacketType.PING));
+
+ final Packet pongPacket =
+ MockPacketDataFactory.mockPongPacket(peers.get(0), pingPacket.getHash());
+ controller.onMessage(pongPacket, peers.get(0));
+
+ verify(outboundMessageHandler, times(1))
+ .send(eq(peers.get(0)), matchPacketOfType(PacketType.FIND_NEIGHBORS));
+
+ assertThat(controller.getPeers()).doesNotContain(peers.get(1));
}
@Test
public void shouldNotBondWithNonWhitelistedPeer()
throws InterruptedException, ExecutionException, TimeoutException {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 3);
- final DiscoveryPeer discoPeer = peers[0];
- final DiscoveryPeer otherPeer = peers[1];
- final DiscoveryPeer otherPeer2 = peers[2];
+ final List peers = createPeersInLastBucket(localPeer, 3);
+ final DiscoveryPeer discoPeer = peers.get(0);
+ final DiscoveryPeer otherPeer = peers.get(1);
+ final DiscoveryPeer otherPeer2 = peers.get(2);
final PeerBlacklist blacklist = new PeerBlacklist();
final PermissioningConfiguration config = new PermissioningConfiguration();
@@ -913,57 +900,50 @@ public class PeerDiscoveryControllerTest {
nodeWhitelistController.addNode(discoPeer);
nodeWhitelistController.addNode(otherPeer2);
+ OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
controller =
- spy(
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Collections.singletonList(discoPeer),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- blacklist,
- nodeWhitelistController));
-
- final Endpoint agentEndpoint = agent.getAdvertisedPeer().getEndpoint();
+ getControllerBuilder()
+ .peers(discoPeer)
+ .blacklist(blacklist)
+ .whitelist(nodeWhitelistController)
+ .outboundMessageHandler(outboundMessageHandler)
+ .build();
+
+ final Endpoint localEndpoint = localPeer.getEndpoint();
// Setup ping to be sent to discoPeer
- SECP256K1.KeyPair[] keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- PingPacketData pingPacketData = PingPacketData.create(agentEndpoint, discoPeer.getEndpoint());
- final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(discoPeerPing).when(agent).sendPacket(eq(discoPeer), eq(PacketType.PING), any());
+ List keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
+ PingPacketData pingPacketData = PingPacketData.create(localEndpoint, discoPeer.getEndpoint());
+ final Packet discoPeerPing = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(discoPeerPing)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(discoPeer));
controller.start();
- await()
- .atMost(5, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1)).sendPacket(any(), eq(PacketType.PING), any());
- });
+ verify(outboundMessageHandler, times(1)).send(any(), matchPacketOfType(PacketType.PING));
final Packet pongFromDiscoPeer =
MockPacketDataFactory.mockPongPacket(discoPeer, discoPeerPing.getHash());
controller.onMessage(pongFromDiscoPeer, discoPeer);
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(
- () -> {
- verify(agent, atLeast(1))
- .sendPacket(eq(discoPeer), eq(PacketType.FIND_NEIGHBORS), any());
- });
+ verify(outboundMessageHandler, times(1))
+ .send(eq(discoPeer), matchPacketOfType(PacketType.FIND_NEIGHBORS));
// Setup ping to be sent to otherPeer after neighbors packet is received
keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- pingPacketData = PingPacketData.create(agentEndpoint, otherPeer.getEndpoint());
- final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(pingPacket).when(agent).sendPacket(eq(otherPeer), eq(PacketType.PING), any());
+ pingPacketData = PingPacketData.create(localEndpoint, otherPeer.getEndpoint());
+ final Packet pingPacket = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(pingPacket)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(otherPeer));
// Setup ping to be sent to otherPeer2 after neighbors packet is received
keyPairs = PeerDiscoveryTestHelper.generateKeyPairs(1);
- pingPacketData = PingPacketData.create(agentEndpoint, otherPeer2.getEndpoint());
- final Packet pingPacket2 = Packet.create(PacketType.PING, pingPacketData, keyPairs[0]);
- doReturn(pingPacket2).when(agent).sendPacket(eq(otherPeer2), eq(PacketType.PING), any());
+ pingPacketData = PingPacketData.create(localEndpoint, otherPeer2.getEndpoint());
+ final Packet pingPacket2 = Packet.create(PacketType.PING, pingPacketData, keyPairs.get(0));
+ doReturn(pingPacket2)
+ .when(controller)
+ .createPacket(eq(PacketType.PING), matchPingDataForPeer(otherPeer2));
final Packet neighborsPacket =
MockPacketDataFactory.mockNeighborsPacket(discoPeer, otherPeer, otherPeer2);
@@ -975,8 +955,8 @@ public class PeerDiscoveryControllerTest {
@Test
public void shouldNotRespondToPingFromNonWhitelistedDiscoveryPeer() {
- final DiscoveryPeer[] peers = createPeersInLastBucket(peer, 3);
- final DiscoveryPeer discoPeer = peers[0];
+ final List peers = createPeersInLastBucket(localPeer, 3);
+ final DiscoveryPeer discoPeer = peers.get(0);
final PeerBlacklist blacklist = new PeerBlacklist();
@@ -986,20 +966,15 @@ public class PeerDiscoveryControllerTest {
NodeWhitelistController nodeWhitelistController = new NodeWhitelistController(config);
controller =
- spy(
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Collections.singletonList(discoPeer),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- blacklist,
- nodeWhitelistController));
-
- final Packet pingPacket = mockPingPacket(peers[0], peer);
- controller.onMessage(pingPacket, peers[0]);
- assertThat(controller.getPeers()).doesNotContain(peers[0]);
+ getControllerBuilder()
+ .peers(discoPeer)
+ .blacklist(blacklist)
+ .whitelist(nodeWhitelistController)
+ .build();
+
+ final Packet pingPacket = mockPingPacket(peers.get(0), localPeer);
+ controller.onMessage(pingPacket, peers.get(0));
+ assertThat(controller.getPeers()).doesNotContain(peers.get(0));
}
private static Packet mockPingPacket(final Peer from, final Peer to) {
@@ -1016,8 +991,8 @@ public class PeerDiscoveryControllerTest {
return packet;
}
- private static DiscoveryPeer[] createPeersInLastBucket(final Peer host, final int n) {
- final DiscoveryPeer[] newPeers = new DiscoveryPeer[n];
+ private List createPeersInLastBucket(final Peer host, final int n) {
+ final List newPeers = new ArrayList(n);
// Flipping the most significant bit of the keccak256 will place the peer
// in the last bucket for the corresponding host peer.
@@ -1035,31 +1010,114 @@ public class PeerDiscoveryControllerTest {
final MutableBytesValue newId = MutableBytesValue.create(64);
UInt256.of(i).getBytes().copyTo(newId, newId.size() - UInt256Value.SIZE);
when(newPeer.getId()).thenReturn(newId);
- when(newPeer.getEndpoint()).thenReturn(host.getEndpoint());
- newPeers[i] = newPeer;
+ when(newPeer.getEndpoint())
+ .thenReturn(
+ new Endpoint(
+ host.getEndpoint().getHost(),
+ 100 + counter.incrementAndGet(),
+ OptionalInt.empty()));
+ newPeers.add(newPeer);
}
return newPeers;
}
- private void startPeerDiscoveryController(final DiscoveryPeer... bootstrapPeers) {
- startPeerDiscoveryController(LONG_DELAY_FUNCTION, bootstrapPeers);
+ private PeerDiscoveryController startPeerDiscoveryController(
+ final DiscoveryPeer... bootstrapPeers) {
+ return startPeerDiscoveryController(LONG_DELAY_FUNCTION, bootstrapPeers);
}
- private void startPeerDiscoveryController(
+ private PeerDiscoveryController startPeerDiscoveryController(
final RetryDelayFunction retryDelayFunction, final DiscoveryPeer... bootstrapPeers) {
// Create the controller.
- controller =
- new PeerDiscoveryController(
- vertx,
- agent,
- peerTable,
- Arrays.asList(bootstrapPeers),
- TABLE_REFRESH_INTERVAL_MS,
- PEER_REQUIREMENT,
- new PeerBlacklist(),
- defaultNodeWhitelistController);
+ controller = getControllerBuilder().peers(bootstrapPeers).build();
controller.setRetryDelayFunction(retryDelayFunction);
controller.start();
+ return controller;
+ }
+
+ static class ControllerBuilder {
+ private Collection discoPeers = Collections.emptyList();
+ private PeerBlacklist blacklist = new PeerBlacklist();
+ private NodeWhitelistController whitelist =
+ new NodeWhitelistController(PermissioningConfiguration.createDefault());
+ private MockTimerUtil timerUtil = new MockTimerUtil();
+ private KeyPair keypair;
+ private DiscoveryPeer localPeer;
+ private PeerTable peerTable;
+ private OutboundMessageHandler outboundMessageHandler = OutboundMessageHandler.NOOP;
+ private static final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
+
+ public static ControllerBuilder create() {
+ return new ControllerBuilder();
+ }
+
+ ControllerBuilder peers(final Collection discoPeers) {
+ this.discoPeers = discoPeers;
+ return this;
+ }
+
+ ControllerBuilder peers(final DiscoveryPeer... discoPeers) {
+ this.discoPeers = Arrays.asList(discoPeers);
+ return this;
+ }
+
+ ControllerBuilder blacklist(final PeerBlacklist blacklist) {
+ this.blacklist = blacklist;
+ return this;
+ }
+
+ ControllerBuilder whitelist(final NodeWhitelistController whitelist) {
+ this.whitelist = whitelist;
+ return this;
+ }
+
+ ControllerBuilder timerUtil(final MockTimerUtil timerUtil) {
+ this.timerUtil = timerUtil;
+ return this;
+ }
+
+ ControllerBuilder keyPair(final KeyPair keypair) {
+ this.keypair = keypair;
+ return this;
+ }
+
+ ControllerBuilder localPeer(final DiscoveryPeer localPeer) {
+ this.localPeer = localPeer;
+ return this;
+ }
+
+ ControllerBuilder peerTable(final PeerTable peerTable) {
+ this.peerTable = peerTable;
+ return this;
+ }
+
+ ControllerBuilder outboundMessageHandler(final OutboundMessageHandler outboundMessageHandler) {
+ this.outboundMessageHandler = outboundMessageHandler;
+ return this;
+ }
+
+ PeerDiscoveryController build() {
+ checkNotNull(keypair);
+ if (localPeer == null) {
+ localPeer = helper.createDiscoveryPeer(keypair);
+ }
+ if (peerTable == null) {
+ peerTable = new PeerTable(localPeer.getId());
+ }
+ return spy(
+ new PeerDiscoveryController(
+ keypair,
+ localPeer,
+ peerTable,
+ discoPeers,
+ outboundMessageHandler,
+ timerUtil,
+ TABLE_REFRESH_INTERVAL_MS,
+ PEER_REQUIREMENT,
+ blacklist,
+ whitelist,
+ new Subscribers<>()));
+ }
}
}
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java
index e3da76d570..bd65bf2ca1 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java
@@ -14,86 +14,98 @@ package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.crypto.SECP256K1;
+import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
-import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryAgent;
import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper;
import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
+import tech.pegasys.pantheon.util.Subscribers;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
-import java.util.concurrent.TimeUnit;
+import java.util.Optional;
+import java.util.stream.Collectors;
-import io.vertx.core.Vertx;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
public class PeerDiscoveryTableRefreshTest {
- private final Vertx vertx = spy(Vertx.vertx());
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Test
public void tableRefreshSingleNode() {
- final SECP256K1.KeyPair[] keypairs = PeerDiscoveryTestHelper.generateKeyPairs(2);
- final DiscoveryPeer[] peers = PeerDiscoveryTestHelper.generateDiscoveryPeers(keypairs);
+ final List keypairs = PeerDiscoveryTestHelper.generateKeyPairs(2);
+ final List peers = helper.createDiscoveryPeers(keypairs);
+ DiscoveryPeer localPeer = peers.get(0);
+ KeyPair localKeyPair = keypairs.get(0);
- final PeerDiscoveryAgent agent = mock(PeerDiscoveryAgent.class);
- when(agent.getAdvertisedPeer()).thenReturn(peers[0]);
-
- // Create and start the PeerDiscoveryController, setting the refresh interval to something
- // small.
+ // Create and start the PeerDiscoveryController
+ final OutboundMessageHandler outboundMessageHandler = mock(OutboundMessageHandler.class);
+ final MockTimerUtil timer = new MockTimerUtil();
final PeerDiscoveryController controller =
- new PeerDiscoveryController(
- vertx,
- agent,
- new PeerTable(agent.getAdvertisedPeer().getId()),
- emptyList(),
- 100,
- () -> true,
- new PeerBlacklist(),
- new NodeWhitelistController(PermissioningConfiguration.createDefault()));
+ spy(
+ new PeerDiscoveryController(
+ localKeyPair,
+ localPeer,
+ new PeerTable(localPeer.getId()),
+ emptyList(),
+ outboundMessageHandler,
+ timer,
+ 0,
+ () -> true,
+ new PeerBlacklist(),
+ new NodeWhitelistController(PermissioningConfiguration.createDefault()),
+ new Subscribers<>()));
controller.start();
// Send a PING, so as to add a Peer in the controller.
final PingPacketData ping =
- PingPacketData.create(peers[1].getEndpoint(), peers[0].getEndpoint());
- final Packet packet = Packet.create(PacketType.PING, ping, keypairs[1]);
- controller.onMessage(packet, peers[1]);
+ PingPacketData.create(peers.get(1).getEndpoint(), peers.get(0).getEndpoint());
+ final Packet packet = Packet.create(PacketType.PING, ping, keypairs.get(1));
+ controller.onMessage(packet, peers.get(1));
// Wait until the controller has added the newly found peer.
- await()
- .atMost(1, TimeUnit.SECONDS)
- .untilAsserted(() -> assertThat(controller.getPeers()).hasSize(1));
+ assertThat(controller.getPeers()).hasSize(1);
// As the controller performs refreshes, it'll send FIND_NEIGHBORS packets with random target
// IDs every time.
// We capture the packets so that we can later assert on them.
// Within 1000ms, there should be ~10 packets. But let's be less ambitious and expect at least
// 5.
- final ArgumentCaptor packetDataCaptor = ArgumentCaptor.forClass(PacketData.class);
- verify(agent, timeout(1000).atLeast(5))
- .sendPacket(eq(peers[1]), eq(PacketType.FIND_NEIGHBORS), packetDataCaptor.capture());
+ final ArgumentCaptor captor = ArgumentCaptor.forClass(Packet.class);
+ for (int i = 0; i < 5; i++) {
+ timer.runPeriodicHandlers();
+ }
+ verify(outboundMessageHandler, atLeast(5)).send(eq(peers.get(1)), captor.capture());
+ List capturedFindNeighborsPackets =
+ captor
+ .getAllValues()
+ .stream()
+ .filter(p -> p.getType().equals(PacketType.FIND_NEIGHBORS))
+ .collect(Collectors.toList());
+ assertThat(capturedFindNeighborsPackets.size()).isEqualTo(5);
- // Assert that all packets were FIND_NEIGHBORS packets.
+ // Collect targets from find neighbors packets
final List targets = new ArrayList<>();
- for (final PacketData data : packetDataCaptor.getAllValues()) {
- assertThat(data).isExactlyInstanceOf(FindNeighborsPacketData.class);
- final FindNeighborsPacketData fnpd = (FindNeighborsPacketData) data;
- targets.add(fnpd.getTarget());
+ for (final Packet captured : capturedFindNeighborsPackets) {
+ Optional maybeData =
+ captured.getPacketData(FindNeighborsPacketData.class);
+ assertThat(maybeData).isPresent();
+ final FindNeighborsPacketData neighborsData = maybeData.get();
+ targets.add(neighborsData.getTarget());
}
- assertThat(targets.size()).isGreaterThanOrEqualTo(5);
+ assertThat(targets.size()).isEqualTo(5);
// All targets are unique.
assertThat(targets.size()).isEqualTo(new HashSet<>(targets).size());
diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTableTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTableTest.java
index edc21281ec..fbd9dd13b1 100644
--- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTableTest.java
+++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTableTest.java
@@ -19,15 +19,17 @@ import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper;
import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.AddResult.Outcome;
import tech.pegasys.pantheon.ethereum.p2p.peers.Peer;
+import java.util.List;
+
import org.junit.Test;
public class PeerTableTest {
+ private final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper();
@Test
public void addPeer() {
final PeerTable table = new PeerTable(Peer.randomId(), 16);
- final DiscoveryPeer[] peers =
- PeerDiscoveryTestHelper.generateDiscoveryPeers(PeerDiscoveryTestHelper.generateKeyPairs(5));
+ final List peers = helper.createDiscoveryPeers(5);
for (final DiscoveryPeer peer : peers) {
final PeerTable.AddResult result = table.tryAdd(peer);
@@ -39,9 +41,9 @@ public class PeerTableTest {
@Test
public void addSelf() {
- final DiscoveryPeer self = new DiscoveryPeer(Peer.randomId(), "127.0.0.1", 12345, 12345);
- final PeerTable table = new PeerTable(self.getId(), 16);
- final PeerTable.AddResult result = table.tryAdd(self);
+ final DiscoveryPeer localPeer = new DiscoveryPeer(Peer.randomId(), "127.0.0.1", 12345, 12345);
+ final PeerTable table = new PeerTable(localPeer.getId(), 16);
+ final PeerTable.AddResult result = table.tryAdd(localPeer);
assertThat(result.getOutcome()).isEqualTo(Outcome.SELF);
assertThat(table.getAllPeers()).hasSize(0);
@@ -50,9 +52,7 @@ public class PeerTableTest {
@Test
public void peerExists() {
final PeerTable table = new PeerTable(Peer.randomId(), 16);
- final DiscoveryPeer peer =
- PeerDiscoveryTestHelper.generateDiscoveryPeers(PeerDiscoveryTestHelper.generateKeyPairs(1))[
- 0];
+ final DiscoveryPeer peer = helper.createDiscoveryPeer();
assertThat(table.tryAdd(peer).getOutcome()).isEqualTo(Outcome.ADDED);
diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/Runner.java b/pantheon/src/main/java/tech/pegasys/pantheon/Runner.java
index 246ebca64a..1e8246f352 100644
--- a/pantheon/src/main/java/tech/pegasys/pantheon/Runner.java
+++ b/pantheon/src/main/java/tech/pegasys/pantheon/Runner.java
@@ -152,6 +152,6 @@ public class Runner implements AutoCloseable {
}
public int getP2pTcpPort() {
- return networkRunner.getNetwork().getSelf().getPort();
+ return networkRunner.getNetwork().getLocalPeerInfo().getPort();
}
}