diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/StaticNodesAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/StaticNodesAcceptanceTest.java new file mode 100644 index 0000000000..91d64beecd --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/StaticNodesAcceptanceTest.java @@ -0,0 +1,44 @@ +/* + * 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.tests.acceptance; + +import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; +import tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; + +public class StaticNodesAcceptanceTest extends AcceptanceTestBase { + + private Node otherNode; + private Node node; + + @Before + public void setUp() throws Exception { + otherNode = pantheon.createNodeWithNoDiscovery("other-node"); + cluster.start(otherNode); + } + + @Test + public void shouldConnectToNodeAddedAsStaticNode() throws Exception { + node = pantheon.createNodeWithStaticNodes("node", Arrays.asList(otherNode)); + cluster.addNode(node); + + node.verify(net.awaitPeerCount(1)); + + WaitUtils.waitFor(1000000, () -> {}); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/StaticNodesUtils.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/StaticNodesUtils.java new file mode 100644 index 0000000000..0a45aa5374 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/StaticNodesUtils.java @@ -0,0 +1,45 @@ +/* + * 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.tests.acceptance.dsl; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +public class StaticNodesUtils { + + public static Path createStaticNodesFile(final Path directory, final List staticNodes) { + try { + final Path tempFile = Files.createTempFile(directory, "", ""); + tempFile.toFile().deleteOnExit(); + + final Path staticNodesFile = tempFile.getParent().resolve("static-nodes.json"); + Files.move(tempFile, staticNodesFile); + staticNodesFile.toFile().deleteOnExit(); + + final String json = + staticNodes.stream() + .map(s -> String.format("\"%s\"", s)) + .collect(Collectors.joining(",", "[", "]")); + Files.write(staticNodesFile, json.getBytes(UTF_8)); + + return staticNodesFile; + } catch (final IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java index 25c687efe3..b638f3cb10 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java @@ -94,7 +94,6 @@ public class PantheonNode implements NodeConfiguration, RunnableNode, AutoClosea private final boolean discoveryEnabled; private final List bootnodes = new ArrayList<>(); private final boolean bootnodeEligible; - private Optional genesisConfig = Optional.empty(); private NodeRequests nodeRequests; private LoginRequestFactory loginRequestFactory; @@ -102,6 +101,7 @@ public class PantheonNode implements NodeConfiguration, RunnableNode, AutoClosea private String token = null; private final List plugins = new ArrayList<>(); private final List extraCLIOptions; + private final List staticNodes; public PantheonNode( final String name, @@ -120,7 +120,8 @@ public PantheonNode( final boolean bootnodeEligible, final boolean revertReasonEnabled, final List plugins, - final List extraCLIOptions) + final List extraCLIOptions, + final List staticNodes) throws IOException { this.bootnodeEligible = bootnodeEligible; this.revertReasonEnabled = revertReasonEnabled; @@ -159,6 +160,7 @@ public PantheonNode( } }); this.extraCLIOptions = extraCLIOptions; + this.staticNodes = staticNodes; LOG.info("Created PantheonNode {}", this.toString()); } @@ -529,6 +531,15 @@ public boolean isRevertReasonEnabled() { return revertReasonEnabled; } + @Override + public List getStaticNodes() { + return staticNodes; + } + + public boolean hasStaticNodes() { + return staticNodes != null && !staticNodes.isEmpty(); + } + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java index 6d23468e39..9b0bc8cbad 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java @@ -18,6 +18,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis; import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; +import tech.pegasys.pantheon.tests.acceptance.dsl.StaticNodesUtils; import java.io.BufferedReader; import java.io.File; @@ -99,6 +100,10 @@ public void startNode(final PantheonNode node) { params.add(node.getBootnodes().stream().map(URI::toString).collect(Collectors.joining(","))); } + if (node.hasStaticNodes()) { + createStaticNodes(node); + } + if (node.isJsonRpcEnabled()) { params.add("--rpc-http-enabled"); params.add("--rpc-http-host"); @@ -244,6 +249,10 @@ private Path createGenesisFile(final PantheonNode node, final String genesisConf } } + private void createStaticNodes(final PantheonNode node) { + StaticNodesUtils.createStaticNodesFile(node.homeDirectory(), node.getStaticNodes()); + } + private String apiList(final Collection rpcApis) { return rpcApis.stream().map(RpcApis::getValue).collect(Collectors.joining(",")); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java index 51ffdcad65..c74bc1433c 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java @@ -154,6 +154,10 @@ public void startNode(final PantheonNode node) { .metricsConfiguration(node.metricsConfiguration()) .p2pEnabled(node.isP2pEnabled()) .graphQLConfiguration(GraphQLConfiguration.createDefault()) + .staticNodes( + node.getStaticNodes().stream() + .map(EnodeURL::fromString) + .collect(Collectors.toList())) .build(); runner.start(); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/NodeConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/NodeConfiguration.java index ce86b46886..f2d306f075 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/NodeConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/NodeConfiguration.java @@ -49,4 +49,6 @@ public interface NodeConfiguration { List getExtraCLIOptions(); boolean isRevertReasonEnabled(); + + List getStaticNodes(); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfiguration.java index fa68bba920..6192e9f030 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfiguration.java @@ -43,6 +43,7 @@ public class PantheonFactoryConfiguration { private final boolean revertReasonEnabled; private final List plugins; private final List extraCLIOptions; + private final List staticNodes; public PantheonFactoryConfiguration( final String name, @@ -61,7 +62,8 @@ public PantheonFactoryConfiguration( final boolean bootnodeEligible, final boolean revertReasonEnabled, final List plugins, - final List extraCLIOptions) { + final List extraCLIOptions, + final List staticNodes) { this.name = name; this.miningParameters = miningParameters; this.privacyParameters = privacyParameters; @@ -79,6 +81,7 @@ public PantheonFactoryConfiguration( this.revertReasonEnabled = revertReasonEnabled; this.plugins = plugins; this.extraCLIOptions = extraCLIOptions; + this.staticNodes = staticNodes; } public String getName() { @@ -148,4 +151,8 @@ public List getExtraCLIOptions() { public boolean isRevertReasonEnabled() { return revertReasonEnabled; } + + public List getStaticNodes() { + return staticNodes; + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfigurationBuilder.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfigurationBuilder.java index 3337181e43..927ac7978f 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfigurationBuilder.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonFactoryConfigurationBuilder.java @@ -52,6 +52,7 @@ public class PantheonFactoryConfigurationBuilder { private boolean revertReasonEnabled = false; private List plugins = new ArrayList<>(); private List extraCLIOptions = new ArrayList<>(); + private List staticNodes = new ArrayList<>(); public PantheonFactoryConfigurationBuilder() { // Check connections more frequently during acceptance tests to cut down on @@ -193,6 +194,11 @@ public PantheonFactoryConfigurationBuilder revertReasonEnabled() { return this; } + public PantheonFactoryConfigurationBuilder staticNodes(final List staticNodes) { + this.staticNodes = staticNodes; + return this; + } + public PantheonFactoryConfiguration build() { return new PantheonFactoryConfiguration( name, @@ -211,6 +217,7 @@ public PantheonFactoryConfiguration build() { bootnodeEligible, revertReasonEnabled, plugins, - extraCLIOptions); + extraCLIOptions, + staticNodes); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java index 3eb1d1669c..408f092f57 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java @@ -13,15 +13,18 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.node.configuration; import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.RunnableNode; import tech.pegasys.pantheon.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationFactory; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.Optional; @@ -49,7 +52,8 @@ public PantheonNode create(final PantheonFactoryConfiguration config) throws IOE config.isBootnodeEligible(), config.isRevertReasonEnabled(), config.getPlugins(), - config.getExtraCLIOptions()); + config.getExtraCLIOptions(), + config.getStaticNodes()); } public PantheonNode createMinerNode(final String name) throws IOException { @@ -263,4 +267,25 @@ public PantheonNode createIbft2NodeWithValidators(final String name, final Strin asList(validators), nodes, genesis::createIbft2GenesisConfig)) .build()); } + + public PantheonNode createNodeWithStaticNodes(final String name, final List staticNodes) + throws IOException { + + final List staticNodesUrls = + staticNodes.stream() + .map(node -> (RunnableNode) node) + .map(RunnableNode::enodeUrl) + .map(URI::toASCIIString) + .collect(toList()); + + return create( + new PantheonFactoryConfigurationBuilder() + .name(name) + .jsonRpcEnabled() + .webSocketEnabled() + .discoveryEnabled(false) + .staticNodes(staticNodesUrls) + .bootnodeEligible(false) + .build()); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfiguration.java index 1d3fbd5fc9..9cc68e48bd 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/privacy/PrivacyPantheonFactoryConfiguration.java @@ -23,6 +23,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.node.configuration.PantheonFactoryConfiguration; import tech.pegasys.pantheon.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -66,7 +67,8 @@ public class PrivacyPantheonFactoryConfiguration extends PantheonFactoryConfigur bootnodeEligible, revertReasonEnabled, plugins, - extraCLIOptions); + extraCLIOptions, + new ArrayList<>()); this.orion = orion; } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java index a66233cd04..f56af47ddf 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivacyNode.java @@ -33,6 +33,7 @@ import tech.pegasys.pantheon.util.bytes.BytesValues; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -83,7 +84,8 @@ public PrivacyNode( bootnodeEligible, revertReasonEnabled, plugins, - extraCLIOptions); + extraCLIOptions, + new ArrayList<>()); this.orion = orion; }