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 28e6d71358..ea9e6d635a 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 @@ -20,6 +20,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; 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.DefaultPeer; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; @@ -58,6 +59,9 @@ public JsonRpcResponse response(final JsonRpcRequest req) { return new JsonRpcErrorResponse(req.getId(), JsonRpcError.PARSE_ERROR); } catch (final P2pDisabledException e) { return new JsonRpcErrorResponse(req.getId(), JsonRpcError.P2P_DISABLED); + } catch (final PeerNotWhitelistedException e) { + return new JsonRpcErrorResponse( + req.getId(), JsonRpcError.NON_WHITELISTED_NODE_CANNOT_BE_ADDED_AS_A_PEER); } catch (final Exception e) { LOG.error("Error processing request: " + req, e); throw e; diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java index 99c4bb5ed1..551662a3a5 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java @@ -25,7 +25,7 @@ public enum JsonRpcError { METHOD_NOT_FOUND(-32601, "Method not found"), INVALID_PARAMS(-32602, "Invalid params"), INTERNAL_ERROR(-32603, "Internal error"), - P2P_DISABLED(-32000, "P2P has been disabled. This functionality is not available."), + P2P_DISABLED(-32000, "P2P has been disabled. This functionality is not available"), // Filter & Subscription Errors FILTER_NOT_FOUND(-32000, "Filter not found"), @@ -44,8 +44,8 @@ public enum JsonRpcError { CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE(-32008, "Initial sync is still in progress"), // Miner failures - COINBASE_NOT_SET(-32010, "Coinbase not set. Unable to start mining without a coinbase."), - NO_HASHES_PER_SECOND(-32011, "No hashes being generated by the current node."), + COINBASE_NOT_SET(-32010, "Coinbase not set. Unable to start mining without a coinbase"), + NO_HASHES_PER_SECOND(-32011, "No hashes being generated by the current node"), // Wallet errors COINBASE_NOT_SPECIFIED(-32000, "Coinbase must be explicitly specified"), @@ -75,8 +75,10 @@ public enum JsonRpcError { "The permissioning whitelist configuration file is out of sync. The changes have been applied, but not persisted to disk"), WHITELIST_RELOAD_ERROR( -32000, - "Error reloading permissions file. Please use perm_getAccountsWhitelist and perm_getNodesWhitelist to review the current state of the whitelists."), + "Error reloading permissions file. Please use perm_getAccountsWhitelist and perm_getNodesWhitelist to review the current state of the whitelists"), PERMISSIONING_NOT_ENABLED(-32000, "Node/Account whitelisting has not been enabled"), + NON_WHITELISTED_NODE_CANNOT_BE_ADDED_AS_A_PEER( + -32000, "Cannot add a non-whitelisted node as a peer"), // Permissioning/Authorization errors UNAUTHORIZED(-40100, "Unauthorized"), 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 1f48fc4944..bbe316380d 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 @@ -23,6 +23,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; import tech.pegasys.pantheon.ethereum.p2p.P2pDisabledException; +import tech.pegasys.pantheon.ethereum.p2p.PeerNotWhitelistedException; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import org.junit.Before; @@ -192,4 +193,31 @@ public void requestReturnsErrorWhenP2pDisabled() { assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); } + + @Test + 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); + + final JsonRpcResponse actualResponse = method.response(request); + + assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + } } diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/PeerNotWhitelistedException.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/PeerNotWhitelistedException.java new file mode 100644 index 0000000000..7e558a9257 --- /dev/null +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/PeerNotWhitelistedException.java @@ -0,0 +1,19 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.p2p; + +public class PeerNotWhitelistedException extends RuntimeException { + public PeerNotWhitelistedException(final String message) { + super(message); + } +} diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java index 67276106ea..a76b657fa0 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java @@ -15,6 +15,7 @@ import static com.google.common.base.Preconditions.checkState; import tech.pegasys.pantheon.crypto.SECP256K1; +import tech.pegasys.pantheon.ethereum.p2p.PeerNotWhitelistedException; import tech.pegasys.pantheon.ethereum.p2p.api.DisconnectCallback; import tech.pegasys.pantheon.ethereum.p2p.api.Message; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; @@ -315,8 +316,15 @@ private boolean isPeerWhitelisted(final PeerConnection connection, final SocketC .orElse(true); } + private boolean isPeerWhitelisted(final Peer peer) { + return nodeWhitelistController.map(nwc -> nwc.isPermitted(peer.getEnodeURI())).orElse(true); + } + @Override public boolean addMaintainConnectionPeer(final Peer peer) { + if (!isPeerWhitelisted(peer)) { + throw new PeerNotWhitelistedException("Cannot add a peer that is not whitelisted"); + } final boolean added = peerMaintainConnectionList.add(peer); if (added) { connect(peer);