Add dns support (#1247)

This PR add the support to DNS. By default Besu refuses the use of a DNS but it is possible to use it by adding the following flag --Xdns-enabled=true. Adding this flag will resolve the hostname when starting besu and then it won't change

If there is a need for a more dynamic update (eg for permissioning) add also this flag --Xdns-update-enabled = true ( this will query the DNS every time. So you must trust the DNS on which you are looking for the IP)

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>
pull/1341/head
matkt 4 years ago committed by GitHub
parent b4e568e025
commit 210c85c82e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 7
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java
  3. 7
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java
  4. 7
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfiguration.java
  5. 7
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java
  6. 1
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java
  7. 20
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/permissioning/PermissionedNodeBuilder.java
  8. 1
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java
  9. 11
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistPersistorAcceptanceTest.java
  10. 79
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistWithDnsPersistorAcceptanceTest.java
  11. 82
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  12. 29
      besu/src/main/java/org/hyperledger/besu/util/PermissioningConfigurationValidator.java
  13. 33
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  14. 124
      besu/src/test/java/org/hyperledger/besu/util/LocalPermissioningConfigurationValidatorTest.java
  15. 4
      besu/src/test/resources/permissioning_config_unknown_hostname.toml
  16. 4
      besu/src/test/resources/permissioning_config_valid_hostname.toml
  17. 3
      ethereum/p2p/build.gradle
  18. 32
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeDnsConfiguration.java
  19. 90
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURL.java
  20. 15
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/StaticNodesParser.java
  21. 148
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLTest.java
  22. 81
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/StaticNodesParserTest.java
  23. 4
      ethereum/p2p/src/test/resources/org/hyperledger/besu/ethereum/p2p/peers/unknown_hostname_static_nodes.json
  24. 4
      ethereum/p2p/src/test/resources/org/hyperledger/besu/ethereum/p2p/peers/valid_hostname_static_nodes.json
  25. 1
      ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/AccountLocalConfigPermissioningController.java
  26. 19
      ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/LocalPermissioningConfiguration.java
  27. 16
      ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningController.java
  28. 12
      ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/PermissioningConfigurationBuilder.java
  29. 83
      ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/LocalPermissioningConfigurationBuilderTest.java
  30. 14
      ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/LocalPermissioningConfigurationTest.java
  31. 62
      ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java
  32. 3
      ethereum/permissioning/src/test/resources/permissioning_config_node_allowlist_with_dns.toml

@ -5,6 +5,7 @@
### Additions and Improvements ### Additions and Improvements
* The EvmTool now processes State Tests from the Ethereum Reference Tests. [\#1311](https://github.com/hyperledger/besu/pull/1311) * The EvmTool now processes State Tests from the Ethereum Reference Tests. [\#1311](https://github.com/hyperledger/besu/pull/1311)
* Experimental dns support added via the `Xdns-enabled` and `Xdns-update-enabled` CLI commands. [\#1247](https://github.com/hyperledger/besu/pull/1247)
### Bug Fixes ### Bug Fixes

@ -109,6 +109,7 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
private final List<String> plugins = new ArrayList<>(); private final List<String> plugins = new ArrayList<>();
private final List<String> extraCLIOptions; private final List<String> extraCLIOptions;
private final List<String> staticNodes; private final List<String> staticNodes;
private boolean isDnsEnabled = false;
private Optional<Integer> exitCode = Optional.empty(); private Optional<Integer> exitCode = Optional.empty();
public BesuNode( public BesuNode(
@ -132,6 +133,7 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
final List<String> plugins, final List<String> plugins,
final List<String> extraCLIOptions, final List<String> extraCLIOptions,
final List<String> staticNodes, final List<String> staticNodes,
final boolean isDnsEnabled,
final Optional<PrivacyParameters> privacyParameters, final Optional<PrivacyParameters> privacyParameters,
final List<String> runCommand) final List<String> runCommand)
throws IOException { throws IOException {
@ -174,6 +176,7 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
}); });
this.extraCLIOptions = extraCLIOptions; this.extraCLIOptions = extraCLIOptions;
this.staticNodes = staticNodes; this.staticNodes = staticNodes;
this.isDnsEnabled = isDnsEnabled;
privacyParameters.ifPresent(this::setPrivacyParameters); privacyParameters.ifPresent(this::setPrivacyParameters);
LOG.info("Created BesuNode {}", this.toString()); LOG.info("Created BesuNode {}", this.toString());
} }
@ -609,6 +612,10 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
return staticNodes; return staticNodes;
} }
public boolean isDnsEnabled() {
return isDnsEnabled;
}
public boolean hasStaticNodes() { public boolean hasStaticNodes() {
return staticNodes != null && !staticNodes.isEmpty(); return staticNodes != null && !staticNodes.isEmpty();
} }

@ -138,6 +138,13 @@ public class ProcessBesuNodeRunner implements BesuNodeRunner {
createStaticNodes(node); createStaticNodes(node);
} }
if (node.isDnsEnabled()) {
params.add("--Xdns-enabled");
params.add("true");
params.add("--Xdns-update-enabled");
params.add("true");
}
if (node.isJsonRpcEnabled()) { if (node.isJsonRpcEnabled()) {
params.add("--rpc-http-enabled"); params.add("--rpc-http-enabled");
params.add("--rpc-http-host"); params.add("--rpc-http-host");

@ -49,6 +49,7 @@ public class BesuNodeConfiguration {
private final List<String> plugins; private final List<String> plugins;
private final List<String> extraCLIOptions; private final List<String> extraCLIOptions;
private final List<String> staticNodes; private final List<String> staticNodes;
private final boolean isDnsEnabled;
private final Optional<PrivacyParameters> privacyParameters; private final Optional<PrivacyParameters> privacyParameters;
private final List<String> runCommand; private final List<String> runCommand;
@ -73,6 +74,7 @@ public class BesuNodeConfiguration {
final List<String> plugins, final List<String> plugins,
final List<String> extraCLIOptions, final List<String> extraCLIOptions,
final List<String> staticNodes, final List<String> staticNodes,
final boolean isDnsEnabled,
final Optional<PrivacyParameters> privacyParameters, final Optional<PrivacyParameters> privacyParameters,
final List<String> runCommand) { final List<String> runCommand) {
this.name = name; this.name = name;
@ -95,6 +97,7 @@ public class BesuNodeConfiguration {
this.plugins = plugins; this.plugins = plugins;
this.extraCLIOptions = extraCLIOptions; this.extraCLIOptions = extraCLIOptions;
this.staticNodes = staticNodes; this.staticNodes = staticNodes;
this.isDnsEnabled = isDnsEnabled;
this.privacyParameters = privacyParameters; this.privacyParameters = privacyParameters;
this.runCommand = runCommand; this.runCommand = runCommand;
} }
@ -179,6 +182,10 @@ public class BesuNodeConfiguration {
return staticNodes; return staticNodes;
} }
public boolean isDnsEnabled() {
return isDnsEnabled;
}
public Optional<PrivacyParameters> getPrivacyParameters() { public Optional<PrivacyParameters> getPrivacyParameters() {
return privacyParameters; return privacyParameters;
} }

@ -60,6 +60,7 @@ public class BesuNodeConfigurationBuilder {
private final List<String> plugins = new ArrayList<>(); private final List<String> plugins = new ArrayList<>();
private final List<String> extraCLIOptions = new ArrayList<>(); private final List<String> extraCLIOptions = new ArrayList<>();
private List<String> staticNodes = new ArrayList<>(); private List<String> staticNodes = new ArrayList<>();
private boolean isDnsEnabled = false;
private Optional<PrivacyParameters> privacyParameters = Optional.empty(); private Optional<PrivacyParameters> privacyParameters = Optional.empty();
private List<String> runCommand = new ArrayList<>(); private List<String> runCommand = new ArrayList<>();
@ -268,6 +269,11 @@ public class BesuNodeConfigurationBuilder {
return this; return this;
} }
public BesuNodeConfigurationBuilder dnsEnabled(final boolean isDnsEnabled) {
this.isDnsEnabled = isDnsEnabled;
return this;
}
public BesuNodeConfigurationBuilder privacyParameters(final PrivacyParameters privacyParameters) { public BesuNodeConfigurationBuilder privacyParameters(final PrivacyParameters privacyParameters) {
this.privacyParameters = Optional.ofNullable(privacyParameters); this.privacyParameters = Optional.ofNullable(privacyParameters);
return this; return this;
@ -300,6 +306,7 @@ public class BesuNodeConfigurationBuilder {
plugins, plugins,
extraCLIOptions, extraCLIOptions,
staticNodes, staticNodes,
isDnsEnabled,
privacyParameters, privacyParameters,
runCommand); runCommand);
} }

@ -68,6 +68,7 @@ public class BesuNodeFactory {
config.getPlugins(), config.getPlugins(),
config.getExtraCLIOptions(), config.getExtraCLIOptions(),
config.getStaticNodes(), config.getStaticNodes(),
config.isDnsEnabled(),
config.getPrivacyParameters(), config.getPrivacyParameters(),
config.getRunCommand()); config.getRunCommand());
} }

@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis;
import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import org.hyperledger.besu.ethereum.permissioning.AllowlistPersistor; import org.hyperledger.besu.ethereum.permissioning.AllowlistPersistor;
import org.hyperledger.besu.ethereum.permissioning.AllowlistPersistor.ALLOWLIST_TYPE; import org.hyperledger.besu.ethereum.permissioning.AllowlistPersistor.ALLOWLIST_TYPE;
import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration;
@ -71,6 +72,7 @@ public class PermissionedNodeBuilder {
private String accountPermissioningSmartContractAddress = null; private String accountPermissioningSmartContractAddress = null;
private List<String> staticNodes = new ArrayList<>(); private List<String> staticNodes = new ArrayList<>();
private boolean isDnsEnabled = false;
private boolean mining = true; private boolean mining = true;
public PermissionedNodeBuilder name(final String name) { public PermissionedNodeBuilder name(final String name) {
@ -139,6 +141,11 @@ public class PermissionedNodeBuilder {
return this; return this;
} }
public PermissionedNodeBuilder dnsEnabled(final boolean isDnsEnabled) {
this.isDnsEnabled = isDnsEnabled;
return this;
}
public PermissionedNodeBuilder disableMining() { public PermissionedNodeBuilder disableMining() {
this.mining = false; this.mining = false;
return this; return this;
@ -188,6 +195,8 @@ public class PermissionedNodeBuilder {
builder.staticNodes(staticNodes); builder.staticNodes(staticNodes);
} }
builder.dnsEnabled(isDnsEnabled);
if (genesisFile != null) { if (genesisFile != null) {
builder.genesisConfigProvider((a) -> Optional.of(genesisFile)); builder.genesisConfigProvider((a) -> Optional.of(genesisFile));
builder.devMode(false); builder.devMode(false);
@ -209,12 +218,15 @@ public class PermissionedNodeBuilder {
localConfigNodesPermissioningFile = createTemporaryPermissionsFile(); localConfigNodesPermissioningFile = createTemporaryPermissionsFile();
} }
List<String> nodesAsListOfStrings = final List<EnodeURL> nodeAllowList =
localConfigPermittedNodes.stream().map(URI::toASCIIString).collect(Collectors.toList()); localConfigPermittedNodes.stream().map(EnodeURL::fromURI).collect(Collectors.toList());
initPermissioningConfigurationFile( initPermissioningConfigurationFile(
ALLOWLIST_TYPE.NODES, nodesAsListOfStrings, localConfigNodesPermissioningFile); ALLOWLIST_TYPE.NODES,
nodeAllowList.stream().map(EnodeURL::toString).collect(Collectors.toList()),
localConfigNodesPermissioningFile);
localPermissioningConfiguration.setNodeAllowlist(localConfigPermittedNodes); localPermissioningConfiguration.setNodeAllowlist(nodeAllowList);
localPermissioningConfiguration.setNodePermissioningConfigFilePath( localPermissioningConfiguration.setNodePermissioningConfigFilePath(
localConfigNodesPermissioningFile.toAbsolutePath().toString()); localConfigNodesPermissioningFile.toAbsolutePath().toString());
} }

@ -103,6 +103,7 @@ public class PrivacyNode implements AutoCloseable {
besuConfig.getPlugins(), besuConfig.getPlugins(),
besuConfig.getExtraCLIOptions(), besuConfig.getExtraCLIOptions(),
Collections.emptyList(), Collections.emptyList(),
besuConfig.isDnsEnabled(),
besuConfig.getPrivacyParameters(), besuConfig.getPrivacyParameters(),
List.of()); List.of());
} }

