diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/AcceptanceTestBase.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/AcceptanceTestBase.java index d42e9eb0d1..14f5b58a6d 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/AcceptanceTestBase.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/AcceptanceTestBase.java @@ -15,6 +15,7 @@ package tech.pegasys.pantheon.tests.acceptance.dsl; import tech.pegasys.pantheon.tests.acceptance.dsl.account.Accounts; import tech.pegasys.pantheon.tests.acceptance.dsl.blockchain.Blockchain; import tech.pegasys.pantheon.tests.acceptance.dsl.contract.ContractVerifier; +import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Admin; import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Clique; import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Eth; import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Ibft; @@ -45,6 +46,7 @@ public class AcceptanceTestBase { protected final Eth eth; protected final Net net; protected final Perm perm; + protected final Admin admin; protected final PantheonNodeFactory pantheon; protected final ContractVerifier contractVerifier; protected final WaitConditions wait; @@ -61,6 +63,7 @@ public class AcceptanceTestBase { cluster = new Cluster(net); transactions = new Transactions(accounts); perm = new Perm(transactions); + admin = new Admin(); web3 = new Web3(new Web3Transactions()); pantheon = new PantheonNodeFactory(); contractVerifier = new ContractVerifier(accounts.getPrimaryBenefactor()); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/Condition.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/Condition.java index df5b4a49c6..31baa2a967 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/Condition.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/Condition.java @@ -14,6 +14,7 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.condition; import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; +@FunctionalInterface public interface Condition { void verify(Node node); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Admin.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Admin.java new file mode 100644 index 0000000000..b047a5ee9e --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Admin.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.dsl.jsonrpc; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; + +import org.web3j.protocol.core.Response; + +public class Admin { + private Transaction addPeerTransaction(final String enode) { + return (n) -> { + try { + final Response resp = n.adminAddPeer(enode).send(); + assertThat(resp).isNotNull(); + assertThat(resp.hasError()).isFalse(); + return resp.getResult(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + }; + } + + public Condition addPeer(final String enode) { + return (n) -> { + final Boolean result = n.execute(addPeerTransaction(enode)); + assertThat(result).isTrue(); + }; + } +} 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 7091374bd4..fc0608c405 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 @@ -92,7 +92,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto final GenesisConfigProvider genesisConfigProvider, final int p2pPort, final Boolean p2pEnabled, - final boolean discovery) + final boolean discoveryEnabled) throws IOException { this.homeDirectory = Files.createTempDirectory("acctest"); this.keyPair = KeyPairUtil.loadKeyPair(homeDirectory); @@ -106,7 +106,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto this.genesisConfigProvider = genesisConfigProvider; this.devMode = devMode; this.p2pEnabled = p2pEnabled; - this.discoveryEnabled = discovery; + this.discoveryEnabled = discoveryEnabled; LOG.info("Created PantheonNode {}", this.toString()); } @@ -188,7 +188,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto final WebSocketService webSocketService = new WebSocketService(url, true); try { webSocketService.connect(); - } catch (ConnectException e) { + } catch (final ConnectException e) { throw new RuntimeException("Error connection to WebSocket endpoint", e); } @@ -200,7 +200,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto } private void checkIfWebSocketEndpointIsAvailable(final String url) { - WebSocketClient webSocketClient = new WebSocketClient(URI.create(url)); + final WebSocketClient webSocketClient = new WebSocketClient(URI.create(url)); // Web3j implementation always invoke the listener (even when one hasn't been set). We are using // this stub implementation to avoid a NullPointerException. webSocketClient.setListener( @@ -226,7 +226,7 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto webSocketClient.connect(); try { Awaitility.await().atMost(5, TimeUnit.SECONDS).until(webSocketClient::isOpen); - } catch (ConditionTimeoutException e) { + } catch (final ConditionTimeoutException e) { throw new WebsocketNotConnectedException(); } finally { webSocketClient.close(); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java index 876508f4a7..5daccc423b 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java @@ -28,9 +28,9 @@ class PantheonFactoryConfiguration { private final MetricsConfiguration metricsConfiguration; private final PermissioningConfiguration permissioningConfiguration; private final boolean devMode; - private final Boolean discoveryEnabled; private final GenesisConfigProvider genesisConfigProvider; private final Boolean p2pEnabled; + private final boolean discoveryEnabled; PantheonFactoryConfiguration( final String name, @@ -42,7 +42,7 @@ class PantheonFactoryConfiguration { final boolean devMode, final GenesisConfigProvider genesisConfigProvider, final Boolean p2pEnabled, - final Boolean discoveryEnabled) { + final boolean discoveryEnabled) { this.name = name; this.miningParameters = miningParameters; this.jsonRpcConfiguration = jsonRpcConfiguration; @@ -83,7 +83,7 @@ class PantheonFactoryConfiguration { return devMode; } - public Boolean isDiscoveryEnabled() { + public boolean isDiscoveryEnabled() { return discoveryEnabled; } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java index d9dcd3d79d..8fae0fb16b 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java @@ -35,9 +35,9 @@ public class PantheonFactoryConfigurationBuilder { private PermissioningConfiguration permissioningConfiguration = PermissioningConfiguration.createDefault(); private boolean devMode = true; - private Boolean discoveryEnabled = true; private GenesisConfigProvider genesisConfigProvider = ignore -> Optional.empty(); private Boolean p2pEnabled = true; + private boolean discoveryEnabled = true; public PantheonFactoryConfigurationBuilder setName(final String name) { this.name = name; @@ -109,13 +109,13 @@ public class PantheonFactoryConfigurationBuilder { return this; } - public PantheonFactoryConfigurationBuilder setDiscoveryEnabled(final Boolean discoveryEnabled) { - this.discoveryEnabled = discoveryEnabled; + public PantheonFactoryConfigurationBuilder setP2pEnabled(final Boolean p2pEnabled) { + this.p2pEnabled = p2pEnabled; return this; } - public PantheonFactoryConfigurationBuilder setP2pEnabled(final Boolean p2pEnabled) { - this.p2pEnabled = p2pEnabled; + public PantheonFactoryConfigurationBuilder setDiscoveryEnabled(final boolean discoveryEnabled) { + this.discoveryEnabled = discoveryEnabled; return this; } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java index 18054fab0a..2d38cb0218 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java @@ -99,6 +99,17 @@ public class PantheonNodeFactory { .build()); } + public PantheonNode createArchiveNodeWithDiscoveryDisabledAndAdmin(final String name) + throws IOException { + return create( + new PantheonFactoryConfigurationBuilder() + .setName(name) + .setJsonRpcConfiguration(jsonRpcConfigWithAdmin()) + .webSocketEnabled() + .setDiscoveryEnabled(false) + .build()); + } + public PantheonNode createArchiveNodeWithP2pDisabled(final String name) throws IOException { return create( new PantheonFactoryConfigurationBuilder() @@ -324,6 +335,10 @@ public class PantheonNodeFactory { return createJsonRpcConfigWithRpcApiEnabled(RpcApis.PERM); } + private JsonRpcConfiguration jsonRpcConfigWithAdmin() { + return createJsonRpcConfigWithRpcApiEnabled(RpcApis.ADMIN); + } + private JsonRpcConfiguration createJsonRpcConfigWithRpcApiEnabled(final RpcApi rpcApi) { final JsonRpcConfiguration jsonRpcConfig = createJsonRpcEnabledConfig(); final List rpcApis = new ArrayList<>(jsonRpcConfig.getRpcApis()); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/PantheonWeb3j.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/PantheonWeb3j.java index d488a8d5c1..3bbbce5c20 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/PantheonWeb3j.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/PantheonWeb3j.java @@ -136,4 +136,14 @@ public class PantheonWeb3j extends JsonRpc2_0Web3j { public static class RemoveNodeResponse extends Response {} public static class GetNodesWhitelistResponse extends Response> {} + + public static class AdminAddPeerResponse extends Response {} + + public Request adminAddPeer(final String enodeAddress) { + return new Request<>( + "admin_addPeer", + Collections.singletonList(enodeAddress), + web3jService, + AdminAddPeerResponse.class); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transaction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transaction.java index beea7962ff..474f378c33 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transaction.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transaction.java @@ -12,6 +12,7 @@ */ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction; +@FunctionalInterface public interface Transaction { T execute(final PantheonWeb3j node); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/admin/AdminAddPeerAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/admin/AdminAddPeerAcceptanceTest.java new file mode 100644 index 0000000000..a023c07548 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/admin/AdminAddPeerAcceptanceTest.java @@ -0,0 +1,53 @@ +/* + * 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.jsonrpc.admin; + +import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.cluster.Cluster; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.cluster.ClusterConfiguration; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class AdminAddPeerAcceptanceTest extends AcceptanceTestBase { + private PantheonNode nodeA; + private PantheonNode nodeB; + private Cluster p2pDisabledCluster; + + @Before + public void setUp() throws Exception { + final ClusterConfiguration clusterConfiguration = + new ClusterConfigurationBuilder().setAwaitPeerDiscovery(false).build(); + p2pDisabledCluster = new Cluster(clusterConfiguration, net); + nodeA = pantheon.createArchiveNodeWithDiscoveryDisabledAndAdmin("nodeA"); + nodeB = pantheon.createArchiveNodeWithDiscoveryDisabledAndAdmin("nodeB"); + p2pDisabledCluster.start(nodeA, nodeB); + } + + @After + public void tearDown() { + p2pDisabledCluster.stop(); + } + + @Test + public void adminAddPeerForcesConnection() { + final String nodeBEnode = nodeB.enodeUrl(); + nodeA.verify(net.awaitPeerCount(0)); + nodeA.verify(admin.addPeer(nodeBEnode)); + nodeA.verify(net.awaitPeerCount(1)); + nodeB.verify(net.awaitPeerCount(1)); + } +}