From f4dc48d94d276f1ed504b707b8f9ccf278a5cc63 Mon Sep 17 00:00:00 2001 From: Justin Florentine Date: Wed, 2 Oct 2024 11:46:34 -0400 Subject: [PATCH] Simplifying BesuCommand step 1 (#7682) Simplifying BesuCommand --------- Signed-off-by: Justin Florentine --- .../dsl/node/ProcessBesuNodeRunner.java | 2 +- .../org/hyperledger/besu/cli/BesuCommand.java | 305 ++++-------------- .../{stable => }/DataStorageOptions.java | 3 +- .../stable/ApiConfigurationOptions.java | 1 + .../stable/EngineRPCConfiguration.java | 36 +++ .../cli/options/stable/EngineRPCOptions.java | 81 +++++ .../cli/options/stable/GraphQlOptions.java | 1 + .../options/stable/JsonRpcHttpOptions.java | 34 +- .../options/stable/MetricsOptionGroup.java | 1 + .../options/stable/P2PDiscoveryOptions.java | 238 ++++++++++++++ .../options/stable/PermissionsOptions.java | 1 + .../options/unstable/MetricsCLIOptions.java | 1 + .../P2PTLSConfigOptions.java | 2 +- .../subcommands/storage/TrieLogHelper.java | 4 +- .../besu/components/BesuCommandModule.java | 16 +- .../besu/cli/CommandTestAbstract.java | 2 +- .../stable/DataStorageOptionsTest.java | 1 + .../besu/config/MergeConfigOptions.java | 2 + .../discovery/P2PDiscoveryConfiguration.java | 39 +++ 19 files changed, 501 insertions(+), 269 deletions(-) rename besu/src/main/java/org/hyperledger/besu/cli/options/{stable => }/DataStorageOptions.java (99%) create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/options/stable/EngineRPCConfiguration.java create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/options/stable/EngineRPCOptions.java create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PDiscoveryOptions.java rename besu/src/main/java/org/hyperledger/besu/cli/options/{stable => unstable}/P2PTLSConfigOptions.java (99%) create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/P2PDiscoveryConfiguration.java diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java index 62951a442d..e08fb29f67 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java @@ -17,8 +17,8 @@ package org.hyperledger.besu.tests.acceptance.dsl.node; import static com.google.common.base.Preconditions.checkState; import static java.nio.charset.StandardCharsets.UTF_8; +import org.hyperledger.besu.cli.options.DataStorageOptions; import org.hyperledger.besu.cli.options.TransactionPoolOptions; -import org.hyperledger.besu.cli.options.stable.DataStorageOptions; import org.hyperledger.besu.cli.options.unstable.NetworkingOptions; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index e93518cad4..fa59f48dd7 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -24,7 +24,6 @@ import static org.hyperledger.besu.cli.config.NetworkName.MAINNET; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; import static org.hyperledger.besu.cli.util.CommandLineUtils.isOptionSet; import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; -import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_ENGINE_JSON_RPC_PORT; import static org.hyperledger.besu.ethereum.api.jsonrpc.authentication.EngineAuthService.EPHEMERAL_JWT_FILE; import static org.hyperledger.besu.nat.kubernetes.KubernetesNatManager.DEFAULT_BESU_SERVICE_NAME_FILTER; @@ -38,22 +37,22 @@ import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.cli.config.ProfilesCompletionCandidates; import org.hyperledger.besu.cli.converter.MetricCategoryConverter; -import org.hyperledger.besu.cli.converter.PercentageConverter; -import org.hyperledger.besu.cli.converter.SubnetInfoConverter; import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty; import org.hyperledger.besu.cli.error.BesuExecutionExceptionHandler; import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler; +import org.hyperledger.besu.cli.options.DataStorageOptions; import org.hyperledger.besu.cli.options.MiningOptions; import org.hyperledger.besu.cli.options.TransactionPoolOptions; import org.hyperledger.besu.cli.options.stable.ApiConfigurationOptions; -import org.hyperledger.besu.cli.options.stable.DataStorageOptions; +import org.hyperledger.besu.cli.options.stable.EngineRPCConfiguration; +import org.hyperledger.besu.cli.options.stable.EngineRPCOptions; import org.hyperledger.besu.cli.options.stable.EthstatsOptions; import org.hyperledger.besu.cli.options.stable.GraphQlOptions; import org.hyperledger.besu.cli.options.stable.JsonRpcHttpOptions; import org.hyperledger.besu.cli.options.stable.LoggingLevelOption; import org.hyperledger.besu.cli.options.stable.MetricsOptionGroup; import org.hyperledger.besu.cli.options.stable.NodePrivateKeyFileOption; -import org.hyperledger.besu.cli.options.stable.P2PTLSConfigOptions; +import org.hyperledger.besu.cli.options.stable.P2PDiscoveryOptions; import org.hyperledger.besu.cli.options.stable.PermissionsOptions; import org.hyperledger.besu.cli.options.stable.PluginsConfigurationOptions; import org.hyperledger.besu.cli.options.stable.RpcWebsocketOptions; @@ -67,6 +66,7 @@ import org.hyperledger.besu.cli.options.unstable.MetricsCLIOptions; import org.hyperledger.besu.cli.options.unstable.NatOptions; import org.hyperledger.besu.cli.options.unstable.NativeLibraryOptions; import org.hyperledger.besu.cli.options.unstable.NetworkingOptions; +import org.hyperledger.besu.cli.options.unstable.P2PTLSConfigOptions; import org.hyperledger.besu.cli.options.unstable.PrivacyPluginOptions; import org.hyperledger.besu.cli.options.unstable.RPCOptions; import org.hyperledger.besu.cli.options.unstable.SynchronizerOptions; @@ -124,6 +124,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolCo import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.mainnet.FrontierTargetingGasLimitCalculator; import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration; +import org.hyperledger.besu.ethereum.p2p.discovery.P2PDiscoveryConfiguration; import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; import org.hyperledger.besu.ethereum.p2p.peers.StaticNodesParser; @@ -209,7 +210,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigInteger; -import java.net.InetAddress; import java.net.SocketException; import java.net.URI; import java.net.URL; @@ -247,7 +247,6 @@ import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; import io.vertx.core.json.DecodeException; import io.vertx.core.metrics.MetricsOptions; -import org.apache.commons.net.util.SubnetUtils.SubnetInfo; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.slf4j.Logger; @@ -416,7 +415,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable { // P2P Discovery Option Group @CommandLine.ArgGroup(validate = false, heading = "@|bold P2P Discovery Options|@%n") - P2PDiscoveryOptionGroup p2PDiscoveryOptionGroup = new P2PDiscoveryOptionGroup(); + P2PDiscoveryOptions p2PDiscoveryOptions = new P2PDiscoveryOptions(); + + P2PDiscoveryConfiguration p2PDiscoveryConfig; private final TransactionSelectionServiceImpl transactionSelectionServiceImpl; private final TransactionPoolValidatorServiceImpl transactionValidatorServiceImpl; @@ -424,163 +425,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private final BlockchainServiceImpl blockchainServiceImpl; private BesuComponent besuComponent; - static class P2PDiscoveryOptionGroup { - - // Public IP stored to prevent having to research it each time we need it. - private InetAddress autoDiscoveredDefaultIP = null; - - // Completely disables P2P within Besu. - @Option( - names = {"--p2p-enabled"}, - description = "Enable P2P functionality (default: ${DEFAULT-VALUE})", - arity = "1") - private final Boolean p2pEnabled = true; - - // Boolean option to indicate if peers should NOT be discovered, default to - // false indicates that - // the peers should be discovered by default. - // - // This negative option is required because of the nature of the option that is - // true when - // added on the command line. You can't do --option=false, so false is set as - // default - // and you have not to set the option at all if you want it false. - // This seems to be the only way it works with Picocli. - // Also many other software use the same negative option scheme for false - // defaults - // meaning that it's probably the right way to handle disabling options. - @Option( - names = {"--discovery-enabled"}, - description = "Enable P2P discovery (default: ${DEFAULT-VALUE})", - arity = "1") - private final Boolean peerDiscoveryEnabled = true; - - // A list of bootstrap nodes can be passed - // and a hardcoded list will be used otherwise by the Runner. - // NOTE: we have no control over default value here. - @Option( - names = {"--bootnodes"}, - paramLabel = "", - description = - "Comma separated enode URLs for P2P discovery bootstrap. " - + "Default is a predefined list.", - split = ",", - arity = "0..*") - private final List bootNodes = null; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @Option( - names = {"--p2p-host"}, - paramLabel = MANDATORY_HOST_FORMAT_HELP, - description = "IP address this node advertises to its peers (default: ${DEFAULT-VALUE})", - arity = "1") - private String p2pHost = autoDiscoverDefaultIP().getHostAddress(); - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @Option( - names = {"--p2p-interface"}, - paramLabel = MANDATORY_HOST_FORMAT_HELP, - description = - "The network interface address on which this node listens for P2P communication (default: ${DEFAULT-VALUE})", - arity = "1") - private String p2pInterface = NetworkUtility.INADDR_ANY; - - @Option( - names = {"--p2p-port"}, - paramLabel = MANDATORY_PORT_FORMAT_HELP, - description = "Port on which to listen for P2P communication (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer p2pPort = EnodeURLImpl.DEFAULT_LISTENING_PORT; - - @Option( - names = {"--max-peers", "--p2p-peer-upper-bound"}, - paramLabel = MANDATORY_INTEGER_FORMAT_HELP, - description = "Maximum P2P connections that can be established (default: ${DEFAULT-VALUE})") - private final Integer maxPeers = DEFAULT_MAX_PEERS; - - @Option( - names = {"--remote-connections-limit-enabled"}, - description = - "Whether to limit the number of P2P connections initiated remotely. (default: ${DEFAULT-VALUE})") - private final Boolean isLimitRemoteWireConnectionsEnabled = true; - - @Option( - names = {"--remote-connections-max-percentage"}, - paramLabel = MANDATORY_DOUBLE_FORMAT_HELP, - description = - "The maximum percentage of P2P connections that can be initiated remotely. Must be between 0 and 100 inclusive. (default: ${DEFAULT-VALUE})", - arity = "1", - converter = PercentageConverter.class) - private final Percentage maxRemoteConnectionsPercentage = - Fraction.fromFloat(DEFAULT_FRACTION_REMOTE_WIRE_CONNECTIONS_ALLOWED).toPercentage(); - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @CommandLine.Option( - names = {"--discovery-dns-url"}, - description = "Specifies the URL to use for DNS discovery") - private String discoveryDnsUrl = null; - - @Option( - names = {"--random-peer-priority-enabled"}, - description = - "Allow for incoming connections to be prioritized randomly. This will prevent (typically small, stable) networks from forming impenetrable peer cliques. (default: ${DEFAULT-VALUE})") - private final Boolean randomPeerPriority = Boolean.FALSE; - - @Option( - names = {"--banned-node-ids", "--banned-node-id"}, - paramLabel = MANDATORY_NODE_ID_FORMAT_HELP, - description = "A list of node IDs to ban from the P2P network.", - split = ",", - arity = "1..*") - void setBannedNodeIds(final List values) { - try { - bannedNodeIds = - values.stream() - .filter(value -> !value.isEmpty()) - .map(EnodeURLImpl::parseNodeId) - .collect(Collectors.toList()); - } catch (final IllegalArgumentException e) { - throw new ParameterException( - new CommandLine(this), - "Invalid ids supplied to '--banned-node-ids'. " + e.getMessage()); - } - } - - // Boolean option to set that in a PoA network the bootnodes should always be queried during - // peer table refresh. If this flag is disabled bootnodes are only sent FINDN requests on first - // startup, meaning that an offline bootnode or network outage at the client can prevent it - // discovering any peers without a restart. - @Option( - names = {"--poa-discovery-retry-bootnodes"}, - description = - "Always use of bootnodes for discovery in PoA networks. Disabling this reverts " - + " to the same behaviour as non-PoA networks, where neighbours are only discovered from bootnodes on first startup." - + "(default: ${DEFAULT-VALUE})", - arity = "1") - private final Boolean poaDiscoveryRetryBootnodes = true; - - private Collection bannedNodeIds = new ArrayList<>(); - - // Used to discover the default IP of the client. - // Loopback IP is used by default as this is how smokeTests require it to be - // and it's probably a good security behaviour to default only on the localhost. - private InetAddress autoDiscoverDefaultIP() { - autoDiscoveredDefaultIP = - Optional.ofNullable(autoDiscoveredDefaultIP).orElseGet(InetAddress::getLoopbackAddress); - - return autoDiscoveredDefaultIP; - } - - @Option( - names = {"--net-restrict"}, - arity = "1..*", - split = ",", - converter = SubnetInfoConverter.class, - description = - "Comma-separated list of allowed IP subnets (e.g., '192.168.1.0/24,10.0.0.0/8').") - private List allowedSubnets; - } - @Option( names = {"--sync-mode"}, paramLabel = MANDATORY_MODE_FORMAT_HELP, @@ -647,42 +491,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable { // Engine JSON-PRC Options @CommandLine.ArgGroup(validate = false, heading = "@|bold Engine JSON-RPC Options|@%n") - EngineRPCOptionGroup engineRPCOptionGroup = new EngineRPCOptionGroup(); - - static class EngineRPCOptionGroup { - @Option( - names = {"--engine-rpc-enabled"}, - description = - "enable the engine api, even in the absence of merge-specific configurations.") - private final Boolean overrideEngineRpcEnabled = false; - - @Option( - names = {"--engine-rpc-port", "--engine-rpc-http-port"}, - paramLabel = MANDATORY_PORT_FORMAT_HELP, - description = "Port to provide consensus client APIS on (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer engineRpcPort = DEFAULT_ENGINE_JSON_RPC_PORT; + EngineRPCOptions engineRPCOptions = new EngineRPCOptions(); - @Option( - names = {"--engine-jwt-secret"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = "Path to file containing shared secret key for JWT signature verification") - private final Path engineJwtKeyFile = null; - - @Option( - names = {"--engine-jwt-disabled"}, - description = "Disable authentication for Engine APIs (default: ${DEFAULT-VALUE})") - private final Boolean isEngineAuthDisabled = false; - - @Option( - names = {"--engine-host-allowlist"}, - paramLabel = "[,...]... or * or all", - description = - "Comma separated list of hostnames to allow for ENGINE API access (applies to both HTTP and websockets), or * to accept any host (default: ${DEFAULT-VALUE})", - defaultValue = "localhost,127.0.0.1") - private final JsonRPCAllowlistHostsProperty engineHostsAllowlist = - new JsonRPCAllowlistHostsProperty(); - } + EngineRPCConfiguration engineRPCConfig = engineRPCOptions.toDomainObject(); // JSON-RPC HTTP Options @CommandLine.ArgGroup(validate = false, heading = "@|bold JSON-RPC HTTP Options|@%n") @@ -1409,13 +1220,13 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private Runner buildRunner() { return synchronize( besuController, - p2PDiscoveryOptionGroup.p2pEnabled, + p2PDiscoveryConfig.p2pEnabled(), p2pTLSConfiguration, - p2PDiscoveryOptionGroup.peerDiscoveryEnabled, + p2PDiscoveryConfig.peerDiscoveryEnabled(), ethNetworkConfig, - p2PDiscoveryOptionGroup.p2pHost, - p2PDiscoveryOptionGroup.p2pInterface, - p2PDiscoveryOptionGroup.p2pPort, + p2PDiscoveryConfig.p2pHost(), + p2PDiscoveryConfig.p2pInterface(), + p2PDiscoveryConfig.p2pPort(), graphQLConfiguration, jsonRpcConfiguration, engineJsonRpcConfiguration, @@ -1614,7 +1425,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private void validateOptions() { validateRequiredOptions(); issueOptionWarnings(); - validateP2PInterface(p2PDiscoveryOptionGroup.p2pInterface); + validateP2PInterface(p2PDiscoveryOptions.p2pInterface); validateMiningParams(); validateNatParams(); validateNetStatsParams(); @@ -1750,13 +1561,12 @@ public class BesuCommand implements DefaultCommandValues, Runnable { } private void ensureValidPeerBoundParams() { - maxPeers = p2PDiscoveryOptionGroup.maxPeers; + maxPeers = p2PDiscoveryOptions.maxPeers; final Boolean isLimitRemoteWireConnectionsEnabled = - p2PDiscoveryOptionGroup.isLimitRemoteWireConnectionsEnabled; + p2PDiscoveryOptions.isLimitRemoteWireConnectionsEnabled; if (isLimitRemoteWireConnectionsEnabled) { final float fraction = - Fraction.fromPercentage(p2PDiscoveryOptionGroup.maxRemoteConnectionsPercentage) - .getValue(); + Fraction.fromPercentage(p2PDiscoveryOptions.maxRemoteConnectionsPercentage).getValue(); checkState( fraction >= 0.0 && fraction <= 1.0, "Fraction of remote connections allowed must be between 0.0 and 1.0 (inclusive)."); @@ -1820,7 +1630,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { logger, commandLine, "--p2p-enabled", - !p2PDiscoveryOptionGroup.p2pEnabled, + !p2PDiscoveryOptions.p2pEnabled, asList( "--bootnodes", "--discovery-enabled", @@ -1861,6 +1671,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { } private void configure() throws Exception { + p2PDiscoveryConfig = p2PDiscoveryOptions.toDomainObject(); + engineRPCConfig = engineRPCOptions.toDomainObject(); checkPortClash(); checkIfRequiredPortsAreAvailable(); syncMode = getDefaultSyncModeIfNotSet(); @@ -1870,25 +1682,18 @@ public class BesuCommand implements DefaultCommandValues, Runnable { jsonRpcConfiguration = jsonRpcHttpOptions.jsonRpcConfiguration( - hostsAllowlist, - p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(), - unstableRPCOptions.getHttpTimeoutSec()); + hostsAllowlist, p2PDiscoveryOptions.p2pHost, unstableRPCOptions.getHttpTimeoutSec()); if (isEngineApiEnabled()) { - engineJsonRpcConfiguration = - createEngineJsonRpcConfiguration( - engineRPCOptionGroup.engineRpcPort, engineRPCOptionGroup.engineHostsAllowlist); + engineJsonRpcConfiguration = createEngineJsonRpcConfiguration(); } p2pTLSConfiguration = p2pTLSConfigOptions.p2pTLSConfiguration(commandLine); graphQLConfiguration = graphQlOptions.graphQLConfiguration( - hostsAllowlist, - p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(), - unstableRPCOptions.getHttpTimeoutSec()); + hostsAllowlist, p2PDiscoveryOptions.p2pHost, unstableRPCOptions.getHttpTimeoutSec()); + webSocketConfiguration = rpcWebsocketOptions.webSocketConfiguration( - hostsAllowlist, - p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(), - unstableRPCOptions.getWsTimeoutSec()); + hostsAllowlist, p2PDiscoveryConfig.p2pHost(), unstableRPCOptions.getWsTimeoutSec()); jsonRpcIpcConfiguration = jsonRpcIpcConfiguration( unstableIpcOptions.isEnabled(), @@ -2008,32 +1813,31 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .requiredBlocks(requiredBlocks) .reorgLoggingThreshold(reorgLoggingThreshold) .evmConfiguration(unstableEvmOptions.toDomainObject()) - .maxPeers(p2PDiscoveryOptionGroup.maxPeers) + .maxPeers(p2PDiscoveryOptions.maxPeers) .maxRemotelyInitiatedPeers(maxRemoteInitiatedPeers) - .randomPeerPriority(p2PDiscoveryOptionGroup.randomPeerPriority) + .randomPeerPriority(p2PDiscoveryOptions.randomPeerPriority) .chainPruningConfiguration(unstableChainPruningOptions.toDomainObject()) .cacheLastBlocks(numberOfblocksToCache) .genesisStateHashCacheEnabled(genesisStateHashCacheEnabled) .besuComponent(besuComponent); } - private JsonRpcConfiguration createEngineJsonRpcConfiguration( - final Integer engineListenPort, final List allowCallsFrom) { + private JsonRpcConfiguration createEngineJsonRpcConfiguration() { jsonRpcHttpOptions.checkDependencies(logger, commandLine); final JsonRpcConfiguration engineConfig = jsonRpcHttpOptions.jsonRpcConfiguration( - allowCallsFrom, - p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(), + engineRPCConfig.engineHostsAllowlist(), + p2PDiscoveryConfig.p2pHost(), unstableRPCOptions.getWsTimeoutSec()); - engineConfig.setPort(engineListenPort); + engineConfig.setPort(engineRPCConfig.engineRpcPort()); engineConfig.setRpcApis(Arrays.asList("ENGINE", "ETH")); engineConfig.setEnabled(isEngineApiEnabled()); - if (!engineRPCOptionGroup.isEngineAuthDisabled) { + if (!engineRPCConfig.isEngineAuthDisabled()) { engineConfig.setAuthenticationEnabled(true); engineConfig.setAuthenticationAlgorithm(JwtAlgorithm.HS256); - if (Objects.nonNull(engineRPCOptionGroup.engineJwtKeyFile) - && java.nio.file.Files.exists(engineRPCOptionGroup.engineJwtKeyFile)) { - engineConfig.setAuthenticationPublicKeyFile(engineRPCOptionGroup.engineJwtKeyFile.toFile()); + if (Objects.nonNull(engineRPCConfig.engineJwtKeyFile()) + && java.nio.file.Files.exists(engineRPCConfig.engineJwtKeyFile())) { + engineConfig.setAuthenticationPublicKeyFile(engineRPCConfig.engineJwtKeyFile().toFile()); } else { logger.warn( "Engine API authentication enabled without key file. Expect ephemeral jwt.hex file in datadir"); @@ -2090,7 +1894,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .enabled(metricsOptionGroup.getMetricsEnabled()) .host( Strings.isNullOrEmpty(metricsOptionGroup.getMetricsHost()) - ? p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress() + ? p2PDiscoveryOptions.p2pHost : metricsOptionGroup.getMetricsHost()) .port(metricsOptionGroup.getMetricsPort()) .protocol(metricsOptionGroup.getMetricsProtocol()) @@ -2098,7 +1902,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .pushEnabled(metricsOptionGroup.getMetricsPushEnabled()) .pushHost( Strings.isNullOrEmpty(metricsOptionGroup.getMetricsPushHost()) - ? p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress() + ? p2PDiscoveryOptions.autoDiscoverDefaultIP().getHostAddress() : metricsOptionGroup.getMetricsPushHost()) .pushPort(metricsOptionGroup.getMetricsPushPort()) .pushInterval(metricsOptionGroup.getMetricsPushInterval()) @@ -2441,7 +2245,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .apiConfiguration(apiConfiguration) .pidPath(pidPath) .dataDir(dataDir()) - .bannedNodeIds(p2PDiscoveryOptionGroup.bannedNodeIds) + .bannedNodeIds(p2PDiscoveryConfig.bannedNodeIds()) .metricsSystem((ObservableMetricsSystem) besuComponent.getMetricsSystem()) .permissioningService(permissioningService) .metricsConfiguration(metricsConfiguration) @@ -2453,8 +2257,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .storageProvider(keyValueStorageProvider(keyValueStorageName)) .rpcEndpointService(rpcEndpointServiceImpl) .enodeDnsConfiguration(getEnodeDnsConfiguration()) - .allowedSubnets(p2PDiscoveryOptionGroup.allowedSubnets) - .poaDiscoveryRetryBootnodes(p2PDiscoveryOptionGroup.poaDiscoveryRetryBootnodes) + .allowedSubnets(p2PDiscoveryConfig.allowedSubnets()) + .poaDiscoveryRetryBootnodes(p2PDiscoveryConfig.poaDiscoveryRetryBootnodes()) .build(); addShutdownHook(runner); @@ -2529,7 +2333,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { } } - if (p2PDiscoveryOptionGroup.bootNodes == null) { + if (p2PDiscoveryOptions.bootNodes == null) { builder.setBootNodes(new ArrayList<>()); } builder.setDnsDiscoveryUrl(null); @@ -2541,8 +2345,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { builder.setNetworkId(networkId); } - if (p2PDiscoveryOptionGroup.discoveryDnsUrl != null) { - builder.setDnsDiscoveryUrl(p2PDiscoveryOptionGroup.discoveryDnsUrl); + if (p2PDiscoveryOptions.discoveryDnsUrl != null) { + builder.setDnsDiscoveryUrl(p2PDiscoveryOptions.discoveryDnsUrl); } else { final Optional discoveryDnsUrlFromGenesis = genesisConfigOptionsSupplier.get().getDiscoveryOptions().getDiscoveryDnsUrl(); @@ -2550,9 +2354,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable { } List listBootNodes = null; - if (p2PDiscoveryOptionGroup.bootNodes != null) { + if (p2PDiscoveryOptions.bootNodes != null) { try { - listBootNodes = buildEnodes(p2PDiscoveryOptionGroup.bootNodes, getEnodeDnsConfiguration()); + listBootNodes = buildEnodes(p2PDiscoveryOptions.bootNodes, getEnodeDnsConfiguration()); } catch (final IllegalArgumentException e) { throw new ParameterException(commandLine, e.getMessage()); } @@ -2564,7 +2368,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { } } if (listBootNodes != null) { - if (!p2PDiscoveryOptionGroup.peerDiscoveryEnabled) { + if (!p2PDiscoveryOptions.peerDiscoveryEnabled) { logger.warn("Discovery disabled: bootnodes will be ignored."); } DiscoveryConfiguration.assertValidBootnodes(listBootNodes); @@ -2699,12 +2503,12 @@ public class BesuCommand implements DefaultCommandValues, Runnable { .filter(port -> port > 0) .forEach( port -> { - if (port.equals(p2PDiscoveryOptionGroup.p2pPort) + if (port.equals(p2PDiscoveryConfig.p2pPort()) && (NetworkUtility.isPortUnavailableForTcp(port) || NetworkUtility.isPortUnavailableForUdp(port))) { unavailablePorts.add(port); } - if (!port.equals(p2PDiscoveryOptionGroup.p2pPort) + if (!port.equals(p2PDiscoveryConfig.p2pPort()) && NetworkUtility.isPortUnavailableForTcp(port)) { unavailablePorts.add(port); } @@ -2724,15 +2528,14 @@ public class BesuCommand implements DefaultCommandValues, Runnable { */ private List getEffectivePorts() { final List effectivePorts = new ArrayList<>(); - addPortIfEnabled( - effectivePorts, p2PDiscoveryOptionGroup.p2pPort, p2PDiscoveryOptionGroup.p2pEnabled); + addPortIfEnabled(effectivePorts, p2PDiscoveryOptions.p2pPort, p2PDiscoveryOptions.p2pEnabled); addPortIfEnabled( effectivePorts, graphQlOptions.getGraphQLHttpPort(), graphQlOptions.isGraphQLHttpEnabled()); addPortIfEnabled( effectivePorts, jsonRpcHttpOptions.getRpcHttpPort(), jsonRpcHttpOptions.isRpcHttpEnabled()); addPortIfEnabled( effectivePorts, rpcWebsocketOptions.getRpcWsPort(), rpcWebsocketOptions.isRpcWsEnabled()); - addPortIfEnabled(effectivePorts, engineRPCOptionGroup.engineRpcPort, isEngineApiEnabled()); + addPortIfEnabled(effectivePorts, engineRPCConfig.engineRpcPort(), isEngineApiEnabled()); addPortIfEnabled( effectivePorts, metricsOptionGroup.getMetricsPort(), @@ -2859,7 +2662,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { } private boolean isEngineApiEnabled() { - return engineRPCOptionGroup.overrideEngineRpcEnabled || isMergeEnabled(); + return engineRPCConfig.overrideEngineRpcEnabled() || isMergeEnabled(); } private SyncMode getDefaultSyncModeIfNotSet() { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/DataStorageOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/DataStorageOptions.java similarity index 99% rename from besu/src/main/java/org/hyperledger/besu/cli/options/stable/DataStorageOptions.java rename to besu/src/main/java/org/hyperledger/besu/cli/options/DataStorageOptions.java index 3d53a595ec..b442f6ee03 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/DataStorageOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/DataStorageOptions.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.cli.options.stable; +package org.hyperledger.besu.cli.options; import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_LIMIT_TRIE_LOGS_ENABLED; import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD; @@ -22,7 +22,6 @@ import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration. import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.Unstable.DEFAULT_BONSAI_CODE_USING_CODE_HASH_ENABLED; import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.Unstable.DEFAULT_BONSAI_FULL_FLAT_DB_ENABLED; -import org.hyperledger.besu.cli.options.CLIOptions; import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/ApiConfigurationOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/ApiConfigurationOptions.java index fbed68de0c..d6bc17026c 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/ApiConfigurationOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/ApiConfigurationOptions.java @@ -28,6 +28,7 @@ import picocli.CommandLine; * Handles configuration options for the API in Besu, including gas price settings, RPC log range, * and trace filter range. */ +// TODO: implement CLIOption public class ApiConfigurationOptions { /** Default constructor. */ public ApiConfigurationOptions() {} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/EngineRPCConfiguration.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/EngineRPCConfiguration.java new file mode 100644 index 0000000000..c0eb9d1be0 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/EngineRPCConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.cli.options.stable; + +import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty; + +import java.nio.file.Path; + +/** + * Command line options for configuring Engine RPC on the node. + * + * @param overrideEngineRpcEnabled enable the engine api, even in the absence of merge-specific + * configurations. + * @param engineRpcPort Port to provide consensus client APIS on + * @param engineJwtKeyFile Path to file containing shared secret key for JWT signature verification + * @param isEngineAuthDisabled Disable authentication for Engine APIs + * @param engineHostsAllowlist List of hosts to allowlist for Engine APIs + */ +public record EngineRPCConfiguration( + Boolean overrideEngineRpcEnabled, + Integer engineRpcPort, + Path engineJwtKeyFile, + Boolean isEngineAuthDisabled, + JsonRPCAllowlistHostsProperty engineHostsAllowlist) {} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/EngineRPCOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/EngineRPCOptions.java new file mode 100644 index 0000000000..1aa5b3d326 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/EngineRPCOptions.java @@ -0,0 +1,81 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.cli.options.stable; + +import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_ENGINE_JSON_RPC_PORT; + +import org.hyperledger.besu.cli.DefaultCommandValues; +import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty; +import org.hyperledger.besu.cli.options.CLIOptions; +import org.hyperledger.besu.cli.util.CommandLineUtils; + +import java.nio.file.Path; +import java.util.List; + +import picocli.CommandLine; + +/** Command line options for configuring Engine RPC on the node. */ +public class EngineRPCOptions implements CLIOptions { + + /** Default constructor */ + public EngineRPCOptions() {} + + @CommandLine.Option( + names = {"--engine-rpc-enabled"}, + description = "enable the engine api, even in the absence of merge-specific configurations.") + private final Boolean overrideEngineRpcEnabled = false; + + @CommandLine.Option( + names = {"--engine-rpc-port", "--engine-rpc-http-port"}, + paramLabel = DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP, + description = "Port to provide consensus client APIS on (default: ${DEFAULT-VALUE})", + arity = "1") + private final Integer engineRpcPort = DEFAULT_ENGINE_JSON_RPC_PORT; + + @CommandLine.Option( + names = {"--engine-jwt-secret"}, + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = "Path to file containing shared secret key for JWT signature verification") + private final Path engineJwtKeyFile = null; + + @CommandLine.Option( + names = {"--engine-jwt-disabled"}, + description = "Disable authentication for Engine APIs (default: ${DEFAULT-VALUE})") + private final Boolean isEngineAuthDisabled = false; + + @CommandLine.Option( + names = {"--engine-host-allowlist"}, + paramLabel = "[,...]... or * or all", + description = + "Comma separated list of hostnames to allow for ENGINE API access (applies to both HTTP and websockets), or * to accept any host (default: ${DEFAULT-VALUE})", + defaultValue = "localhost,127.0.0.1") + private final JsonRPCAllowlistHostsProperty engineHostsAllowlist = + new JsonRPCAllowlistHostsProperty(); + + @Override + public EngineRPCConfiguration toDomainObject() { + return new EngineRPCConfiguration( + overrideEngineRpcEnabled, + engineRpcPort, + engineJwtKeyFile, + isEngineAuthDisabled, + engineHostsAllowlist); + } + + @Override + public List getCLIOptions() { + return CommandLineUtils.getCLIOptions(this, new EngineRPCOptions()); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/GraphQlOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/GraphQlOptions.java index 77cc170305..6aac24a6fb 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/GraphQlOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/GraphQlOptions.java @@ -29,6 +29,7 @@ import org.slf4j.Logger; import picocli.CommandLine; /** Handles configuration options for the GraphQL HTTP service in Besu. */ +// TODO: implement CLIOptions public class GraphQlOptions { @CommandLine.Option( names = {"--graphql-http-enabled"}, diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/JsonRpcHttpOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/JsonRpcHttpOptions.java index 026b83b553..1c7e0f543e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/JsonRpcHttpOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/JsonRpcHttpOptions.java @@ -52,6 +52,7 @@ import picocli.CommandLine; * Handles configuration options for the JSON-RPC HTTP service, including validation and creation of * a JSON-RPC configuration. */ +// TODO: implement CLIOption public class JsonRpcHttpOptions { @CommandLine.Option( names = {"--rpc-http-enabled"}, @@ -265,37 +266,50 @@ public class JsonRpcHttpOptions { /** * Creates a JsonRpcConfiguration based on the provided options. * - * @param hostsAllowlist List of hosts allowed - * @param defaultHostAddress Default host address - * @param timoutSec timeout in seconds - * @return A JsonRpcConfiguration instance + * @return configuration populated from options or defaults */ - public JsonRpcConfiguration jsonRpcConfiguration( - final List hostsAllowlist, final String defaultHostAddress, final Long timoutSec) { + public JsonRpcConfiguration jsonRpcConfiguration() { final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); jsonRpcConfiguration.setEnabled(isRpcHttpEnabled); - jsonRpcConfiguration.setHost( - Strings.isNullOrEmpty(rpcHttpHost) ? defaultHostAddress : rpcHttpHost); jsonRpcConfiguration.setPort(rpcHttpPort); jsonRpcConfiguration.setMaxActiveConnections(rpcHttpMaxConnections); jsonRpcConfiguration.setCorsAllowedDomains(rpcHttpCorsAllowedOrigins); jsonRpcConfiguration.setRpcApis(rpcHttpApis.stream().distinct().collect(Collectors.toList())); jsonRpcConfiguration.setNoAuthRpcApis( rpcHttpApiMethodsNoAuth.stream().distinct().collect(Collectors.toList())); - jsonRpcConfiguration.setHostsAllowlist(hostsAllowlist); jsonRpcConfiguration.setAuthenticationEnabled(isRpcHttpAuthenticationEnabled); jsonRpcConfiguration.setAuthenticationCredentialsFile(rpcHttpAuthenticationCredentialsFile); jsonRpcConfiguration.setAuthenticationPublicKeyFile(rpcHttpAuthenticationPublicKeyFile); jsonRpcConfiguration.setAuthenticationAlgorithm(rpcHttpAuthenticationAlgorithm); jsonRpcConfiguration.setTlsConfiguration(rpcHttpTlsConfiguration()); - jsonRpcConfiguration.setHttpTimeoutSec(timoutSec); jsonRpcConfiguration.setMaxBatchSize(rpcHttpMaxBatchSize); jsonRpcConfiguration.setMaxRequestContentLength(rpcHttpMaxRequestContentLength); jsonRpcConfiguration.setPrettyJsonEnabled(prettyJsonEnabled); return jsonRpcConfiguration; } + /** + * Creates a JsonRpcConfiguration based on the provided options. + * + * @param hostsAllowlist List of hosts allowed + * @param defaultHostAddress Default host address + * @param timoutSec timeout in seconds + * @return A JsonRpcConfiguration instance + */ + public JsonRpcConfiguration jsonRpcConfiguration( + final List hostsAllowlist, final String defaultHostAddress, final Long timoutSec) { + + final JsonRpcConfiguration jsonRpcConfiguration = this.jsonRpcConfiguration(); + + jsonRpcConfiguration.setHost( + Strings.isNullOrEmpty(rpcHttpHost) ? defaultHostAddress : rpcHttpHost); + jsonRpcConfiguration.setHostsAllowlist(hostsAllowlist); + ; + jsonRpcConfiguration.setHttpTimeoutSec(timoutSec); + return jsonRpcConfiguration; + } + /** * Checks dependencies between options. * diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptionGroup.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptionGroup.java index add2bf1655..1c17e1cf9d 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptionGroup.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptionGroup.java @@ -30,6 +30,7 @@ import java.util.Set; import picocli.CommandLine; /** Command line options for configuring metrics. */ +// TODO: implement CLIOption and rename to drop the Group public class MetricsOptionGroup { @CommandLine.Option( names = {"--metrics-enabled"}, diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PDiscoveryOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PDiscoveryOptions.java new file mode 100644 index 0000000000..21c24b3e27 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PDiscoveryOptions.java @@ -0,0 +1,238 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.cli.options.stable; + +import org.hyperledger.besu.cli.DefaultCommandValues; +import org.hyperledger.besu.cli.converter.PercentageConverter; +import org.hyperledger.besu.cli.converter.SubnetInfoConverter; +import org.hyperledger.besu.cli.options.CLIOptions; +import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.ethereum.p2p.discovery.P2PDiscoveryConfiguration; +import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; +import org.hyperledger.besu.util.NetworkUtility; +import org.hyperledger.besu.util.number.Fraction; +import org.hyperledger.besu.util.number.Percentage; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.commons.net.util.SubnetUtils; +import org.apache.tuweni.bytes.Bytes; +import picocli.CommandLine; + +/** Command line options for configuring P2P discovery on the node. */ +public class P2PDiscoveryOptions implements CLIOptions { + + /** Default constructor */ + public P2PDiscoveryOptions() {} + + // Public IP stored to prevent having to research it each time we need it. + private InetAddress autoDiscoveredDefaultIP = null; + + /** Completely disables P2P within Besu. */ + @CommandLine.Option( + names = {"--p2p-enabled"}, + description = "Enable P2P functionality (default: ${DEFAULT-VALUE})", + arity = "1") + public final Boolean p2pEnabled = true; + + /** + * Boolean option to indicate if peers should NOT be discovered, default to false indicates that + * the peers should be discovered by default. + */ + // + // This negative option is required because of the nature of the option that is + // true when + // added on the command line. You can't do --option=false, so false is set as + // default + // and you have not to set the option at all if you want it false. + // This seems to be the only way it works with Picocli. + // Also many other software use the same negative option scheme for false + // defaults + // meaning that it's probably the right way to handle disabling options. + @CommandLine.Option( + names = {"--discovery-enabled"}, + description = "Enable P2P discovery (default: ${DEFAULT-VALUE})", + arity = "1") + public final Boolean peerDiscoveryEnabled = true; + + /** + * A list of bootstrap nodes can be passed and a hardcoded list will be used otherwise by the + * Runner. + */ + // NOTE: we have no control over default value here. + @CommandLine.Option( + names = {"--bootnodes"}, + paramLabel = "", + description = + "Comma separated enode URLs for P2P discovery bootstrap. " + + "Default is a predefined list.", + split = ",", + arity = "0..*") + public final List bootNodes = null; + + /** The IP the node advertises to peers for P2P communication. */ + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--p2p-host"}, + paramLabel = DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP, + description = "IP address this node advertises to its peers (default: ${DEFAULT-VALUE})", + arity = "1") + public String p2pHost = autoDiscoverDefaultIP().getHostAddress(); + + /** The network interface address on which this node listens for P2P communication. */ + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--p2p-interface"}, + paramLabel = DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP, + description = + "The network interface address on which this node listens for P2P communication (default: ${DEFAULT-VALUE})", + arity = "1") + public String p2pInterface = NetworkUtility.INADDR_ANY; + + /** The port on which this node listens for P2P communication. */ + @CommandLine.Option( + names = {"--p2p-port"}, + paramLabel = DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP, + description = "Port on which to listen for P2P communication (default: ${DEFAULT-VALUE})", + arity = "1") + public final Integer p2pPort = EnodeURLImpl.DEFAULT_LISTENING_PORT; + + /** The maximum number of peers this node can connect to. */ + @CommandLine.Option( + names = {"--max-peers", "--p2p-peer-upper-bound"}, + paramLabel = DefaultCommandValues.MANDATORY_INTEGER_FORMAT_HELP, + description = "Maximum P2P connections that can be established (default: ${DEFAULT-VALUE})") + public final Integer maxPeers = DefaultCommandValues.DEFAULT_MAX_PEERS; + + /** Boolean option to limit the number of P2P connections initiated remotely. */ + @CommandLine.Option( + names = {"--remote-connections-limit-enabled"}, + description = + "Whether to limit the number of P2P connections initiated remotely. (default: ${DEFAULT-VALUE})") + public final Boolean isLimitRemoteWireConnectionsEnabled = true; + + /** The maximum percentage of P2P connections that can be initiated remotely. */ + @CommandLine.Option( + names = {"--remote-connections-max-percentage"}, + paramLabel = DefaultCommandValues.MANDATORY_DOUBLE_FORMAT_HELP, + description = + "The maximum percentage of P2P connections that can be initiated remotely. Must be between 0 and 100 inclusive. (default: ${DEFAULT-VALUE})", + arity = "1", + converter = PercentageConverter.class) + public final Percentage maxRemoteConnectionsPercentage = + Fraction.fromFloat(DefaultCommandValues.DEFAULT_FRACTION_REMOTE_WIRE_CONNECTIONS_ALLOWED) + .toPercentage(); + + /** The URL to use for DNS discovery. */ + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--discovery-dns-url"}, + description = "Specifies the URL to use for DNS discovery") + public String discoveryDnsUrl = null; + + /** Boolean option to allow for incoming connections to be prioritized randomly. */ + @CommandLine.Option( + names = {"--random-peer-priority-enabled"}, + description = + "Allow for incoming connections to be prioritized randomly. This will prevent (typically small, stable) networks from forming impenetrable peer cliques. (default: ${DEFAULT-VALUE})") + public final Boolean randomPeerPriority = Boolean.FALSE; + + /** A list of node IDs to ban from the P2P network. */ + @CommandLine.Option( + names = {"--banned-node-ids", "--banned-node-id"}, + paramLabel = DefaultCommandValues.MANDATORY_NODE_ID_FORMAT_HELP, + description = "A list of node IDs to ban from the P2P network.", + split = ",", + arity = "1..*") + void setBannedNodeIds(final List values) { + try { + bannedNodeIds = + values.stream() + .filter(value -> !value.isEmpty()) + .map(EnodeURLImpl::parseNodeId) + .collect(Collectors.toList()); + } catch (final IllegalArgumentException e) { + throw new CommandLine.ParameterException( + new CommandLine(this), "Invalid ids supplied to '--banned-node-ids'. " + e.getMessage()); + } + } + + // Boolean option to set that in a PoA network the bootnodes should always be queried during + // peer table refresh. If this flag is disabled bootnodes are only sent FINDN requests on first + // startup, meaning that an offline bootnode or network outage at the client can prevent it + // discovering any peers without a restart. + @CommandLine.Option( + names = {"--poa-discovery-retry-bootnodes"}, + description = + "Always use of bootnodes for discovery in PoA networks. Disabling this reverts " + + " to the same behaviour as non-PoA networks, where neighbours are only discovered from bootnodes on first startup." + + "(default: ${DEFAULT-VALUE})", + arity = "1") + private final Boolean poaDiscoveryRetryBootnodes = true; + + private Collection bannedNodeIds = new ArrayList<>(); + + /** + * Auto-discovers the default IP of the client. + * + * @return machine loopback address + */ + // Loopback IP is used by default as this is how smokeTests require it to be + // and it's probably a good security behaviour to default only on the localhost. + public InetAddress autoDiscoverDefaultIP() { + autoDiscoveredDefaultIP = + Optional.ofNullable(autoDiscoveredDefaultIP).orElseGet(InetAddress::getLoopbackAddress); + + return autoDiscoveredDefaultIP; + } + + @CommandLine.Option( + names = {"--net-restrict"}, + arity = "1..*", + split = ",", + converter = SubnetInfoConverter.class, + description = + "Comma-separated list of allowed IP subnets (e.g., '192.168.1.0/24,10.0.0.0/8').") + private List allowedSubnets; + + @Override + public P2PDiscoveryConfiguration toDomainObject() { + return new P2PDiscoveryConfiguration( + p2pEnabled, + peerDiscoveryEnabled, + p2pHost, + p2pInterface, + p2pPort, + maxPeers, + isLimitRemoteWireConnectionsEnabled, + maxRemoteConnectionsPercentage, + randomPeerPriority, + bannedNodeIds, + allowedSubnets, + poaDiscoveryRetryBootnodes, + bootNodes, + discoveryDnsUrl); + } + + @Override + public List getCLIOptions() { + return CommandLineUtils.getCLIOptions(this, new P2PDiscoveryOptions()); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PermissionsOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PermissionsOptions.java index f1eafaab16..4dc693d36e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PermissionsOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PermissionsOptions.java @@ -30,6 +30,7 @@ import org.slf4j.Logger; import picocli.CommandLine; /** Handles configuration options for permissions in Besu. */ +// TODO: implement CLIOption public class PermissionsOptions { @CommandLine.Option( names = {"--permissions-nodes-config-file-enabled"}, diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/MetricsCLIOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/MetricsCLIOptions.java index 95164c31cc..4149d9869e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/MetricsCLIOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/MetricsCLIOptions.java @@ -23,6 +23,7 @@ import java.util.List; import picocli.CommandLine; /** The Metrics cli options. */ +// TODO: combine into MetricsOptionGroup, use Unstable inner class pattern (see MiningOptions) public class MetricsCLIOptions implements CLIOptions { private static final String TIMERS_ENABLED_FLAG = "--Xmetrics-timers-enabled"; private static final String IDLE_TIMEOUT_FLAG = "--Xmetrics-idle-timeout"; diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PTLSConfigOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/P2PTLSConfigOptions.java similarity index 99% rename from besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PTLSConfigOptions.java rename to besu/src/main/java/org/hyperledger/besu/cli/options/unstable/P2PTLSConfigOptions.java index c3f8c56219..5939eb3d39 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PTLSConfigOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/P2PTLSConfigOptions.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.cli.options.stable; +package org.hyperledger.besu.cli.options.unstable; import static java.util.Arrays.asList; import static org.hyperledger.besu.cli.DefaultCommandValues.DEFAULT_KEYSTORE_TYPE; diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java index 6bcfe4d353..1ac309c430 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java @@ -15,11 +15,11 @@ package org.hyperledger.besu.cli.subcommands.storage; import static com.google.common.base.Preconditions.checkArgument; -import static org.hyperledger.besu.cli.options.stable.DataStorageOptions.BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD; +import static org.hyperledger.besu.cli.options.DataStorageOptions.BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD; import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE; -import org.hyperledger.besu.cli.options.stable.DataStorageOptions; +import org.hyperledger.besu.cli.options.DataStorageOptions; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; diff --git a/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java b/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java index 59e2d60bf3..e757fd62a2 100644 --- a/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java @@ -20,7 +20,10 @@ import org.hyperledger.besu.chainexport.RlpBlockExporter; import org.hyperledger.besu.chainimport.JsonBlockImporter; import org.hyperledger.besu.chainimport.RlpBlockImporter; import org.hyperledger.besu.cli.BesuCommand; +import org.hyperledger.besu.cli.options.stable.P2PDiscoveryOptions; +import org.hyperledger.besu.cli.options.unstable.RPCOptions; import org.hyperledger.besu.controller.BesuController; +import org.hyperledger.besu.ethereum.p2p.discovery.P2PDiscoveryConfiguration; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.services.BesuPluginContextImpl; @@ -53,7 +56,6 @@ public class BesuCommandModule { new BesuPluginContextImpl(), System.getenv(), commandLogger); - besuCommand.toCommandLine(); return besuCommand; } @@ -63,6 +65,18 @@ public class BesuCommandModule { return provideFrom.metricsConfiguration(); } + @Provides + @Singleton + RPCOptions provideRPCOptions() { + return RPCOptions.create(); + } + + @Provides + @Singleton + P2PDiscoveryConfiguration provideP2PDiscoveryConfiguration() { + return new P2PDiscoveryOptions().toDomainObject(); + } + @Provides @Named("besuCommandLogger") @Singleton diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index 65e51c9213..7657dcd26a 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -34,9 +34,9 @@ import org.hyperledger.besu.chainexport.RlpBlockExporter; import org.hyperledger.besu.chainimport.JsonBlockImporter; import org.hyperledger.besu.chainimport.RlpBlockImporter; import org.hyperledger.besu.cli.config.EthNetworkConfig; +import org.hyperledger.besu.cli.options.DataStorageOptions; import org.hyperledger.besu.cli.options.MiningOptions; import org.hyperledger.besu.cli.options.TransactionPoolOptions; -import org.hyperledger.besu.cli.options.stable.DataStorageOptions; import org.hyperledger.besu.cli.options.stable.EthstatsOptions; import org.hyperledger.besu.cli.options.unstable.EthProtocolOptions; import org.hyperledger.besu.cli.options.unstable.MetricsCLIOptions; diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/stable/DataStorageOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/stable/DataStorageOptionsTest.java index 29fa3d6607..6be6429adb 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/stable/DataStorageOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/stable/DataStorageOptionsTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT; import org.hyperledger.besu.cli.options.AbstractCLIOptionsTest; +import org.hyperledger.besu.cli.options.DataStorageOptions; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; diff --git a/config/src/main/java/org/hyperledger/besu/config/MergeConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/MergeConfigOptions.java index 53faf69052..6ba865e113 100644 --- a/config/src/main/java/org/hyperledger/besu/config/MergeConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/MergeConfigOptions.java @@ -17,6 +17,8 @@ package org.hyperledger.besu.config; import java.util.concurrent.atomic.AtomicBoolean; /** The Merge config options. */ +// TODO: naming this with Options as the suffix is misleading, it should be MergeConfig - doesn't +// use picocli public class MergeConfigOptions { private static final AtomicBoolean mergeEnabled = new AtomicBoolean(false); diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/P2PDiscoveryConfiguration.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/P2PDiscoveryConfiguration.java new file mode 100644 index 0000000000..721a3100a0 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/P2PDiscoveryConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.discovery; + +import org.hyperledger.besu.util.number.Percentage; + +import java.util.Collection; +import java.util.List; + +import org.apache.commons.net.util.SubnetUtils; +import org.apache.tuweni.bytes.Bytes; + +public record P2PDiscoveryConfiguration( + Boolean p2pEnabled, + Boolean peerDiscoveryEnabled, + String p2pHost, + String p2pInterface, + Integer p2pPort, + Integer maxPeers, + Boolean isLimitRemoteWireConnectionsEnabled, + Percentage maxRemoteConnectionsPercentage, + Boolean randomPeerPriority, + Collection bannedNodeIds, + List allowedSubnets, + Boolean poaDiscoveryRetryBootnodes, + List bootNodes, + String discoveryDnsUrl) {}