diff --git a/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java b/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java index cf331a51f7..f38ef5b7b2 100644 --- a/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java +++ b/ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java @@ -35,6 +35,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.metrics.MetricsSystem; @@ -85,6 +86,7 @@ public Map methods() { final Optional accountWhitelistController = Optional.of(mock(AccountWhitelistController.class)); final PrivacyParameters privacyParameters = mock(PrivacyParameters.class); + final PeerCache peerCache = mock(PeerCache.class); return new JsonRpcMethodsFactory() .methods( @@ -102,6 +104,7 @@ public Map methods() { new HashSet<>(), accountWhitelistController, RpcApis.DEFAULT_JSON_RPC_APIS, - privacyParameters); + privacyParameters, + peerCache); } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java index b9e7e8938e..eee97ba47a 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java @@ -90,6 +90,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.BlockResultFactory; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; @@ -124,7 +125,8 @@ public Map methods( final Collection rpcApis, final FilterManager filterManager, final Optional accountsWhitelistController, - final PrivacyParameters privacyParameters) { + final PrivacyParameters privacyParameters, + final PeerCache peerCache) { final BlockchainQueries blockchainQueries = new BlockchainQueries(blockchain, worldStateArchive); return methods( @@ -142,7 +144,8 @@ public Map methods( supportedCapabilities, accountsWhitelistController, rpcApis, - privacyParameters); + privacyParameters, + peerCache); } public Map methods( @@ -160,7 +163,8 @@ public Map methods( final Set supportedCapabilities, final Optional accountsWhitelistController, final Collection rpcApis, - final PrivacyParameters privacyParameters) { + final PrivacyParameters privacyParameters, + final PeerCache peerCache) { final Map enabledMethods = new HashMap<>(); if (!rpcApis.isEmpty()) { addMethods(enabledMethods, new RpcModules(rpcApis)); @@ -267,7 +271,7 @@ blockchainQueries, new TransactionTracer(blockReplay), parameter), if (rpcApis.contains(RpcApis.ADMIN)) { addMethods( enabledMethods, - new AdminAddPeer(p2pNetwork, parameter), + new AdminAddPeer(p2pNetwork, parameter, peerCache), new AdminNodeInfo( clientVersion, networkId, genesisConfigOptions, p2pNetwork, blockchainQueries), new AdminPeers(p2pNetwork)); diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeer.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeer.java index ea9e6d635a..df8a73ffd3 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeer.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeer.java @@ -24,18 +24,24 @@ import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; +import tech.pegasys.pantheon.util.enode.EnodeURL; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class AdminAddPeer implements JsonRpcMethod { + private static final Logger LOG = LogManager.getLogger(); private final P2PNetwork peerNetwork; private final JsonRpcParameter parameters; + private final PeerCache peerCache; - public AdminAddPeer(final P2PNetwork peerNetwork, final JsonRpcParameter parameters) { + public AdminAddPeer( + final P2PNetwork peerNetwork, final JsonRpcParameter parameters, final PeerCache peerCache) { this.peerNetwork = peerNetwork; this.parameters = parameters; + this.peerCache = peerCache; } @Override @@ -50,9 +56,11 @@ public JsonRpcResponse response(final JsonRpcRequest req) { } try { final String enodeString = parameters.required(req.getParams(), 0, String.class); - final Peer peer = DefaultPeer.fromURI(enodeString); - final boolean added = peerNetwork.addMaintainConnectionPeer(peer); - return new JsonRpcSuccessResponse(req.getId(), added); + final EnodeURL enodeURL = new EnodeURL(enodeString); + final Peer peer = DefaultPeer.fromEnodeURL(enodeURL); + boolean addedToNetwork = peerNetwork.addMaintainConnectionPeer(peer); + boolean addedToCache = peerCache.add(enodeURL); + return new JsonRpcSuccessResponse(req.getId(), addedToNetwork && addedToCache); } catch (final InvalidJsonRpcParameters e) { return new JsonRpcErrorResponse(req.getId(), JsonRpcError.INVALID_PARAMS); } catch (final IllegalArgumentException e) { diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java index 37264f01fc..6b12e5bb7b 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java @@ -45,6 +45,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.util.RawBlockIterator; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; @@ -187,7 +188,8 @@ public void setupTest() throws Exception { supportedCapabilities, Optional.empty(), JSON_RPC_APIS, - privacyParameters); + privacyParameters, + mock(PeerCache.class)); final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault(); config.setPort(0); service = diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java index 26c2e3752a..5f6cffd53b 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java @@ -27,6 +27,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; @@ -101,7 +102,8 @@ public void initServerAndClient() throws Exception { supportedCapabilities, Optional.of(mock(AccountWhitelistController.class)), JSON_RPC_APIS, - mock(PrivacyParameters.class))); + mock(PrivacyParameters.class), + mock(PeerCache.class))); service = createJsonRpcHttpService(); service.start().join(); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java index abd3d94b8e..f6264c4816 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -33,6 +33,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; @@ -132,7 +133,8 @@ public static void initServerAndClient() throws Exception { supportedCapabilities, Optional.empty(), JSON_RPC_APIS, - mock(PrivacyParameters.class))); + mock(PrivacyParameters.class), + mock(PeerCache.class))); service = createJsonRpcHttpService(); jwtAuth = service.authenticationService.get().getJwtAuthProvider(); service.start().join(); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java index eb10d8e40e..e4814ce44e 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java @@ -29,6 +29,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; @@ -184,7 +185,8 @@ private JsonRpcHttpService createJsonRpcHttpServiceWithRpcApis(final JsonRpcConf supportedCapabilities, Optional.of(mock(AccountWhitelistController.class)), config.getRpcApis(), - mock(PrivacyParameters.class))); + mock(PrivacyParameters.class), + mock(PeerCache.class))); final JsonRpcHttpService jsonRpcHttpService = new JsonRpcHttpService( vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), rpcMethods); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java index b4d7431c5e..fb8377e5e2 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java @@ -42,6 +42,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; @@ -129,7 +130,8 @@ public static void initServerAndClient() throws Exception { supportedCapabilities, Optional.of(mock(AccountWhitelistController.class)), JSON_RPC_APIS, - mock(PrivacyParameters.class))); + mock(PrivacyParameters.class), + mock(PeerCache.class))); service = createJsonRpcHttpService(); service.start().join(); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeerTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeerTest.java index bbe316380d..e9bde201b4 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeerTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/AdminAddPeerTest.java @@ -14,8 +14,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import tech.pegasys.pantheon.crypto.SECP256K1.PublicKey; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError; @@ -25,6 +28,10 @@ import tech.pegasys.pantheon.ethereum.p2p.P2pDisabledException; import tech.pegasys.pantheon.ethereum.p2p.PeerNotWhitelistedException; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; +import tech.pegasys.pantheon.util.enode.EnodeURL; + +import java.math.BigInteger; import org.junit.Before; import org.junit.Test; @@ -36,13 +43,23 @@ public class AdminAddPeerTest { @Mock private P2PNetwork p2pNetwork; + @Mock private PeerCache peerCache; private final JsonRpcParameter parameter = new JsonRpcParameter(); private AdminAddPeer method; + private final EnodeURL validEnodeURL = + new EnodeURL( + PublicKey.create(BigInteger.valueOf(0)).toString().substring(2), "127.0.0.1", 30303); + + private final JsonRpcRequest validRequest = + new JsonRpcRequest("2.0", "admin_addPeer", new String[] {validEnodeURL.toString()}); + @Before public void setup() { - method = new AdminAddPeer(p2pNetwork, parameter); + + method = new AdminAddPeer(p2pNetwork, parameter, peerCache); + when(peerCache.add(any())).thenReturn(true); } @Test @@ -94,46 +111,38 @@ public void requestHasInvalidEnode() { public void requestAddsValidEnode() { when(p2pNetwork.addMaintainConnectionPeer(any())).thenReturn(true); + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(validRequest.getId(), true); + + final JsonRpcResponse actualResponse = method.response(validRequest); + + assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + verify(peerCache).add(eq(validEnodeURL)); + } + + @Test + public void ifPeerIsNotAddedToCacheFalseIsReturned() { + when(p2pNetwork.addMaintainConnectionPeer(any())).thenReturn(true); + when(peerCache.add(any())).thenReturn(false); + final JsonRpcRequest request = - new JsonRpcRequest( - "2.0", - "admin_addPeer", - new String[] { - "enode://" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "@127.0.0.1:30303" - }); + new JsonRpcRequest("2.0", "admin_addPeer", new String[] {validEnodeURL.toString()}); - final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(request.getId(), true); + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(request.getId(), false); final JsonRpcResponse actualResponse = method.response(request); assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + verify(peerCache).add(eq(validEnodeURL)); } @Test public void requestRefusesListOfNodes() { + final JsonRpcRequest request = new JsonRpcRequest( "2.0", "admin_addPeer", - new String[] { - "enode://" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "@127.0.0.1:30303", - "enode://" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000001" - + "@127.0.0.2:30303" - }); + new String[] {validEnodeURL.toString(), validEnodeURL.toString()}); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS); @@ -147,22 +156,10 @@ public void requestRefusesListOfNodes() { public void requestReturnsFalseIfAddFails() { when(p2pNetwork.addMaintainConnectionPeer(any())).thenReturn(false); - final JsonRpcRequest request = - new JsonRpcRequest( - "2.0", - "admin_addPeer", - new String[] { - "enode://" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "@127.0.0.1:30303" - }); + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(validRequest.getId(), false); - final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(request.getId(), false); - - final JsonRpcResponse actualResponse = method.response(request); + final JsonRpcResponse actualResponse = method.response(validRequest); assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); } @@ -173,23 +170,10 @@ public void requestReturnsErrorWhenP2pDisabled() { .thenThrow( new P2pDisabledException("P2P networking disabled. Unable to connect to add peer.")); - final JsonRpcRequest request = - new JsonRpcRequest( - "2.0", - "admin_addPeer", - new String[] { - "enode://" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "@127.0.0.1:30303" - }); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getId(), JsonRpcError.P2P_DISABLED); + new JsonRpcErrorResponse(validRequest.getId(), JsonRpcError.P2P_DISABLED); - final JsonRpcResponse actualResponse = method.response(request); + final JsonRpcResponse actualResponse = method.response(validRequest); assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); } @@ -199,24 +183,11 @@ public void requestReturnsErrorWhenPeerNotWhitelisted() { when(p2pNetwork.addMaintainConnectionPeer(any())) .thenThrow(new PeerNotWhitelistedException("Cannot add peer that is not whitelisted")); - final JsonRpcRequest request = - new JsonRpcRequest( - "2.0", - "admin_addPeer", - new String[] { - "enode://" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "00000000000000000000000000000000" - + "@127.0.0.1:30303" - }); - final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse( - request.getId(), JsonRpcError.NON_WHITELISTED_NODE_CANNOT_BE_ADDED_AS_A_PEER); + validRequest.getId(), JsonRpcError.NON_WHITELISTED_NODE_CANNOT_BE_ADDED_AS_A_PEER); - final JsonRpcResponse actualResponse = method.response(request); + final JsonRpcResponse actualResponse = method.response(validRequest); assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); } diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/DefaultPeer.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/DefaultPeer.java index b1c94d19aa..0ba705b673 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/DefaultPeer.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/DefaultPeer.java @@ -20,6 +20,7 @@ import tech.pegasys.pantheon.ethereum.rlp.RLPInput; import tech.pegasys.pantheon.util.NetworkUtility; import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.enode.EnodeURL; import java.net.URI; import java.util.Objects; @@ -42,6 +43,18 @@ public class DefaultPeer extends DefaultPeerId implements Peer { private final Endpoint endpoint; + public static DefaultPeer fromEnodeURL(final EnodeURL enodeURL) { + final int udpPort = enodeURL.getDiscoveryPort().orElse(enodeURL.getListeningPort()); + + final Endpoint endpoint = + new Endpoint( + enodeURL.getInetAddress().getHostAddress(), + udpPort, + OptionalInt.of(enodeURL.getListeningPort())); + + return new DefaultPeer(BytesValue.fromHexString(enodeURL.getNodeId()), endpoint); + } + /** * Creates a {@link DefaultPeer} instance from a String representation of an enode URL. * diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/cache/PeerCache.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/cache/PeerCache.java new file mode 100644 index 0000000000..070648516a --- /dev/null +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/cache/PeerCache.java @@ -0,0 +1,41 @@ +/* + * 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.peers.cache; + +import tech.pegasys.pantheon.util.enode.EnodeURL; + +import java.util.HashSet; +import java.util.Set; + +import com.google.common.collect.ImmutableList; + +public class PeerCache { + + private final Set nodes = new HashSet<>(); + + public PeerCache(final Set initialNodes) { + nodes.addAll(initialNodes); + } + + public boolean add(final EnodeURL enode) throws IllegalArgumentException { + return nodes.add(enode); + } + + public boolean remove(final EnodeURL enode) { + return nodes.remove(enode); + } + + public ImmutableList getStaticNodes() { + return ImmutableList.copyOf(nodes); + } +} diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/cache/PersistentJsonPeerCache.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/cache/PersistentJsonPeerCache.java new file mode 100644 index 0000000000..33ada81cec --- /dev/null +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/cache/PersistentJsonPeerCache.java @@ -0,0 +1,101 @@ +/* + * 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.peers.cache; + +import tech.pegasys.pantheon.util.enode.EnodeURL; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import io.vertx.core.json.DecodeException; +import io.vertx.core.json.JsonArray; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class PersistentJsonPeerCache extends PersistentPeerCache { + + private static final Logger LOG = LogManager.getLogger(); + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private final Path path; + + public PersistentJsonPeerCache(final Set initialNodes, final Path path) { + super(initialNodes); + this.path = path; + } + + public static PersistentJsonPeerCache fromPath(final Path path) + throws IOException, IllegalArgumentException { + final Set result = new HashSet<>(); + + final byte[] staticNodesContent; + try { + staticNodesContent = Files.readAllBytes(path); + if (staticNodesContent.length == 0) { + return new PersistentJsonPeerCache(result, path); + } + } catch (FileNotFoundException | NoSuchFileException ex) { + LOG.info("No StaticNodes file ({}) exists, creating empty cache.", path); + return new PersistentJsonPeerCache(result, path); + } catch (IOException ex) { + LOG.info("Unable to parse static nodes file ({})", path); + throw ex; + } + + try { + final JsonArray enodeJsonArray = new JsonArray(new String(staticNodesContent, UTF_8)); + for (Object jsonObj : enodeJsonArray.getList()) { + final String enodeString = (String) jsonObj; + result.add(decodeString(enodeString)); + } + return new PersistentJsonPeerCache(result, path); + } catch (DecodeException ex) { + LOG.info("Content of ({}} was invalid json, and could not be decoded.", path); + throw ex; + } catch (IllegalArgumentException ex) { + LOG.info("Parsing ({}) has failed due incorrectly formatted enode element.", path); + throw ex; + } + } + + private static EnodeURL decodeString(final String input) { + try { + return new EnodeURL(input); + } catch (IllegalArgumentException ex) { + LOG.info("Illegally constructed enode supplied ({})", input); + throw ex; + } + } + + // TODO: This should potentially be using an atomicMove to prevent file corruption + // on sudden terminations. + @Override + protected void persist() { + final JsonArray array = + new JsonArray( + getStaticNodes().stream().map(EnodeURL::toString).collect(Collectors.toList())); + + try { + Files.write(path, array.toString().getBytes(UTF_8)); + } catch (IOException ex) { + LOG.info("Unable to persist peers to disk, file write failed."); + } + } +} diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/cache/PersistentPeerCache.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/cache/PersistentPeerCache.java new file mode 100644 index 0000000000..befae4c800 --- /dev/null +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/cache/PersistentPeerCache.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.ethereum.p2p.peers.cache; + +import tech.pegasys.pantheon.util.enode.EnodeURL; + +import java.util.Set; + +public abstract class PersistentPeerCache extends PeerCache { + + public PersistentPeerCache(final Set initialNodes) { + super(initialNodes); + } + + @Override + public boolean add(final EnodeURL enode) throws IllegalArgumentException { + boolean cachedNodesAltered = super.add(enode); + if (cachedNodesAltered) { + persist(); + } + return cachedNodesAltered; + } + + @Override + public boolean remove(final EnodeURL enode) { + boolean cachedNodesAltered = super.remove(enode); + if (cachedNodesAltered) { + persist(); + } + return cachedNodesAltered; + } + + protected abstract void persist(); +} diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/peers/PersistentJsonPeerCacheTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/peers/PersistentJsonPeerCacheTest.java new file mode 100644 index 0000000000..fbe4eaa80a --- /dev/null +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/peers/PersistentJsonPeerCacheTest.java @@ -0,0 +1,159 @@ +/* + * 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.peers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PersistentJsonPeerCache; +import tech.pegasys.pantheon.util.enode.EnodeURL; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import com.google.common.collect.ImmutableList; +import io.vertx.core.json.DecodeException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class PersistentJsonPeerCacheTest { + + // NOTE: The invalid_static_nodes file is identical to the valid, however one node's port is set + // to "A". + + // First peer ion the valid_static_nodes file. + private final EnodeURL firstItemInValidList = + new EnodeURL( + "50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa", + "127.0.0.1", + 30303); + + @Rule public TemporaryFolder testFolder = new TemporaryFolder(); + + @Test + public void validFileLoadsWithExpectedNumberOfPeers() throws IOException { + final URL resource = PersistentJsonPeerCacheTest.class.getResource("valid_static_nodes.json"); + final Path path = Paths.get(resource.getPath()); + + final PeerCache cache = PersistentJsonPeerCache.fromPath(path); + final ImmutableList enodes = cache.getStaticNodes(); + + assertThat(enodes.size()).isEqualTo(4); + } + + @Test + public void invalidFileThrowsAnException() { + final URL resource = PersistentJsonPeerCacheTest.class.getResource("invalid_static_nodes.json"); + final Path path = Paths.get(resource.getPath()); + + assertThatThrownBy(() -> PersistentJsonPeerCache.fromPath(path)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void nonJsonFileThrowsAnException() throws IOException { + final File tempFile = testFolder.newFile("file.txt"); + tempFile.deleteOnExit(); + Files.write(tempFile.toPath(), "This Is Not Json".getBytes(Charset.forName("UTF-8"))); + + assertThatThrownBy(() -> PersistentJsonPeerCache.fromPath(tempFile.toPath())) + .isInstanceOf(DecodeException.class); + } + + @Test + public void anEmptyCacheIsCreatedIfTheFileDoesNotExist() throws IOException { + final Path path = Paths.get("./arbirtraryFilename.txt"); + + final PeerCache cache = PersistentJsonPeerCache.fromPath(path); + assertThat(cache.getStaticNodes().size()).isZero(); + } + + @Test + public void cacheIsCreatedIfFileExistsButIsEmpty() throws IOException { + final File tempFile = testFolder.newFile("file.txt"); + tempFile.deleteOnExit(); + + final PeerCache cache = PersistentJsonPeerCache.fromPath(tempFile.toPath()); + final ImmutableList enodes = cache.getStaticNodes(); + + assertThat(enodes.size()).isEqualTo(0); + } + + @Test + public void addPeerReturnsFalseIfSuppliedPeerIsAlreadyInList() throws IOException { + final URL resource = PersistentJsonPeerCacheTest.class.getResource("valid_static_nodes.json"); + final Path original = Paths.get(resource.getPath()); + final File tempFile = testFolder.newFile("file.txt"); + tempFile.deleteOnExit(); + final Path tempPath = tempFile.toPath(); + Files.copy(original, tempPath, StandardCopyOption.REPLACE_EXISTING); + + final PeerCache cache = PersistentJsonPeerCache.fromPath(tempPath); + + final ImmutableList preAddList = cache.getStaticNodes(); + assertThat(cache.add(firstItemInValidList)).isFalse(); + assertThat(cache.getStaticNodes()).containsExactlyElementsOf(preAddList); + } + + @Test + public void addPeerWritesToFileIfPeerIsNotAlreadyInList() throws IOException { + final File tempFile = testFolder.newFile("file.txt"); + tempFile.deleteOnExit(); + final PeerCache cache = PersistentJsonPeerCache.fromPath(tempFile.toPath()); + + assertThat(cache.add(firstItemInValidList)).isTrue(); + assertThat(cache.getStaticNodes()).containsExactly(firstItemInValidList); + + // Ensure file has been updated by reloading into a new cache (ensures file validity as well). + final PersistentJsonPeerCache newCache = PersistentJsonPeerCache.fromPath(tempFile.toPath()); + assertThat(newCache.getStaticNodes()).contains(firstItemInValidList); + } + + @Test + public void removePeerReturnsFalseIfSuppliedPeerIsNotInList() throws IOException { + final File tempFile = testFolder.newFile("file.txt"); + tempFile.deleteOnExit(); + final PeerCache cache = PersistentJsonPeerCache.fromPath(tempFile.toPath()); + + assertThat(cache.remove(firstItemInValidList)).isFalse(); + assertThat(cache.getStaticNodes().size()).isZero(); + } + + @Test + public void removePeerReturnsTrueAndRemovesItemIfPeerIsInList() + throws IOException { + final URL resource = PersistentJsonPeerCacheTest.class.getResource("valid_static_nodes.json"); + final Path original = Paths.get(resource.getPath()); + final File tempFile = testFolder.newFile("file.txt"); + tempFile.deleteOnExit(); + final Path tempPath = tempFile.toPath(); + Files.copy(original, tempPath, StandardCopyOption.REPLACE_EXISTING); + + final PeerCache cache = PersistentJsonPeerCache.fromPath(tempPath); + assertThat(cache.remove(firstItemInValidList)).isTrue(); + assertThat(cache.getStaticNodes()).doesNotContain(firstItemInValidList); + + // Ensure file has been updated by reloading into a new cache (ensures file validity as well). + final PersistentJsonPeerCache newCache = PersistentJsonPeerCache.fromPath(tempPath); + assertThat(newCache.getStaticNodes()).doesNotContain(firstItemInValidList); + } +} diff --git a/ethereum/p2p/src/test/resources/tech/pegasys/pantheon/ethereum/p2p/peers/invalid_static_nodes.json b/ethereum/p2p/src/test/resources/tech/pegasys/pantheon/ethereum/p2p/peers/invalid_static_nodes.json new file mode 100644 index 0000000000..3b55676f90 --- /dev/null +++ b/ethereum/p2p/src/test/resources/tech/pegasys/pantheon/ethereum/p2p/peers/invalid_static_nodes.json @@ -0,0 +1 @@ +["enode://50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa@127.0.0.1:A","enode://02beb46bc17227616be44234071dfa18516684e45eed88049190b6cb56b0bae218f045fd0450f123b8f55c60b96b78c45e8e478004293a8de6818aa4e02eff97@127.0.0.1:30304","enode://819e5cbd81f123516b10f04bf620daa2b385efef06d77253148b814bf1bb6197ff58ebd1fd7bf5dc765b49a4440c733bf941e479c800173f2bfeb887e4fbcbc2@127.0.0.1:30305","enode://6cf53e25d2a98a22e7e205a86bda7077e3c8a7bc99e5ff88ddfd2037a550969ab566f069ffa455df0cfae0c21f7aec3447e414eccc473a3e8b20984b90f164ac@127.0.0.1:30306"] \ No newline at end of file diff --git a/ethereum/p2p/src/test/resources/tech/pegasys/pantheon/ethereum/p2p/peers/valid_static_nodes.json b/ethereum/p2p/src/test/resources/tech/pegasys/pantheon/ethereum/p2p/peers/valid_static_nodes.json new file mode 100644 index 0000000000..1e1764425d --- /dev/null +++ b/ethereum/p2p/src/test/resources/tech/pegasys/pantheon/ethereum/p2p/peers/valid_static_nodes.json @@ -0,0 +1,4 @@ +["enode://50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa@127.0.0.1:30303", + "enode://02beb46bc17227616be44234071dfa18516684e45eed88049190b6cb56b0bae218f045fd0450f123b8f55c60b96b78c45e8e478004293a8de6818aa4e02eff97@127.0.0.1:30304", + "enode://819e5cbd81f123516b10f04bf620daa2b385efef06d77253148b814bf1bb6197ff58ebd1fd7bf5dc765b49a4440c733bf941e479c800173f2bfeb887e4fbcbc2@127.0.0.1:30305", + "enode://6cf53e25d2a98a22e7e205a86bda7077e3c8a7bc99e5ff88ddfd2037a550969ab566f069ffa455df0cfae0c21f7aec3447e414eccc473a3e8b20984b90f164ac@127.0.0.1:30306"] \ No newline at end of file diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java index b48943a795..1860ae8055 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java @@ -49,7 +49,10 @@ import tech.pegasys.pantheon.ethereum.p2p.config.RlpxConfiguration; import tech.pegasys.pantheon.ethereum.p2p.config.SubProtocolConfiguration; import tech.pegasys.pantheon.ethereum.p2p.netty.NettyP2PNetwork; +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.peers.cache.PeerCache; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol; import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; @@ -91,6 +94,7 @@ public class RunnerBuilder { private MetricsConfiguration metricsConfiguration; private MetricsSystem metricsSystem; private Optional permissioningConfiguration = Optional.empty(); + private PeerCache peerCache; private EnodeURL getSelfEnode() { String nodeId = pantheonController.getLocalNodeKeyPair().getPublicKey().toString(); @@ -173,6 +177,11 @@ public RunnerBuilder metricsSystem(final MetricsSystem metricsSystem) { return this; } + public RunnerBuilder peerCache(final PeerCache peerCache) { + this.peerCache = peerCache; + return this; + } + public Runner build() { Preconditions.checkNotNull(pantheonController); @@ -270,6 +279,9 @@ public Runner build() { final PrivacyParameters privacyParameters = pantheonController.getPrivacyParameters(); final FilterManager filterManager = createFilterManager(vertx, context, transactionPool); + final P2PNetwork peerNetwork = networkRunner.getNetwork(); + addCachedPeersToNetwork(peerNetwork); + Optional jsonRpcHttpService = Optional.empty(); if (jsonRpcConfiguration.isEnabled()) { final Map jsonRpcMethods = @@ -277,7 +289,7 @@ public Runner build() { context, protocolSchedule, pantheonController, - networkRunner.getNetwork(), + peerNetwork, synchronizer, transactionPool, miningCoordinator, @@ -286,7 +298,8 @@ public Runner build() { jsonRpcConfiguration.getRpcApis(), filterManager, accountWhitelistController, - privacyParameters); + privacyParameters, + peerCache); jsonRpcHttpService = Optional.of( new JsonRpcHttpService( @@ -300,7 +313,7 @@ public Runner build() { context, protocolSchedule, pantheonController, - networkRunner.getNetwork(), + peerNetwork, synchronizer, transactionPool, miningCoordinator, @@ -309,7 +322,8 @@ public Runner build() { webSocketConfiguration.getRpcApis(), filterManager, accountWhitelistController, - privacyParameters); + privacyParameters, + peerCache); final SubscriptionManager subscriptionManager = createSubscriptionManager(vertx, transactionPool); @@ -368,7 +382,8 @@ private Map jsonRpcMethods( final Collection jsonRpcApis, final FilterManager filterManager, final Optional accountWhitelistController, - final PrivacyParameters privacyParameters) { + final PrivacyParameters privacyParameters, + final PeerCache peerCache) { final Map methods = new JsonRpcMethodsFactory() .methods( @@ -387,7 +402,8 @@ private Map jsonRpcMethods( jsonRpcApis, filterManager, accountWhitelistController, - privacyParameters); + privacyParameters, + peerCache); methods.putAll(pantheonController.getAdditionalJsonRpcMethods(jsonRpcApis)); return methods; } @@ -447,4 +463,13 @@ private MetricsService createMetricsService( final Vertx vertx, final MetricsConfiguration configuration) { return MetricsService.create(vertx, configuration, metricsSystem); } + + private void addCachedPeersToNetwork(final P2PNetwork peerNetwork) { + peerCache.getStaticNodes().stream() + .forEach( + enodeURL -> { + final Peer peer = DefaultPeer.fromEnodeURL(enodeURL); + peerNetwork.addMaintainConnectionPeer(peer); + }); + } } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index 1ea10af439..d30e1ea7dd 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -48,6 +48,8 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PersistentJsonPeerCache; import tech.pegasys.pantheon.ethereum.permissioning.LocalPermissioningConfiguration; import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfigurationBuilder; import tech.pegasys.pantheon.metrics.MetricCategory; @@ -618,6 +620,8 @@ public void run() { permissioningConfiguration.ifPresent( p -> ensureAllBootnodesAreInWhitelist(ethNetworkConfig, p)); + final PeerCache peerCache = createStaticNodesCache(); + synchronize( buildController(), p2pEnabled, @@ -629,7 +633,8 @@ public void run() { jsonRpcConfiguration, webSocketConfiguration, metricsConfiguration(), - permissioningConfiguration); + permissioningConfiguration, + peerCache); } catch (final Exception e) { throw new ParameterException(this.commandLine, e.getMessage(), e); } @@ -847,7 +852,8 @@ private void synchronize( final JsonRpcConfiguration jsonRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, final MetricsConfiguration metricsConfiguration, - final Optional permissioningConfiguration) { + final Optional permissioningConfiguration, + final PeerCache peerCache) { checkNotNull(runnerBuilder); @@ -869,6 +875,7 @@ private void synchronize( .bannedNodeIds(bannedNodeIds) .metricsSystem(metricsSystem.get()) .metricsConfiguration(metricsConfiguration) + .peerCache(peerCache) .build(); addShutdownHook(runner); @@ -1086,4 +1093,11 @@ public MetricsSystem getMetricsSystem() { public PantheonExceptionHandler exceptionHandler() { return exceptionHandlerSupplier.get(); } + + private PeerCache createStaticNodesCache() throws IOException { + final String STATIC_NODES_FILENAME = "static_nodes.json"; + final Path staticNodesPath = dataDir().resolve(STATIC_NODES_FILENAME); + + return PersistentJsonPeerCache.fromPath(staticNodesPath); + } } diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java index 874c992444..faf621ff79 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java @@ -12,6 +12,7 @@ */ package tech.pegasys.pantheon; +import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; import static tech.pegasys.pantheon.cli.EthNetworkConfig.DEV_NETWORK_ID; import static tech.pegasys.pantheon.cli.NetworkName.DEV; @@ -39,6 +40,8 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PeerCache; +import tech.pegasys.pantheon.ethereum.p2p.peers.cache.PersistentJsonPeerCache; import tech.pegasys.pantheon.ethereum.permissioning.LocalPermissioningConfiguration; import tech.pegasys.pantheon.ethereum.storage.StorageProvider; import tech.pegasys.pantheon.ethereum.storage.keyvalue.RocksDbStorageProvider; @@ -52,6 +55,7 @@ import java.net.InetAddress; import java.net.URI; import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; import java.util.Collections; import java.util.List; @@ -138,6 +142,8 @@ private void syncFromGenesis(final SyncMode mode) throws Exception { final MetricsConfiguration aheadMetricsConfiguration = metricsConfiguration(); final LocalPermissioningConfiguration aheadPermissioningConfiguration = permissioningConfiguration(); + final PeerCache peerCache = + new PersistentJsonPeerCache(emptySet(), Paths.get("./arbitraryPath.txt")); final RunnerBuilder runnerBuilder = new RunnerBuilder() .vertx(Vertx.vertx()) @@ -146,7 +152,8 @@ private void syncFromGenesis(final SyncMode mode) throws Exception { .discoveryPort(0) .maxPeers(3) .metricsSystem(noOpMetricsSystem) - .bannedNodeIds(Collections.emptySet()); + .bannedNodeIds(emptySet()) + .peerCache(peerCache); Runner runnerBehind = null; final Runner runnerAhead = diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java index a4fd7f3858..69bc411665 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java @@ -124,6 +124,7 @@ public void initMocks() throws Exception { when(mockRunnerBuilder.bannedNodeIds(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.metricsSystem(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.metricsConfiguration(any())).thenReturn(mockRunnerBuilder); + when(mockRunnerBuilder.peerCache(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.build()).thenReturn(mockRunner); }