[PAN-3140] Expand p2p config options (#1940)

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
mbaxter 5 years ago committed by GitHub
parent 995ef1adba
commit 684a01e202
  1. 3
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/config/DiscoveryConfiguration.java
  2. 3
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/config/RlpxConfiguration.java
  3. 1
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgent.java
  4. 13
      pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java
  5. 64
      pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
  6. 6
      pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java
  7. 17
      pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java
  8. 1
      pantheon/src/test/resources/everything_config.toml
  9. 21
      util/src/main/java/tech/pegasys/pantheon/util/NetworkUtility.java

@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.p2p.config;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import tech.pegasys.pantheon.ethereum.p2p.peers.EnodeURL; import tech.pegasys.pantheon.ethereum.p2p.peers.EnodeURL;
import tech.pegasys.pantheon.util.NetworkUtility;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -78,7 +79,7 @@ public class DiscoveryConfiguration {
.collect(toList())); .collect(toList()));
private boolean active = true; private boolean active = true;
private String bindHost = "0.0.0.0"; private String bindHost = NetworkUtility.INADDR_ANY;
private int bindPort = 30303; private int bindPort = 30303;
private String advertisedHost = "127.0.0.1"; private String advertisedHost = "127.0.0.1";
private int bucketSize = 16; private int bucketSize = 16;

@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.p2p.config;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import tech.pegasys.pantheon.ethereum.p2p.rlpx.wire.SubProtocol; import tech.pegasys.pantheon.ethereum.p2p.rlpx.wire.SubProtocol;
import tech.pegasys.pantheon.util.NetworkUtility;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -24,7 +25,7 @@ import java.util.Objects;
public class RlpxConfiguration { public class RlpxConfiguration {
public static final float DEFAULT_FRACTION_REMOTE_CONNECTIONS_ALLOWED = 0.6f; public static final float DEFAULT_FRACTION_REMOTE_CONNECTIONS_ALLOWED = 0.6f;
private String clientId = "TestClient/1.0.0"; private String clientId = "TestClient/1.0.0";
private String bindHost = "0.0.0.0"; private String bindHost = NetworkUtility.INADDR_ANY;
private int bindPort = 30303; private int bindPort = 30303;
private int maxPeers = 25; private int maxPeers = 25;
private boolean limitRemoteWireConnectionsEnabled = false; private boolean limitRemoteWireConnectionsEnabled = false;

@ -157,6 +157,7 @@ public abstract class PeerDiscoveryAgent {
.build()); .build());
this.localNode = Optional.of(ourNode); this.localNode = Optional.of(ourNode);
isActive = true; isActive = true;
LOG.info("P2P peer discovery agent started and listening on {}", localAddress);
startController(ourNode); startController(ourNode);
return discoveryPort; return discoveryPort;
}); });