@ -25,8 +25,10 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import org.assertj.core.api.Assertions;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.web3j.protocol.exceptions.ClientConnectionException;
public class AllowlistPersistorAcceptanceTest extends AcceptanceTestBase { public class AllowlistPersistorAcceptanceTest extends AcceptanceTestBase {
@ -36,6 +38,8 @@ public class AllowlistPersistorAcceptanceTest extends AcceptanceTestBase {
"enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567"; "enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567";
private static final String ENODE_THREE = private static final String ENODE_THREE =
"enode://4f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567"; "enode://4f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567";
private static final String ENODE_FOURTH =
"enode://4f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@localhost:4567";
private Node node; private Node node;
private Account senderA; private Account senderA;
@ -99,4 +103,11 @@ public class AllowlistPersistorAcceptanceTest extends AcceptanceTestBase {
perm.expectPermissioningAllowlistFileKeyValue( perm.expectPermissioningAllowlistFileKeyValue(
ALLOWLIST_TYPE.NODES, tempFile, ENODE_TWO, ENODE_ONE, ENODE_THREE)); ALLOWLIST_TYPE.NODES, tempFile, ENODE_TWO, ENODE_ONE, ENODE_THREE));
} }
@Test
public void manipulatedNodesWhitelistWithHostnameShouldNotWorkWhenDnsDisabled() {
Assertions.assertThatThrownBy(() -> node.verify(perm.addNodesToAllowlist(ENODE_FOURTH)))
.isInstanceOf(ClientConnectionException.class)
.hasMessageContaining("Request contains an invalid node");
}
} }

@ -0,0 +1,79 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.tests.acceptance.permissioning;
import static org.hyperledger.besu.ethereum.permissioning.AllowlistPersistor.ALLOWLIST_TYPE;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.node.Node;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
public class AllowlistWithDnsPersistorAcceptanceTest extends AcceptanceTestBase {
private static final String ENODE_ONE =
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@localhost:4567";
private static final String ENODE_TWO =
"enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567";
private static final String ENODE_THREE =
"enode://4f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.11:4567";
private Node node;
private Account senderA;
private Path tempFile;
@Before
public void setUp() throws Exception {
senderA = accounts.getPrimaryBenefactor();
tempFile = Files.createTempFile("test", "test");
this.node =
permissionedNodeBuilder
.name("node")
.nodesConfigFile(tempFile)
.nodesPermittedInConfig(new ArrayList<>())
.accountsConfigFile(tempFile)
.accountsPermittedInConfig(Collections.singletonList(senderA.getAddress()))
.dnsEnabled(true)
.build();
cluster.start(this.node);
}
@Test
public void manipulatedNodesWhitelistWithHostnameShouldWorkWhenDnsEnabled() {
node.verify(perm.addNodesToAllowlist(ENODE_ONE, ENODE_TWO));
node.verify(
perm.expectPermissioningAllowlistFileKeyValue(
ALLOWLIST_TYPE.NODES, tempFile, ENODE_ONE, ENODE_TWO));
node.verify(perm.removeNodesFromAllowlist(ENODE_ONE));
node.verify(
perm.expectPermissioningAllowlistFileKeyValue(ALLOWLIST_TYPE.NODES, tempFile, ENODE_TWO));
node.verify(perm.addNodesToAllowlist(ENODE_ONE, ENODE_THREE));
node.verify(
perm.expectPermissioningAllowlistFileKeyValue(
ALLOWLIST_TYPE.NODES, tempFile, ENODE_TWO, ENODE_ONE, ENODE_THREE));
}
}

