[PAN-2595] Consolidate local enode representation (#1376)

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
mbaxter 6 years ago committed by GitHub
parent d09e6bcee6
commit 43e4530752
  1. 6
      ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNodeList.java
  2. 1
      ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TransactionPoolPropagationTest.java
  3. 21
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeer.java
  4. 29
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeerTest.java
  5. 23
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/ConnectingToLocalNodeException.java
  6. 20
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/InsufficientPeersPermissioningProvider.java
  7. 23
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/PeerNotPermittedException.java
  8. 16
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/api/P2PNetwork.java
  9. 129
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/DefaultP2PNetwork.java
  10. 2
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/PeerBlacklist.java
  11. 35
      ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/InsufficientPeersPermissioningProviderTest.java
  12. 6
      ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java
  13. 41
      ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/network/DefaultP2PNetworkTest.java
  14. 3
      ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/network/P2PNetworkTest.java
  15. 18
      ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningController.java
  16. 5
      ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodePermissioningControllerFactory.java
  17. 21
      ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java
  18. 12
      ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/node/NodePermissioningControllerFactoryTest.java
  19. 34
      pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java

@ -59,12 +59,6 @@ public class TestNodeList implements Closeable {
return node;
}
public void startNetworks() {
for (final TestNode node : nodes) {
node.network.start();
}
}
public void connectAndAssertAll()
throws InterruptedException, ExecutionException, TimeoutException {
for (int i = 0; i < nodes.size(); i++) {

@ -47,7 +47,6 @@ public class TransactionPoolPropagationTest {
/** Helper to do common setup tasks. */
private void initTest(final TestNodeList txNodes) throws Exception {
txNodes.startNetworks();
txNodes.connectAndAssertAll();
txNodes.logPeerConnections();
txNodes.assertPeerCounts();

@ -13,12 +13,8 @@
package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.p2p.ConnectingToLocalNodeException;
import tech.pegasys.pantheon.ethereum.p2p.PeerNotPermittedException;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer;
import tech.pegasys.pantheon.ethereum.p2p.peers.Peer;
@ -42,17 +38,10 @@ public class AdminAddPeer extends AdminModifyPeer {
@Override
protected JsonRpcResponse performOperation(final Object id, final String enode) {
try {
LOG.debug("Adding ({}) to peers", enode);
final EnodeURL enodeURL = EnodeURL.fromString(enode);
final Peer peer = DefaultPeer.fromEnodeURL(enodeURL);
boolean addedToNetwork = peerNetwork.addMaintainConnectionPeer(peer);
return new JsonRpcSuccessResponse(id, addedToNetwork);
} catch (final PeerNotPermittedException e) {
return new JsonRpcErrorResponse(
id, JsonRpcError.NON_PERMITTED_NODE_CANNOT_BE_ADDED_AS_A_PEER);
} catch (final ConnectingToLocalNodeException e) {
return new JsonRpcErrorResponse(id, JsonRpcError.CANT_CONNECT_TO_LOCAL_PEER);
}
LOG.debug("Adding ({}) to peers", enode);
final EnodeURL enodeURL = EnodeURL.fromString(enode);
final Peer peer = DefaultPeer.fromEnodeURL(enodeURL);
boolean addedToNetwork = peerNetwork.addMaintainConnectionPeer(peer);
return new JsonRpcSuccessResponse(id, addedToNetwork);
}
}

@ -22,9 +22,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.p2p.ConnectingToLocalNodeException;
import tech.pegasys.pantheon.ethereum.p2p.P2pDisabledException;
import tech.pegasys.pantheon.ethereum.p2p.PeerNotPermittedException;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import org.junit.Before;
@ -151,31 +149,4 @@ public class AdminAddPeerTest {
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void requestReturnsErrorWhenPeerNotWhitelisted() {
when(p2pNetwork.addMaintainConnectionPeer(any())).thenThrow(new PeerNotPermittedException());
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(
validRequest.getId(), JsonRpcError.NON_PERMITTED_NODE_CANNOT_BE_ADDED_AS_A_PEER);
final JsonRpcResponse actualResponse = method.response(validRequest);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void
p2pNetworkThrowsConnectingToLocalNodeExceptionReturnsCantConnectToLocalNodeJsonError() {
when(p2pNetwork.addMaintainConnectionPeer(any()))
.thenThrow(new ConnectingToLocalNodeException());
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(validRequest.getId(), JsonRpcError.CANT_CONNECT_TO_LOCAL_PEER);
final JsonRpcResponse actualResponse = method.response(validRequest);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
}

@ -1,23 +0,0 @@
/*
* 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;
public class ConnectingToLocalNodeException extends RuntimeException {
public ConnectingToLocalNodeException(final String message) {
super(message);
}
public ConnectingToLocalNodeException() {
super("Cannot add the local node as a peer connection");
}
}

@ -21,13 +21,14 @@ import tech.pegasys.pantheon.util.enode.EnodeURL;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Supplier;
/**
* A permissioning provider that only provides an answer when we have no peers outside of our
* bootnodes
*/
public class InsufficientPeersPermissioningProvider implements ContextualNodePermissioningProvider {
private final EnodeURL selfEnode;
private final Supplier<Optional<EnodeURL>> selfEnode;
private final P2PNetwork p2pNetwork;
private final Collection<EnodeURL> bootnodeEnodes;
private long nonBootnodePeerConnections;
@ -37,12 +38,13 @@ public class InsufficientPeersPermissioningProvider implements ContextualNodePer
* Creates the provider observing the provided p2p network
*
* @param p2pNetwork the p2p network to observe
* @param selfEnode the advertised enode address of this node
* @param selfEnode A supplier that provides a representation of the locally running node, if
* available
* @param bootnodeEnodes the bootnodes that this node is configured to connection to
*/
public InsufficientPeersPermissioningProvider(
final P2PNetwork p2pNetwork,
final EnodeURL selfEnode,
final Supplier<Optional<EnodeURL>> selfEnode,
final Collection<EnodeURL> bootnodeEnodes) {
this.selfEnode = selfEnode;
this.p2pNetwork = p2pNetwork;
@ -64,17 +66,23 @@ public class InsufficientPeersPermissioningProvider implements ContextualNodePer
@Override
public Optional<Boolean> isPermitted(
final EnodeURL sourceEnode, final EnodeURL destinationEnode) {
Optional<EnodeURL> maybeSelfEnode = selfEnode.get();
if (nonBootnodePeerConnections > 0) {
return Optional.empty();
} else if (checkEnode(sourceEnode) && checkEnode(destinationEnode)) {
} else if (!maybeSelfEnode.isPresent()) {
// The local node is not yet ready, so we can't validate enodes yet
return Optional.empty();
} else if (checkEnode(maybeSelfEnode.get(), sourceEnode)
&& checkEnode(maybeSelfEnode.get(), destinationEnode)) {
return Optional.of(true);
} else {
return Optional.empty();
}
}
private boolean checkEnode(final EnodeURL enode) {
return (enode.sameEndpoint(selfEnode) || bootnodeEnodes.stream().anyMatch(enode::sameEndpoint));
private boolean checkEnode(final EnodeURL localEnode, final EnodeURL enode) {
return (enode.sameEndpoint(localEnode)
|| bootnodeEnodes.stream().anyMatch(enode::sameEndpoint));
}
private void handleConnect(final PeerConnection peerConnection) {

@ -1,23 +0,0 @@
/*
* 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;
public class PeerNotPermittedException extends RuntimeException {
public PeerNotPermittedException(final String message) {
super(message);
}
public PeerNotPermittedException() {
super("Cannot add a peer that is not permitted");
}
}

@ -77,21 +77,23 @@ public interface P2PNetwork extends Closeable {
void subscribeDisconnect(DisconnectCallback consumer);
/**
* Adds a {@link Peer} to a list indicating efforts should be made to always stay connected to it
* Adds a {@link Peer} to a list indicating efforts should be made to always stay connected
* regardless of maxPeer limits. Non-permitted peers may be added to this list, but will not
* actually be connected to as long as they are prohibited.
*
* @param peer The peer that should be connected to
* @return boolean representing whether or not the peer has been added to the list or was already
* on it
* @return boolean representing whether or not the peer has been added to the list, false is
* returned if the peer was already on the list
*/
boolean addMaintainConnectionPeer(final Peer peer);
/**
* Removes a {@link Peer} from a list indicating any existing efforts to connect to a given peer
* should be removed, and if connected, the peer should be disconnected
* Disconnect and remove the given {@link Peer} from the maintained peer list. Peer is
* disconnected even if it is not in the maintained peer list. See {@link
* #addMaintainConnectionPeer(Peer)} for details on the maintained peer list.
*
* @param peer The peer to which connections are not longer required
* @return boolean representing whether or not the peer has been disconnected, or if it was not
* currently connected.
* @return boolean representing whether the peer was removed from the maintained peer list
*/
boolean removeMaintainedConnectionPeer(final Peer peer);

@ -19,8 +19,6 @@ import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent;
import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.p2p.ConnectingToLocalNodeException;
import tech.pegasys.pantheon.ethereum.p2p.PeerNotPermittedException;
import tech.pegasys.pantheon.ethereum.p2p.api.DisconnectCallback;
import tech.pegasys.pantheon.ethereum.p2p.api.Message;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
@ -71,6 +69,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -182,12 +181,14 @@ public class DefaultP2PNetwork implements P2PNetwork {
private final String advertisedHost;
private volatile EnodeURL ourEnodeURL;
private volatile Optional<EnodeURL> localEnode = Optional.empty();
private final Optional<NodePermissioningController> nodePermissioningController;
private final Optional<Blockchain> blockchain;
private OptionalLong blockAddedObserverId = OptionalLong.empty();
private final AtomicBoolean started = new AtomicBoolean(false);
/**
* Creates a peer networking service for production purposes.
*
@ -346,7 +347,7 @@ public class DefaultP2PNetwork implements P2PNetwork {
return;
}
if (!isPeerConnectionAllowed(connection)) {
if (!isPeerAllowed(connection)) {
connection.disconnect(DisconnectReason.UNKNOWN);
return;
}
@ -362,21 +363,13 @@ public class DefaultP2PNetwork implements P2PNetwork {
@Override
public boolean addMaintainConnectionPeer(final Peer peer) {
if (!isPeerAllowed(peer)) {
throw new PeerNotPermittedException();
}
if (peer.getId().equals(ourPeerInfo.getNodeId())) {
throw new ConnectingToLocalNodeException();
}
final boolean added = peerMaintainConnectionList.add(peer);
if (added) {
if (isPeerAllowed(peer) && !isConnectingOrConnected(peer)) {
// Connect immediately if appropriate
connect(peer);
return true;
} else {
return false;
}
return added;
}
@Override
@ -394,12 +387,11 @@ public class DefaultP2PNetwork implements P2PNetwork {
return removed;
}
public void checkMaintainedConnectionPeers() {
for (final Peer peer : peerMaintainConnectionList) {
if (!(isConnecting(peer) || isConnected(peer))) {
connect(peer);
}
}
void checkMaintainedConnectionPeers() {
peerMaintainConnectionList.stream()
.filter(p -> !isConnectingOrConnected(p))
.filter(this::isPeerAllowed)
.forEach(this::connect);
}
@VisibleForTesting
@ -529,6 +521,10 @@ public class DefaultP2PNetwork implements P2PNetwork {
@Override
public void start() {
if (!started.compareAndSet(false, true)) {
LOG.warn("Attempted to start an already started " + getClass().getSimpleName());
}
peerDiscoveryAgent.start(ourPeerInfo.getPort()).join();
peerBondedObserverId =
OptionalLong.of(peerDiscoveryAgent.observePeerBondedEvents(handlePeerBondedEvent()));
@ -549,11 +545,10 @@ public class DefaultP2PNetwork implements P2PNetwork {
}
}
this.ourEnodeURL = buildSelfEnodeURL();
LOG.info("Enode URL {}", ourEnodeURL.toString());
createLocalEnode();
peerConnectionScheduler.scheduleWithFixedDelay(
this::checkMaintainedConnectionPeers, 60, 60, TimeUnit.SECONDS);
this::checkMaintainedConnectionPeers, 2, 60, TimeUnit.SECONDS);
peerConnectionScheduler.scheduleWithFixedDelay(
this::attemptPeerConnections, 30, 30, TimeUnit.SECONDS);
}
@ -584,7 +579,7 @@ public class DefaultP2PNetwork implements P2PNetwork {
.getPeerConnections()
.forEach(
peerConnection -> {
if (!isPeerConnectionAllowed(peerConnection)) {
if (!isPeerAllowed(peerConnection)) {
peerConnection.disconnect(DisconnectReason.REQUESTED);
}
});
@ -595,38 +590,36 @@ public class DefaultP2PNetwork implements P2PNetwork {
.getPeerConnections()
.forEach(
peerConnection -> {
if (!isPeerConnectionAllowed(peerConnection)) {
if (!isPeerAllowed(peerConnection)) {
peerConnection.disconnect(DisconnectReason.REQUESTED);
}
});
}
private boolean isPeerConnectionAllowed(final PeerConnection peerConnection) {
if (peerBlacklist.contains(peerConnection)) {
return false;
}
LOG.trace(
"Checking if connection with peer {} is permitted",
peerConnection.getPeerInfo().getNodeId());
return nodePermissioningController
.map(
c -> {
final EnodeURL localPeerEnodeURL = getLocalEnode().orElse(buildSelfEnodeURL());
final EnodeURL remotePeerEnodeURL = peerConnection.getRemoteEnode();
return c.isPermitted(localPeerEnodeURL, remotePeerEnodeURL);
})
.orElse(true);
private boolean isPeerAllowed(final PeerConnection conn) {
return isPeerAllowed(conn.getRemoteEnode());
}
private boolean isPeerAllowed(final Peer peer) {
if (peerBlacklist.contains(peer)) {
return isPeerAllowed(peer.getEnodeURL());
}
private boolean isPeerAllowed(final EnodeURL enode) {
if (peerBlacklist.contains(enode.getNodeId())) {
return false;
}
if (enode.getNodeId().equals(ourPeerInfo.getNodeId())) {
// Peer matches our node id
return false;
}
Optional<EnodeURL> maybeEnode = getLocalEnode();
if (!maybeEnode.isPresent()) {
// If local enode isn't yet available we can't evaluate permissions
return false;
}
return nodePermissioningController
.map(c -> c.isPermitted(ourEnodeURL, peer.getEnodeURL()))
.map(c -> c.isPermitted(maybeEnode.get(), enode))
.orElse(true);
}
@ -640,6 +633,10 @@ public class DefaultP2PNetwork implements P2PNetwork {
return connections.isAlreadyConnected(peer.getId());
}
private boolean isConnectingOrConnected(final Peer peer) {
return isConnected(peer) || isConnecting(peer);
}
@Override
public void stop() {
sendClientQuittingToPeers();
@ -691,10 +688,14 @@ public class DefaultP2PNetwork implements P2PNetwork {
@Override
public Optional<EnodeURL> getLocalEnode() {
return Optional.ofNullable(ourEnodeURL);
return localEnode;
}
private EnodeURL buildSelfEnodeURL() {
private void createLocalEnode() {
if (localEnode.isPresent()) {
return;
}
final BytesValue nodeId = ourPeerInfo.getNodeId();
final int listeningPort = ourPeerInfo.getPort();
final OptionalInt discoveryPort =
@ -704,12 +705,16 @@ public class DefaultP2PNetwork implements P2PNetwork {
.filter(port -> port.getAsInt() != listeningPort)
.orElse(OptionalInt.empty());
return EnodeURL.builder()
.nodeId(nodeId)
.ipAddress(advertisedHost)
.listeningPort(listeningPort)
.discoveryPort(discoveryPort)
.build();
final EnodeURL localEnode =
EnodeURL.builder()
.nodeId(nodeId)
.ipAddress(advertisedHost)
.listeningPort(listeningPort)
.discoveryPort(discoveryPort)
.build();
LOG.info("Enode URL {}", localEnode.toString());
this.localEnode = Optional.of(localEnode);
}
private void onConnectionEstablished(final PeerConnection connection) {
@ -719,14 +724,14 @@ public class DefaultP2PNetwork implements P2PNetwork {
public static class Builder {
protected PeerDiscoveryAgent peerDiscoveryAgent;
protected KeyPair keyPair;
protected NetworkingConfiguration config = NetworkingConfiguration.create();
protected List<Capability> supportedCapabilities;
protected PeerBlacklist peerBlacklist;
protected MetricsSystem metricsSystem;
protected Optional<NodePermissioningController> nodePermissioningController = Optional.empty();
protected Blockchain blockchain = null;
private PeerDiscoveryAgent peerDiscoveryAgent;
private KeyPair keyPair;
private NetworkingConfiguration config = NetworkingConfiguration.create();
private List<Capability> supportedCapabilities;
private PeerBlacklist peerBlacklist;
private MetricsSystem metricsSystem;
private Optional<NodePermissioningController> nodePermissioningController = Optional.empty();
private Blockchain blockchain = null;
private Vertx vertx;
private Optional<NodeLocalConfigPermissioningController>
nodeLocalConfigPermissioningController = Optional.empty();

@ -79,7 +79,7 @@ public class PeerBlacklist implements DisconnectCallback {
this(DEFAULT_BLACKLIST_CAP, Collections.emptySet());
}
private boolean contains(final BytesValue nodeId) {
public boolean contains(final BytesValue nodeId) {
return blacklistedNodeIds.contains(nodeId) || bannedNodeIds.contains(nodeId);
}

@ -26,6 +26,7 @@ import tech.pegasys.pantheon.util.enode.EnodeURL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.function.Consumer;
import org.junit.Test;
@ -60,7 +61,8 @@ public class InsufficientPeersPermissioningProviderTest {
when(p2pNetwork.getPeers()).thenReturn(Collections.emptyList());
final InsufficientPeersPermissioningProvider provider =
new InsufficientPeersPermissioningProvider(p2pNetwork, SELF_ENODE, bootnodes);
new InsufficientPeersPermissioningProvider(
p2pNetwork, () -> Optional.of(SELF_ENODE), bootnodes);
assertThat(provider.isPermitted(SELF_ENODE, ENODE_2)).isEmpty();
}
@ -74,7 +76,8 @@ public class InsufficientPeersPermissioningProviderTest {
final Collection<EnodeURL> bootnodes = Collections.singletonList(ENODE_2);
final InsufficientPeersPermissioningProvider provider =
new InsufficientPeersPermissioningProvider(p2pNetwork, SELF_ENODE, bootnodes);
new InsufficientPeersPermissioningProvider(
p2pNetwork, () -> Optional.of(SELF_ENODE), bootnodes);
assertThat(provider.isPermitted(SELF_ENODE, ENODE_3)).isEmpty();
assertThat(provider.isPermitted(SELF_ENODE, ENODE_2)).isEmpty();
@ -87,12 +90,26 @@ public class InsufficientPeersPermissioningProviderTest {
when(p2pNetwork.getPeers()).thenReturn(Collections.emptyList());
final InsufficientPeersPermissioningProvider provider =
new InsufficientPeersPermissioningProvider(p2pNetwork, SELF_ENODE, bootnodes);
new InsufficientPeersPermissioningProvider(
p2pNetwork, () -> Optional.of(SELF_ENODE), bootnodes);
assertThat(provider.isPermitted(SELF_ENODE, ENODE_2)).contains(true);
assertThat(provider.isPermitted(SELF_ENODE, ENODE_3)).isEmpty();
}
@Test
public void noResultWhenLocalNodeNotReady() {
final Collection<EnodeURL> bootnodes = Collections.singletonList(ENODE_2);
when(p2pNetwork.getPeers()).thenReturn(Collections.emptyList());
final InsufficientPeersPermissioningProvider provider =
new InsufficientPeersPermissioningProvider(p2pNetwork, Optional::empty, bootnodes);
assertThat(provider.isPermitted(SELF_ENODE, ENODE_2)).isEmpty();
assertThat(provider.isPermitted(SELF_ENODE, ENODE_3)).isEmpty();
}
@Test
public void allowsConnectionIfBootnodeAndOnlyBootnodesConnected() {
final Collection<EnodeURL> bootnodes = Collections.singletonList(ENODE_2);
@ -102,7 +119,8 @@ public class InsufficientPeersPermissioningProviderTest {
when(p2pNetwork.getPeers()).thenReturn(Collections.singletonList(bootnodeMatchPeerConnection));
final InsufficientPeersPermissioningProvider provider =
new InsufficientPeersPermissioningProvider(p2pNetwork, SELF_ENODE, bootnodes);
new InsufficientPeersPermissioningProvider(
p2pNetwork, () -> Optional.of(SELF_ENODE), bootnodes);
assertThat(provider.isPermitted(SELF_ENODE, ENODE_2)).contains(true);
assertThat(provider.isPermitted(SELF_ENODE, ENODE_3)).isEmpty();
@ -125,7 +143,8 @@ public class InsufficientPeersPermissioningProviderTest {
when(p2pNetwork.getPeers()).thenReturn(pcs);
final InsufficientPeersPermissioningProvider provider =
new InsufficientPeersPermissioningProvider(p2pNetwork, SELF_ENODE, bootnodes);
new InsufficientPeersPermissioningProvider(
p2pNetwork, () -> Optional.of(SELF_ENODE), bootnodes);
final ArgumentCaptor<DisconnectCallback> callbackCaptor =
ArgumentCaptor.forClass(DisconnectCallback.class);
@ -152,7 +171,8 @@ public class InsufficientPeersPermissioningProviderTest {
when(p2pNetwork.getPeers()).thenReturn(pcs);
final InsufficientPeersPermissioningProvider provider =
new InsufficientPeersPermissioningProvider(p2pNetwork, SELF_ENODE, bootnodes);
new InsufficientPeersPermissioningProvider(
p2pNetwork, () -> Optional.of(SELF_ENODE), bootnodes);
@SuppressWarnings("unchecked")
final ArgumentCaptor<Consumer<PeerConnection>> callbackCaptor =
@ -185,7 +205,8 @@ public class InsufficientPeersPermissioningProviderTest {
when(p2pNetwork.getPeers()).thenReturn(pcs);
final InsufficientPeersPermissioningProvider provider =
new InsufficientPeersPermissioningProvider(p2pNetwork, SELF_ENODE, bootnodes);
new InsufficientPeersPermissioningProvider(
p2pNetwork, () -> Optional.of(SELF_ENODE), bootnodes);
@SuppressWarnings("unchecked")
final ArgumentCaptor<Consumer<PeerConnection>> connectCallbackCaptor =

@ -1074,7 +1074,8 @@ public class PeerDiscoveryControllerTest {
final URI peerURI = URI.create(peer.getEnodeURLString());
config.setNodeWhitelist(Lists.newArrayList(peerURI));
final NodeLocalConfigPermissioningController nodeLocalConfigPermissioningController =
new NodeLocalConfigPermissioningController(config, Collections.emptyList(), selfEnode);
new NodeLocalConfigPermissioningController(
config, Collections.emptyList(), selfEnode.getNodeId());
controller =
getControllerBuilder()
@ -1101,7 +1102,8 @@ public class PeerDiscoveryControllerTest {
final URI peerURI = URI.create(peer.getEnodeURLString());
config.setNodeWhitelist(Lists.newArrayList(peerURI));
final NodeLocalConfigPermissioningController nodeLocalConfigPermissioningController =
new NodeLocalConfigPermissioningController(config, Collections.emptyList(), selfEnode);
new NodeLocalConfigPermissioningController(
config, Collections.emptyList(), selfEnode.getNodeId());
final Consumer<PeerDroppedEvent> peerDroppedEventConsumer = mock(Consumer.class);
final Subscribers<Consumer<PeerDroppedEvent>> peerDroppedSubscribers = new Subscribers();

@ -103,6 +103,7 @@ public final class DefaultP2PNetworkTest {
@Test
public void addingMaintainedNetworkPeerStartsConnection() {
final DefaultP2PNetwork network = mockNetwork();
network.start();
final Peer peer = mockPeer();
assertThat(network.addMaintainConnectionPeer(peer)).isTrue();
@ -114,6 +115,7 @@ public final class DefaultP2PNetworkTest {
@Test
public void addingRepeatMaintainedPeersReturnsFalse() {
final P2PNetwork network = network();
network.start();
final Peer peer = mockPeer();
assertThat(network.addMaintainConnectionPeer(peer)).isTrue();
assertThat(network.addMaintainConnectionPeer(peer)).isFalse();
@ -122,6 +124,8 @@ public final class DefaultP2PNetworkTest {
@Test
public void checkMaintainedConnectionPeersTriesToConnect() {
final DefaultP2PNetwork network = mockNetwork();
network.start();
final Peer peer = mockPeer();
network.peerMaintainConnectionList.add(peer);
@ -129,6 +133,20 @@ public final class DefaultP2PNetworkTest {
verify(network, times(1)).connect(peer);
}
@Test
public void checkMaintainedConnectionPeersDoesNotConnectToDisallowedPeer() {
final DefaultP2PNetwork network = mockNetwork();
network.start();
// Add peer that is not permitted
final Peer peer = mockPeer();
lenient().when(nodePermissioningController.isPermitted(any(), any())).thenReturn(false);
network.peerMaintainConnectionList.add(peer);
network.checkMaintainedConnectionPeers();
verify(network, never()).connect(peer);
}
@Test
public void checkMaintainedConnectionPeersDoesntReconnectPendingPeers() {
final DefaultP2PNetwork network = mockNetwork();
@ -143,24 +161,21 @@ public final class DefaultP2PNetworkTest {
@Test
public void checkMaintainedConnectionPeersDoesntReconnectConnectedPeers() {
final DefaultP2PNetwork network = spy(network());
network.start();
final Peer peer = mockPeer();
// Connect to Peer
verify(network, never()).connect(peer);
assertThat(network.addMaintainConnectionPeer(peer)).isTrue();
network.connect(peer);
verify(network, times(1)).connect(peer);
{
final CompletableFuture<PeerConnection> connection;
connection = network.pendingConnections.remove(peer);
assertThat(connection).isNotNull();
assertThat(connection.cancel(true)).isTrue();
}
// Add peer to maintained list
assertThat(network.addMaintainConnectionPeer(peer)).isTrue();
verify(network, times(1)).connect(peer);
{
final PeerConnection peerConnection = mockPeerConnection(peer.getId());
network.connections.registerConnection(peerConnection);
network.checkMaintainedConnectionPeers();
verify(network, times(1)).connect(peer);
}
// Check maintained connections
network.checkMaintainedConnectionPeers();
verify(network, times(1)).connect(peer);
}
@Test

@ -319,7 +319,8 @@ public class P2PNetworkTest {
config.setNodePermissioningConfigFilePath(tempFile.toAbsolutePath().toString());
final NodeLocalConfigPermissioningController localWhitelistController =
new NodeLocalConfigPermissioningController(config, Collections.emptyList(), selfEnode);
new NodeLocalConfigPermissioningController(
config, Collections.emptyList(), selfEnode.getNodeId());
// turn on whitelisting by adding a different node NOT remote node
localWhitelistController.addNode(
EnodeURL.builder().ipAddress("127.0.0.1").nodeId(Peer.randomId()).build());

@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.permissioning;
import tech.pegasys.pantheon.ethereum.permissioning.node.NodePermissioningProvider;
import tech.pegasys.pantheon.ethereum.permissioning.node.NodeWhitelistUpdatedEvent;
import tech.pegasys.pantheon.util.Subscribers;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.enode.EnodeURL;
import java.io.IOException;
@ -24,6 +25,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -38,7 +40,7 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning
private LocalPermissioningConfiguration configuration;
private final List<EnodeURL> fixedNodes;
private final EnodeURL selfEnode;
private final BytesValue localNodeId;
private final List<EnodeURL> nodesWhitelist = new ArrayList<>();
private final WhitelistPersistor whitelistPersistor;
private final Subscribers<Consumer<NodeWhitelistUpdatedEvent>> nodeWhitelistUpdatedObservers =
@ -47,22 +49,22 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning
public NodeLocalConfigPermissioningController(
final LocalPermissioningConfiguration permissioningConfiguration,
final List<EnodeURL> fixedNodes,
final EnodeURL selfEnode) {
final BytesValue localNodeId) {
this(
permissioningConfiguration,
fixedNodes,
selfEnode,
localNodeId,
new WhitelistPersistor(permissioningConfiguration.getNodePermissioningConfigFilePath()));
}
public NodeLocalConfigPermissioningController(
final LocalPermissioningConfiguration configuration,
final List<EnodeURL> fixedNodes,
final EnodeURL selfEnode,
final BytesValue localNodeId,
final WhitelistPersistor whitelistPersistor) {
this.configuration = configuration;
this.fixedNodes = fixedNodes;
this.selfEnode = selfEnode;
this.localNodeId = localNodeId;
this.whitelistPersistor = whitelistPersistor;
readNodesFromConfig(configuration);
}
@ -197,10 +199,6 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning
return peers.parallelStream().map(EnodeURL::toString).collect(Collectors.toList());
}
private boolean checkSelfEnode(final EnodeURL node) {
return selfEnode.getNodeId().equals(node.getNodeId());
}
private boolean compareEnodes(final EnodeURL nodeA, final EnodeURL nodeB) {
boolean idsMatch = nodeA.getNodeId().equals(nodeB.getNodeId());
boolean hostsMatch = nodeA.getIp().equals(nodeB.getIp());
@ -219,7 +217,7 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning
}
public boolean isPermitted(final EnodeURL node) {
if (checkSelfEnode(node)) {
if (Objects.equals(localNodeId, node.getNodeId())) {
return true;
}
return nodesWhitelist.stream().anyMatch(p -> compareEnodes(p, node));

@ -17,6 +17,7 @@ import tech.pegasys.pantheon.ethereum.permissioning.node.NodePermissioningContro
import tech.pegasys.pantheon.ethereum.permissioning.node.NodePermissioningProvider;
import tech.pegasys.pantheon.ethereum.permissioning.node.provider.SyncStatusNodePermissioningProvider;
import tech.pegasys.pantheon.ethereum.transaction.TransactionSimulator;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.enode.EnodeURL;
import java.util.ArrayList;
@ -30,7 +31,7 @@ public class NodePermissioningControllerFactory {
final PermissioningConfiguration permissioningConfiguration,
final Synchronizer synchronizer,
final Collection<EnodeURL> fixedNodes,
final EnodeURL selfEnode,
final BytesValue localNodeId,
final TransactionSimulator transactionSimulator) {
Optional<SyncStatusNodePermissioningProvider> syncStatusProviderOptional;
@ -42,7 +43,7 @@ public class NodePermissioningControllerFactory {
if (localPermissioningConfiguration.isNodeWhitelistEnabled()) {
NodeLocalConfigPermissioningController localProvider =
new NodeLocalConfigPermissioningController(
localPermissioningConfiguration, new ArrayList<>(fixedNodes), selfEnode);
localPermissioningConfiguration, new ArrayList<>(fixedNodes), localNodeId);
providers.add(localProvider);
}
}

@ -58,8 +58,9 @@ public class NodeLocalConfigPermissioningControllerTest {
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567";
private final String enode2 =
"enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567";
private final String selfEnode =
"enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:1111";
private final EnodeURL selfEnode =
EnodeURL.fromString(
"enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:1111");
@Before
public void setUp() {
@ -68,7 +69,7 @@ public class NodeLocalConfigPermissioningControllerTest {
new NodeLocalConfigPermissioningController(
LocalPermissioningConfiguration.createDefault(),
bootnodesList,
EnodeURL.fromString(selfEnode),
selfEnode.getNodeId(),
whitelistPersistor);
}
@ -219,10 +220,8 @@ public class NodeLocalConfigPermissioningControllerTest {
@Test
public void whenCheckingIfNodeIsPermittedOrderDoesNotMatter() {
controller.addNodes(Arrays.asList(enode1));
assertThat(controller.isPermitted(EnodeURL.fromString(enode1), EnodeURL.fromString(selfEnode)))
.isTrue();
assertThat(controller.isPermitted(EnodeURL.fromString(selfEnode), EnodeURL.fromString(enode1)))
.isTrue();
assertThat(controller.isPermitted(EnodeURL.fromString(enode1), selfEnode)).isTrue();
assertThat(controller.isPermitted(selfEnode, EnodeURL.fromString(enode1))).isTrue();
}
@Test
@ -262,7 +261,7 @@ public class NodeLocalConfigPermissioningControllerTest {
.thenReturn(Arrays.asList(URI.create(expectedEnodeURL)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, EnodeURL.fromString(selfEnode));
permissioningConfig, bootnodesList, selfEnode.getNodeId());
controller.reload();
@ -282,7 +281,7 @@ public class NodeLocalConfigPermissioningControllerTest {
.thenReturn(Arrays.asList(URI.create(expectedEnodeURI)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, EnodeURL.fromString(selfEnode));
permissioningConfig, bootnodesList, selfEnode.getNodeId());
final Throwable thrown = catchThrowable(() -> controller.reload());
@ -381,7 +380,7 @@ public class NodeLocalConfigPermissioningControllerTest {
when(permissioningConfig.getNodeWhitelist()).thenReturn(Arrays.asList(URI.create(enode1)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, EnodeURL.fromString(selfEnode));
permissioningConfig, bootnodesList, selfEnode.getNodeId());
controller.subscribeToListUpdatedEvent(consumer);
controller.reload();
@ -404,7 +403,7 @@ public class NodeLocalConfigPermissioningControllerTest {
when(permissioningConfig.getNodeWhitelist()).thenReturn(Arrays.asList(URI.create(enode1)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, EnodeURL.fromString(selfEnode));
permissioningConfig, bootnodesList, selfEnode.getNodeId());
controller.subscribeToListUpdatedEvent(consumer);
controller.reload();

@ -54,7 +54,8 @@ public class NodePermissioningControllerFactoryTest {
config = new PermissioningConfiguration(Optional.empty(), Optional.empty());
NodePermissioningControllerFactory factory = new NodePermissioningControllerFactory();
NodePermissioningController controller =
factory.create(config, synchronizer, bootnodes, selfEnode, transactionSimulator);
factory.create(
config, synchronizer, bootnodes, selfEnode.getNodeId(), transactionSimulator);
List<NodePermissioningProvider> providers = controller.getProviders();
assertThat(providers.size()).isEqualTo(0);
@ -73,7 +74,8 @@ public class NodePermissioningControllerFactoryTest {
NodePermissioningControllerFactory factory = new NodePermissioningControllerFactory();
NodePermissioningController controller =
factory.create(config, synchronizer, bootnodes, selfEnode, transactionSimulator);
factory.create(
config, synchronizer, bootnodes, selfEnode.getNodeId(), transactionSimulator);
List<NodePermissioningProvider> providers = controller.getProviders();
assertThat(providers.size()).isEqualTo(1);
@ -93,7 +95,8 @@ public class NodePermissioningControllerFactoryTest {
NodePermissioningControllerFactory factory = new NodePermissioningControllerFactory();
NodePermissioningController controller =
factory.create(config, synchronizer, bootnodes, selfEnode, transactionSimulator);
factory.create(
config, synchronizer, bootnodes, selfEnode.getNodeId(), transactionSimulator);
List<NodePermissioningProvider> providers = controller.getProviders();
assertThat(providers.size()).isEqualTo(1);
@ -120,7 +123,8 @@ public class NodePermissioningControllerFactoryTest {
NodePermissioningControllerFactory factory = new NodePermissioningControllerFactory();
NodePermissioningController controller =
factory.create(config, synchronizer, bootnodes, selfEnode, transactionSimulator);
factory.create(
config, synchronizer, bootnodes, selfEnode.getNodeId(), transactionSimulator);
List<NodePermissioningProvider> providers = controller.getProviders();
assertThat(providers.size()).isEqualTo(2);

@ -41,7 +41,6 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.pending.Pen
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.pending.PendingTransactionSubscriptionService;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.syncing.SyncingSubscriptionService;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.ConnectingToLocalNodeException;
import tech.pegasys.pantheon.ethereum.p2p.InsufficientPeersPermissioningProvider;
import tech.pegasys.pantheon.ethereum.p2p.NetworkRunner;
import tech.pegasys.pantheon.ethereum.p2p.NetworkRunner.NetworkBuilder;
@ -54,7 +53,6 @@ import tech.pegasys.pantheon.ethereum.p2p.config.RlpxConfiguration;
import tech.pegasys.pantheon.ethereum.p2p.config.SubProtocolConfiguration;
import tech.pegasys.pantheon.ethereum.p2p.network.DefaultP2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer;
import tech.pegasys.pantheon.ethereum.p2p.peers.Peer;
import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol;
@ -106,15 +104,6 @@ public class RunnerBuilder {
private Optional<PermissioningConfiguration> permissioningConfiguration = Optional.empty();
private Collection<EnodeURL> staticNodes = Collections.emptyList();
private EnodeURL getSelfEnode() {
BytesValue nodeId = pantheonController.getLocalNodeKeyPair().getPublicKey().getEncodedBytes();
return EnodeURL.builder()
.nodeId(nodeId)
.ipAddress(p2pAdvertisedHost)
.listeningPort(p2pListenPort)
.build();
}
public RunnerBuilder vertx(final Vertx vertx) {
this.vertx = vertx;
return this;
@ -257,8 +246,10 @@ public class RunnerBuilder {
new TransactionSimulator(
context.getBlockchain(), context.getWorldStateArchive(), protocolSchedule);
BytesValue localNodeId = keyPair.getPublicKey().getEncodedBytes();
final Optional<NodePermissioningController> nodePermissioningController =
buildNodePermissioningController(bootnodesAsEnodeURLs, synchronizer, transactionSimulator);
buildNodePermissioningController(
bootnodesAsEnodeURLs, synchronizer, transactionSimulator, localNodeId);
final Optional<NodeLocalConfigPermissioningController> nodeWhitelistController =
nodePermissioningController
@ -292,11 +283,12 @@ public class RunnerBuilder {
.metricsSystem(metricsSystem)
.build();
final P2PNetwork network = networkRunner.getNetwork();
nodePermissioningController.ifPresent(
n ->
n.setInsufficientPeersPermissioningProvider(
new InsufficientPeersPermissioningProvider(
networkRunner.getNetwork(), getSelfEnode(), bootnodesAsEnodeURLs)));
networkRunner.getNetwork(), network::getLocalEnode, bootnodesAsEnodeURLs)));
final TransactionPool transactionPool = pantheonController.getTransactionPool();
final MiningCoordinator miningCoordinator = pantheonController.getMiningCoordinator();
@ -316,14 +308,9 @@ public class RunnerBuilder {
final P2PNetwork peerNetwork = networkRunner.getNetwork();
staticNodes.forEach(
enodeURL -> {
final Peer peer = DefaultPeer.fromEnodeURL(enodeURL);
try {
peerNetwork.addMaintainConnectionPeer(peer);
} catch (ConnectingToLocalNodeException ex) {
}
});
staticNodes.stream()
.map(DefaultPeer::fromEnodeURL)
.forEach(peerNetwork::addMaintainConnectionPeer);
Optional<JsonRpcHttpService> jsonRpcHttpService = Optional.empty();
if (jsonRpcConfiguration.isEnabled()) {
@ -409,12 +396,13 @@ public class RunnerBuilder {
private Optional<NodePermissioningController> buildNodePermissioningController(
final List<EnodeURL> bootnodesAsEnodeURLs,
final Synchronizer synchronizer,
final TransactionSimulator transactionSimulator) {
final TransactionSimulator transactionSimulator,
final BytesValue localNodeId) {
Collection<EnodeURL> fixedNodes = getFixedNodes(bootnodesAsEnodeURLs, staticNodes);
return permissioningConfiguration.map(
config ->
new NodePermissioningControllerFactory()
.create(config, synchronizer, fixedNodes, getSelfEnode(), transactionSimulator));
.create(config, synchronizer, fixedNodes, localNodeId, transactionSimulator));
}
@VisibleForTesting

Loading…
Cancel
Save