@ -12,6 +12,9 @@
*/ */
package tech.pegasys.pantheon; package tech.pegasys.pantheon;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.isNull;
import tech.pegasys.pantheon.cli.config.EthNetworkConfig; import tech.pegasys.pantheon.cli.config.EthNetworkConfig;
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;
@ -81,6 +84,7 @@ import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration;
import tech.pegasys.pantheon.metrics.prometheus.MetricsService; import tech.pegasys.pantheon.metrics.prometheus.MetricsService;
import tech.pegasys.pantheon.nat.NatMethod; import tech.pegasys.pantheon.nat.NatMethod;
import tech.pegasys.pantheon.nat.upnp.UpnpNatManager; import tech.pegasys.pantheon.nat.upnp.UpnpNatManager;
import tech.pegasys.pantheon.util.NetworkUtility;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException; import java.io.IOException;
@ -109,6 +113,7 @@ public class RunnerBuilder {
private boolean p2pEnabled = true; private boolean p2pEnabled = true;
private boolean discovery; private boolean discovery;
private String p2pAdvertisedHost; private String p2pAdvertisedHost;
private String p2pListenInterface = NetworkUtility.INADDR_ANY;
private int p2pListenPort; private int p2pListenPort;
private NatMethod natMethod = NatMethod.NONE; private NatMethod natMethod = NatMethod.NONE;
private int maxPeers; private int maxPeers;
@ -161,6 +166,12 @@ public class RunnerBuilder {
return this; return this;
} }
public RunnerBuilder p2pListenInterface(final String ip) {
checkArgument(!isNull(ip), "Invalid null value supplied for p2pListenInterface");
this.p2pListenInterface = ip;
return this;
}
public RunnerBuilder p2pListenPort(final int p2pListenPort) { public RunnerBuilder p2pListenPort(final int p2pListenPort) {
this.p2pListenPort = p2pListenPort; this.p2pListenPort = p2pListenPort;
return this; return this;
@ -248,6 +259,7 @@ public class RunnerBuilder {
} }
discoveryConfiguration = discoveryConfiguration =
DiscoveryConfiguration.create() DiscoveryConfiguration.create()
.setBindHost(p2pListenInterface)
.setBindPort(p2pListenPort) .setBindPort(p2pListenPort)
.setAdvertisedHost(p2pAdvertisedHost) .setAdvertisedHost(p2pAdvertisedHost)
.setBootnodes(bootstrap); .setBootnodes(bootstrap);
@ -272,6 +284,7 @@ public class RunnerBuilder {
final RlpxConfiguration rlpxConfiguration = final RlpxConfiguration rlpxConfiguration =
RlpxConfiguration.create() RlpxConfiguration.create()
.setBindHost(p2pListenInterface)
.setBindPort(p2pListenPort) .setBindPort(p2pListenPort)
.setMaxPeers(maxPeers) .setMaxPeers(maxPeers)
.setSupportedProtocols(subProtocols) .setSupportedProtocols(subProtocols)

@ -103,6 +103,7 @@ import tech.pegasys.pantheon.services.PantheonEventsImpl;
import tech.pegasys.pantheon.services.PantheonPluginContextImpl; import tech.pegasys.pantheon.services.PantheonPluginContextImpl;
import tech.pegasys.pantheon.services.PicoCLIOptionsImpl; import tech.pegasys.pantheon.services.PicoCLIOptionsImpl;
import tech.pegasys.pantheon.services.StorageServiceImpl; import tech.pegasys.pantheon.services.StorageServiceImpl;
import tech.pegasys.pantheon.util.NetworkUtility;
import tech.pegasys.pantheon.util.PermissioningConfigurationValidator; import tech.pegasys.pantheon.util.PermissioningConfigurationValidator;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.number.Fraction; import tech.pegasys.pantheon.util.number.Fraction;
@ -114,7 +115,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.SocketException;
import java.net.URI; import java.net.URI;
import java.net.UnknownHostException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.Clock; import java.time.Clock;
@ -322,6 +325,14 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
arity = "1") arity = "1")
private String p2pHost = autoDiscoverDefaultIP().getHostAddress(); private String p2pHost = autoDiscoverDefaultIP().getHostAddress();
@Option(
names = {"--p2p-interface"},
paramLabel = MANDATORY_HOST_FORMAT_HELP,
description =
"The network interface address on which this node listens for p2p communication (default: ${DEFAULT-VALUE})",
arity = "1")
private String p2pInterface = NetworkUtility.INADDR_ANY;
@Option( @Option(
names = {"--p2p-port"}, names = {"--p2p-port"},
paramLabel = MANDATORY_PORT_FORMAT_HELP, paramLabel = MANDATORY_PORT_FORMAT_HELP,
@ -750,7 +761,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
try { try {
prepareLogging(); prepareLogging();
logger.info("Starting Pantheon version: {}", PantheonInfo.version()); logger.info("Starting Pantheon version: {}", PantheonInfo.version());
checkOptions().configure().controller().startPlugins().startSynchronization(); validateOptions().configure().controller().startPlugins().startSynchronization();
} catch (final Exception e) { } catch (final Exception e) {
throw new ParameterException(this.commandLine, e.getMessage(), e); throw new ParameterException(this.commandLine, e.getMessage(), e);
} }
@ -862,6 +873,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
ethNetworkConfig, ethNetworkConfig,
maxPeers, maxPeers,
p2pHost, p2pHost,
p2pInterface,
p2pPort, p2pPort,
graphQLConfiguration, graphQLConfiguration,
jsonRpcConfiguration, jsonRpcConfiguration,
@ -891,8 +903,38 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
} }
} }
private PantheonCommand checkOptions() { private PantheonCommand validateOptions() {
// Check that P2P options are able to work or send an error issueOptionWarnings();
validateP2PInterface(p2pInterface);
validateMiningParams();
return this;
}
private void validateMiningParams() {
// noinspection ConstantConditions
if (isMiningEnabled && coinbase == null) {
throw new ParameterException(
this.commandLine,
"Unable to mine without a valid coinbase. Either disable mining (remove --miner-enabled)"
+ "or specify the beneficiary of mining (via --miner-coinbase <Address>)");
}
}
protected void validateP2PInterface(final String p2pInterface) {
final String failMessage = "The provided --p2p-interface is not available: " + p2pInterface;
try {
if (!NetworkUtility.isNetworkInterfaceAvailable(p2pInterface)) {
throw new ParameterException(commandLine, failMessage);
}
} catch (UnknownHostException | SocketException e) {
throw new ParameterException(commandLine, failMessage, e);
}
}
private void issueOptionWarnings() {
// Check that P2P options are able to work
checkOptionDependencies( checkOptionDependencies(
logger, logger,
commandLine, commandLine,
@ -904,8 +946,11 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
"--max-peers", "--max-peers",
"--banned-node-id", "--banned-node-id",
"--banned-node-ids", "--banned-node-ids",
"--p2p-host",
"--p2p-interface",
"--p2p-port",
"--remote-connections-max-percentage")); "--remote-connections-max-percentage"));
// Check that mining options are able to work or send an error // Check that mining options are able to work
checkOptionDependencies( checkOptionDependencies(
logger, logger,
commandLine, commandLine,
@ -926,15 +971,6 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
"--pruning-enabled", "--pruning-enabled",
!isPruningEnabled, !isPruningEnabled,
asList("--pruning-block-confirmations", "--pruning-blocks-retained")); asList("--pruning-block-confirmations", "--pruning-blocks-retained"));
// noinspection ConstantConditions
if (isMiningEnabled && coinbase == null) {
throw new ParameterException(
this.commandLine,
"Unable to mine without a valid coinbase. Either disable mining (remove --miner-enabled)"
+ "or specify the beneficiary of mining (via --miner-coinbase <Address>)");
}
return this;
} }
private PantheonCommand configure() throws Exception { private PantheonCommand configure() throws Exception {
@ -1302,6 +1338,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
final EthNetworkConfig ethNetworkConfig, final EthNetworkConfig ethNetworkConfig,
final int maxPeers, final int maxPeers,
final String p2pAdvertisedHost, final String p2pAdvertisedHost,
final String p2pListenInterface,
final int p2pListenPort, final int p2pListenPort,
final GraphQLConfiguration graphQLConfiguration, final GraphQLConfiguration graphQLConfiguration,
final JsonRpcConfiguration jsonRpcConfiguration, final JsonRpcConfiguration jsonRpcConfiguration,
@ -1324,6 +1361,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
.discovery(peerDiscoveryEnabled) .discovery(peerDiscoveryEnabled)
.ethNetworkConfig(ethNetworkConfig) .ethNetworkConfig(ethNetworkConfig)
.p2pAdvertisedHost(p2pAdvertisedHost) .p2pAdvertisedHost(p2pAdvertisedHost)
.p2pListenInterface(p2pListenInterface)
.p2pListenPort(p2pListenPort) .p2pListenPort(p2pListenPort)
.maxPeers(maxPeers) .maxPeers(maxPeers)
.limitRemoteWireConnectionsEnabled(isLimitRemoteWireConnectionsEnabled) .limitRemoteWireConnectionsEnabled(isLimitRemoteWireConnectionsEnabled)

@ -184,6 +184,7 @@ public abstract class CommandTestAbstract {
when(mockRunnerBuilder.networkingConfiguration(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.networkingConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.p2pAdvertisedHost(anyString())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.p2pAdvertisedHost(anyString())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.p2pListenPort(anyInt())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.p2pListenPort(anyInt())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.p2pListenInterface(anyString())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.maxPeers(anyInt())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.maxPeers(anyInt())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.limitRemoteWireConnectionsEnabled(anyBoolean())) when(mockRunnerBuilder.limitRemoteWireConnectionsEnabled(anyBoolean()))
.thenReturn(mockRunnerBuilder); .thenReturn(mockRunnerBuilder);
@ -306,6 +307,11 @@ public abstract class CommandTestAbstract {
this.keyLoader = keyLoader; this.keyLoader = keyLoader;
} }
@Override
protected void validateP2PInterface(final String p2pInterface) {
// For testing, don't actually query for networking interfaces to validate this option
}
public CommandSpec getSpec() { public CommandSpec getSpec() {
return spec; return spec;
} }

@ -1179,7 +1179,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
} }
@Test @Test
public void p2pHostAndPortOptionMustBeUsed() { public void p2pHostAndPortOptionsAreRespected() {
final String host = "1.2.3.4"; final String host = "1.2.3.4";
final int port = 1234; final int port = 1234;
@ -1196,6 +1196,21 @@ public class PantheonCommandTest extends CommandTestAbstract {
assertThat(commandErrorOutput.toString()).isEmpty(); assertThat(commandErrorOutput.toString()).isEmpty();
} }
@Test
public void p2pInterfaceOptionIsRespected() {
final String ip = "1.2.3.4";
parseCommand("--p2p-interface", ip);
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty();
verify(mockRunnerBuilder).p2pListenInterface(stringArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(stringArgumentCaptor.getValue()).isEqualTo(ip);
}
@Test @Test
public void p2pHostMayBeLocalhost() { public void p2pHostMayBeLocalhost() {

@ -25,6 +25,7 @@ bootnodes=[
banned-node-ids=["0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0","0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"] banned-node-ids=["0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0","0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"]
banned-node-id=["0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"] banned-node-id=["0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"]
p2p-host="1.2.3.4" p2p-host="1.2.3.4"
p2p-interface="0.0.0.0"
p2p-port=1234 p2p-port=1234
max-peers=42 max-peers=42
remote-connections-limit-enabled=true remote-connections-limit-enabled=true

@ -16,12 +16,16 @@ import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.function.Supplier; import java.util.function.Supplier;
import com.google.common.base.Suppliers; import com.google.common.base.Suppliers;
public class NetworkUtility { public class NetworkUtility {
public static final String INADDR_ANY = "0.0.0.0";
public static final String INADDR6_ANY = "0:0:0:0:0:0:0:0";
private NetworkUtility() {} private NetworkUtility() {}
@ -74,10 +78,7 @@ public class NetworkUtility {
public static String urlForSocketAddress(final String scheme, final InetSocketAddress address) { public static String urlForSocketAddress(final String scheme, final InetSocketAddress address) {
String hostName = address.getHostName(); String hostName = address.getHostName();
if ("0.0.0.0".equals(hostName)) { if (isUnspecifiedAddress(hostName)) {
hostName = InetAddress.getLoopbackAddress().getHostName();
}
if ("0:0:0:0:0:0:0:0".equals(hostName)) {
hostName = InetAddress.getLoopbackAddress().getHostName(); hostName = InetAddress.getLoopbackAddress().getHostName();
} }
if (hostName.contains(":")) { if (hostName.contains(":")) {
@ -85,4 +86,16 @@ public class NetworkUtility {
} }
return scheme + "://" + hostName + ":" + address.getPort(); return scheme + "://" + hostName + ":" + address.getPort();
} }
public static boolean isNetworkInterfaceAvailable(final String ipAddress)
throws SocketException, UnknownHostException {
if (isUnspecifiedAddress(ipAddress)) {
return true;
}
return NetworkInterface.getByInetAddress(InetAddress.getByName(ipAddress)) != null;
}
public static boolean isUnspecifiedAddress(final String ipAddress) {
return INADDR_ANY.equals(ipAddress) || INADDR6_ANY.equals(ipAddress);
}
} }

Loading…
Cancel
Save