@ -95,7 +95,9 @@ import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.mainnet.precompiles.AltBN128PairingPrecompiledContract; import org.hyperledger.besu.ethereum.mainnet.precompiles.AltBN128PairingPrecompiledContract;
import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration; import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import org.hyperledger.besu.ethereum.p2p.peers.ImmutableEnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.StaticNodesParser; import org.hyperledger.besu.ethereum.p2p.peers.StaticNodesParser;
import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration;
import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration;
@ -310,20 +312,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
+ "Default is a predefined list.", + "Default is a predefined list.",
split = ",", split = ",",
arity = "0..*") arity = "0..*")
void setBootnodes(final List<String> values) { private final List<String> bootNodes = null;
try {
bootNodes =
values.stream()
.filter(value -> !value.isEmpty())
.map(EnodeURL::fromString)
.collect(Collectors.toList());
DiscoveryConfiguration.assertValidBootnodes(bootNodes);
} catch (final IllegalArgumentException e) {
throw new ParameterException(commandLine, e.getMessage());
}
}
private List<EnodeURL> bootNodes = null;
@Option( @Option(
names = {"--max-peers"}, names = {"--max-peers"},
@ -1047,6 +1036,20 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
arity = "1") arity = "1")
private final Long wsTimeoutSec = TimeoutOptions.defaultOptions().getTimeoutSeconds(); private final Long wsTimeoutSec = TimeoutOptions.defaultOptions().getTimeoutSeconds();
@CommandLine.Option(
hidden = true,
names = {"--Xdns-enabled"},
description = "Enabled DNS support",
arity = "1")
private final Boolean dnsEnabled = Boolean.FALSE;
@CommandLine.Option(
hidden = true,
names = {"--Xdns-update-enabled"},
description = "Allow to detect an IP update automatically",
arity = "1")
private final Boolean dnsUpdateEnabled = Boolean.FALSE;
private EthNetworkConfig ethNetworkConfig; private EthNetworkConfig ethNetworkConfig;
private JsonRpcConfiguration jsonRpcConfiguration; private JsonRpcConfiguration jsonRpcConfiguration;
private GraphQLConfiguration graphQLConfiguration; private GraphQLConfiguration graphQLConfiguration;
@ -1059,6 +1062,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
private final Supplier<ObservableMetricsSystem> metricsSystem = private final Supplier<ObservableMetricsSystem> metricsSystem =
Suppliers.memoize(() -> PrometheusMetricsSystem.init(metricsConfiguration())); Suppliers.memoize(() -> PrometheusMetricsSystem.init(metricsConfiguration()));
private Vertx vertx; private Vertx vertx;
private EnodeDnsConfiguration enodeDnsConfiguration;
public BesuCommand( public BesuCommand(
final Logger logger, final Logger logger,
@ -1329,6 +1333,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
validateMiningParams(); validateMiningParams();
validateNatParams(); validateNatParams();
validateNetStatsParams(); validateNetStatsParams();
validateDnsOptionsParams();
return this; return this;
} }
@ -1387,6 +1392,15 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
} }
} }
private void validateDnsOptionsParams() {
if (!dnsEnabled && dnsUpdateEnabled) {
throw new ParameterException(
this.commandLine,
"The `--Xdns-update-enabled` requires dns to be enabled. Either remove --Xdns-update-enabled"
+ " or specify dns is enabled (--Xdns-enabled)");
}
}
private void issueOptionWarnings() { private void issueOptionWarnings() {
// Check that P2P options are able to work // Check that P2P options are able to work
CommandLineUtils.checkOptionDependencies( CommandLineUtils.checkOptionDependencies(
@ -1441,26 +1455,25 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
genesisFile == null && !isPrivacyEnabled && network != NetworkName.DEV genesisFile == null && !isPrivacyEnabled && network != NetworkName.DEV
? SyncMode.FAST ? SyncMode.FAST
: SyncMode.FULL); : SyncMode.FULL);
ethNetworkConfig = updateNetworkConfig(getNetwork()); ethNetworkConfig = updateNetworkConfig(getNetwork());
jsonRpcConfiguration = jsonRpcConfiguration(); jsonRpcConfiguration = jsonRpcConfiguration();
graphQLConfiguration = graphQLConfiguration(); graphQLConfiguration = graphQLConfiguration();
webSocketConfiguration = webSocketConfiguration(); webSocketConfiguration = webSocketConfiguration();
permissioningConfiguration = permissioningConfiguration(); permissioningConfiguration = permissioningConfiguration();
staticNodes = loadStaticNodes(); staticNodes = loadStaticNodes();
logger.info("Connecting to {} static nodes.", staticNodes.size()); logger.info("Connecting to {} static nodes.", staticNodes.size());
logger.trace("Static Nodes = {}", staticNodes); logger.trace("Static Nodes = {}", staticNodes);
final List<URI> enodeURIs = final List<EnodeURL> enodeURIs = ethNetworkConfig.getBootNodes();
ethNetworkConfig.getBootNodes().stream().map(EnodeURL::toURI).collect(Collectors.toList());
permissioningConfiguration permissioningConfiguration
.flatMap(PermissioningConfiguration::getLocalConfig) .flatMap(PermissioningConfiguration::getLocalConfig)
.ifPresent(p -> ensureAllNodesAreInAllowlist(enodeURIs, p)); .ifPresent(p -> ensureAllNodesAreInAllowlist(enodeURIs, p));
permissioningConfiguration permissioningConfiguration
.flatMap(PermissioningConfiguration::getLocalConfig) .flatMap(PermissioningConfiguration::getLocalConfig)
.ifPresent( .ifPresent(p -> ensureAllNodesAreInAllowlist(staticNodes, p));
p ->
ensureAllNodesAreInAllowlist(
staticNodes.stream().map(EnodeURL::toURI).collect(Collectors.toList()), p));
metricsConfiguration = metricsConfiguration(); metricsConfiguration = metricsConfiguration();
logger.info("Security Module: {}", securityModuleName); logger.info("Security Module: {}", securityModuleName);
@ -1474,7 +1487,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
} }
private void ensureAllNodesAreInAllowlist( private void ensureAllNodesAreInAllowlist(
final Collection<URI> enodeAddresses, final Collection<EnodeURL> enodeAddresses,
final LocalPermissioningConfiguration permissioningConfiguration) { final LocalPermissioningConfiguration permissioningConfiguration) {
try { try {
PermissioningConfigurationValidator.areAllNodesAreInAllowlist( PermissioningConfigurationValidator.areAllNodesAreInAllowlist(
@ -1781,6 +1794,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
final LocalPermissioningConfiguration localPermissioningConfiguration = final LocalPermissioningConfiguration localPermissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
permissionsNodesEnabled, permissionsNodesEnabled,
getEnodeDnsConfiguration(),
nodePermissioningConfigFile.orElse(getDefaultPermissioningFilePath()), nodePermissioningConfigFile.orElse(getDefaultPermissioningFilePath()),
permissionsAccountsEnabled, permissionsAccountsEnabled,
accountPermissioningConfigFile.orElse(getDefaultPermissioningFilePath())); accountPermissioningConfigFile.orElse(getDefaultPermissioningFilePath()));
@ -2189,7 +2203,18 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
} }
if (bootNodes != null) { if (bootNodes != null) {
builder.setBootNodes(bootNodes); try {
final List<EnodeURL> listBootNodes =
bootNodes.stream()
.filter(value -> !value.isEmpty())
.map(url -> EnodeURL.fromString(url, getEnodeDnsConfiguration()))
.collect(Collectors.toList());
DiscoveryConfiguration.assertValidBootnodes(listBootNodes);
builder.setBootNodes(listBootNodes);
} catch (final IllegalArgumentException e) {
throw new ParameterException(commandLine, e.getMessage());
}
} }
return builder.build(); return builder.build();
@ -2267,13 +2292,24 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
final String staticNodesFilename = "static-nodes.json"; final String staticNodesFilename = "static-nodes.json";
final Path staticNodesPath = dataDir().resolve(staticNodesFilename); final Path staticNodesPath = dataDir().resolve(staticNodesFilename);
return StaticNodesParser.fromPath(staticNodesPath); return StaticNodesParser.fromPath(staticNodesPath, getEnodeDnsConfiguration());
} }
public BesuExceptionHandler exceptionHandler() { public BesuExceptionHandler exceptionHandler() {
return new BesuExceptionHandler(this::getLogLevel); return new BesuExceptionHandler(this::getLogLevel);
} }
public EnodeDnsConfiguration getEnodeDnsConfiguration() {
if (enodeDnsConfiguration == null) {
enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder()
.dnsEnabled(dnsEnabled)
.updateEnabled(dnsUpdateEnabled)
.build();
}
return enodeDnsConfiguration;
}
@VisibleForTesting @VisibleForTesting
Level getLogLevel() { Level getLogLevel() {
return logLevel; return logLevel;

@ -14,10 +14,12 @@
*/ */
package org.hyperledger.besu.util; package org.hyperledger.besu.util;
import static java.util.stream.Collectors.toList;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -25,21 +27,21 @@ import java.util.stream.Collectors;
public class PermissioningConfigurationValidator { public class PermissioningConfigurationValidator {
public static void areAllNodesAreInAllowlist( public static void areAllNodesAreInAllowlist(
final Collection<URI> nodeURIs, final Collection<EnodeURL> nodeURIs,
final LocalPermissioningConfiguration permissioningConfiguration) final LocalPermissioningConfiguration permissioningConfiguration)
throws Exception { throws Exception {
if (permissioningConfiguration.isNodeAllowlistEnabled() && nodeURIs != null) { if (permissioningConfiguration.isNodeAllowlistEnabled() && nodeURIs != null) {
final List<URI> allowlistNodesWithoutQueryParam = final List<URI> allowlistNodesWithoutQueryParam =
permissioningConfiguration.getNodeAllowlist().stream() permissioningConfiguration.getNodeAllowlist().stream()
.map(PermissioningConfigurationValidator::removeQueryFromURI) .map(EnodeURL::toURIWithoutDiscoveryPort)
.collect(Collectors.toList()); .collect(toList());
final List<URI> nodeURIsNotInAllowlist = final List<URI> nodeURIsNotInAllowlist =
nodeURIs.stream() nodeURIs.stream()
.map(PermissioningConfigurationValidator::removeQueryFromURI) .map(EnodeURL::toURIWithoutDiscoveryPort)
.filter(uri -> !allowlistNodesWithoutQueryParam.contains(uri)) .filter(uri -> !allowlistNodesWithoutQueryParam.contains(uri))
.collect(Collectors.toList()); .collect(toList());
if (!nodeURIsNotInAllowlist.isEmpty()) { if (!nodeURIsNotInAllowlist.isEmpty()) {
throw new Exception( throw new Exception(
@ -48,21 +50,6 @@ public class PermissioningConfigurationValidator {
} }
} }
private static URI removeQueryFromURI(final URI uri) {
try {
return new URI(
uri.getScheme(),
uri.getUserInfo(),
uri.getHost(),
uri.getPort(),
uri.getPath(),
null,
uri.getFragment());
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
private static Collection<String> enodesAsStrings(final List<URI> enodes) { private static Collection<String> enodesAsStrings(final List<URI> enodes) {
return enodes.parallelStream().map(URI::toASCIIString).collect(Collectors.toList()); return enodes.parallelStream().map(URI::toASCIIString).collect(Collectors.toList());
} }

@ -590,11 +590,11 @@ public class BesuCommandTest extends CommandTestAbstract {
@Test @Test
public void nodePermissioningTomlPathMustUseOption() throws IOException { public void nodePermissioningTomlPathMustUseOption() throws IOException {
final List<URI> allowedNodes = final List<EnodeURL> allowedNodes =
Lists.newArrayList( Lists.newArrayList(
URI.create( EnodeURL.fromString(
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567"), "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567"),
URI.create( EnodeURL.fromString(
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.169.0.9:4568")); "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.169.0.9:4568"));
final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML);
@ -1347,6 +1347,33 @@ public class BesuCommandTest extends CommandTestAbstract {
"The `--Xethstats-contact` requires ethstats server URL to be provided. Either remove --Xethstats-contact or provide an url (via --Xethstats=nodename:secret@host:port)"); "The `--Xethstats-contact` requires ethstats server URL to be provided. Either remove --Xethstats-contact or provide an url (via --Xethstats=nodename:secret@host:port)");
} }
@Test
public void dnsEnabledOptionIsParsedCorrectly() {
TestBesuCommand besuCommand = parseCommand("--Xdns-enabled", "true");
assertThat(besuCommand.getEnodeDnsConfiguration().dnsEnabled()).isTrue();
assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isFalse();
}
@Test
public void dnsUpdateEnabledOptionIsParsedCorrectly() {
TestBesuCommand besuCommand =
parseCommand("--Xdns-enabled", "true", "--Xdns-update-enabled", "true");
assertThat(besuCommand.getEnodeDnsConfiguration().dnsEnabled()).isTrue();
assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isTrue();
}
@Test
public void dnsUpdateEnabledOptionCannotBeUsedWithoutDnsEnabled() {
parseCommand("--Xdns-update-enabled", "true");
Mockito.verifyZeroInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString())
.contains(
"The `--Xdns-update-enabled` requires dns to be enabled. Either remove --Xdns-update-enabled or specify dns is enabled (--Xdns-enabled)");
}
@Test @Test
public void helpShouldDisplayNatMethodInfo() { public void helpShouldDisplayNatMethodInfo() {
parseCommand("--help"); parseCommand("--help");

@ -15,20 +15,21 @@
package org.hyperledger.besu.util; package org.hyperledger.besu.util;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.Assertions.fail;
import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.EthNetworkConfig;
import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.cli.config.NetworkName;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import org.hyperledger.besu.ethereum.p2p.peers.ImmutableEnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration;
import org.hyperledger.besu.ethereum.permissioning.PermissioningConfigurationBuilder; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfigurationBuilder;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.io.Resources; import com.google.common.io.Resources;
@ -39,6 +40,10 @@ public class LocalPermissioningConfigurationValidatorTest {
static final String PERMISSIONING_CONFIG_ROPSTEN_BOOTNODES = static final String PERMISSIONING_CONFIG_ROPSTEN_BOOTNODES =
"/permissioning_config_ropsten_bootnodes.toml"; "/permissioning_config_ropsten_bootnodes.toml";
static final String PERMISSIONING_CONFIG = "/permissioning_config.toml"; static final String PERMISSIONING_CONFIG = "/permissioning_config.toml";
static final String PERMISSIONING_CONFIG_VALID_HOSTNAME =
"/permissioning_config_valid_hostname.toml";
static final String PERMISSIONING_CONFIG_UNKNOWN_HOSTNAME =
"/permissioning_config_unknown_hostname.toml";
@Test @Test
public void ropstenWithNodesAllowlistOptionWhichDoesIncludeRopstenBootnodesMustNotError() public void ropstenWithNodesAllowlistOptionWhichDoesIncludeRopstenBootnodesMustNotError()
@ -52,10 +57,13 @@ public class LocalPermissioningConfigurationValidatorTest {
LocalPermissioningConfiguration permissioningConfiguration = LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toAbsolutePath().toString(), true, toml.toAbsolutePath().toString()); true,
EnodeDnsConfiguration.DEFAULT_CONFIG,
toml.toAbsolutePath().toString(),
true,
toml.toAbsolutePath().toString());
final List<URI> enodeURIs = final List<EnodeURL> enodeURIs = ethNetworkConfig.getBootNodes();
ethNetworkConfig.getBootNodes().stream().map(EnodeURL::toURI).collect(Collectors.toList());
PermissioningConfigurationValidator.areAllNodesAreInAllowlist( PermissioningConfigurationValidator.areAllNodesAreInAllowlist(
enodeURIs, permissioningConfiguration); enodeURIs, permissioningConfiguration);
} }
@ -72,13 +80,14 @@ public class LocalPermissioningConfigurationValidatorTest {
LocalPermissioningConfiguration permissioningConfiguration = LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toAbsolutePath().toString(), true, toml.toAbsolutePath().toString()); true,
EnodeDnsConfiguration.DEFAULT_CONFIG,
toml.toAbsolutePath().toString(),
true,
toml.toAbsolutePath().toString());
try { try {
final List<URI> enodeURIs = final List<EnodeURL> enodeURIs = ethNetworkConfig.getBootNodes();
ethNetworkConfig.getBootNodes().stream()
.map(EnodeURL::toURI)
.collect(Collectors.toList());
PermissioningConfigurationValidator.areAllNodesAreInAllowlist( PermissioningConfigurationValidator.areAllNodesAreInAllowlist(
enodeURIs, permissioningConfiguration); enodeURIs, permissioningConfiguration);
fail("expected exception because ropsten bootnodes are not in node-allowlist"); fail("expected exception because ropsten bootnodes are not in node-allowlist");
@ -102,11 +111,15 @@ public class LocalPermissioningConfigurationValidatorTest {
final LocalPermissioningConfiguration permissioningConfiguration = final LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toAbsolutePath().toString(), true, toml.toAbsolutePath().toString()); true,
EnodeDnsConfiguration.DEFAULT_CONFIG,
toml.toAbsolutePath().toString(),
true,
toml.toAbsolutePath().toString());
// This node is defined in the PERMISSIONING_CONFIG file without the discovery port // This node is defined in the PERMISSIONING_CONFIG file without the discovery port
final URI enodeURL = final EnodeURL enodeURL =
URI.create( EnodeURL.fromString(
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567?discport=30303"); "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567?discport=30303");
// In an URI comparison the URLs should not match // In an URI comparison the URLs should not match
@ -123,4 +136,89 @@ public class LocalPermissioningConfigurationValidatorTest {
"Exception not expected. Validation of nodes in allowlist should ignore the optional discovery port param."); "Exception not expected. Validation of nodes in allowlist should ignore the optional discovery port param.");
} }
} }
@Test
public void nodeAllowlistCheckShouldWorkWithHostnameIfDnsEnabled() throws Exception {
final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_VALID_HOSTNAME);
final Path toml = Files.createTempFile("toml", "");
toml.toFile().deleteOnExit();
Files.write(toml, Resources.toByteArray(configFile));
final ImmutableEnodeDnsConfiguration enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(false).build();
final LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration(
true,
enodeDnsConfiguration,
toml.toAbsolutePath().toString(),
true,
toml.toAbsolutePath().toString());
// This node is defined in the PERMISSIONING_CONFIG_DNS file without the discovery port
final EnodeURL enodeURL =
EnodeURL.fromString(
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@localhost:4567?discport=30303",
enodeDnsConfiguration);
// In an URI comparison the URLs should not match
boolean isInAllowlist = permissioningConfiguration.getNodeAllowlist().contains(enodeURL);
assertThat(isInAllowlist).isFalse();
// However, for the allowlist validation, we should ignore the discovery port and don't throw an
// error
try {
PermissioningConfigurationValidator.areAllNodesAreInAllowlist(
Lists.newArrayList(enodeURL), permissioningConfiguration);
} catch (Exception e) {
fail(
"Exception not expected. Validation of nodes in allowlist should ignore the optional discovery port param.");
}
}
@Test
public void nodeAllowlistCheckShouldNotWorkWithHostnameWhenDnsDisabled() throws Exception {
final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_VALID_HOSTNAME);
final Path toml = Files.createTempFile("toml", "");
toml.toFile().deleteOnExit();
Files.write(toml, Resources.toByteArray(configFile));
final ImmutableEnodeDnsConfiguration enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(false).updateEnabled(false).build();
assertThatThrownBy(
() ->
PermissioningConfigurationBuilder.permissioningConfiguration(
true,
enodeDnsConfiguration,
toml.toAbsolutePath().toString(),
true,
toml.toAbsolutePath().toString()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(
"Invalid enode URL syntax. Enode URL should have the following format 'enode://<node_id>@<ip>:<listening_port>[?discport=<discovery_port>]'. Invalid ip address.");
}
@Test
public void nodeAllowlistCheckShouldNotWorkWithUnknownHostnameWhenOnlyDnsEnabled()
throws Exception {
final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_UNKNOWN_HOSTNAME);
final Path toml = Files.createTempFile("toml", "");
toml.toFile().deleteOnExit();
Files.write(toml, Resources.toByteArray(configFile));
final ImmutableEnodeDnsConfiguration enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(false).updateEnabled(false).build();
assertThatThrownBy(
() ->
PermissioningConfigurationBuilder.permissioningConfiguration(
true,
enodeDnsConfiguration,
toml.toAbsolutePath().toString(),
true,
toml.toAbsolutePath().toString()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(
"Invalid enode URL syntax. Enode URL should have the following format 'enode://<node_id>@<ip>:<listening_port>[?discport=<discovery_port>]'. Invalid ip address.");
}
} }

@ -0,0 +1,4 @@
# Permissioning TOML file
accounts-allowlist=["0x0000000000000000000000000000000000000009"]
nodes-allowlist=["enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@hostname:4567","enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@127.0.0.1:4568"]

@ -0,0 +1,4 @@
# Permissioning TOML file
accounts-allowlist=["0x0000000000000000000000000000000000000009"]
nodes-allowlist=["enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@localhost:4567","enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@127.0.0.1:4568"]

@ -42,6 +42,9 @@ dependencies {
implementation 'org.apache.tuweni:tuweni-units' implementation 'org.apache.tuweni:tuweni-units'
implementation 'org.xerial.snappy:snappy-java' implementation 'org.xerial.snappy:snappy-java'
annotationProcessor "org.immutables:value"
implementation "org.immutables:value-annotations"
runtimeOnly 'org.apache.logging.log4j:log4j-core' runtimeOnly 'org.apache.logging.log4j:log4j-core'
// test dependencies. // test dependencies.

@ -0,0 +1,32 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.peers;
import org.immutables.value.Value;
@Value.Immutable
public interface EnodeDnsConfiguration {
EnodeDnsConfiguration DEFAULT_CONFIG =
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(false).updateEnabled(false).build();
static EnodeDnsConfiguration dnsDisabled() {
return DEFAULT_CONFIG;
}
boolean dnsEnabled();
boolean updateEnabled();
}

@ -21,6 +21,7 @@ import org.hyperledger.besu.util.NetworkUtility;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.URI; import java.net.URI;
import java.net.UnknownHostException;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.OptionalInt; import java.util.OptionalInt;
@ -40,13 +41,15 @@ public class EnodeURL {
private static final Pattern NODE_ID_PATTERN = Pattern.compile("^[0-9a-fA-F]{128}$"); private static final Pattern NODE_ID_PATTERN = Pattern.compile("^[0-9a-fA-F]{128}$");
private final Bytes nodeId; private final Bytes nodeId;
private final InetAddress ip; private InetAddress ip;
private final Optional<String> maybeHostname;
private final Optional<Integer> listeningPort; private final Optional<Integer> listeningPort;
private final Optional<Integer> discoveryPort; private final Optional<Integer> discoveryPort;
private EnodeURL( private EnodeURL(
final Bytes nodeId, final Bytes nodeId,
final InetAddress address, final InetAddress address,
final Optional<String> maybeHostname,
final Optional<Integer> listeningPort, final Optional<Integer> listeningPort,
final Optional<Integer> discoveryPort) { final Optional<Integer> discoveryPort) {
checkArgument( checkArgument(
@ -56,6 +59,7 @@ public class EnodeURL {
this.nodeId = nodeId; this.nodeId = nodeId;
this.ip = address; this.ip = address;
this.maybeHostname = maybeHostname;
this.listeningPort = listeningPort; this.listeningPort = listeningPort;
this.discoveryPort = discoveryPort; this.discoveryPort = discoveryPort;
} }
@ -65,9 +69,14 @@ public class EnodeURL {
} }
public static EnodeURL fromString(final String value) { public static EnodeURL fromString(final String value) {
return fromString(value, EnodeDnsConfiguration.dnsDisabled());
}
public static EnodeURL fromString(
final String value, final EnodeDnsConfiguration enodeDnsConfiguration) {
try { try {
checkStringArgumentNotEmpty(value, "Invalid empty value."); checkStringArgumentNotEmpty(value, "Invalid empty value.");
return fromURI(URI.create(value)); return fromURI(URI.create(value), enodeDnsConfiguration);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
String message = String message =
"Invalid enode URL syntax. Enode URL should have the following format 'enode://<node_id>@<ip>:<listening_port>[?discport=<discovery_port>]'."; "Invalid enode URL syntax. Enode URL should have the following format 'enode://<node_id>@<ip>:<listening_port>[?discport=<discovery_port>]'.";
@ -79,6 +88,10 @@ public class EnodeURL {
} }
public static EnodeURL fromURI(final URI uri) { public static EnodeURL fromURI(final URI uri) {
return fromURI(uri, EnodeDnsConfiguration.dnsDisabled());
}
public static EnodeURL fromURI(final URI uri, final EnodeDnsConfiguration enodeDnsConfiguration) {
checkArgument(uri != null, "URI cannot be null"); checkArgument(uri != null, "URI cannot be null");
checkStringArgumentNotEmpty(uri.getScheme(), "Missing 'enode' scheme."); checkStringArgumentNotEmpty(uri.getScheme(), "Missing 'enode' scheme.");
checkStringArgumentNotEmpty(uri.getHost(), "Missing or invalid ip address."); checkStringArgumentNotEmpty(uri.getHost(), "Missing or invalid ip address.");
@ -108,7 +121,7 @@ public class EnodeURL {
} }
return builder() return builder()
.ipAddress(host) .ipAddress(host, enodeDnsConfiguration)
.nodeId(id) .nodeId(id)
.listeningPort(tcpPort) .listeningPort(tcpPort)
.discoveryPort(discoveryPort) .discoveryPort(discoveryPort)
@ -124,9 +137,9 @@ public class EnodeURL {
return false; return false;
} }
return Objects.equals(enodeA.nodeId, enodeB.nodeId) return Objects.equals(enodeA.getNodeId(), enodeB.getNodeId())
&& Objects.equals(enodeA.ip, enodeB.ip) && Objects.equals(enodeA.getIp(), enodeB.getIp())
&& Objects.equals(enodeA.listeningPort, enodeB.listeningPort); && Objects.equals(enodeA.getListeningPort(), enodeB.getListeningPort());
} }
public static Bytes parseNodeId(final String nodeId) { public static Bytes parseNodeId(final String nodeId) {
@ -141,11 +154,12 @@ public class EnodeURL {
} }
public URI toURI() { public URI toURI() {
final String uri = final String uri =
String.format( String.format(
"enode://%s@%s:%d", "enode://%s@%s:%d",
nodeId.toUnprefixedHexString(), nodeId.toUnprefixedHexString(),
InetAddresses.toUriString(ip), maybeHostname.orElse(InetAddresses.toUriString(getIp())),
getListeningPortOrZero()); getListeningPortOrZero());
final OptionalInt discPort = getDiscPortQueryParam(); final OptionalInt discPort = getDiscPortQueryParam();
if (discPort.isPresent()) { if (discPort.isPresent()) {
@ -160,7 +174,7 @@ public class EnodeURL {
String.format( String.format(
"enode://%s@%s:%d", "enode://%s@%s:%d",
nodeId.toUnprefixedHexString(), nodeId.toUnprefixedHexString(),
InetAddresses.toUriString(ip), maybeHostname.orElse(InetAddresses.toUriString(getIp())),
getListeningPortOrZero()); getListeningPortOrZero());
return URI.create(uri); return URI.create(uri);
@ -181,7 +195,11 @@ public class EnodeURL {
} }
public static URI asURI(final String url) { public static URI asURI(final String url) {
return fromString(url).toURI(); return asURI(url, EnodeDnsConfiguration.dnsDisabled());
}
public static URI asURI(final String url, final EnodeDnsConfiguration enodeDnsConfiguration) {
return fromString(url, enodeDnsConfiguration).toURI();
} }
public Bytes getNodeId() { public Bytes getNodeId() {
@ -189,10 +207,32 @@ public class EnodeURL {
} }
public String getIpAsString() { public String getIpAsString() {
return ip.getHostAddress(); return getIp().getHostAddress();
} }
/**
* Get IP of the EnodeURL
*
* <p>If "dns" and "dns-update" are enabled -&gt; DNS lookup every time to have the IP up to date
* and not to rely on an invalid cache
*
* <p>If the "dns" is enabled but "dns-update" is disabled -&gt; IP is retrieved only one time and
* the hostname is no longer stored (maybeHostname is empty).
*
* @return ip
*/
public InetAddress getIp() { public InetAddress getIp() {
this.ip =
maybeHostname
.map(
hostname -> {
try {
return InetAddress.getByName(hostname);
} catch (UnknownHostException e) {
return ip;
}
})
.orElse(ip);
return ip; return ip;
} }
@ -229,15 +269,15 @@ public class EnodeURL {
return false; return false;
} }
EnodeURL enodeURL = (EnodeURL) o; EnodeURL enodeURL = (EnodeURL) o;
return Objects.equals(nodeId, enodeURL.nodeId) return Objects.equals(getNodeId(), enodeURL.getNodeId())
&& Objects.equals(ip, enodeURL.ip) && Objects.equals(getIp(), enodeURL.getIp())
&& Objects.equals(listeningPort, enodeURL.listeningPort) && Objects.equals(getListeningPort(), enodeURL.getListeningPort())
&& Objects.equals(discoveryPort, enodeURL.discoveryPort); && Objects.equals(getDiscoveryPort(), enodeURL.getDiscoveryPort());
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(nodeId, ip, listeningPort, discoveryPort); return Objects.hash(getNodeId(), getIp(), getListeningPort(), getDiscoveryPort());
} }
@Override @Override
@ -250,13 +290,14 @@ public class EnodeURL {
private Bytes nodeId; private Bytes nodeId;
private Optional<Integer> listeningPort; private Optional<Integer> listeningPort;
private Optional<Integer> discoveryPort; private Optional<Integer> discoveryPort;
private Optional<String> maybeHostname = Optional.empty();
private InetAddress ip; private InetAddress ip;
private Builder() {} private Builder() {}
public EnodeURL build() { public EnodeURL build() {
validate(); validate();
return new EnodeURL(nodeId, ip, listeningPort, discoveryPort); return new EnodeURL(nodeId, ip, maybeHostname, listeningPort, discoveryPort);
} }
private void validate() { private void validate() {
@ -294,10 +335,27 @@ public class EnodeURL {
} }
public Builder ipAddress(final String ip) { public Builder ipAddress(final String ip) {
return ipAddress(ip, EnodeDnsConfiguration.dnsDisabled());
}
public Builder ipAddress(final String ip, final EnodeDnsConfiguration enodeDnsConfiguration) {
if (InetAddresses.isUriInetAddress(ip)) { if (InetAddresses.isUriInetAddress(ip)) {
this.ip = InetAddresses.forUriString(ip); this.ip = InetAddresses.forUriString(ip);
} else if (InetAddresses.isInetAddress(ip)) { } else if (InetAddresses.isInetAddress(ip)) {
this.ip = InetAddresses.forString(ip); this.ip = InetAddresses.forString(ip);
} else if (enodeDnsConfiguration.dnsEnabled()) {
try {
if (enodeDnsConfiguration.updateEnabled()) {
this.maybeHostname = Optional.of(ip);
}
this.ip = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
if (!enodeDnsConfiguration.updateEnabled()) {
throw new IllegalArgumentException("Invalid ip address or hostname.");
} else {
this.ip = InetAddresses.forString("127.0.0.1");
}
}
} else { } else {
throw new IllegalArgumentException("Invalid ip address."); throw new IllegalArgumentException("Invalid ip address.");
} }

@ -35,11 +35,12 @@ public class StaticNodesParser {
private static final Logger LOG = LogManager.getLogger(); private static final Logger LOG = LogManager.getLogger();
public static Set<EnodeURL> fromPath(final Path path) public static Set<EnodeURL> fromPath(
final Path path, final EnodeDnsConfiguration enodeDnsConfiguration)
throws IOException, IllegalArgumentException { throws IOException, IllegalArgumentException {
try { try {
return readEnodesFromPath(path); return readEnodesFromPath(path, enodeDnsConfiguration);
} catch (FileNotFoundException | NoSuchFileException ex) { } catch (FileNotFoundException | NoSuchFileException ex) {
LOG.info("StaticNodes file {} does not exist, no static connections will be created.", path); LOG.info("StaticNodes file {} does not exist, no static connections will be created.", path);
return emptySet(); return emptySet();
@ -55,7 +56,8 @@ public class StaticNodesParser {
} }
} }
private static Set<EnodeURL> readEnodesFromPath(final Path path) throws IOException { private static Set<EnodeURL> readEnodesFromPath(
final Path path, final EnodeDnsConfiguration enodeDnsConfiguration) throws IOException {
final byte[] staticNodesContent = Files.readAllBytes(path); final byte[] staticNodesContent = Files.readAllBytes(path);
if (staticNodesContent.length == 0) { if (staticNodesContent.length == 0) {
return emptySet(); return emptySet();
@ -63,13 +65,14 @@ public class StaticNodesParser {
final JsonArray enodeJsonArray = new JsonArray(new String(staticNodesContent, UTF_8)); final JsonArray enodeJsonArray = new JsonArray(new String(staticNodesContent, UTF_8));
return enodeJsonArray.stream() return enodeJsonArray.stream()
.map(obj -> decodeString((String) obj)) .map(obj -> decodeString((String) obj, enodeDnsConfiguration))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
private static EnodeURL decodeString(final String input) { private static EnodeURL decodeString(
final String input, final EnodeDnsConfiguration enodeDnsConfiguration) {
try { try {
final EnodeURL enode = EnodeURL.fromString(input); final EnodeURL enode = EnodeURL.fromString(input, enodeDnsConfiguration);
checkArgument( checkArgument(
enode.isListening(), "Static node must be configured with a valid listening port."); enode.isListening(), "Static node must be configured with a valid listening port.");
return enode; return enode;

@ -376,6 +376,82 @@ public class EnodeURLTest {
"Invalid node ID: node ID must have exactly 128 hexadecimal characters and should not include any '0x' hex prefix."); "Invalid node ID: node ID must have exactly 128 hexadecimal characters and should not include any '0x' hex prefix.");
} }
@Test
public void fromString_withHostnameEnodeURLShouldFailWhenDnsDisabled() {
final String enodeURLString =
"enode://" + VALID_NODE_ID + "@" + "localhost" + ":" + P2P_PORT + "?" + DISCOVERY_QUERY;
assertThatThrownBy(
() ->
EnodeURL.fromString(
enodeURLString,
ImmutableEnodeDnsConfiguration.builder()
.dnsEnabled(false)
.updateEnabled(false)
.build()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Invalid enode URL syntax");
}
@Test
public void fromString_withHostnameEnodeURLShouldFailWhenDnsDisabledAndUpdateEnabled() {
final String enodeURLString =
"enode://" + VALID_NODE_ID + "@" + "localhost" + ":" + P2P_PORT + "?" + DISCOVERY_QUERY;
assertThatThrownBy(
() ->
EnodeURL.fromString(
enodeURLString,
ImmutableEnodeDnsConfiguration.builder()
.dnsEnabled(false)
.updateEnabled(true)
.build()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Invalid enode URL syntax");
}
@Test
public void fromString_withHostnameEnodeURLShouldWorkWhenDnsEnabled() {
final EnodeURL expectedEnodeURL =
EnodeURL.builder()
.nodeId(VALID_NODE_ID)
.ipAddress("127.0.0.1")
.listeningPort(P2P_PORT)
.discoveryPort(Optional.of(DISCOVERY_PORT))
.build();
final String enodeURLString =
"enode://" + VALID_NODE_ID + "@" + "localhost" + ":" + P2P_PORT + "?" + DISCOVERY_QUERY;
final EnodeURL enodeURL =
EnodeURL.fromString(
enodeURLString,
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(false).build());
;
assertThat(enodeURL).isEqualTo(expectedEnodeURL);
}
@Test
public void fromString_withHostnameEnodeURLShouldWorkWhenDnsEnabledAndUpdateEnabled() {
final EnodeURL expectedEnodeURL =
EnodeURL.builder()
.nodeId(VALID_NODE_ID)
.ipAddress("127.0.0.1")
.listeningPort(P2P_PORT)
.discoveryPort(Optional.of(DISCOVERY_PORT))
.build();
final String enodeURLString =
"enode://" + VALID_NODE_ID + "@" + "localhost" + ":" + P2P_PORT + "?" + DISCOVERY_QUERY;
final EnodeURL enodeURL =
EnodeURL.fromString(
enodeURLString,
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(false).build());
;
assertThat(enodeURL).isEqualTo(expectedEnodeURL);
}
@Test @Test
public void toURI_WithDiscoveryPortCreateExpectedURI() { public void toURI_WithDiscoveryPortCreateExpectedURI() {
final String enodeURLString = final String enodeURLString =
@ -395,6 +471,78 @@ public class EnodeURLTest {
assertThat(createdURI).isEqualTo(expectedURI); assertThat(createdURI).isEqualTo(expectedURI);
} }
@Test
public void toURI_WithHostnameShouldWorkWhenDnsEnabled() {
final String enodeURLString = "enode://" + VALID_NODE_ID + "@" + "localhost" + ":" + P2P_PORT;
final URI expectedURI =
URI.create("enode://" + VALID_NODE_ID + "@" + "127.0.0.1" + ":" + P2P_PORT);
final URI createdURI =
EnodeURL.fromString(
enodeURLString,
ImmutableEnodeDnsConfiguration.builder()
.dnsEnabled(true)
.updateEnabled(false)
.build())
.toURI();
assertThat(createdURI).isEqualTo(expectedURI);
}
@Test
public void toURI_WithHostnameShouldWorkWhenDnsEnabledAndUpdateEnabled() {
final String enodeURLString = "enode://" + VALID_NODE_ID + "@" + "localhost" + ":" + P2P_PORT;
final URI expectedURI = URI.create(enodeURLString);
final URI createdURI =
EnodeURL.fromString(
enodeURLString,
ImmutableEnodeDnsConfiguration.builder()
.dnsEnabled(true)
.updateEnabled(true)
.build())
.toURI();
assertThat(createdURI).isEqualTo(expectedURI);
}
@Test
public void fromURI_withHostnameShouldFailWhenDnsDisabled() {
final String enodeURLString =
"enode://" + VALID_NODE_ID + "@" + "localhost" + ":" + P2P_PORT + "?" + DISCOVERY_QUERY;
final URI expectedURI = URI.create(enodeURLString);
assertThatThrownBy(
() ->
EnodeURL.fromURI(
expectedURI,
ImmutableEnodeDnsConfiguration.builder()
.dnsEnabled(false)
.updateEnabled(false)
.build()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Invalid ip address");
}
@Test
public void fromURI_withHostnameEnodeURLShouldWorkWhenDnsEnabled() {
final EnodeURL expectedEnodeURL =
EnodeURL.builder()
.nodeId(VALID_NODE_ID)
.ipAddress("127.0.0.1")
.listeningPort(P2P_PORT)
.discoveryPort(Optional.of(DISCOVERY_PORT))
.build();
final String enodeURLString =
"enode://" + VALID_NODE_ID + "@" + "localhost" + ":" + P2P_PORT + "?" + DISCOVERY_QUERY;
final URI expectedURI = URI.create(enodeURLString);
final EnodeURL enodeURL =
EnodeURL.fromURI(
expectedURI,
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(false).build());
assertThat(enodeURL).isEqualTo(expectedEnodeURL);
}
@Test @Test
public void builder_setInvalidPorts() { public void builder_setInvalidPorts() {
final EnodeURL.Builder validBuilder = final EnodeURL.Builder validBuilder =

@ -73,18 +73,80 @@ public class StaticNodesParserTest {
public void validFileLoadsWithExpectedEnodes() throws IOException, URISyntaxException { public void validFileLoadsWithExpectedEnodes() throws IOException, URISyntaxException {
final URL resource = StaticNodesParserTest.class.getResource("valid_static_nodes.json"); final URL resource = StaticNodesParserTest.class.getResource("valid_static_nodes.json");
final File validFile = new File(resource.getFile()); final File validFile = new File(resource.getFile());
final Set<EnodeURL> enodes = StaticNodesParser.fromPath(validFile.toPath()); final Set<EnodeURL> enodes =
StaticNodesParser.fromPath(validFile.toPath(), EnodeDnsConfiguration.DEFAULT_CONFIG);
assertThat(enodes) assertThat(enodes)
.containsExactlyInAnyOrder(validFileItems.toArray(new EnodeURL[validFileItems.size()])); .containsExactlyInAnyOrder(validFileItems.toArray(new EnodeURL[validFileItems.size()]));
} }
@Test
public void validFileLoadsWithExpectedEnodesWhenDnsEnabled()
throws IOException, URISyntaxException {
final URL resource =
StaticNodesParserTest.class.getResource("valid_hostname_static_nodes.json");
final File validFile = new File(resource.getFile());
final EnodeDnsConfiguration enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(false).build();
final Set<EnodeURL> enodes =
StaticNodesParser.fromPath(validFile.toPath(), enodeDnsConfiguration);
assertThat(enodes)
.containsExactlyInAnyOrder(validFileItems.toArray(new EnodeURL[validFileItems.size()]));
}
@Test
public void fileWithHostnameThrowsAnExceptionWhenDnsDisabled()
throws IOException, URISyntaxException {
final URL resource =
StaticNodesParserTest.class.getResource("valid_hostname_static_nodes.json");
final File invalidFile = new File(resource.getFile());
assertThatThrownBy(
() ->
StaticNodesParser.fromPath(
invalidFile.toPath(), EnodeDnsConfiguration.DEFAULT_CONFIG))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
public void fileWithUnknownHostnameNotThrowsAnExceptionWhenDnsAndUpdateEnabled()
throws IOException, URISyntaxException {
final URL resource =
StaticNodesParserTest.class.getResource("unknown_hostname_static_nodes.json");
final File validFile = new File(resource.getFile());
final EnodeDnsConfiguration enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(true).build();
final Set<EnodeURL> enodes =
StaticNodesParser.fromPath(validFile.toPath(), enodeDnsConfiguration);
assertThat(enodes)
.containsExactlyInAnyOrder(validFileItems.toArray(new EnodeURL[validFileItems.size()]));
}
@Test
public void fileWithUnknownHostnameThrowsAnExceptionWhenOnlyDnsEnabled()
throws IOException, URISyntaxException {
final URL resource =
StaticNodesParserTest.class.getResource("unknown_hostname_static_nodes.json");
final File invalidFile = new File(resource.getFile());
final EnodeDnsConfiguration enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(false).build();
assertThatThrownBy(
() -> StaticNodesParser.fromPath(invalidFile.toPath(), enodeDnsConfiguration))
.isInstanceOf(IllegalArgumentException.class);
}
@Test @Test
public void invalidFileThrowsAnException() { public void invalidFileThrowsAnException() {
final URL resource = StaticNodesParserTest.class.getResource("invalid_static_nodes.json"); final URL resource = StaticNodesParserTest.class.getResource("invalid_static_nodes.json");
final File invalidFile = new File(resource.getFile()); final File invalidFile = new File(resource.getFile());
assertThatThrownBy(() -> StaticNodesParser.fromPath(invalidFile.toPath())) assertThatThrownBy(
() ->
StaticNodesParser.fromPath(
invalidFile.toPath(), EnodeDnsConfiguration.DEFAULT_CONFIG))
.isInstanceOf(IllegalArgumentException.class); .isInstanceOf(IllegalArgumentException.class);
} }
@ -94,7 +156,10 @@ public class StaticNodesParserTest {
StaticNodesParserTest.class.getResource("invalid_static_nodes_no_listening_port.json"); StaticNodesParserTest.class.getResource("invalid_static_nodes_no_listening_port.json");
final File invalidFile = new File(resource.getFile()); final File invalidFile = new File(resource.getFile());
assertThatThrownBy(() -> StaticNodesParser.fromPath(invalidFile.toPath())) assertThatThrownBy(
() ->
StaticNodesParser.fromPath(
invalidFile.toPath(), EnodeDnsConfiguration.DEFAULT_CONFIG))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Static node must be configured with a valid listening port"); .hasMessageContaining("Static node must be configured with a valid listening port");
} }
@ -105,7 +170,9 @@ public class StaticNodesParserTest {
tempFile.deleteOnExit(); tempFile.deleteOnExit();
Files.write(tempFile.toPath(), "This Is Not Json".getBytes(Charset.forName("UTF-8"))); Files.write(tempFile.toPath(), "This Is Not Json".getBytes(Charset.forName("UTF-8")));
assertThatThrownBy(() -> StaticNodesParser.fromPath(tempFile.toPath())) assertThatThrownBy(
() ->
StaticNodesParser.fromPath(tempFile.toPath(), EnodeDnsConfiguration.DEFAULT_CONFIG))
.isInstanceOf(DecodeException.class); .isInstanceOf(DecodeException.class);
} }
@ -113,7 +180,8 @@ public class StaticNodesParserTest {
public void anEmptyCacheIsCreatedIfTheFileDoesNotExist() throws IOException { public void anEmptyCacheIsCreatedIfTheFileDoesNotExist() throws IOException {
final Path path = Paths.get("./arbirtraryFilename.txt"); final Path path = Paths.get("./arbirtraryFilename.txt");
final Set<EnodeURL> enodes = StaticNodesParser.fromPath(path); final Set<EnodeURL> enodes =
StaticNodesParser.fromPath(path, EnodeDnsConfiguration.DEFAULT_CONFIG);
assertThat(enodes.size()).isZero(); assertThat(enodes.size()).isZero();
} }
@ -122,7 +190,8 @@ public class StaticNodesParserTest {
final File tempFile = testFolder.newFile("file.txt"); final File tempFile = testFolder.newFile("file.txt");
tempFile.deleteOnExit(); tempFile.deleteOnExit();
final Set<EnodeURL> enodes = StaticNodesParser.fromPath(tempFile.toPath()); final Set<EnodeURL> enodes =
StaticNodesParser.fromPath(tempFile.toPath(), EnodeDnsConfiguration.DEFAULT_CONFIG);
assertThat(enodes.size()).isZero(); assertThat(enodes.size()).isZero();
} }
} }

@ -0,0 +1,4 @@
["enode://50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa@localhost:30303",
"enode://02beb46bc17227616be44234071dfa18516684e45eed88049190b6cb56b0bae218f045fd0450f123b8f55c60b96b78c45e8e478004293a8de6818aa4e02eff97@nodfound:30304",
"enode://819e5cbd81f123516b10f04bf620daa2b385efef06d77253148b814bf1bb6197ff58ebd1fd7bf5dc765b49a4440c733bf941e479c800173f2bfeb887e4fbcbc2@127.0.0.1:30305",
"enode://6cf53e25d2a98a22e7e205a86bda7077e3c8a7bc99e5ff88ddfd2037a550969ab566f069ffa455df0cfae0c21f7aec3447e414eccc473a3e8b20984b90f164ac@127.0.0.1:30306"]

@ -0,0 +1,4 @@
["enode://50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa@localhost:30303",
"enode://02beb46bc17227616be44234071dfa18516684e45eed88049190b6cb56b0bae218f045fd0450f123b8f55c60b96b78c45e8e478004293a8de6818aa4e02eff97@localhost:30304",
"enode://819e5cbd81f123516b10f04bf620daa2b385efef06d77253148b814bf1bb6197ff58ebd1fd7bf5dc765b49a4440c733bf941e479c800173f2bfeb887e4fbcbc2@127.0.0.1:30305",
"enode://6cf53e25d2a98a22e7e205a86bda7077e3c8a7bc99e5ff88ddfd2037a550969ab566f069ffa455df0cfae0c21f7aec3447e414eccc473a3e8b20984b90f164ac@127.0.0.1:30306"]

@ -209,6 +209,7 @@ public class AccountLocalConfigPermissioningController implements TransactionPer
final LocalPermissioningConfiguration updatedConfig = final LocalPermissioningConfiguration updatedConfig =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
configuration.isNodeAllowlistEnabled(), configuration.isNodeAllowlistEnabled(),
configuration.getEnodeDnsConfiguration(),
configuration.getNodePermissioningConfigFilePath(), configuration.getNodePermissioningConfigFilePath(),
configuration.isAccountAllowlistEnabled(), configuration.isAccountAllowlistEnabled(),
configuration.getAccountPermissioningConfigFilePath()); configuration.getAccountPermissioningConfigFilePath());

@ -14,20 +14,23 @@
*/ */
package org.hyperledger.besu.ethereum.permissioning; package org.hyperledger.besu.ethereum.permissioning;
import java.net.URI; import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
public class LocalPermissioningConfiguration { public class LocalPermissioningConfiguration {
private List<URI> nodeAllowlist; private List<EnodeURL> nodeAllowlist;
private List<String> accountAllowlist; private List<String> accountAllowlist;
private boolean nodeAllowlistEnabled; private boolean nodeAllowlistEnabled;
private EnodeDnsConfiguration enodeDnsConfiguration = EnodeDnsConfiguration.dnsDisabled();
private String nodePermissioningConfigFilePath; private String nodePermissioningConfigFilePath;
private boolean accountAllowlistEnabled; private boolean accountAllowlistEnabled;
private String accountPermissioningConfigFilePath; private String accountPermissioningConfigFilePath;
public List<URI> getNodeAllowlist() { public List<EnodeURL> getNodeAllowlist() {
return nodeAllowlist; return nodeAllowlist;
} }
@ -38,13 +41,21 @@ public class LocalPermissioningConfiguration {
return config; return config;
} }
public void setNodeAllowlist(final Collection<URI> nodeAllowlist) { public void setEnodeDnsConfiguration(final EnodeDnsConfiguration enodeDnsConfiguration) {
this.enodeDnsConfiguration = enodeDnsConfiguration;
}
public void setNodeAllowlist(final Collection<EnodeURL> nodeAllowlist) {
if (nodeAllowlist != null) { if (nodeAllowlist != null) {
this.nodeAllowlist.addAll(nodeAllowlist); this.nodeAllowlist.addAll(nodeAllowlist);
this.nodeAllowlistEnabled = true; this.nodeAllowlistEnabled = true;
} }
} }
public EnodeDnsConfiguration getEnodeDnsConfiguration() {
return enodeDnsConfiguration;
}
public boolean isNodeAllowlistEnabled() { public boolean isNodeAllowlistEnabled() {
return nodeAllowlistEnabled; return nodeAllowlistEnabled;
} }

@ -24,7 +24,6 @@ import org.hyperledger.besu.plugin.services.metrics.Counter;
import org.hyperledger.besu.util.Subscribers; import org.hyperledger.besu.util.Subscribers;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -100,8 +99,8 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning
private void readNodesFromConfig(final LocalPermissioningConfiguration configuration) { private void readNodesFromConfig(final LocalPermissioningConfiguration configuration) {
if (configuration.isNodeAllowlistEnabled() && configuration.getNodeAllowlist() != null) { if (configuration.isNodeAllowlistEnabled() && configuration.getNodeAllowlist() != null) {
for (URI uri : configuration.getNodeAllowlist()) { for (EnodeURL enodeURL : configuration.getNodeAllowlist()) {
addNode(EnodeURL.fromString(uri.toString())); addNode(enodeURL);
} }
} }
} }
@ -112,7 +111,9 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning
return inputValidationResult; return inputValidationResult;
} }
final List<EnodeURL> peers = final List<EnodeURL> peers =
enodeURLs.stream().map(EnodeURL::fromString).collect(Collectors.toList()); enodeURLs.stream()
.map(url -> EnodeURL.fromString(url, configuration.getEnodeDnsConfiguration()))
.collect(Collectors.toList());
for (EnodeURL peer : peers) { for (EnodeURL peer : peers) {
if (nodesAllowlist.contains(peer)) { if (nodesAllowlist.contains(peer)) {
@ -144,7 +145,9 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning
return inputValidationResult; return inputValidationResult;
} }
final List<EnodeURL> peers = final List<EnodeURL> peers =
enodeURLs.stream().map(EnodeURL::fromString).collect(Collectors.toList()); enodeURLs.stream()
.map(url -> EnodeURL.fromString(url, configuration.getEnodeDnsConfiguration()))
.collect(Collectors.toList());
boolean anyBootnode = peers.stream().anyMatch(fixedNodes::contains); boolean anyBootnode = peers.stream().anyMatch(fixedNodes::contains);
if (anyBootnode) { if (anyBootnode) {
@ -228,7 +231,7 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning
} }
public boolean isPermitted(final String enodeURL) { public boolean isPermitted(final String enodeURL) {
return isPermitted(EnodeURL.fromString(enodeURL)); return isPermitted(EnodeURL.fromString(enodeURL, configuration.getEnodeDnsConfiguration()));
} }
public boolean isPermitted(final EnodeURL node) { public boolean isPermitted(final EnodeURL node) {
@ -250,6 +253,7 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning
final LocalPermissioningConfiguration updatedConfig = final LocalPermissioningConfiguration updatedConfig =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
configuration.isNodeAllowlistEnabled(), configuration.isNodeAllowlistEnabled(),
configuration.getEnodeDnsConfiguration(),
configuration.getNodePermissioningConfigFilePath(), configuration.getNodePermissioningConfigFilePath(),
configuration.isAccountAllowlistEnabled(), configuration.isAccountAllowlistEnabled(),
configuration.getAccountPermissioningConfigFilePath()); configuration.getAccountPermissioningConfigFilePath());

@ -15,9 +15,9 @@
package org.hyperledger.besu.ethereum.permissioning; package org.hyperledger.besu.ethereum.permissioning;
import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import java.net.URI;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -41,6 +41,7 @@ public class PermissioningConfigurationBuilder {
public static LocalPermissioningConfiguration permissioningConfiguration( public static LocalPermissioningConfiguration permissioningConfiguration(
final boolean nodePermissioningEnabled, final boolean nodePermissioningEnabled,
final EnodeDnsConfiguration enodeDnsConfiguration,
final String nodePermissioningConfigFilepath, final String nodePermissioningConfigFilepath,
final boolean accountPermissioningEnabled, final boolean accountPermissioningEnabled,
final String accountPermissioningConfigFilepath) final String accountPermissioningConfigFilepath)
@ -48,7 +49,7 @@ public class PermissioningConfigurationBuilder {
final LocalPermissioningConfiguration permissioningConfiguration = final LocalPermissioningConfiguration permissioningConfiguration =
LocalPermissioningConfiguration.createDefault(); LocalPermissioningConfiguration.createDefault();
permissioningConfiguration.setEnodeDnsConfiguration(enodeDnsConfiguration);
loadNodePermissioning( loadNodePermissioning(
permissioningConfiguration, nodePermissioningEnabled, nodePermissioningConfigFilepath); permissioningConfiguration, nodePermissioningEnabled, nodePermissioningConfigFilepath);
loadAccountPermissioning( loadAccountPermissioning(
@ -74,12 +75,15 @@ public class PermissioningConfigurationBuilder {
nodePermissioningConfigFilepath); nodePermissioningConfigFilepath);
if (nodeAllowlistTomlArray != null) { if (nodeAllowlistTomlArray != null) {
List<URI> nodesAllowlistToml = List<EnodeURL> nodesAllowlistToml =
nodeAllowlistTomlArray nodeAllowlistTomlArray
.toList() .toList()
.parallelStream() .parallelStream()
.map(Object::toString) .map(Object::toString)
.map(EnodeURL::asURI) .map(
url ->
EnodeURL.fromString(
url, permissioningConfiguration.getEnodeDnsConfiguration()))
.collect(Collectors.toList()); .collect(Collectors.toList());
permissioningConfiguration.setNodeAllowlist(nodesAllowlistToml); permissioningConfiguration.setNodeAllowlist(nodesAllowlistToml);
} else { } else {

@ -16,10 +16,14 @@ package org.hyperledger.besu.ethereum.permissioning;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.catchThrowable; import static org.assertj.core.api.Assertions.catchThrowable;
import static org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration.dnsDisabled;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import org.hyperledger.besu.ethereum.p2p.peers.ImmutableEnodeDnsConfiguration;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -52,7 +56,8 @@ public class LocalPermissioningConfigurationBuilderTest {
"/permissioning_config_unrecognized_key.toml"; "/permissioning_config_unrecognized_key.toml";
private static final String PERMISSIONING_CONFIG_NODE_ALLOWLIST_ONLY_MULTILINE = private static final String PERMISSIONING_CONFIG_NODE_ALLOWLIST_ONLY_MULTILINE =
"/permissioning_config_node_allowlist_only_multiline.toml"; "/permissioning_config_node_allowlist_only_multiline.toml";
private static final String PERMISSIONING_CONFIG_NODE_ALLOWLIST_WITH_DNS =
"/permissioning_config_node_allowlist_with_dns.toml";
private final String VALID_NODE_ID = private final String VALID_NODE_ID =
"6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"; "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0";
@ -71,7 +76,7 @@ public class LocalPermissioningConfigurationBuilderTest {
.containsExactly("0x0000000000000000000000000000000000000009"); .containsExactly("0x0000000000000000000000000000000000000009");
assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue(); assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue();
assertThat(permissioningConfiguration.getNodeAllowlist()) assertThat(permissioningConfiguration.getNodeAllowlist())
.containsExactly(URI.create(uri), URI.create(uri2)); .containsExactly(EnodeURL.fromString(uri), EnodeURL.fromString(uri2));
} }
@Test @Test
@ -89,7 +94,7 @@ public class LocalPermissioningConfigurationBuilderTest {
.containsExactly("0x0000000000000000000000000000000000000009"); .containsExactly("0x0000000000000000000000000000000000000009");
assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue(); assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue();
assertThat(permissioningConfiguration.getNodeAllowlist()) assertThat(permissioningConfiguration.getNodeAllowlist())
.containsExactly(URI.create(uri), URI.create(uri2)); .containsExactly(EnodeURL.fromString(uri), EnodeURL.fromString(uri2));
} }
@Test @Test
@ -101,11 +106,57 @@ public class LocalPermissioningConfigurationBuilderTest {
LocalPermissioningConfiguration permissioningConfiguration = LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toAbsolutePath().toString(), false, toml.toAbsolutePath().toString()); true,
dnsDisabled(),
toml.toAbsolutePath().toString(),
false,
toml.toAbsolutePath().toString());
assertThat(permissioningConfiguration.isAccountAllowlistEnabled()).isFalse(); assertThat(permissioningConfiguration.isAccountAllowlistEnabled()).isFalse();
assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue(); assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue();
assertThat(permissioningConfiguration.getNodeAllowlist()).containsExactly(URI.create(uri)); assertThat(permissioningConfiguration.getNodeAllowlist())
.containsExactly(EnodeURL.fromString(uri));
}
@Test
public void permissioningConfigWithNodeAllowlistSetWithDnsEnabled() throws Exception {
final String uri = "enode://" + VALID_NODE_ID + "@127.0.0.1:4567";
final URL configFile =
this.getClass().getResource(PERMISSIONING_CONFIG_NODE_ALLOWLIST_WITH_DNS);
final Path toml = createTempFile("toml", Resources.toByteArray(configFile));
LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration(
true,
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(false).build(),
toml.toAbsolutePath().toString(),
false,
toml.toAbsolutePath().toString());
assertThat(permissioningConfiguration.isAccountAllowlistEnabled()).isFalse();
assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue();
assertThat(permissioningConfiguration.getNodeAllowlist())
.containsExactly(EnodeURL.fromString(uri));
}
@Test
public void permissioningConfigWithNodeAllowlistSetWithDnsDisabled() throws Exception {
final URL configFile =
this.getClass().getResource(PERMISSIONING_CONFIG_NODE_ALLOWLIST_WITH_DNS);
final Path toml = createTempFile("toml", Resources.toByteArray(configFile));
assertThatThrownBy(
() ->
PermissioningConfigurationBuilder.permissioningConfiguration(
true,
dnsDisabled(),
toml.toAbsolutePath().toString(),
false,
toml.toAbsolutePath().toString()))
.hasMessageContaining("Invalid enode URL syntax")
.isInstanceOf(IllegalArgumentException.class);
} }
@Test @Test
@ -115,7 +166,11 @@ public class LocalPermissioningConfigurationBuilderTest {
LocalPermissioningConfiguration permissioningConfiguration = LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
false, toml.toAbsolutePath().toString(), true, toml.toAbsolutePath().toString()); false,
dnsDisabled(),
toml.toAbsolutePath().toString(),
true,
toml.toAbsolutePath().toString());
assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isFalse(); assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isFalse();
assertThat(permissioningConfiguration.isAccountAllowlistEnabled()).isTrue(); assertThat(permissioningConfiguration.isAccountAllowlistEnabled()).isTrue();
@ -200,7 +255,7 @@ public class LocalPermissioningConfigurationBuilderTest {
LocalPermissioningConfiguration permissioningConfiguration = LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toString(), true, toml.toString()); true, dnsDisabled(), toml.toString(), true, toml.toString());
assertThat(permissioningConfiguration.getNodePermissioningConfigFilePath()) assertThat(permissioningConfiguration.getNodePermissioningConfigFilePath())
.isEqualTo(toml.toString()); .isEqualTo(toml.toString());
@ -224,7 +279,7 @@ public class LocalPermissioningConfigurationBuilderTest {
this.getClass().getResource(PERMISSIONING_CONFIG_NODE_ALLOWLIST_ONLY_MULTILINE); this.getClass().getResource(PERMISSIONING_CONFIG_NODE_ALLOWLIST_ONLY_MULTILINE);
final LocalPermissioningConfiguration permissioningConfiguration = final LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration( PermissioningConfigurationBuilder.permissioningConfiguration(
true, configFile.getPath(), false, configFile.getPath()); true, dnsDisabled(), configFile.getPath(), false, configFile.getPath());
assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue(); assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue();
assertThat(permissioningConfiguration.getNodeAllowlist().size()).isEqualTo(5); assertThat(permissioningConfiguration.getNodeAllowlist().size()).isEqualTo(5);
@ -233,18 +288,22 @@ public class LocalPermissioningConfigurationBuilderTest {
private LocalPermissioningConfiguration accountOnlyPermissioningConfig(final Path toml) private LocalPermissioningConfiguration accountOnlyPermissioningConfig(final Path toml)
throws Exception { throws Exception {
return PermissioningConfigurationBuilder.permissioningConfiguration( return PermissioningConfigurationBuilder.permissioningConfiguration(
false, null, true, toml.toAbsolutePath().toString()); false, dnsDisabled(), null, true, toml.toAbsolutePath().toString());
} }
private LocalPermissioningConfiguration nodeOnlyPermissioningConfig(final Path toml) private LocalPermissioningConfiguration nodeOnlyPermissioningConfig(final Path toml)
throws Exception { throws Exception {
return PermissioningConfigurationBuilder.permissioningConfiguration( return PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toAbsolutePath().toString(), false, null); true, dnsDisabled(), toml.toAbsolutePath().toString(), false, null);
} }
private LocalPermissioningConfiguration permissioningConfig(final Path toml) throws Exception { private LocalPermissioningConfiguration permissioningConfig(final Path toml) throws Exception {
return PermissioningConfigurationBuilder.permissioningConfiguration( return PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toAbsolutePath().toString(), true, toml.toAbsolutePath().toString()); true,
dnsDisabled(),
toml.toAbsolutePath().toString(),
true,
toml.toAbsolutePath().toString());
} }
private Path createTempFile(final String filename, final byte[] contents) throws IOException { private Path createTempFile(final String filename, final byte[] contents) throws IOException {

@ -16,17 +16,21 @@ package org.hyperledger.besu.ethereum.permissioning;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import java.net.URI; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Test; import org.junit.Test;
public class LocalPermissioningConfigurationTest { public class LocalPermissioningConfigurationTest {
final URI[] nodes = { final EnodeURL[] nodes = {
URI.create("enode://001@123:4567"), EnodeURL.fromString(
URI.create("enode://002@123:4567"), "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@127.0.0.1:4567"),
URI.create("enode://003@123:4567") EnodeURL.fromString(
"enode://7f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@127.0.0.1:4568"),
EnodeURL.fromString(
"enode://8f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@127.0.0.1:4569")
}; };
@Test @Test

@ -27,7 +27,9 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import org.hyperledger.besu.ethereum.p2p.peers.ImmutableEnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController.NodesAllowlistResult; import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController.NodesAllowlistResult;
import org.hyperledger.besu.ethereum.permissioning.node.NodeAllowlistUpdatedEvent; import org.hyperledger.besu.ethereum.permissioning.node.NodeAllowlistUpdatedEvent;
import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.metrics.BesuMetricCategory;
@ -35,7 +37,6 @@ import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.Counter; import org.hyperledger.besu.plugin.services.metrics.Counter;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -66,6 +67,9 @@ public class NodeLocalConfigPermissioningControllerTest {
private final EnodeURL selfEnode = private final EnodeURL selfEnode =
EnodeURL.fromString( EnodeURL.fromString(
"enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:1111"); "enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:1111");
private final String enodeDns =
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@localhost:4567";
@Mock private MetricsSystem metricsSystem; @Mock private MetricsSystem metricsSystem;
@Mock private Counter checkCounter; @Mock private Counter checkCounter;
@Mock private Counter checkPermittedCounter; @Mock private Counter checkPermittedCounter;
@ -340,7 +344,7 @@ public class NodeLocalConfigPermissioningControllerTest {
.thenReturn(permissionsFile.toAbsolutePath().toString()); .thenReturn(permissionsFile.toAbsolutePath().toString());
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true); when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist()) when(permissioningConfig.getNodeAllowlist())
.thenReturn(Arrays.asList(URI.create(expectedEnodeURL))); .thenReturn(Arrays.asList(EnodeURL.fromString(expectedEnodeURL)));
controller = controller =
new NodeLocalConfigPermissioningController( new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem); permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
@ -360,7 +364,7 @@ public class NodeLocalConfigPermissioningControllerTest {
when(permissioningConfig.getNodePermissioningConfigFilePath()).thenReturn("foo"); when(permissioningConfig.getNodePermissioningConfigFilePath()).thenReturn("foo");
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true); when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist()) when(permissioningConfig.getNodeAllowlist())
.thenReturn(Arrays.asList(URI.create(expectedEnodeURI))); .thenReturn(Arrays.asList(EnodeURL.fromString(expectedEnodeURI)));
controller = controller =
new NodeLocalConfigPermissioningController( new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem); permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
@ -459,7 +463,8 @@ public class NodeLocalConfigPermissioningControllerTest {
when(permissioningConfig.getNodePermissioningConfigFilePath()) when(permissioningConfig.getNodePermissioningConfigFilePath())
.thenReturn(permissionsFile.toAbsolutePath().toString()); .thenReturn(permissionsFile.toAbsolutePath().toString());
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true); when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist()).thenReturn(Arrays.asList(URI.create(enode1))); when(permissioningConfig.getNodeAllowlist())
.thenReturn(Arrays.asList(EnodeURL.fromString(enode1)));
controller = controller =
new NodeLocalConfigPermissioningController( new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem); permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
@ -482,7 +487,8 @@ public class NodeLocalConfigPermissioningControllerTest {
when(permissioningConfig.getNodePermissioningConfigFilePath()) when(permissioningConfig.getNodePermissioningConfigFilePath())
.thenReturn(permissionsFile.toAbsolutePath().toString()); .thenReturn(permissionsFile.toAbsolutePath().toString());
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true); when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist()).thenReturn(Arrays.asList(URI.create(enode1))); when(permissioningConfig.getNodeAllowlist())
.thenReturn(Arrays.asList(EnodeURL.fromString(enode1)));
controller = controller =
new NodeLocalConfigPermissioningController( new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem); permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
@ -493,6 +499,52 @@ public class NodeLocalConfigPermissioningControllerTest {
verifyZeroInteractions(consumer); verifyZeroInteractions(consumer);
} }
@Test
@SuppressWarnings("unchecked")
public void getNodeAllowlistShouldWorkWhenOnlyDnsEnabled() throws Exception {
final Path permissionsFile = createPermissionsFileWithNode(enodeDns);
final LocalPermissioningConfiguration permissioningConfig =
mock(LocalPermissioningConfiguration.class);
final EnodeDnsConfiguration enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(false).build();
when(permissioningConfig.getNodePermissioningConfigFilePath())
.thenReturn(permissionsFile.toAbsolutePath().toString());
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist())
.thenReturn(singletonList(EnodeURL.fromString(enodeDns, enodeDnsConfiguration)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
assertThat(controller.getNodesAllowlist()).hasSize(1);
assertThat(controller.getNodesAllowlist())
.contains(
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@127.0.0.1:4567");
}
@Test
@SuppressWarnings("unchecked")
public void getNodeAllowlistShouldWorkWhenDnsAndUpdateEnabled() throws Exception {
final Path permissionsFile = createPermissionsFileWithNode(enodeDns);
final LocalPermissioningConfiguration permissioningConfig =
mock(LocalPermissioningConfiguration.class);
final EnodeDnsConfiguration enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(true).build();
when(permissioningConfig.getNodePermissioningConfigFilePath())
.thenReturn(permissionsFile.toAbsolutePath().toString());
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist())
.thenReturn(singletonList(EnodeURL.fromString(enodeDns, enodeDnsConfiguration)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
assertThat(controller.getNodesAllowlist()).hasSize(1);
assertThat(controller.getNodesAllowlist()).contains(enodeDns);
}
private Path createPermissionsFileWithNode(final String node) throws IOException { private Path createPermissionsFileWithNode(final String node) throws IOException {
final String nodePermissionsFileContent = "nodes-allowlist=[\"" + node + "\"]"; final String nodePermissionsFileContent = "nodes-allowlist=[\"" + node + "\"]";
final Path permissionsFile = Files.createTempFile("node_permissions", ""); final Path permissionsFile = Files.createTempFile("node_permissions", "");

@ -0,0 +1,3 @@
# Permissioning TOML file (node allowlist with dns only)
nodes-allowlist=["enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@localhost:4567"]
Loading…
Cancel
Save