added --nodes-whitelist param to CLI and NodeWhitelistController (#346)

* added --nodes-whitelist param to CLI and NodeWhitelistController
Sally MacFarlane 6 years ago committed by GitHub
parent 665ae97b84
commit e3a58b6b7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 43
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/PermissioningConfiguration.java
  2. 46
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/PermissioningConfigurationTest.java
  3. 7
      pantheon/src/main/java/tech/pegasys/pantheon/Runner.java
  4. 20
      pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java
  5. 28
      pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
  6. 49
      pantheon/src/main/java/tech/pegasys/pantheon/controller/NodeWhitelistController.java
  7. 8
      pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java
  8. 2
      pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java
  9. 35
      pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java

@ -0,0 +1,43 @@
/*
* 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.permissioning;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class PermissioningConfiguration {
private List<String> nodeWhitelist;
private boolean nodeWhitelistSet;
public List<String> getNodeWhitelist() {
return nodeWhitelist;
}
public static PermissioningConfiguration createDefault() {
final PermissioningConfiguration config = new PermissioningConfiguration();
config.nodeWhitelist = new ArrayList<>();
return config;
}
public void setNodeWhitelist(final Collection<String> nodeWhitelist) {
if (nodeWhitelist != null) {
this.nodeWhitelist.addAll(nodeWhitelist);
this.nodeWhitelistSet = true;
}
}
public boolean isNodeWhitelistSet() {
return nodeWhitelistSet;
}
}

@ -0,0 +1,46 @@
/*
* 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.permissioning;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import org.junit.Test;
public class PermissioningConfigurationTest {
@Test
public void defaultConfiguration() {
final PermissioningConfiguration configuration = PermissioningConfiguration.createDefault();
assertThat(configuration.getNodeWhitelist()).isEmpty();
assertThat(configuration.isNodeWhitelistSet()).isFalse();
}
@Test
public void setNodeWhitelist() {
final String[] nodes = {"enode://001@123:4567", "enode://002@123:4567", "enode://003@123:4567"};
final PermissioningConfiguration configuration = PermissioningConfiguration.createDefault();
configuration.setNodeWhitelist(Arrays.asList(nodes));
assertThat(configuration.getNodeWhitelist()).containsExactlyInAnyOrder(nodes);
assertThat(configuration.isNodeWhitelistSet()).isTrue();
}
@Test
public void setNodeWhiteListPassingNull() {
final PermissioningConfiguration configuration = PermissioningConfiguration.createDefault();
configuration.setNodeWhitelist(null);
assertThat(configuration.getNodeWhitelist()).isEmpty();
assertThat(configuration.isNodeWhitelistSet()).isFalse();
}
}

@ -12,6 +12,7 @@
*/ */
package tech.pegasys.pantheon; package tech.pegasys.pantheon;
import tech.pegasys.pantheon.controller.NodeWhitelistController;
import tech.pegasys.pantheon.controller.PantheonController; import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcHttpService; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcHttpService;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketService; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketService;
@ -46,19 +47,23 @@ public class Runner implements AutoCloseable {
private final PantheonController<?> pantheonController; private final PantheonController<?> pantheonController;
private final Path dataDir; private final Path dataDir;
private final NodeWhitelistController nodeWhitelistController;
Runner( Runner(
final Vertx vertx, final Vertx vertx,
final NetworkRunner networkRunner, final NetworkRunner networkRunner,
final Optional<JsonRpcHttpService> jsonRpc, final Optional<JsonRpcHttpService> jsonRpc,
final Optional<WebSocketService> websocketRpc, final Optional<WebSocketService> websocketRpc,
final PantheonController<?> pantheonController, final PantheonController<?> pantheonController,
final Path dataDir) { final Path dataDir,
final NodeWhitelistController nodeWhitelistController) {
this.vertx = vertx; this.vertx = vertx;
this.networkRunner = networkRunner; this.networkRunner = networkRunner;
this.jsonRpc = jsonRpc; this.jsonRpc = jsonRpc;
this.websocketRpc = websocketRpc; this.websocketRpc = websocketRpc;
this.pantheonController = pantheonController; this.pantheonController = pantheonController;
this.dataDir = dataDir; this.dataDir = dataDir;
this.nodeWhitelistController = nodeWhitelistController;
} }
public void execute() { public void execute() {

@ -12,6 +12,7 @@
*/ */
package tech.pegasys.pantheon; package tech.pegasys.pantheon;
import tech.pegasys.pantheon.controller.NodeWhitelistController;
import tech.pegasys.pantheon.controller.PantheonController; import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.ProtocolContext;
@ -50,6 +51,7 @@ import tech.pegasys.pantheon.ethereum.p2p.netty.NettyP2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol; import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.metrics.prometheus.PrometheusMetricsSystem; import tech.pegasys.pantheon.metrics.prometheus.PrometheusMetricsSystem;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
@ -78,6 +80,7 @@ public class RunnerBuilder {
private WebSocketConfiguration webSocketConfiguration; private WebSocketConfiguration webSocketConfiguration;
private Path dataDir; private Path dataDir;
private Collection<String> bannedNodeIds; private Collection<String> bannedNodeIds;
private PermissioningConfiguration permissioningConfiguration;
public RunnerBuilder vertx(final Vertx vertx) { public RunnerBuilder vertx(final Vertx vertx) {
this.vertx = vertx; this.vertx = vertx;
@ -124,6 +127,12 @@ public class RunnerBuilder {
return this; return this;
} }
public RunnerBuilder permissioningConfiguration(
final PermissioningConfiguration permissioningConfiguration) {
this.permissioningConfiguration = permissioningConfiguration;
return this;
}
public RunnerBuilder dataDir(final Path dataDir) { public RunnerBuilder dataDir(final Path dataDir) {
this.dataDir = dataDir; this.dataDir = dataDir;
return this; return this;
@ -259,8 +268,17 @@ public class RunnerBuilder {
vertx, webSocketConfiguration, subscriptionManager, webSocketsJsonRpcMethods)); vertx, webSocketConfiguration, subscriptionManager, webSocketsJsonRpcMethods));
} }
NodeWhitelistController nodeWhitelistController =
new NodeWhitelistController(permissioningConfiguration);
return new Runner( return new Runner(
vertx, networkRunner, jsonRpcHttpService, webSocketService, pantheonController, dataDir); vertx,
networkRunner,
jsonRpcHttpService,
webSocketService,
pantheonController,
dataDir,
nodeWhitelistController);
} }
private FilterManager createFilterManager( private FilterManager createFilterManager(

@ -33,6 +33,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer; import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.util.InvalidConfigurationException; import tech.pegasys.pantheon.ethereum.util.InvalidConfigurationException;
import tech.pegasys.pantheon.util.BlockImporter; import tech.pegasys.pantheon.util.BlockImporter;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
@ -377,6 +378,17 @@ public class PantheonCommand implements Runnable {
) )
private final BytesValue extraData = DEFAULT_EXTRA_DATA; private final BytesValue extraData = DEFAULT_EXTRA_DATA;
// Permissioning: A list of whitelist nodes can be passed.
@Option(
names = {"--nodes-whitelist"},
paramLabel = "<enode://id@host:port>",
description =
"Comma separated enode URLs for permissioned networks. You may specify an empty list.",
split = ",",
arity = "0..*"
)
private final Collection<String> nodesWhitelist = null;
public PantheonCommand( public PantheonCommand(
final BlockImporter blockImporter, final BlockImporter blockImporter,
final RunnerBuilder runnerBuilder, final RunnerBuilder runnerBuilder,
@ -442,7 +454,8 @@ public class PantheonCommand implements Runnable {
maxPeers, maxPeers,
p2pHostAndPort, p2pHostAndPort,
jsonRpcConfiguration(), jsonRpcConfiguration(),
webSocketConfiguration()); webSocketConfiguration(),
permissioningConfiguration());
} }
PantheonController<?> buildController() { PantheonController<?> buildController() {
@ -487,6 +500,13 @@ public class PantheonCommand implements Runnable {
return webSocketConfiguration; return webSocketConfiguration;
} }
private PermissioningConfiguration permissioningConfiguration() {
final PermissioningConfiguration permissioningConfiguration =
PermissioningConfiguration.createDefault();
permissioningConfiguration.setNodeWhitelist(nodesWhitelist);
return permissioningConfiguration;
}
private SynchronizerConfiguration buildSyncConfig(final SyncMode syncMode) { private SynchronizerConfiguration buildSyncConfig(final SyncMode syncMode) {
checkNotNull(syncMode); checkNotNull(syncMode);
synchronizerConfigurationBuilder.syncMode(syncMode); synchronizerConfigurationBuilder.syncMode(syncMode);
@ -502,11 +522,12 @@ public class PantheonCommand implements Runnable {
final int maxPeers, final int maxPeers,
final HostAndPort discoveryHostAndPort, final HostAndPort discoveryHostAndPort,
final JsonRpcConfiguration jsonRpcConfiguration, final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration) { final WebSocketConfiguration webSocketConfiguration,
final PermissioningConfiguration permissioningConfiguration) {
checkNotNull(runnerBuilder); checkNotNull(runnerBuilder);
final Runner runner = Runner runner =
runnerBuilder runnerBuilder
.vertx(Vertx.vertx()) .vertx(Vertx.vertx())
.pantheonController(controller) .pantheonController(controller)
@ -520,6 +541,7 @@ public class PantheonCommand implements Runnable {
.webSocketConfiguration(webSocketConfiguration) .webSocketConfiguration(webSocketConfiguration)
.dataDir(dataDir) .dataDir(dataDir)
.bannedNodeIds(bannedNodeIds) .bannedNodeIds(bannedNodeIds)
.permissioningConfiguration(permissioningConfiguration)
.build(); .build();
addShutdownHook(runner); addShutdownHook(runner);

@ -0,0 +1,49 @@
/*
* 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.controller;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class NodeWhitelistController {
private static final Logger LOG = LogManager.getLogger();
private static List<String> nodeWhitelist;
private static boolean nodeWhitelistSet = false;
public NodeWhitelistController(final PermissioningConfiguration configuration) {
nodeWhitelist = new ArrayList<>();
if (configuration != null && configuration.getNodeWhitelist() != null) {
nodeWhitelist.addAll(configuration.getNodeWhitelist());
nodeWhitelistSet = true;
}
}
public boolean addNode(final String nodeId) {
return nodeWhitelist.add(nodeId);
}
public boolean removeNode(final String nodeId) {
return nodeWhitelist.remove(nodeId);
}
public static boolean isNodeWhitelistSet() {
return nodeWhitelistSet;
}
}

@ -34,6 +34,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec;
import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer; import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.storage.StorageProvider; import tech.pegasys.pantheon.ethereum.storage.StorageProvider;
import tech.pegasys.pantheon.ethereum.storage.keyvalue.RocksDbStorageProvider; import tech.pegasys.pantheon.ethereum.storage.keyvalue.RocksDbStorageProvider;
import tech.pegasys.pantheon.util.uint.UInt256; import tech.pegasys.pantheon.util.uint.UInt256;
@ -118,6 +119,7 @@ public final class RunnerTest {
final ExecutorService executorService = Executors.newFixedThreadPool(2); final ExecutorService executorService = Executors.newFixedThreadPool(2);
final JsonRpcConfiguration aheadJsonRpcConfiguration = jsonRpcConfiguration(); final JsonRpcConfiguration aheadJsonRpcConfiguration = jsonRpcConfiguration();
final WebSocketConfiguration aheadWebSocketConfiguration = wsRpcConfiguration(); final WebSocketConfiguration aheadWebSocketConfiguration = wsRpcConfiguration();
final PermissioningConfiguration aheadPermissioningConfiguration = permissioningConfiguration();
final RunnerBuilder runnerBuilder = final RunnerBuilder runnerBuilder =
new RunnerBuilder() new RunnerBuilder()
.vertx(Vertx.vertx()) .vertx(Vertx.vertx())
@ -134,6 +136,7 @@ public final class RunnerTest {
.jsonRpcConfiguration(aheadJsonRpcConfiguration) .jsonRpcConfiguration(aheadJsonRpcConfiguration)
.webSocketConfiguration(aheadWebSocketConfiguration) .webSocketConfiguration(aheadWebSocketConfiguration)
.dataDir(dbAhead) .dataDir(dbAhead)
.permissioningConfiguration(aheadPermissioningConfiguration)
.build(); .build();
try { try {
@ -251,6 +254,11 @@ public final class RunnerTest {
return configuration; return configuration;
} }
private PermissioningConfiguration permissioningConfiguration() {
final PermissioningConfiguration configuration = PermissioningConfiguration.createDefault();
return configuration;
}
private static void setupState( private static void setupState(
final int count, final int count,
final ProtocolSchedule<Void> protocolSchedule, final ProtocolSchedule<Void> protocolSchedule,

@ -22,6 +22,7 @@ import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.util.BlockImporter; import tech.pegasys.pantheon.util.BlockImporter;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -71,6 +72,7 @@ public abstract class CommandTestAbstract {
@Captor ArgumentCaptor<Integer> intArgumentCaptor; @Captor ArgumentCaptor<Integer> intArgumentCaptor;
@Captor ArgumentCaptor<JsonRpcConfiguration> jsonRpcConfigArgumentCaptor; @Captor ArgumentCaptor<JsonRpcConfiguration> jsonRpcConfigArgumentCaptor;
@Captor ArgumentCaptor<WebSocketConfiguration> wsRpcConfigArgumentCaptor; @Captor ArgumentCaptor<WebSocketConfiguration> wsRpcConfigArgumentCaptor;
@Captor ArgumentCaptor<PermissioningConfiguration> permissioningConfigurationArgumentCaptor;
@Before @Before
public void initMocks() throws Exception { public void initMocks() throws Exception {

@ -93,6 +93,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
when(mockRunnerBuilder.maxPeers(anyInt())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.maxPeers(anyInt())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.jsonRpcConfiguration(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.jsonRpcConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.webSocketConfiguration(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.webSocketConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.permissioningConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.dataDir(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.dataDir(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.bannedNodeIds(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.bannedNodeIds(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.build()).thenReturn(mockRunner); when(mockRunnerBuilder.build()).thenReturn(mockRunner);
@ -420,7 +421,39 @@ public class PantheonCommandTest extends CommandTestAbstract {
} }
@Test @Test
public void banNodeIdsOptionMustBeUsed() { public void callingWithNodesWhitelistOptionButNoValueMustNotError() {
parseCommand("--nodes-whitelist");
verify(mockRunnerBuilder)
.permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(permissioningConfigurationArgumentCaptor.getValue().getNodeWhitelist()).isEmpty();
assertThat(permissioningConfigurationArgumentCaptor.getValue().isNodeWhitelistSet()).isTrue();
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty();
}
@Test
public void nodesWhitelistOptionMustBeUsed() {
final String[] nodes = {"enode://001@123:4567", "enode://002@123:4567", "enode://003@123:4567"};
parseCommand("--nodes-whitelist", String.join(",", nodes));
verify(mockRunnerBuilder)
.permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(permissioningConfigurationArgumentCaptor.getValue().getNodeWhitelist())
.containsExactlyInAnyOrder(nodes);
assertThat(permissioningConfigurationArgumentCaptor.getValue().isNodeWhitelistSet()).isTrue();
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty();
}
@Test
public void bannedNodeIdsOptionMustBeUsed() {
final String[] nodes = {"0001", "0002", "0003"}; final String[] nodes = {"0001", "0002", "0003"};
parseCommand("--banned-nodeids", String.join(",", nodes)); parseCommand("--banned-nodeids", String.join(",", nodes));

Loading…
Cancel
Save