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
* 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

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

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

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

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

@ -68,6 +68,7 @@ public class BesuNodeFactory {
config.getPlugins(),
config.getExtraCLIOptions(),
config.getStaticNodes(),
config.isDnsEnabled(),
config.getPrivacyParameters(),
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.RpcApis;
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.ALLOWLIST_TYPE;
import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration;
@ -71,6 +72,7 @@ public class PermissionedNodeBuilder {
private String accountPermissioningSmartContractAddress = null;
private List<String> staticNodes = new ArrayList<>();
private boolean isDnsEnabled = false;
private boolean mining = true;
public PermissionedNodeBuilder name(final String name) {
@ -139,6 +141,11 @@ public class PermissionedNodeBuilder {
return this;
}
public PermissionedNodeBuilder dnsEnabled(final boolean isDnsEnabled) {
this.isDnsEnabled = isDnsEnabled;
return this;
}
public PermissionedNodeBuilder disableMining() {
this.mining = false;
return this;
@ -188,6 +195,8 @@ public class PermissionedNodeBuilder {
builder.staticNodes(staticNodes);
}
builder.dnsEnabled(isDnsEnabled);
if (genesisFile != null) {
builder.genesisConfigProvider((a) -> Optional.of(genesisFile));
builder.devMode(false);
@ -209,12 +218,15 @@ public class PermissionedNodeBuilder {
localConfigNodesPermissioningFile = createTemporaryPermissionsFile();
}
List<String> nodesAsListOfStrings =
localConfigPermittedNodes.stream().map(URI::toASCIIString).collect(Collectors.toList());
final List<EnodeURL> nodeAllowList =
localConfigPermittedNodes.stream().map(EnodeURL::fromURI).collect(Collectors.toList());
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(
localConfigNodesPermissioningFile.toAbsolutePath().toString());
}

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

@ -25,8 +25,10 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.web3j.protocol.exceptions.ClientConnectionException;
public class AllowlistPersistorAcceptanceTest extends AcceptanceTestBase {
@ -36,6 +38,8 @@ public class AllowlistPersistorAcceptanceTest extends AcceptanceTestBase {
"enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567";
private static final String ENODE_THREE =
"enode://4f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567";
private static final String ENODE_FOURTH =
"enode://4f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@localhost:4567";
private Node node;
private Account senderA;
@ -99,4 +103,11 @@ public class AllowlistPersistorAcceptanceTest extends AcceptanceTestBase {
perm.expectPermissioningAllowlistFileKeyValue(
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.mainnet.precompiles.AltBN128PairingPrecompiledContract;
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.ImmutableEnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.StaticNodesParser;
import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration;
import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration;
@ -310,20 +312,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
+ "Default is a predefined list.",
split = ",",
arity = "0..*")
void setBootnodes(final List<String> values) {
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;
private final List<String> bootNodes = null;
@Option(
names = {"--max-peers"},
@ -1047,6 +1036,20 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
arity = "1")
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 JsonRpcConfiguration jsonRpcConfiguration;
private GraphQLConfiguration graphQLConfiguration;
@ -1059,6 +1062,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
private final Supplier<ObservableMetricsSystem> metricsSystem =
Suppliers.memoize(() -> PrometheusMetricsSystem.init(metricsConfiguration()));
private Vertx vertx;
private EnodeDnsConfiguration enodeDnsConfiguration;
public BesuCommand(
final Logger logger,
@ -1329,6 +1333,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
validateMiningParams();
validateNatParams();
validateNetStatsParams();
validateDnsOptionsParams();
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() {
// Check that P2P options are able to work
CommandLineUtils.checkOptionDependencies(
@ -1441,26 +1455,25 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
genesisFile == null && !isPrivacyEnabled && network != NetworkName.DEV
? SyncMode.FAST
: SyncMode.FULL);
ethNetworkConfig = updateNetworkConfig(getNetwork());
jsonRpcConfiguration = jsonRpcConfiguration();
graphQLConfiguration = graphQLConfiguration();
webSocketConfiguration = webSocketConfiguration();
permissioningConfiguration = permissioningConfiguration();
staticNodes = loadStaticNodes();
logger.info("Connecting to {} static nodes.", staticNodes.size());
logger.trace("Static Nodes = {}", staticNodes);
final List<URI> enodeURIs =
ethNetworkConfig.getBootNodes().stream().map(EnodeURL::toURI).collect(Collectors.toList());
final List<EnodeURL> enodeURIs = ethNetworkConfig.getBootNodes();
permissioningConfiguration
.flatMap(PermissioningConfiguration::getLocalConfig)
.ifPresent(p -> ensureAllNodesAreInAllowlist(enodeURIs, p));
permissioningConfiguration
.flatMap(PermissioningConfiguration::getLocalConfig)
.ifPresent(
p ->
ensureAllNodesAreInAllowlist(
staticNodes.stream().map(EnodeURL::toURI).collect(Collectors.toList()), p));
.ifPresent(p -> ensureAllNodesAreInAllowlist(staticNodes, p));
metricsConfiguration = metricsConfiguration();
logger.info("Security Module: {}", securityModuleName);
@ -1474,7 +1487,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
}
private void ensureAllNodesAreInAllowlist(
final Collection<URI> enodeAddresses,
final Collection<EnodeURL> enodeAddresses,
final LocalPermissioningConfiguration permissioningConfiguration) {
try {
PermissioningConfigurationValidator.areAllNodesAreInAllowlist(
@ -1781,6 +1794,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
final LocalPermissioningConfiguration localPermissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration(
permissionsNodesEnabled,
getEnodeDnsConfiguration(),
nodePermissioningConfigFile.orElse(getDefaultPermissioningFilePath()),
permissionsAccountsEnabled,
accountPermissioningConfigFile.orElse(getDefaultPermissioningFilePath()));
@ -2189,7 +2203,18 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
}
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();
@ -2267,13 +2292,24 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
final String staticNodesFilename = "static-nodes.json";
final Path staticNodesPath = dataDir().resolve(staticNodesFilename);
return StaticNodesParser.fromPath(staticNodesPath);
return StaticNodesParser.fromPath(staticNodesPath, getEnodeDnsConfiguration());
}
public BesuExceptionHandler exceptionHandler() {
return new BesuExceptionHandler(this::getLogLevel);
}
public EnodeDnsConfiguration getEnodeDnsConfiguration() {
if (enodeDnsConfiguration == null) {
enodeDnsConfiguration =
ImmutableEnodeDnsConfiguration.builder()
.dnsEnabled(dnsEnabled)
.updateEnabled(dnsUpdateEnabled)
.build();
}
return enodeDnsConfiguration;
}
@VisibleForTesting
Level getLogLevel() {
return logLevel;

@ -14,10 +14,12 @@
*/
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 java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@ -25,21 +27,21 @@ import java.util.stream.Collectors;
public class PermissioningConfigurationValidator {
public static void areAllNodesAreInAllowlist(
final Collection<URI> nodeURIs,
final Collection<EnodeURL> nodeURIs,
final LocalPermissioningConfiguration permissioningConfiguration)
throws Exception {
if (permissioningConfiguration.isNodeAllowlistEnabled() && nodeURIs != null) {
final List<URI> allowlistNodesWithoutQueryParam =
permissioningConfiguration.getNodeAllowlist().stream()
.map(PermissioningConfigurationValidator::removeQueryFromURI)
.collect(Collectors.toList());
.map(EnodeURL::toURIWithoutDiscoveryPort)
.collect(toList());
final List<URI> nodeURIsNotInAllowlist =
nodeURIs.stream()
.map(PermissioningConfigurationValidator::removeQueryFromURI)
.map(EnodeURL::toURIWithoutDiscoveryPort)
.filter(uri -> !allowlistNodesWithoutQueryParam.contains(uri))
.collect(Collectors.toList());
.collect(toList());
if (!nodeURIsNotInAllowlist.isEmpty()) {
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) {
return enodes.parallelStream().map(URI::toASCIIString).collect(Collectors.toList());
}

@ -590,11 +590,11 @@ public class BesuCommandTest extends CommandTestAbstract {
@Test
public void nodePermissioningTomlPathMustUseOption() throws IOException {
final List<URI> allowedNodes =
final List<EnodeURL> allowedNodes =
Lists.newArrayList(
URI.create(
EnodeURL.fromString(
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567"),
URI.create(
EnodeURL.fromString(
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.169.0.9:4568"));
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)");
}
@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
public void helpShouldDisplayNatMethodInfo() {
parseCommand("--help");

@ -15,20 +15,21 @@
package org.hyperledger.besu.util;
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 org.hyperledger.besu.cli.config.EthNetworkConfig;
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.ImmutableEnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration;
import org.hyperledger.besu.ethereum.permissioning.PermissioningConfigurationBuilder;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import com.google.common.collect.Lists;
import com.google.common.io.Resources;
@ -39,6 +40,10 @@ public class LocalPermissioningConfigurationValidatorTest {
static final String PERMISSIONING_CONFIG_ROPSTEN_BOOTNODES =
"/permissioning_config_ropsten_bootnodes.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
public void ropstenWithNodesAllowlistOptionWhichDoesIncludeRopstenBootnodesMustNotError()
@ -52,10 +57,13 @@ public class LocalPermissioningConfigurationValidatorTest {
LocalPermissioningConfiguration 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 =
ethNetworkConfig.getBootNodes().stream().map(EnodeURL::toURI).collect(Collectors.toList());
final List<EnodeURL> enodeURIs = ethNetworkConfig.getBootNodes();
PermissioningConfigurationValidator.areAllNodesAreInAllowlist(
enodeURIs, permissioningConfiguration);
}
@ -72,13 +80,14 @@ public class LocalPermissioningConfigurationValidatorTest {
LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toAbsolutePath().toString(), true, toml.toAbsolutePath().toString());
true,
EnodeDnsConfiguration.DEFAULT_CONFIG,
toml.toAbsolutePath().toString(),
true,
toml.toAbsolutePath().toString());
try {
final List<URI> enodeURIs =
ethNetworkConfig.getBootNodes().stream()
.map(EnodeURL::toURI)
.collect(Collectors.toList());
final List<EnodeURL> enodeURIs = ethNetworkConfig.getBootNodes();
PermissioningConfigurationValidator.areAllNodesAreInAllowlist(
enodeURIs, permissioningConfiguration);
fail("expected exception because ropsten bootnodes are not in node-allowlist");
@ -102,11 +111,15 @@ public class LocalPermissioningConfigurationValidatorTest {
final LocalPermissioningConfiguration 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
final URI enodeURL =
URI.create(
final EnodeURL enodeURL =
EnodeURL.fromString(
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567?discport=30303");
// 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.");
}
}
@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.xerial.snappy:snappy-java'
annotationProcessor "org.immutables:value"
implementation "org.immutables:value-annotations"
runtimeOnly 'org.apache.logging.log4j:log4j-core'
// 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.URI;
import java.net.UnknownHostException;
import java.util.Objects;
import java.util.Optional;
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 final Bytes nodeId;
private final InetAddress ip;
private InetAddress ip;
private final Optional<String> maybeHostname;
private final Optional<Integer> listeningPort;
private final Optional<Integer> discoveryPort;
private EnodeURL(
final Bytes nodeId,
final InetAddress address,
final Optional<String> maybeHostname,
final Optional<Integer> listeningPort,
final Optional<Integer> discoveryPort) {
checkArgument(
@ -56,6 +59,7 @@ public class EnodeURL {
this.nodeId = nodeId;
this.ip = address;
this.maybeHostname = maybeHostname;
this.listeningPort = listeningPort;
this.discoveryPort = discoveryPort;
}
@ -65,9 +69,14 @@ public class EnodeURL {
}
public static EnodeURL fromString(final String value) {
return fromString(value, EnodeDnsConfiguration.dnsDisabled());
}
public static EnodeURL fromString(
final String value, final EnodeDnsConfiguration enodeDnsConfiguration) {
try {
checkStringArgumentNotEmpty(value, "Invalid empty value.");
return fromURI(URI.create(value));
return fromURI(URI.create(value), enodeDnsConfiguration);
} catch (IllegalArgumentException e) {
String message =
"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) {
return fromURI(uri, EnodeDnsConfiguration.dnsDisabled());
}
public static EnodeURL fromURI(final URI uri, final EnodeDnsConfiguration enodeDnsConfiguration) {
checkArgument(uri != null, "URI cannot be null");
checkStringArgumentNotEmpty(uri.getScheme(), "Missing 'enode' scheme.");
checkStringArgumentNotEmpty(uri.getHost(), "Missing or invalid ip address.");
@ -108,7 +121,7 @@ public class EnodeURL {
}
return builder()
.ipAddress(host)
.ipAddress(host, enodeDnsConfiguration)
.nodeId(id)
.listeningPort(tcpPort)
.discoveryPort(discoveryPort)
@ -124,9 +137,9 @@ public class EnodeURL {
return false;
}
return Objects.equals(enodeA.nodeId, enodeB.nodeId)
&& Objects.equals(enodeA.ip, enodeB.ip)
&& Objects.equals(enodeA.listeningPort, enodeB.listeningPort);
return Objects.equals(enodeA.getNodeId(), enodeB.getNodeId())
&& Objects.equals(enodeA.getIp(), enodeB.getIp())
&& Objects.equals(enodeA.getListeningPort(), enodeB.getListeningPort());
}
public static Bytes parseNodeId(final String nodeId) {
@ -141,11 +154,12 @@ public class EnodeURL {
}
public URI toURI() {
final String uri =
String.format(
"enode://%s@%s:%d",
nodeId.toUnprefixedHexString(),
InetAddresses.toUriString(ip),
maybeHostname.orElse(InetAddresses.toUriString(getIp())),
getListeningPortOrZero());
final OptionalInt discPort = getDiscPortQueryParam();
if (discPort.isPresent()) {
@ -160,7 +174,7 @@ public class EnodeURL {
String.format(
"enode://%s@%s:%d",
nodeId.toUnprefixedHexString(),
InetAddresses.toUriString(ip),
maybeHostname.orElse(InetAddresses.toUriString(getIp())),
getListeningPortOrZero());
return URI.create(uri);
@ -181,7 +195,11 @@ public class EnodeURL {
}
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() {
@ -189,10 +207,32 @@ public class EnodeURL {
}
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() {
this.ip =
maybeHostname
.map(
hostname -> {
try {
return InetAddress.getByName(hostname);
} catch (UnknownHostException e) {
return ip;
}
})
.orElse(ip);
return ip;
}
@ -229,15 +269,15 @@ public class EnodeURL {
return false;
}
EnodeURL enodeURL = (EnodeURL) o;
return Objects.equals(nodeId, enodeURL.nodeId)
&& Objects.equals(ip, enodeURL.ip)
&& Objects.equals(listeningPort, enodeURL.listeningPort)
&& Objects.equals(discoveryPort, enodeURL.discoveryPort);
return Objects.equals(getNodeId(), enodeURL.getNodeId())
&& Objects.equals(getIp(), enodeURL.getIp())
&& Objects.equals(getListeningPort(), enodeURL.getListeningPort())
&& Objects.equals(getDiscoveryPort(), enodeURL.getDiscoveryPort());
}
@Override
public int hashCode() {
return Objects.hash(nodeId, ip, listeningPort, discoveryPort);
return Objects.hash(getNodeId(), getIp(), getListeningPort(), getDiscoveryPort());
}
@Override
@ -250,13 +290,14 @@ public class EnodeURL {
private Bytes nodeId;
private Optional<Integer> listeningPort;
private Optional<Integer> discoveryPort;
private Optional<String> maybeHostname = Optional.empty();
private InetAddress ip;
private Builder() {}
public EnodeURL build() {
validate();
return new EnodeURL(nodeId, ip, listeningPort, discoveryPort);
return new EnodeURL(nodeId, ip, maybeHostname, listeningPort, discoveryPort);
}
private void validate() {
@ -294,10 +335,27 @@ public class EnodeURL {
}
public Builder ipAddress(final String ip) {
return ipAddress(ip, EnodeDnsConfiguration.dnsDisabled());
}
public Builder ipAddress(final String ip, final EnodeDnsConfiguration enodeDnsConfiguration) {
if (InetAddresses.isUriInetAddress(ip)) {
this.ip = InetAddresses.forUriString(ip);
} else if (InetAddresses.isInetAddress(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 {
throw new IllegalArgumentException("Invalid ip address.");
}

@ -35,11 +35,12 @@ public class StaticNodesParser {
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 {
try {
return readEnodesFromPath(path);
return readEnodesFromPath(path, enodeDnsConfiguration);
} catch (FileNotFoundException | NoSuchFileException ex) {
LOG.info("StaticNodes file {} does not exist, no static connections will be created.", path);
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);
if (staticNodesContent.length == 0) {
return emptySet();
@ -63,13 +65,14 @@ public class StaticNodesParser {
final JsonArray enodeJsonArray = new JsonArray(new String(staticNodesContent, UTF_8));
return enodeJsonArray.stream()
.map(obj -> decodeString((String) obj))
.map(obj -> decodeString((String) obj, enodeDnsConfiguration))
.collect(Collectors.toSet());
}
private static EnodeURL decodeString(final String input) {
private static EnodeURL decodeString(
final String input, final EnodeDnsConfiguration enodeDnsConfiguration) {
try {
final EnodeURL enode = EnodeURL.fromString(input);
final EnodeURL enode = EnodeURL.fromString(input, enodeDnsConfiguration);
checkArgument(
enode.isListening(), "Static node must be configured with a valid listening port.");
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.");
}
@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
public void toURI_WithDiscoveryPortCreateExpectedURI() {
final String enodeURLString =
@ -395,6 +471,78 @@ public class EnodeURLTest {
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
public void builder_setInvalidPorts() {
final EnodeURL.Builder validBuilder =

@ -73,18 +73,80 @@ public class StaticNodesParserTest {
public void validFileLoadsWithExpectedEnodes() throws IOException, URISyntaxException {
final URL resource = StaticNodesParserTest.class.getResource("valid_static_nodes.json");
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)
.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
public void invalidFileThrowsAnException() {
final URL resource = StaticNodesParserTest.class.getResource("invalid_static_nodes.json");
final File invalidFile = new File(resource.getFile());
assertThatThrownBy(() -> StaticNodesParser.fromPath(invalidFile.toPath()))
assertThatThrownBy(
() ->
StaticNodesParser.fromPath(
invalidFile.toPath(), EnodeDnsConfiguration.DEFAULT_CONFIG))
.isInstanceOf(IllegalArgumentException.class);
}
@ -94,7 +156,10 @@ public class StaticNodesParserTest {
StaticNodesParserTest.class.getResource("invalid_static_nodes_no_listening_port.json");
final File invalidFile = new File(resource.getFile());
assertThatThrownBy(() -> StaticNodesParser.fromPath(invalidFile.toPath()))
assertThatThrownBy(
() ->
StaticNodesParser.fromPath(
invalidFile.toPath(), EnodeDnsConfiguration.DEFAULT_CONFIG))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Static node must be configured with a valid listening port");
}
@ -105,7 +170,9 @@ public class StaticNodesParserTest {
tempFile.deleteOnExit();
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);
}
@ -113,7 +180,8 @@ public class StaticNodesParserTest {
public void anEmptyCacheIsCreatedIfTheFileDoesNotExist() throws IOException {
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();
}
@ -122,7 +190,8 @@ public class StaticNodesParserTest {
final File tempFile = testFolder.newFile("file.txt");
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();
}
}

@ -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 =
PermissioningConfigurationBuilder.permissioningConfiguration(
configuration.isNodeAllowlistEnabled(),
configuration.getEnodeDnsConfiguration(),
configuration.getNodePermissioningConfigFilePath(),
configuration.isAccountAllowlistEnabled(),
configuration.getAccountPermissioningConfigFilePath());

@ -14,20 +14,23 @@
*/
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.Collection;
import java.util.List;
public class LocalPermissioningConfiguration {
private List<URI> nodeAllowlist;
private List<EnodeURL> nodeAllowlist;
private List<String> accountAllowlist;
private boolean nodeAllowlistEnabled;
private EnodeDnsConfiguration enodeDnsConfiguration = EnodeDnsConfiguration.dnsDisabled();
private String nodePermissioningConfigFilePath;
private boolean accountAllowlistEnabled;
private String accountPermissioningConfigFilePath;
public List<URI> getNodeAllowlist() {
public List<EnodeURL> getNodeAllowlist() {
return nodeAllowlist;
}
@ -38,13 +41,21 @@ public class LocalPermissioningConfiguration {
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) {
this.nodeAllowlist.addAll(nodeAllowlist);
this.nodeAllowlistEnabled = true;
}
}
public EnodeDnsConfiguration getEnodeDnsConfiguration() {
return enodeDnsConfiguration;
}
public boolean isNodeAllowlistEnabled() {
return nodeAllowlistEnabled;
}

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

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

@ -16,10 +16,14 @@ package org.hyperledger.besu.ethereum.permissioning;
import static java.nio.charset.StandardCharsets.UTF_8;
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.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.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
@ -52,7 +56,8 @@ public class LocalPermissioningConfigurationBuilderTest {
"/permissioning_config_unrecognized_key.toml";
private static final String PERMISSIONING_CONFIG_NODE_ALLOWLIST_ONLY_MULTILINE =
"/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 =
"6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0";
@ -71,7 +76,7 @@ public class LocalPermissioningConfigurationBuilderTest {
.containsExactly("0x0000000000000000000000000000000000000009");
assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue();
assertThat(permissioningConfiguration.getNodeAllowlist())
.containsExactly(URI.create(uri), URI.create(uri2));
.containsExactly(EnodeURL.fromString(uri), EnodeURL.fromString(uri2));
}
@Test
@ -89,7 +94,7 @@ public class LocalPermissioningConfigurationBuilderTest {
.containsExactly("0x0000000000000000000000000000000000000009");
assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue();
assertThat(permissioningConfiguration.getNodeAllowlist())
.containsExactly(URI.create(uri), URI.create(uri2));
.containsExactly(EnodeURL.fromString(uri), EnodeURL.fromString(uri2));
}
@Test
@ -101,11 +106,57 @@ public class LocalPermissioningConfigurationBuilderTest {
LocalPermissioningConfiguration 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.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
@ -115,7 +166,11 @@ public class LocalPermissioningConfigurationBuilderTest {
LocalPermissioningConfiguration 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.isAccountAllowlistEnabled()).isTrue();
@ -200,7 +255,7 @@ public class LocalPermissioningConfigurationBuilderTest {
LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toString(), true, toml.toString());
true, dnsDisabled(), toml.toString(), true, toml.toString());
assertThat(permissioningConfiguration.getNodePermissioningConfigFilePath())
.isEqualTo(toml.toString());
@ -224,7 +279,7 @@ public class LocalPermissioningConfigurationBuilderTest {
this.getClass().getResource(PERMISSIONING_CONFIG_NODE_ALLOWLIST_ONLY_MULTILINE);
final LocalPermissioningConfiguration permissioningConfiguration =
PermissioningConfigurationBuilder.permissioningConfiguration(
true, configFile.getPath(), false, configFile.getPath());
true, dnsDisabled(), configFile.getPath(), false, configFile.getPath());
assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue();
assertThat(permissioningConfiguration.getNodeAllowlist().size()).isEqualTo(5);
@ -233,18 +288,22 @@ public class LocalPermissioningConfigurationBuilderTest {
private LocalPermissioningConfiguration accountOnlyPermissioningConfig(final Path toml)
throws Exception {
return PermissioningConfigurationBuilder.permissioningConfiguration(
false, null, true, toml.toAbsolutePath().toString());
false, dnsDisabled(), null, true, toml.toAbsolutePath().toString());
}
private LocalPermissioningConfiguration nodeOnlyPermissioningConfig(final Path toml)
throws Exception {
return PermissioningConfigurationBuilder.permissioningConfiguration(
true, toml.toAbsolutePath().toString(), false, null);
true, dnsDisabled(), toml.toAbsolutePath().toString(), false, null);
}
private LocalPermissioningConfiguration permissioningConfig(final Path toml) throws Exception {
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 {

@ -16,17 +16,21 @@ package org.hyperledger.besu.ethereum.permissioning;
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 org.junit.Test;
public class LocalPermissioningConfigurationTest {
final URI[] nodes = {
URI.create("enode://001@123:4567"),
URI.create("enode://002@123:4567"),
URI.create("enode://003@123:4567")
final EnodeURL[] nodes = {
EnodeURL.fromString(
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@127.0.0.1:4567"),
EnodeURL.fromString(
"enode://7f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@127.0.0.1:4568"),
EnodeURL.fromString(
"enode://8f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@127.0.0.1:4569")
};
@Test

@ -27,7 +27,9 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
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.ImmutableEnodeDnsConfiguration;
import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController.NodesAllowlistResult;
import org.hyperledger.besu.ethereum.permissioning.node.NodeAllowlistUpdatedEvent;
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 java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@ -66,6 +67,9 @@ public class NodeLocalConfigPermissioningControllerTest {
private final EnodeURL selfEnode =
EnodeURL.fromString(
"enode://5f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:1111");
private final String enodeDns =
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@localhost:4567";
@Mock private MetricsSystem metricsSystem;
@Mock private Counter checkCounter;
@Mock private Counter checkPermittedCounter;
@ -340,7 +344,7 @@ public class NodeLocalConfigPermissioningControllerTest {
.thenReturn(permissionsFile.toAbsolutePath().toString());
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist())
.thenReturn(Arrays.asList(URI.create(expectedEnodeURL)));
.thenReturn(Arrays.asList(EnodeURL.fromString(expectedEnodeURL)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
@ -360,7 +364,7 @@ public class NodeLocalConfigPermissioningControllerTest {
when(permissioningConfig.getNodePermissioningConfigFilePath()).thenReturn("foo");
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist())
.thenReturn(Arrays.asList(URI.create(expectedEnodeURI)));
.thenReturn(Arrays.asList(EnodeURL.fromString(expectedEnodeURI)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
@ -459,7 +463,8 @@ public class NodeLocalConfigPermissioningControllerTest {
when(permissioningConfig.getNodePermissioningConfigFilePath())
.thenReturn(permissionsFile.toAbsolutePath().toString());
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist()).thenReturn(Arrays.asList(URI.create(enode1)));
when(permissioningConfig.getNodeAllowlist())
.thenReturn(Arrays.asList(EnodeURL.fromString(enode1)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
@ -482,7 +487,8 @@ public class NodeLocalConfigPermissioningControllerTest {
when(permissioningConfig.getNodePermissioningConfigFilePath())
.thenReturn(permissionsFile.toAbsolutePath().toString());
when(permissioningConfig.isNodeAllowlistEnabled()).thenReturn(true);
when(permissioningConfig.getNodeAllowlist()).thenReturn(Arrays.asList(URI.create(enode1)));
when(permissioningConfig.getNodeAllowlist())
.thenReturn(Arrays.asList(EnodeURL.fromString(enode1)));
controller =
new NodeLocalConfigPermissioningController(
permissioningConfig, bootnodesList, selfEnode.getNodeId(), metricsSystem);
@ -493,6 +499,52 @@ public class NodeLocalConfigPermissioningControllerTest {
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 {
final String nodePermissionsFileContent = "nodes-allowlist=[\"" + node + "\"]";
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