Merge branch 'main' into feature/worldstate-refactor

pull/6209/head
Karim TAAM 10 months ago committed by GitHub
commit 6620e1d951
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 27
      CHANGELOG.md
  2. 428
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  3. 492
      besu/src/main/java/org/hyperledger/besu/cli/options/stable/JsonRpcHttpOptions.java
  4. 1213
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  5. 926
      besu/src/test/java/org/hyperledger/besu/cli/options/JsonRpcHttpOptionsTest.java
  6. 13
      besu/src/test/java/org/hyperledger/besu/cli/options/RpcWebsocketOptionsTest.java
  7. 10
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BftFinalState.java
  8. 2
      ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/ForkchoiceEvent.java
  9. 2
      evm/src/main/java/org/hyperledger/besu/collections/undo/UndoScalar.java
  10. 2
      plugin-api/build.gradle
  11. 2
      plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogAccumulator.java

@ -3,7 +3,9 @@
## 24.1.2-SNAPSHOT ## 24.1.2-SNAPSHOT
### Breaking Changes ### Breaking Changes
- The `trace-filter` method in JSON-RPC API now has a default block range limit of 1000, adjustable with `--rpc-max-trace-filter-range` [#6446](https://github.com/hyperledger/besu/pull/6446) - The `trace-filter` method in JSON-RPC API now has a default block range limit of 1000, adjustable with `--rpc-max-trace-filter-range` (thanks @alyokaz) [#6446](https://github.com/hyperledger/besu/pull/6446)
- Requesting the Ethereum Node Record (ENR) to acquire the fork id from bonded peers is now enabled by default, so the following change has been made [#5628](https://github.com/hyperledger/besu/pull/5628):
- `--Xfilter-on-enr-fork-id` has been removed. To disable the feature use `--filter-on-enr-fork-id=false`.
### Deprecations ### Deprecations
@ -11,6 +13,8 @@
- Add `OperationTracer.tracePrepareTransaction`, where the sender account has not yet been altered[#6453](https://github.com/hyperledger/besu/pull/6453) - Add `OperationTracer.tracePrepareTransaction`, where the sender account has not yet been altered[#6453](https://github.com/hyperledger/besu/pull/6453)
- Improve the high spec flag by limiting it to a few column families [#6354](https://github.com/hyperledger/besu/pull/6354) - Improve the high spec flag by limiting it to a few column families [#6354](https://github.com/hyperledger/besu/pull/6354)
- Log blob count when importing a block via Engine API [#6466](https://github.com/hyperledger/besu/pull/6466) - Log blob count when importing a block via Engine API [#6466](https://github.com/hyperledger/besu/pull/6466)
- Introduce `--Xbonsai-limit-trie-logs-enabled` experimental feature which by default will only retain the latest 512 trie logs, saving about 3GB per week in database growth [#5390](https://github.com/hyperledger/besu/issues/5390)
- Introduce `besu storage x-trie-log prune` experimental offline subcommand which will prune all redundant trie logs except the latest 512 [#6303](https://github.com/hyperledger/besu/pull/6303)
### Bug fixes ### Bug fixes
- Fix the way an advertised host configured with `--p2p-host` is treated when communicating with the originator of a PING packet [#6225](https://github.com/hyperledger/besu/pull/6225) - Fix the way an advertised host configured with `--p2p-host` is treated when communicating with the originator of a PING packet [#6225](https://github.com/hyperledger/besu/pull/6225)
@ -22,11 +26,9 @@
### Breaking Changes ### Breaking Changes
- New `EXECUTION_HALTED` error returned if there is an error executing or simulating a transaction, with the reason for execution being halted. Replaces the generic `INTERNAL_ERROR` return code in certain cases which some applications may be checking for [#6343](https://github.com/hyperledger/besu/pull/6343) - New `EXECUTION_HALTED` error returned if there is an error executing or simulating a transaction, with the reason for execution being halted. Replaces the generic `INTERNAL_ERROR` return code in certain cases which some applications may be checking for [#6343](https://github.com/hyperledger/besu/pull/6343)
- The Besu Docker images with `openjdk-latest` tags since 23.10.3 were incorrectly using UID 1001 instead of 1000 for the container's `besu` user. The user now uses 1000 again. Containers created from or migrated to images using UID 1001 will need to chown their persistent database files to UID 1000 [#6360](https://github.com/hyperledger/besu/pull/6360) - The Besu Docker images with `openjdk-latest` tags since 23.10.3 were incorrectly using UID 1001 instead of 1000 for the container's `besu` user. The user now uses 1000 again. Containers created from or migrated to images using UID 1001 will need to chown their persistent database files to UID 1000 (thanks @h4l) [#6360](https://github.com/hyperledger/besu/pull/6360)
- The deprecated `--privacy-onchain-groups-enabled` option has now been removed. Use the `--privacy-flexible-groups-enabled` option instead. [#6411](https://github.com/hyperledger/besu/pull/6411) - The deprecated `--privacy-onchain-groups-enabled` option has now been removed. Use the `--privacy-flexible-groups-enabled` option instead. [#6411](https://github.com/hyperledger/besu/pull/6411)
- Requesting the Ethereum Node Record (ENR) to acquire the fork id from bonded peers is now enabled by default, so the following change has been made [#5628](https://github.com/hyperledger/besu/pull/5628): - The time that can be spent selecting transactions during block creation is not capped at 5 seconds for PoS and PoW networks, and for PoA networks, at 75% of the block period specified in the genesis, this to prevent possible DoS in case a single transaction is taking too long to execute, and to have a stable block production rate, but it could be a breaking change if an existing network used to have transactions that takes more time to executed that the newly introduced limit, if it is mandatory for these network to keep processing these long processing transaction, then the default value of `block-txs-selection-max-time` or `poa-block-txs-selection-max-time` needs to be tuned accordingly. [#6423](https://github.com/hyperledger/besu/pull/6423)
- `--Xfilter-on-enr-fork-id` has been removed. To disable the feature use `--filter-on-enr-fork-id=false`.
- The time that can be spent selecting transactions during block creation is not capped at 5 seconds for PoS and PoW networks, and for PoA networks, at 75% of the block period specified in the genesis, this to prevent possible DoS in case a single transaction is taking too long to execute, and to have a stable block production rate, but it could be a breaking change if an existing network used to have transactions that takes more time to executed that the newly introduced limit, if it is mandatory for these network to keep processing these long processing transaction, then the default value of `block-txs-selection-max-time` or `poa-block-txs-selection-max-time` needs to be tuned accordingly.
### Deprecations ### Deprecations
@ -40,8 +42,6 @@
- Upgrade Mockito [#6397](https://github.com/hyperledger/besu/pull/6397) - Upgrade Mockito [#6397](https://github.com/hyperledger/besu/pull/6397)
- Upgrade `tech.pegasys.discovery:discovery` [#6414](https://github.com/hyperledger/besu/pull/6414) - Upgrade `tech.pegasys.discovery:discovery` [#6414](https://github.com/hyperledger/besu/pull/6414)
- Options to tune the max allowed time that can be spent selecting transactions during block creation are now stable [#6423](https://github.com/hyperledger/besu/pull/6423) - Options to tune the max allowed time that can be spent selecting transactions during block creation are now stable [#6423](https://github.com/hyperledger/besu/pull/6423)
- Introduce `--Xbonsai-limit-trie-logs-enabled` experimental feature which by default will only retain the latest 512 trie logs, saving about 3GB per week in database growth [#5390](https://github.com/hyperledger/besu/issues/5390)
- Introduce `besu storage x-trie-log prune` experimental offline subcommand which will prune all redundant trie logs except the latest 512 [#6303](https://github.com/hyperledger/besu/pull/6303)
### Bug fixes ### Bug fixes
- INTERNAL_ERROR from `eth_estimateGas` JSON/RPC calls [#6344](https://github.com/hyperledger/besu/issues/6344) - INTERNAL_ERROR from `eth_estimateGas` JSON/RPC calls [#6344](https://github.com/hyperledger/besu/issues/6344)
@ -49,8 +49,17 @@
- Fluent EVM API definition for Tangerine Whistle had incorrect code size validation configured [#6382](https://github.com/hyperledger/besu/pull/6382) - Fluent EVM API definition for Tangerine Whistle had incorrect code size validation configured [#6382](https://github.com/hyperledger/besu/pull/6382)
- Correct mining beneficiary for Clique networks in TraceServiceImpl [#6390](https://github.com/hyperledger/besu/pull/6390) - Correct mining beneficiary for Clique networks in TraceServiceImpl [#6390](https://github.com/hyperledger/besu/pull/6390)
- Fix to gas limit delta calculations used in block production. Besu should now increment or decrement the block gas limit towards its target correctly (thanks @arbora) #6425 - Fix to gas limit delta calculations used in block production. Besu should now increment or decrement the block gas limit towards its target correctly (thanks @arbora) #6425
- Ensure Backward Sync waits for initial sync before starting a session [#6455](https://github.com/hyperledger/besu/issues/6455)
- Silence the noisy DNS query errors [#6458](https://github.com/hyperledger/besu/issues/6458)
### Download Links ### Download Links
https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.1/besu-24.1.1.zip / sha256 e23c5b790180756964a70dcdd575ee2ed2c2efa79af00bce956d23bd2f7dc67c
https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.1/besu-24.1.1.tar.gz / sha256 4b0ddd5a25be2df5d2324bff935785eb63e4e3a5f421614ea690bacb5b9cb344
### Errata
Note, due to a CI race with the release job, the initial published version of 24.1.1 were overwritten by artifacts generated from the same sources, but differ in their embedded timestamps. The initial SHAs are noted here but are deprecated:
~~https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.1/besu-24.1.1.zip / sha256 b6b64f939e0bb4937ce90fc647e0a7073ce3e359c10352b502059955070a60c6
https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.1/besu-24.1.1.tar.gz / sha256 cfcae04c30769bf338b0740ac65870f9346d3469931bb46cdba3b2f65d311e7a~~
## 24.1.0 ## 24.1.0
@ -73,8 +82,8 @@
- mitigation for trielog failure [#6315]((https://github.com/hyperledger/besu/pull/6315) - mitigation for trielog failure [#6315]((https://github.com/hyperledger/besu/pull/6315)
### Download Links ### Download Links
https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.0/besu-24.1.0.zip / sha256 TBA https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.0/besu-24.1.0.zip / sha256 d36c8aeef70f0a516d4c26d3bc696c3e2a671e515c9e6e9475a31fe759e39f64
https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.0/besu-24.1.0.tar.gz / sha256 TBA https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.0/besu-24.1.0.tar.gz / sha256 602b04c0729a7b17361d1f0b39f4ce6a2ebe47932165add666560fe594d9ca99
## 23.10.3-hotfix ## 23.10.3-hotfix

@ -26,10 +26,6 @@ import static org.hyperledger.besu.cli.util.CommandLineUtils.isOptionSet;
import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH;
import static org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration.DEFAULT_GRAPHQL_HTTP_PORT; import static org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration.DEFAULT_GRAPHQL_HTTP_PORT;
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_ENGINE_JSON_RPC_PORT; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_ENGINE_JSON_RPC_PORT;
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_JSON_RPC_PORT;
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_PRETTY_JSON_ENABLED;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_RPC_APIS;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.VALID_APIS;
import static org.hyperledger.besu.ethereum.api.jsonrpc.authentication.EngineAuthService.EPHEMERAL_JWT_FILE; import static org.hyperledger.besu.ethereum.api.jsonrpc.authentication.EngineAuthService.EPHEMERAL_JWT_FILE;
import static org.hyperledger.besu.metrics.BesuMetricCategory.DEFAULT_METRIC_CATEGORIES; import static org.hyperledger.besu.metrics.BesuMetricCategory.DEFAULT_METRIC_CATEGORIES;
import static org.hyperledger.besu.metrics.MetricsProtocol.PROMETHEUS; import static org.hyperledger.besu.metrics.MetricsProtocol.PROMETHEUS;
@ -50,13 +46,13 @@ import org.hyperledger.besu.cli.converter.MetricCategoryConverter;
import org.hyperledger.besu.cli.converter.PercentageConverter; import org.hyperledger.besu.cli.converter.PercentageConverter;
import org.hyperledger.besu.cli.custom.CorsAllowedOriginsProperty; import org.hyperledger.besu.cli.custom.CorsAllowedOriginsProperty;
import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty; import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty;
import org.hyperledger.besu.cli.custom.RpcAuthFileValidator;
import org.hyperledger.besu.cli.error.BesuExecutionExceptionHandler; import org.hyperledger.besu.cli.error.BesuExecutionExceptionHandler;
import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler; import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler;
import org.hyperledger.besu.cli.options.MiningOptions; import org.hyperledger.besu.cli.options.MiningOptions;
import org.hyperledger.besu.cli.options.TransactionPoolOptions; import org.hyperledger.besu.cli.options.TransactionPoolOptions;
import org.hyperledger.besu.cli.options.stable.DataStorageOptions; import org.hyperledger.besu.cli.options.stable.DataStorageOptions;
import org.hyperledger.besu.cli.options.stable.EthstatsOptions; import org.hyperledger.besu.cli.options.stable.EthstatsOptions;
import org.hyperledger.besu.cli.options.stable.JsonRpcHttpOptions;
import org.hyperledger.besu.cli.options.stable.LoggingLevelOption; import org.hyperledger.besu.cli.options.stable.LoggingLevelOption;
import org.hyperledger.besu.cli.options.stable.NodePrivateKeyFileOption; import org.hyperledger.besu.cli.options.stable.NodePrivateKeyFileOption;
import org.hyperledger.besu.cli.options.stable.P2PTLSConfigOptions; import org.hyperledger.besu.cli.options.stable.P2PTLSConfigOptions;
@ -115,14 +111,10 @@ import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm;
import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.tls.FileBasedPasswordProvider;
import org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration;
import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration;
import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters;
@ -212,8 +204,6 @@ import java.net.SocketException;
import java.net.URI; import java.net.URI;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.time.Clock; import java.time.Clock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -231,8 +221,6 @@ import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings; import com.google.common.base.Strings;
@ -647,162 +635,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
// JSON-RPC HTTP Options // JSON-RPC HTTP Options
@CommandLine.ArgGroup(validate = false, heading = "@|bold JSON-RPC HTTP Options|@%n") @CommandLine.ArgGroup(validate = false, heading = "@|bold JSON-RPC HTTP Options|@%n")
JsonRPCHttpOptionGroup jsonRPCHttpOptionGroup = new JsonRPCHttpOptionGroup(); JsonRpcHttpOptions jsonRpcHttpOptions = new JsonRpcHttpOptions();
static class JsonRPCHttpOptionGroup {
@Option(
names = {"--rpc-http-enabled"},
description = "Set to start the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpEnabled = false;
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
@Option(
names = {"--rpc-http-host"},
paramLabel = MANDATORY_HOST_FORMAT_HELP,
description = "Host for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})",
arity = "1")
private String rpcHttpHost;
@Option(
names = {"--rpc-http-port"},
paramLabel = MANDATORY_PORT_FORMAT_HELP,
description = "Port for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})",
arity = "1")
private final Integer rpcHttpPort = DEFAULT_JSON_RPC_PORT;
@Option(
names = {"--rpc-http-max-active-connections"},
description =
"Maximum number of HTTP connections allowed for JSON-RPC (default: ${DEFAULT-VALUE}). Once this limit is reached, incoming connections will be rejected.",
arity = "1")
private final Integer rpcHttpMaxConnections = DEFAULT_HTTP_MAX_CONNECTIONS;
// A list of origins URLs that are accepted by the JsonRpcHttpServer (CORS)
@Option(
names = {"--rpc-http-cors-origins"},
description = "Comma separated origin domain URLs for CORS validation (default: none)")
private final CorsAllowedOriginsProperty rpcHttpCorsAllowedOrigins =
new CorsAllowedOriginsProperty();
@Option(
names = {"--rpc-http-api", "--rpc-http-apis"},
paramLabel = "<api name>",
split = " {0,1}, {0,1}",
arity = "1..*",
description =
"Comma separated list of APIs to enable on JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final List<String> rpcHttpApis = DEFAULT_RPC_APIS;
@Option(
names = {"--rpc-http-api-method-no-auth", "--rpc-http-api-methods-no-auth"},
paramLabel = "<api name>",
split = " {0,1}, {0,1}",
arity = "1..*",
description =
"Comma separated list of API methods to exclude from RPC authentication services, RPC HTTP authentication must be enabled")
private final List<String> rpcHttpApiMethodsNoAuth = new ArrayList<String>();
@Option(
names = {"--rpc-http-authentication-enabled"},
description =
"Require authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpAuthenticationEnabled = false;
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
@CommandLine.Option(
names = {"--rpc-http-authentication-credentials-file"},
paramLabel = MANDATORY_FILE_FORMAT_HELP,
description =
"Storage file for JSON-RPC HTTP authentication credentials (default: ${DEFAULT-VALUE})",
arity = "1")
private String rpcHttpAuthenticationCredentialsFile = null;
@CommandLine.Option(
names = {"--rpc-http-authentication-jwt-public-key-file"},
paramLabel = MANDATORY_FILE_FORMAT_HELP,
description = "JWT public key file for JSON-RPC HTTP authentication",
arity = "1")
private final File rpcHttpAuthenticationPublicKeyFile = null;
@Option(
names = {"--rpc-http-authentication-jwt-algorithm"},
description =
"Encryption algorithm used for HTTP JWT public key. Possible values are ${COMPLETION-CANDIDATES}"
+ " (default: ${DEFAULT-VALUE})",
arity = "1")
private final JwtAlgorithm rpcHttpAuthenticationAlgorithm = DEFAULT_JWT_ALGORITHM;
@Option(
names = {"--rpc-http-tls-enabled"},
description = "Enable TLS for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpTlsEnabled = false;
@Option(
names = {"--rpc-http-tls-keystore-file"},
paramLabel = MANDATORY_FILE_FORMAT_HELP,
description =
"Keystore (PKCS#12) containing key/certificate for the JSON-RPC HTTP service. Required if TLS is enabled.")
private final Path rpcHttpTlsKeyStoreFile = null;
@Option(
names = {"--rpc-http-tls-keystore-password-file"},
paramLabel = MANDATORY_FILE_FORMAT_HELP,
description =
"File containing password to unlock keystore for the JSON-RPC HTTP service. Required if TLS is enabled.")
private final Path rpcHttpTlsKeyStorePasswordFile = null;
@Option(
names = {"--rpc-http-tls-client-auth-enabled"},
description =
"Enable TLS client authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpTlsClientAuthEnabled = false;
@Option(
names = {"--rpc-http-tls-known-clients-file"},
paramLabel = MANDATORY_FILE_FORMAT_HELP,
description =
"Path to file containing clients certificate common name and fingerprint for client authentication")
private final Path rpcHttpTlsKnownClientsFile = null;
@Option(
names = {"--rpc-http-tls-ca-clients-enabled"},
description =
"Enable to accept clients certificate signed by a valid CA for client authentication (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpTlsCAClientsEnabled = false;
@Option(
names = {"--rpc-http-tls-protocol", "--rpc-http-tls-protocols"},
description =
"Comma separated list of TLS protocols to support (default: ${DEFAULT-VALUE})",
split = ",",
arity = "1..*")
private final List<String> rpcHttpTlsProtocols = new ArrayList<>(DEFAULT_TLS_PROTOCOLS);
@Option(
names = {"--rpc-http-tls-cipher-suite", "--rpc-http-tls-cipher-suites"},
description = "Comma separated list of TLS cipher suites to support",
split = ",",
arity = "1..*")
private final List<String> rpcHttpTlsCipherSuites = new ArrayList<>();
@CommandLine.Option(
names = {"--rpc-http-max-batch-size"},
paramLabel = MANDATORY_INTEGER_FORMAT_HELP,
description =
"Specifies the maximum number of requests in a single RPC batch request via RPC. -1 specifies no limit (default: ${DEFAULT-VALUE})")
private final Integer rpcHttpMaxBatchSize = DEFAULT_HTTP_MAX_BATCH_SIZE;
@CommandLine.Option(
names = {"--rpc-http-max-request-content-length"},
paramLabel = MANDATORY_LONG_FORMAT_HELP,
description = "Specifies the maximum request content length. (default: ${DEFAULT-VALUE})")
private final Long rpcHttpMaxRequestContentLength = DEFAULT_MAX_REQUEST_CONTENT_LENGTH;
@Option(
names = {"--json-pretty-print-enabled"},
description = "Enable JSON pretty print format (default: ${DEFAULT-VALUE})")
private final Boolean prettyJsonEnabled = DEFAULT_PRETTY_JSON_ENABLED;
}
// JSON-RPC Websocket Options // JSON-RPC Websocket Options
@CommandLine.ArgGroup(validate = false, heading = "@|bold JSON-RPC Websocket Options|@%n") @CommandLine.ArgGroup(validate = false, heading = "@|bold JSON-RPC Websocket Options|@%n")
@ -1866,26 +1699,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
Arrays.stream(RpcApis.values()) Arrays.stream(RpcApis.values())
.anyMatch(builtInApi -> apiName.equals(builtInApi.name())) .anyMatch(builtInApi -> apiName.equals(builtInApi.name()))
|| rpcEndpointServiceImpl.hasNamespace(apiName); || rpcEndpointServiceImpl.hasNamespace(apiName);
jsonRpcHttpOptions.validate(logger, commandLine, configuredApis);
if (!jsonRPCHttpOptionGroup.rpcHttpApis.stream().allMatch(configuredApis)) {
final List<String> invalidHttpApis =
new ArrayList<String>(jsonRPCHttpOptionGroup.rpcHttpApis);
invalidHttpApis.removeAll(VALID_APIS);
throw new ParameterException(
this.commandLine,
"Invalid value for option '--rpc-http-api': invalid entries found "
+ invalidHttpApis.toString());
}
final boolean validHttpApiMethods =
jsonRPCHttpOptionGroup.rpcHttpApiMethodsNoAuth.stream()
.allMatch(RpcMethod::rpcMethodExists);
if (!validHttpApiMethods) {
throw new ParameterException(
this.commandLine,
"Invalid value for option '--rpc-http-api-methods-no-auth', options must be valid RPC methods");
}
} }
private void validateRpcWsOptions() { private void validateRpcWsOptions() {
@ -1987,8 +1801,10 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
ethNetworkConfig = updateNetworkConfig(network); ethNetworkConfig = updateNetworkConfig(network);
jsonRpcConfiguration = jsonRpcConfiguration =
jsonRpcConfiguration( jsonRpcHttpOptions.jsonRpcConfiguration(
jsonRPCHttpOptionGroup.rpcHttpPort, jsonRPCHttpOptionGroup.rpcHttpApis, hostsAllowlist); hostsAllowlist,
p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(),
unstableRPCOptions.getHttpTimeoutSec());
if (isEngineApiEnabled()) { if (isEngineApiEnabled()) {
engineJsonRpcConfiguration = engineJsonRpcConfiguration =
createEngineJsonRpcConfiguration( createEngineJsonRpcConfiguration(
@ -2160,9 +1976,15 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
} }
private JsonRpcConfiguration createEngineJsonRpcConfiguration( private JsonRpcConfiguration createEngineJsonRpcConfiguration(
final Integer listenPort, final List<String> allowCallsFrom) { final Integer engineListenPort, final List<String> allowCallsFrom) {
jsonRpcHttpOptions.checkDependencies(logger, commandLine);
final JsonRpcConfiguration engineConfig = final JsonRpcConfiguration engineConfig =
jsonRpcConfiguration(listenPort, Arrays.asList("ENGINE", "ETH"), allowCallsFrom); jsonRpcHttpOptions.jsonRpcConfiguration(
allowCallsFrom,
p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(),
unstableRPCOptions.getWsTimeoutSec());
engineConfig.setPort(engineListenPort);
engineConfig.setRpcApis(Arrays.asList("ENGINE", "ETH"));
engineConfig.setEnabled(isEngineApiEnabled()); engineConfig.setEnabled(isEngineApiEnabled());
if (!engineRPCOptionGroup.isEngineAuthDisabled) { if (!engineRPCOptionGroup.isEngineAuthDisabled) {
engineConfig.setAuthenticationEnabled(true); engineConfig.setAuthenticationEnabled(true);
@ -2178,116 +2000,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
return engineConfig; return engineConfig;
} }
private JsonRpcConfiguration jsonRpcConfiguration(
final Integer listenPort, final List<String> apiGroups, final List<String> allowCallsFrom) {
checkRpcTlsClientAuthOptionsDependencies();
checkRpcTlsOptionsDependencies();
checkRpcHttpOptionsDependencies();
if (jsonRPCHttpOptionGroup.isRpcHttpAuthenticationEnabled) {
CommandLineUtils.checkOptionDependencies(
logger,
commandLine,
"--rpc-http-authentication-public-key-file",
jsonRPCHttpOptionGroup.rpcHttpAuthenticationPublicKeyFile == null,
asList("--rpc-http-authentication-jwt-algorithm"));
}
if (jsonRPCHttpOptionGroup.isRpcHttpAuthenticationEnabled
&& rpcHttpAuthenticationCredentialsFile() == null
&& jsonRPCHttpOptionGroup.rpcHttpAuthenticationPublicKeyFile == null) {
throw new ParameterException(
commandLine,
"Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file");
}
final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault();
jsonRpcConfiguration.setEnabled(jsonRPCHttpOptionGroup.isRpcHttpEnabled);
jsonRpcConfiguration.setHost(
Strings.isNullOrEmpty(jsonRPCHttpOptionGroup.rpcHttpHost)
? p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress()
: jsonRPCHttpOptionGroup.rpcHttpHost);
jsonRpcConfiguration.setPort(listenPort);
jsonRpcConfiguration.setMaxActiveConnections(jsonRPCHttpOptionGroup.rpcHttpMaxConnections);
jsonRpcConfiguration.setCorsAllowedDomains(jsonRPCHttpOptionGroup.rpcHttpCorsAllowedOrigins);
jsonRpcConfiguration.setRpcApis(apiGroups.stream().distinct().collect(Collectors.toList()));
jsonRpcConfiguration.setNoAuthRpcApis(
jsonRPCHttpOptionGroup.rpcHttpApiMethodsNoAuth.stream()
.distinct()
.collect(Collectors.toList()));
jsonRpcConfiguration.setHostsAllowlist(allowCallsFrom);
jsonRpcConfiguration.setAuthenticationEnabled(
jsonRPCHttpOptionGroup.isRpcHttpAuthenticationEnabled);
jsonRpcConfiguration.setAuthenticationCredentialsFile(rpcHttpAuthenticationCredentialsFile());
jsonRpcConfiguration.setAuthenticationPublicKeyFile(
jsonRPCHttpOptionGroup.rpcHttpAuthenticationPublicKeyFile);
jsonRpcConfiguration.setAuthenticationAlgorithm(
jsonRPCHttpOptionGroup.rpcHttpAuthenticationAlgorithm);
jsonRpcConfiguration.setTlsConfiguration(rpcHttpTlsConfiguration());
jsonRpcConfiguration.setHttpTimeoutSec(unstableRPCOptions.getHttpTimeoutSec());
jsonRpcConfiguration.setMaxBatchSize(jsonRPCHttpOptionGroup.rpcHttpMaxBatchSize);
jsonRpcConfiguration.setMaxRequestContentLength(
jsonRPCHttpOptionGroup.rpcHttpMaxRequestContentLength);
jsonRpcConfiguration.setPrettyJsonEnabled(jsonRPCHttpOptionGroup.prettyJsonEnabled);
return jsonRpcConfiguration;
}
private void checkRpcHttpOptionsDependencies() {
CommandLineUtils.checkOptionDependencies(
logger,
commandLine,
"--rpc-http-enabled",
!jsonRPCHttpOptionGroup.isRpcHttpEnabled,
asList(
"--rpc-http-api",
"--rpc-http-apis",
"--rpc-http-api-method-no-auth",
"--rpc-http-api-methods-no-auth",
"--rpc-http-cors-origins",
"--rpc-http-host",
"--rpc-http-port",
"--rpc-http-max-active-connections",
"--rpc-http-authentication-enabled",
"--rpc-http-authentication-credentials-file",
"--rpc-http-authentication-public-key-file",
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
"--rpc-http-tls-keystore-password-file",
"--rpc-http-tls-client-auth-enabled",
"--rpc-http-tls-known-clients-file",
"--rpc-http-tls-ca-clients-enabled",
"--rpc-http-authentication-jwt-algorithm",
"--rpc-http-tls-protocols",
"--rpc-http-tls-cipher-suite",
"--rpc-http-tls-cipher-suites"));
}
private void checkRpcTlsOptionsDependencies() {
CommandLineUtils.checkOptionDependencies(
logger,
commandLine,
"--rpc-http-tls-enabled",
!jsonRPCHttpOptionGroup.isRpcHttpTlsEnabled,
asList(
"--rpc-http-tls-keystore-file",
"--rpc-http-tls-keystore-password-file",
"--rpc-http-tls-client-auth-enabled",
"--rpc-http-tls-known-clients-file",
"--rpc-http-tls-ca-clients-enabled",
"--rpc-http-tls-protocols",
"--rpc-http-tls-cipher-suite",
"--rpc-http-tls-cipher-suites"));
}
private void checkRpcTlsClientAuthOptionsDependencies() {
CommandLineUtils.checkOptionDependencies(
logger,
commandLine,
"--rpc-http-tls-client-auth-enabled",
!jsonRPCHttpOptionGroup.isRpcHttpTlsClientAuthEnabled,
asList("--rpc-http-tls-known-clients-file", "--rpc-http-tls-ca-clients-enabled"));
}
private void checkPrivacyTlsOptionsDependencies() { private void checkPrivacyTlsOptionsDependencies() {
CommandLineUtils.checkOptionDependencies( CommandLineUtils.checkOptionDependencies(
logger, logger,
@ -2300,75 +2012,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
"--privacy-tls-known-enclave-file")); "--privacy-tls-known-enclave-file"));
} }
private Optional<TlsConfiguration> rpcHttpTlsConfiguration() {
if (!isRpcTlsConfigurationRequired()) {
return Optional.empty();
}
if (jsonRPCHttpOptionGroup.rpcHttpTlsKeyStoreFile == null) {
throw new ParameterException(
commandLine, "Keystore file is required when TLS is enabled for JSON-RPC HTTP endpoint");
}
if (jsonRPCHttpOptionGroup.rpcHttpTlsKeyStorePasswordFile == null) {
throw new ParameterException(
commandLine,
"File containing password to unlock keystore is required when TLS is enabled for JSON-RPC HTTP endpoint");
}
if (jsonRPCHttpOptionGroup.isRpcHttpTlsClientAuthEnabled
&& !jsonRPCHttpOptionGroup.isRpcHttpTlsCAClientsEnabled
&& jsonRPCHttpOptionGroup.rpcHttpTlsKnownClientsFile == null) {
throw new ParameterException(
commandLine,
"Known-clients file must be specified or CA clients must be enabled when TLS client authentication is enabled for JSON-RPC HTTP endpoint");
}
jsonRPCHttpOptionGroup.rpcHttpTlsProtocols.retainAll(getJDKEnabledProtocols());
if (jsonRPCHttpOptionGroup.rpcHttpTlsProtocols.isEmpty()) {
throw new ParameterException(
commandLine,
"No valid TLS protocols specified (the following protocols are enabled: "
+ getJDKEnabledProtocols()
+ ")");
}
for (final String cipherSuite : jsonRPCHttpOptionGroup.rpcHttpTlsCipherSuites) {
if (!getJDKEnabledCipherSuites().contains(cipherSuite)) {
throw new ParameterException(
commandLine, "Invalid TLS cipher suite specified " + cipherSuite);
}
}
jsonRPCHttpOptionGroup.rpcHttpTlsCipherSuites.retainAll(getJDKEnabledCipherSuites());
return Optional.of(
TlsConfiguration.Builder.aTlsConfiguration()
.withKeyStorePath(jsonRPCHttpOptionGroup.rpcHttpTlsKeyStoreFile)
.withKeyStorePasswordSupplier(
new FileBasedPasswordProvider(
jsonRPCHttpOptionGroup.rpcHttpTlsKeyStorePasswordFile))
.withClientAuthConfiguration(rpcHttpTlsClientAuthConfiguration())
.withSecureTransportProtocols(jsonRPCHttpOptionGroup.rpcHttpTlsProtocols)
.withCipherSuites(jsonRPCHttpOptionGroup.rpcHttpTlsCipherSuites)
.build());
}
private TlsClientAuthConfiguration rpcHttpTlsClientAuthConfiguration() {
if (jsonRPCHttpOptionGroup.isRpcHttpTlsClientAuthEnabled) {
return TlsClientAuthConfiguration.Builder.aTlsClientAuthConfiguration()
.withKnownClientsFile(jsonRPCHttpOptionGroup.rpcHttpTlsKnownClientsFile)
.withCaClientsEnabled(jsonRPCHttpOptionGroup.isRpcHttpTlsCAClientsEnabled)
.build();
}
return null;
}
private boolean isRpcTlsConfigurationRequired() {
return jsonRPCHttpOptionGroup.isRpcHttpEnabled && jsonRPCHttpOptionGroup.isRpcHttpTlsEnabled;
}
private ApiConfiguration apiConfiguration() { private ApiConfiguration apiConfiguration() {
checkApiOptionsDependencies(); checkApiOptionsDependencies();
var builder = var builder =
@ -2450,7 +2093,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
private Optional<PermissioningConfiguration> permissioningConfiguration() throws Exception { private Optional<PermissioningConfiguration> permissioningConfiguration() throws Exception {
if (!(localPermissionsEnabled() || contractPermissionsEnabled())) { if (!(localPermissionsEnabled() || contractPermissionsEnabled())) {
if (jsonRPCHttpOptionGroup.rpcHttpApis.contains(RpcApis.PERM.name()) if (jsonRpcHttpOptions.getRpcHttpApis().contains(RpcApis.PERM.name())
|| rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.PERM.name())) { || rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.PERM.name())) {
logger.warn( logger.warn(
"Permissions are disabled. Cannot enable PERM APIs when not using Permissions."); "Permissions are disabled. Cannot enable PERM APIs when not using Permissions.");
@ -2653,9 +2296,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
} }
private boolean anyPrivacyApiEnabled() { private boolean anyPrivacyApiEnabled() {
return jsonRPCHttpOptionGroup.rpcHttpApis.contains(RpcApis.EEA.name()) return jsonRpcHttpOptions.getRpcHttpApis().contains(RpcApis.EEA.name())
|| rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.EEA.name()) || rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.EEA.name())
|| jsonRPCHttpOptionGroup.rpcHttpApis.contains(RpcApis.PRIV.name()) || jsonRpcHttpOptions.getRpcHttpApis().contains(RpcApis.PRIV.name())
|| rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.PRIV.name()); || rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.PRIV.name());
} }
@ -3018,15 +2661,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
.orElseGet(() -> KeyPairUtil.getDefaultKeyFile(dataDir())); .orElseGet(() -> KeyPairUtil.getDefaultKeyFile(dataDir()));
} }
private String rpcHttpAuthenticationCredentialsFile() {
final String filename = jsonRPCHttpOptionGroup.rpcHttpAuthenticationCredentialsFile;
if (filename != null) {
RpcAuthFileValidator.validate(commandLine, filename, "HTTP");
}
return filename;
}
private String getDefaultPermissioningFilePath() { private String getDefaultPermissioningFilePath() {
return dataDir() return dataDir()
+ System.getProperty("file.separator") + System.getProperty("file.separator")
@ -3160,9 +2794,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
graphQlOptionGroup.graphQLHttpPort, graphQlOptionGroup.graphQLHttpPort,
graphQlOptionGroup.isGraphQLHttpEnabled); graphQlOptionGroup.isGraphQLHttpEnabled);
addPortIfEnabled( addPortIfEnabled(
effectivePorts, effectivePorts, jsonRpcHttpOptions.getRpcHttpPort(), jsonRpcHttpOptions.isRpcHttpEnabled());
jsonRPCHttpOptionGroup.rpcHttpPort,
jsonRPCHttpOptionGroup.isRpcHttpEnabled);
addPortIfEnabled( addPortIfEnabled(
effectivePorts, rpcWebsocketOptions.getRpcWsPort(), rpcWebsocketOptions.isRpcWsEnabled()); effectivePorts, rpcWebsocketOptions.getRpcWsPort(), rpcWebsocketOptions.isRpcWsEnabled());
addPortIfEnabled(effectivePorts, engineRPCOptionGroup.engineRpcPort, isEngineApiEnabled()); addPortIfEnabled(effectivePorts, engineRPCOptionGroup.engineRpcPort, isEngineApiEnabled());
@ -3310,28 +2942,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
return engineRPCOptionGroup.overrideEngineRpcEnabled || isMergeEnabled(); return engineRPCOptionGroup.overrideEngineRpcEnabled || isMergeEnabled();
} }
private static List<String> getJDKEnabledCipherSuites() {
try {
final SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
final SSLEngine engine = context.createSSLEngine();
return Arrays.asList(engine.getEnabledCipherSuites());
} catch (final KeyManagementException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private static List<String> getJDKEnabledProtocols() {
try {
final SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
final SSLEngine engine = context.createSSLEngine();
return Arrays.asList(engine.getEnabledProtocols());
} catch (final KeyManagementException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private SyncMode getDefaultSyncModeIfNotSet() { private SyncMode getDefaultSyncModeIfNotSet() {
return Optional.ofNullable(syncMode) return Optional.ofNullable(syncMode)
.orElse( .orElse(

@ -0,0 +1,492 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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 java.util.Arrays.asList;
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_JSON_RPC_PORT;
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_PRETTY_JSON_ENABLED;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_RPC_APIS;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.VALID_APIS;
import org.hyperledger.besu.cli.DefaultCommandValues;
import org.hyperledger.besu.cli.custom.CorsAllowedOriginsProperty;
import org.hyperledger.besu.cli.custom.RpcAuthFileValidator;
import org.hyperledger.besu.cli.util.CommandLineUtils;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm;
import org.hyperledger.besu.ethereum.api.tls.FileBasedPasswordProvider;
import org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration;
import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration;
import java.io.File;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import picocli.CommandLine;
/**
* Handles configuration options for the JSON-RPC HTTP service, including validation and creation of
* a JSON-RPC configuration.
*/
public class JsonRpcHttpOptions {
@CommandLine.Option(
names = {"--rpc-http-enabled"},
description = "Set to start the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpEnabled = false;
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
@CommandLine.Option(
names = {"--rpc-http-host"},
paramLabel = DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP,
description = "Host for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})",
arity = "1")
private String rpcHttpHost;
@CommandLine.Option(
names = {"--rpc-http-port"},
paramLabel = DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP,
description = "Port for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})",
arity = "1")
private final Integer rpcHttpPort = DEFAULT_JSON_RPC_PORT;
@CommandLine.Option(
names = {"--rpc-http-max-active-connections"},
description =
"Maximum number of HTTP connections allowed for JSON-RPC (default: ${DEFAULT-VALUE}). Once this limit is reached, incoming connections will be rejected.",
arity = "1")
private final Integer rpcHttpMaxConnections = DefaultCommandValues.DEFAULT_HTTP_MAX_CONNECTIONS;
// A list of origins URLs that are accepted by the JsonRpcHttpServer (CORS)
@CommandLine.Option(
names = {"--rpc-http-cors-origins"},
description = "Comma separated origin domain URLs for CORS validation (default: none)")
private final CorsAllowedOriginsProperty rpcHttpCorsAllowedOrigins =
new CorsAllowedOriginsProperty();
@CommandLine.Option(
names = {"--rpc-http-api", "--rpc-http-apis"},
paramLabel = "<api name>",
split = " {0,1}, {0,1}",
arity = "1..*",
description =
"Comma separated list of APIs to enable on JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final List<String> rpcHttpApis = DEFAULT_RPC_APIS;
@CommandLine.Option(
names = {"--rpc-http-api-method-no-auth", "--rpc-http-api-methods-no-auth"},
paramLabel = "<api name>",
split = " {0,1}, {0,1}",
arity = "1..*",
description =
"Comma separated list of API methods to exclude from RPC authentication services, RPC HTTP authentication must be enabled")
private final List<String> rpcHttpApiMethodsNoAuth = new ArrayList<String>();
@CommandLine.Option(
names = {"--rpc-http-authentication-enabled"},
description =
"Require authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpAuthenticationEnabled = false;
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
@CommandLine.Option(
names = {"--rpc-http-authentication-credentials-file"},
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
description =
"Storage file for JSON-RPC HTTP authentication credentials (default: ${DEFAULT-VALUE})",
arity = "1")
private String rpcHttpAuthenticationCredentialsFile = null;
@CommandLine.Option(
names = {"--rpc-http-authentication-jwt-public-key-file"},
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
description = "JWT public key file for JSON-RPC HTTP authentication",
arity = "1")
private final File rpcHttpAuthenticationPublicKeyFile = null;
@CommandLine.Option(
names = {"--rpc-http-authentication-jwt-algorithm"},
description =
"Encryption algorithm used for HTTP JWT public key. Possible values are ${COMPLETION-CANDIDATES}"
+ " (default: ${DEFAULT-VALUE})",
arity = "1")
private final JwtAlgorithm rpcHttpAuthenticationAlgorithm =
DefaultCommandValues.DEFAULT_JWT_ALGORITHM;
@CommandLine.Option(
names = {"--rpc-http-tls-enabled"},
description = "Enable TLS for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpTlsEnabled = false;
@CommandLine.Option(
names = {"--rpc-http-tls-keystore-file"},
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
description =
"Keystore (PKCS#12) containing key/certificate for the JSON-RPC HTTP service. Required if TLS is enabled.")
private final Path rpcHttpTlsKeyStoreFile = null;
@CommandLine.Option(
names = {"--rpc-http-tls-keystore-password-file"},
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
description =
"File containing password to unlock keystore for the JSON-RPC HTTP service. Required if TLS is enabled.")
private final Path rpcHttpTlsKeyStorePasswordFile = null;
@CommandLine.Option(
names = {"--rpc-http-tls-client-auth-enabled"},
description =
"Enable TLS client authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpTlsClientAuthEnabled = false;
@CommandLine.Option(
names = {"--rpc-http-tls-known-clients-file"},
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP,
description =
"Path to file containing clients certificate common name and fingerprint for client authentication")
private final Path rpcHttpTlsKnownClientsFile = null;
@CommandLine.Option(
names = {"--rpc-http-tls-ca-clients-enabled"},
description =
"Enable to accept clients certificate signed by a valid CA for client authentication (default: ${DEFAULT-VALUE})")
private final Boolean isRpcHttpTlsCAClientsEnabled = false;
@CommandLine.Option(
names = {"--rpc-http-tls-protocol", "--rpc-http-tls-protocols"},
description = "Comma separated list of TLS protocols to support (default: ${DEFAULT-VALUE})",
split = ",",
arity = "1..*")
private final List<String> rpcHttpTlsProtocols =
new ArrayList<>(DefaultCommandValues.DEFAULT_TLS_PROTOCOLS);
@CommandLine.Option(
names = {"--rpc-http-tls-cipher-suite", "--rpc-http-tls-cipher-suites"},
description = "Comma separated list of TLS cipher suites to support",
split = ",",
arity = "1..*")
private final List<String> rpcHttpTlsCipherSuites = new ArrayList<>();
@CommandLine.Option(
names = {"--rpc-http-max-batch-size"},
paramLabel = DefaultCommandValues.MANDATORY_INTEGER_FORMAT_HELP,
description =
"Specifies the maximum number of requests in a single RPC batch request via RPC. -1 specifies no limit (default: ${DEFAULT-VALUE})")
private final Integer rpcHttpMaxBatchSize = DefaultCommandValues.DEFAULT_HTTP_MAX_BATCH_SIZE;
@CommandLine.Option(
names = {"--rpc-http-max-request-content-length"},
paramLabel = DefaultCommandValues.MANDATORY_LONG_FORMAT_HELP,
description = "Specifies the maximum request content length. (default: ${DEFAULT-VALUE})")
private final Long rpcHttpMaxRequestContentLength =
DefaultCommandValues.DEFAULT_MAX_REQUEST_CONTENT_LENGTH;
@CommandLine.Option(
names = {"--json-pretty-print-enabled"},
description = "Enable JSON pretty print format (default: ${DEFAULT-VALUE})")
private final Boolean prettyJsonEnabled = DEFAULT_PRETTY_JSON_ENABLED;
/**
* Validates the Rpc Http options.
*
* @param logger Logger instance
* @param commandLine CommandLine instance
* @param configuredApis Predicate for configured APIs
*/
public void validate(
final Logger logger, final CommandLine commandLine, final Predicate<String> configuredApis) {
if (!rpcHttpApis.stream().allMatch(configuredApis)) {
final List<String> invalidHttpApis = new ArrayList<>(rpcHttpApis);
invalidHttpApis.removeAll(VALID_APIS);
throw new CommandLine.ParameterException(
commandLine,
"Invalid value for option '--rpc-http-api': invalid entries found " + invalidHttpApis);
}
final boolean validHttpApiMethods =
rpcHttpApiMethodsNoAuth.stream().allMatch(RpcMethod::rpcMethodExists);
if (!validHttpApiMethods) {
throw new CommandLine.ParameterException(
commandLine,
"Invalid value for option '--rpc-http-api-methods-no-auth', options must be valid RPC methods");
}
if (isRpcHttpAuthenticationEnabled) {
CommandLineUtils.checkOptionDependencies(
logger,
commandLine,
"--rpc-http-authentication-public-key-file",
rpcHttpAuthenticationPublicKeyFile == null,
List.of("--rpc-http-authentication-jwt-algorithm"));
}
if (isRpcHttpAuthenticationEnabled
&& rpcHttpAuthenticationCredentialsFile(commandLine) == null
&& rpcHttpAuthenticationPublicKeyFile == null) {
throw new CommandLine.ParameterException(
commandLine,
"Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file");
}
checkDependencies(logger, commandLine);
if (isRpcTlsConfigurationRequired()) {
validateTls(commandLine);
}
}
/**
* 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<String> hostsAllowlist, final String defaultHostAddress, final Long timoutSec) {
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;
}
/**
* Checks dependencies between options.
*
* @param logger Logger instance
* @param commandLine CommandLine instance
*/
public void checkDependencies(final Logger logger, final CommandLine commandLine) {
checkRpcTlsClientAuthOptionsDependencies(logger, commandLine);
checkRpcTlsOptionsDependencies(logger, commandLine);
checkRpcHttpOptionsDependencies(logger, commandLine);
}
private void checkRpcTlsClientAuthOptionsDependencies(
final Logger logger, final CommandLine commandLine) {
CommandLineUtils.checkOptionDependencies(
logger,
commandLine,
"--rpc-http-tls-client-auth-enabled",
!isRpcHttpTlsClientAuthEnabled,
asList("--rpc-http-tls-known-clients-file", "--rpc-http-tls-ca-clients-enabled"));
}
private void checkRpcTlsOptionsDependencies(final Logger logger, final CommandLine commandLine) {
CommandLineUtils.checkOptionDependencies(
logger,
commandLine,
"--rpc-http-tls-enabled",
!isRpcHttpTlsEnabled,
asList(
"--rpc-http-tls-keystore-file",
"--rpc-http-tls-keystore-password-file",
"--rpc-http-tls-client-auth-enabled",
"--rpc-http-tls-known-clients-file",
"--rpc-http-tls-ca-clients-enabled",
"--rpc-http-tls-protocols",
"--rpc-http-tls-cipher-suite",
"--rpc-http-tls-cipher-suites"));
}
private void checkRpcHttpOptionsDependencies(final Logger logger, final CommandLine commandLine) {
CommandLineUtils.checkOptionDependencies(
logger,
commandLine,
"--rpc-http-enabled",
!isRpcHttpEnabled,
asList(
"--rpc-http-api",
"--rpc-http-apis",
"--rpc-http-api-method-no-auth",
"--rpc-http-api-methods-no-auth",
"--rpc-http-cors-origins",
"--rpc-http-host",
"--rpc-http-port",
"--rpc-http-max-active-connections",
"--rpc-http-authentication-enabled",
"--rpc-http-authentication-credentials-file",
"--rpc-http-authentication-public-key-file",
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
"--rpc-http-tls-keystore-password-file",
"--rpc-http-tls-client-auth-enabled",
"--rpc-http-tls-known-clients-file",
"--rpc-http-tls-ca-clients-enabled",
"--rpc-http-authentication-jwt-algorithm",
"--rpc-http-tls-protocols",
"--rpc-http-tls-cipher-suite",
"--rpc-http-tls-cipher-suites"));
}
private void validateTls(final CommandLine commandLine) {
if (rpcHttpTlsKeyStoreFile == null) {
throw new CommandLine.ParameterException(
commandLine, "Keystore file is required when TLS is enabled for JSON-RPC HTTP endpoint");
}
if (rpcHttpTlsKeyStorePasswordFile == null) {
throw new CommandLine.ParameterException(
commandLine,
"File containing password to unlock keystore is required when TLS is enabled for JSON-RPC HTTP endpoint");
}
if (isRpcHttpTlsClientAuthEnabled
&& !isRpcHttpTlsCAClientsEnabled
&& rpcHttpTlsKnownClientsFile == null) {
throw new CommandLine.ParameterException(
commandLine,
"Known-clients file must be specified or CA clients must be enabled when TLS client authentication is enabled for JSON-RPC HTTP endpoint");
}
rpcHttpTlsProtocols.retainAll(getJDKEnabledProtocols());
if (rpcHttpTlsProtocols.isEmpty()) {
throw new CommandLine.ParameterException(
commandLine,
"No valid TLS protocols specified (the following protocols are enabled: "
+ getJDKEnabledProtocols()
+ ")");
}
for (final String cipherSuite : rpcHttpTlsCipherSuites) {
if (!getJDKEnabledCipherSuites().contains(cipherSuite)) {
throw new CommandLine.ParameterException(
commandLine, "Invalid TLS cipher suite specified " + cipherSuite);
}
}
}
private Optional<TlsConfiguration> rpcHttpTlsConfiguration() {
if (!isRpcTlsConfigurationRequired()) {
return Optional.empty();
}
rpcHttpTlsCipherSuites.retainAll(getJDKEnabledCipherSuites());
return Optional.of(
TlsConfiguration.Builder.aTlsConfiguration()
.withKeyStorePath(rpcHttpTlsKeyStoreFile)
.withKeyStorePasswordSupplier(
new FileBasedPasswordProvider(rpcHttpTlsKeyStorePasswordFile))
.withClientAuthConfiguration(rpcHttpTlsClientAuthConfiguration())
.withSecureTransportProtocols(rpcHttpTlsProtocols)
.withCipherSuites(rpcHttpTlsCipherSuites)
.build());
}
private boolean isRpcTlsConfigurationRequired() {
return isRpcHttpEnabled && isRpcHttpTlsEnabled;
}
private TlsClientAuthConfiguration rpcHttpTlsClientAuthConfiguration() {
if (isRpcHttpTlsClientAuthEnabled) {
return TlsClientAuthConfiguration.Builder.aTlsClientAuthConfiguration()
.withKnownClientsFile(rpcHttpTlsKnownClientsFile)
.withCaClientsEnabled(isRpcHttpTlsCAClientsEnabled)
.build();
}
return null;
}
private static List<String> getJDKEnabledCipherSuites() {
try {
final SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
final SSLEngine engine = context.createSSLEngine();
return Arrays.asList(engine.getEnabledCipherSuites());
} catch (final KeyManagementException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private static List<String> getJDKEnabledProtocols() {
try {
final SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
final SSLEngine engine = context.createSSLEngine();
return Arrays.asList(engine.getEnabledProtocols());
} catch (final KeyManagementException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private String rpcHttpAuthenticationCredentialsFile(final CommandLine commandLine) {
final String filename = rpcHttpAuthenticationCredentialsFile;
if (filename != null) {
RpcAuthFileValidator.validate(commandLine, filename, "HTTP");
}
return filename;
}
/**
* Returns the list of APIs enabled for RPC over HTTP.
*
* @return A list of APIs
*/
public List<String> getRpcHttpApis() {
return rpcHttpApis;
}
/**
* Returns the port for RPC over HTTP.
*
* @return The port number
*/
public Integer getRpcHttpPort() {
return rpcHttpPort;
}
/**
* Checks if RPC over HTTP is enabled.
*
* @return true if enabled, false otherwise
*/
public Boolean isRpcHttpEnabled() {
return isRpcHttpEnabled;
}
}

@ -0,0 +1,926 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ETH;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.NET;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.PERM;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import org.hyperledger.besu.cli.CommandTestAbstract;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm;
import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration;
import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest;
import java.io.IOException;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class JsonRpcHttpOptionsTest extends CommandTestAbstract {
@Test
public void rpcHttpEnabledPropertyMustBeUsed() {
parseCommand("--rpc-http-enabled");
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().isEnabled()).isTrue();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcApisPropertyMustBeUsed() {
parseCommand("--rpc-http-api", "ETH,NET,PERM", "--rpc-http-enabled");
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
verify(mockLogger)
.warn("Permissions are disabled. Cannot enable PERM APIs when not using Permissions.");
assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis())
.containsExactlyInAnyOrder(ETH.name(), NET.name(), PERM.name());
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcApisPropertyIgnoresDuplicatesAndMustBeUsed() {
parseCommand("--rpc-http-api", "ETH,NET,NET", "--rpc-http-enabled");
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis())
.containsExactlyInAnyOrder(ETH.name(), NET.name());
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcApiNoAuthMethodsIgnoresDuplicatesAndMustBeUsed() {
parseCommand(
"--rpc-http-api-methods-no-auth",
"admin_peers, admin_peers, eth_getWork",
"--rpc-http-enabled");
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getNoAuthRpcApis())
.containsExactlyInAnyOrder(
RpcMethod.ADMIN_PEERS.getMethodName(), RpcMethod.ETH_GET_WORK.getMethodName());
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpNoAuthApiMethodsCannotBeInvalid() {
parseCommand("--rpc-http-enabled", "--rpc-http-api-method-no-auth", "invalid");
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains(
"Invalid value for option '--rpc-http-api-methods-no-auth', options must be valid RPC methods");
}
@Test
public void rpcHttpOptionsRequiresServiceToBeEnabled() {
parseCommand(
"--rpc-http-api",
"ETH,NET",
"--rpc-http-host",
"0.0.0.0",
"--rpc-http-port",
"1234",
"--rpc-http-cors-origins",
"all",
"--rpc-http-max-active-connections",
"88");
verifyOptionsConstraintLoggerCall(
"--rpc-http-enabled",
"--rpc-http-host",
"--rpc-http-port",
"--rpc-http-cors-origins",
"--rpc-http-api",
"--rpc-http-max-active-connections");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpOptionsRequiresServiceToBeEnabledToml() throws IOException {
final Path toml =
createTempFile(
"toml",
"rpc-http-api=[\"ETH\",\"NET\"]\n"
+ "rpc-http-host=\"0.0.0.0\"\n"
+ "rpc-http-port=1234\n"
+ "rpc-http-cors-origins=[\"all\"]\n"
+ "rpc-http-max-active-connections=88");
parseCommand("--config-file", toml.toString());
verifyOptionsConstraintLoggerCall(
"--rpc-http-enabled",
"--rpc-http-host",
"--rpc-http-port",
"--rpc-http-cors-origins",
"--rpc-http-api",
"--rpc-http-max-active-connections");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpHostAndPortOptionsMustBeUsed() {
final String host = "1.2.3.4";
final int port = 1234;
parseCommand(
"--rpc-http-enabled", "--rpc-http-host", host, "--rpc-http-port", String.valueOf(port));
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpHostMayBeLocalhost() {
final String host = "localhost";
parseCommand("--rpc-http-enabled", "--rpc-http-host", host);
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpHostMayBeIPv6() {
final String host = "2600:DB8::8545";
parseCommand("--rpc-http-enabled", "--rpc-http-host", host);
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpMaxActiveConnectionsPropertyMustBeUsed() {
final int maxConnections = 99;
parseCommand("--rpc-http-max-active-connections", String.valueOf(maxConnections));
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxActiveConnections())
.isEqualTo(maxConnections);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpTlsRequiresRpcHttpEnabled() {
parseCommand("--rpc-http-tls-enabled");
verifyOptionsConstraintLoggerCall("--rpc-http-enabled", "--rpc-http-tls-enabled");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpTlsRequiresRpcHttpEnabledToml() throws IOException {
final Path toml = createTempFile("toml", "rpc-http-tls-enabled=true\n");
parseCommand("--config-file", toml.toString());
verifyOptionsConstraintLoggerCall("--rpc-http-enabled", "--rpc-http-tls-enabled");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpTlsWithoutKeystoreReportsError() {
parseCommand("--rpc-http-enabled", "--rpc-http-tls-enabled");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Keystore file is required when TLS is enabled for JSON-RPC HTTP endpoint");
}
@Test
public void rpcHttpTlsWithoutPasswordfileReportsError() {
parseCommand(
"--rpc-http-enabled",
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
"/tmp/test.p12");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains(
"File containing password to unlock keystore is required when TLS is enabled for JSON-RPC HTTP endpoint");
}
@Test
public void rpcHttpTlsKeystoreAndPasswordMustBeUsed() {
final String host = "1.2.3.4";
final int port = 1234;
final String keystoreFile = "/tmp/test.p12";
final String keystorePasswordFile = "/tmp/test.txt";
parseCommand(
"--rpc-http-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
keystoreFile,
"--rpc-http-tls-keystore-password-file",
keystorePasswordFile);
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port);
final Optional<TlsConfiguration> tlsConfiguration =
jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration();
assertThat(tlsConfiguration.isPresent()).isTrue();
assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile));
assertThat(tlsConfiguration.get().getClientAuthConfiguration().isEmpty()).isTrue();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpTlsClientAuthWithoutKnownFileReportsError() {
final String host = "1.2.3.4";
final int port = 1234;
final String keystoreFile = "/tmp/test.p12";
final String keystorePasswordFile = "/tmp/test.txt";
parseCommand(
"--rpc-http-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
keystoreFile,
"--rpc-http-tls-keystore-password-file",
keystorePasswordFile,
"--rpc-http-tls-client-auth-enabled");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains(
"Known-clients file must be specified or CA clients must be enabled when TLS client authentication is enabled for JSON-RPC HTTP endpoint");
}
@Test
public void rpcHttpTlsClientAuthWithKnownClientFile() {
final String host = "1.2.3.4";
final int port = 1234;
final String keystoreFile = "/tmp/test.p12";
final String keystorePasswordFile = "/tmp/test.txt";
final String knownClientFile = "/tmp/knownClientFile";
parseCommand(
"--rpc-http-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
keystoreFile,
"--rpc-http-tls-keystore-password-file",
keystorePasswordFile,
"--rpc-http-tls-client-auth-enabled",
"--rpc-http-tls-known-clients-file",
knownClientFile);
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port);
final Optional<TlsConfiguration> tlsConfiguration =
jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration();
assertThat(tlsConfiguration.isPresent()).isTrue();
assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile));
assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue();
assertThat(
tlsConfiguration.get().getClientAuthConfiguration().get().getKnownClientsFile().get())
.isEqualTo(Path.of(knownClientFile));
assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled())
.isFalse();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpTlsClientAuthWithCAClient() {
final String host = "1.2.3.4";
final int port = 1234;
final String keystoreFile = "/tmp/test.p12";
final String keystorePasswordFile = "/tmp/test.txt";
parseCommand(
"--rpc-http-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
keystoreFile,
"--rpc-http-tls-keystore-password-file",
keystorePasswordFile,
"--rpc-http-tls-client-auth-enabled",
"--rpc-http-tls-ca-clients-enabled");
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port);
final Optional<TlsConfiguration> tlsConfiguration =
jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration();
assertThat(tlsConfiguration.isPresent()).isTrue();
assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile));
assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue();
assertThat(
tlsConfiguration
.get()
.getClientAuthConfiguration()
.get()
.getKnownClientsFile()
.isEmpty())
.isTrue();
assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled())
.isTrue();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpTlsClientAuthWithCAClientAndKnownClientFile() {
final String host = "1.2.3.4";
final int port = 1234;
final String keystoreFile = "/tmp/test.p12";
final String keystorePasswordFile = "/tmp/test.txt";
final String knownClientFile = "/tmp/knownClientFile";
parseCommand(
"--rpc-http-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
keystoreFile,
"--rpc-http-tls-keystore-password-file",
keystorePasswordFile,
"--rpc-http-tls-client-auth-enabled",
"--rpc-http-tls-ca-clients-enabled",
"--rpc-http-tls-known-clients-file",
knownClientFile);
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port);
final Optional<TlsConfiguration> tlsConfiguration =
jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration();
assertThat(tlsConfiguration.isPresent()).isTrue();
assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile));
assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue();
assertThat(
tlsConfiguration.get().getClientAuthConfiguration().get().getKnownClientsFile().get())
.isEqualTo(Path.of(knownClientFile));
assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled())
.isTrue();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpTlsCheckDefaultProtocolsAndCipherSuites() {
final String host = "1.2.3.4";
final int port = 1234;
final String keystoreFile = "/tmp/test.p12";
final String keystorePasswordFile = "/tmp/test.txt";
parseCommand(
"--rpc-http-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
keystoreFile,
"--rpc-http-tls-keystore-password-file",
keystorePasswordFile);
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port);
final Optional<TlsConfiguration> tlsConfiguration =
jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration();
assertThat(tlsConfiguration).isPresent();
assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile));
assertThat(tlsConfiguration.get().getClientAuthConfiguration()).isEmpty();
assertThat(tlsConfiguration.get().getCipherSuites().get()).isEmpty();
assertThat(tlsConfiguration.get().getSecureTransportProtocols().get())
.containsExactly("TLSv1.3", "TLSv1.2");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpTlsCheckInvalidProtocols() {
final String host = "1.2.3.4";
final int port = 1234;
final String keystoreFile = "/tmp/test.p12";
final String keystorePasswordFile = "/tmp/test.txt";
final String protocol = "TLsv1.4";
parseCommand(
"--rpc-http-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
keystoreFile,
"--rpc-http-tls-keystore-password-file",
keystorePasswordFile,
"--rpc-http-tls-protocols",
protocol);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).contains("No valid TLS protocols specified");
}
@Test
public void rpcHttpTlsCheckInvalidCipherSuites() {
final String host = "1.2.3.4";
final int port = 1234;
final String keystoreFile = "/tmp/test.p12";
final String keystorePasswordFile = "/tmp/test.txt";
final String cipherSuites = "Invalid";
parseCommand(
"--rpc-http-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
keystoreFile,
"--rpc-http-tls-keystore-password-file",
keystorePasswordFile,
"--rpc-http-tls-cipher-suites",
cipherSuites);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Invalid TLS cipher suite specified " + cipherSuites);
}
@Test
public void rpcHttpTlsCheckValidProtocolsAndCipherSuites() {
final String host = "1.2.3.4";
final int port = 1234;
final String keystoreFile = "/tmp/test.p12";
final String keystorePasswordFile = "/tmp/test.txt";
final String protocols = "TLSv1.3,TLSv1.2";
final String cipherSuites =
"TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
parseCommand(
"--rpc-http-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-enabled",
"--rpc-http-tls-keystore-file",
keystoreFile,
"--rpc-http-tls-keystore-password-file",
keystorePasswordFile,
"--rpc-http-tls-protocols",
protocols,
"--rpc-http-tls-cipher-suites",
cipherSuites);
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host);
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port);
final Optional<TlsConfiguration> tlsConfiguration =
jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration();
assertThat(tlsConfiguration).isPresent();
assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile));
assertThat(tlsConfiguration.get().getClientAuthConfiguration()).isEmpty();
assertThat(tlsConfiguration.get().getCipherSuites().get())
.containsExactlyInAnyOrder(
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
assertThat(tlsConfiguration.get().getSecureTransportProtocols().get())
.containsExactlyInAnyOrder("TLSv1.2", "TLSv1.3");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpTlsWarnIfCipherSuitesSpecifiedWithoutTls() {
final String host = "1.2.3.4";
final int port = 1234;
final String cipherSuites = "Invalid";
parseCommand(
"--rpc-http-enabled",
"--engine-rpc-enabled",
"--rpc-http-host",
host,
"--rpc-http-port",
String.valueOf(port),
"--rpc-http-tls-cipher-suite",
cipherSuites);
verify(
mockLogger,
times(2)) // this is verified for both the full suite of apis, and the engine group.
.warn(
"{} has been ignored because {} was not defined on the command line.",
"--rpc-http-tls-cipher-suite",
"--rpc-http-tls-enabled");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpCorsOriginsTwoDomainsMustBuildListWithBothDomains() {
final String[] origins = {"http://domain1.com", "https://domain2.com"};
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins));
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray())
.isEqualTo(origins);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpCorsOriginsDoubleCommaFilteredOut() {
final String[] origins = {"http://domain1.com", "https://domain2.com"};
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",,", origins));
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray())
.isEqualTo(origins);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpCorsOriginsWithWildcardMustBuildListWithWildcard() {
final String[] origins = {"*"};
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins));
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray())
.isEqualTo(origins);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpCorsOriginsWithAllMustBuildListWithWildcard() {
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", "all");
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains()).containsExactly("*");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpCorsOriginsWithNoneMustBuildEmptyList() {
final String[] origins = {"none"};
parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins));
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains()).isEmpty();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpCorsOriginsNoneWithAnotherDomainMustFail() {
final String[] origins = {"http://domain1.com", "none"};
parseCommand("--rpc-http-cors-origins", String.join(",", origins));
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Value 'none' can't be used with other domains");
}
@Test
public void rpcHttpCorsOriginsNoneWithAnotherDomainMustFailNoneFirst() {
final String[] origins = {"none", "http://domain1.com"};
parseCommand("--rpc-http-cors-origins", String.join(",", origins));
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Value 'none' can't be used with other domains");
}
@Test
public void rpcHttpCorsOriginsAllWithAnotherDomainMustFail() {
parseCommand("--rpc-http-cors-origins=http://domain1.com,all");
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Values '*' or 'all' can't be used with other domains");
}
@Test
public void rpcHttpCorsOriginsAllWithAnotherDomainMustFailAsFlags() {
parseCommand("--rpc-http-cors-origins=http://domain1.com", "--rpc-http-cors-origins=all");
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Values '*' or 'all' can't be used with other domains");
}
@Test
public void rpcHttpCorsOriginsWildcardWithAnotherDomainMustFail() {
parseCommand("--rpc-http-cors-origins=http://domain1.com,*");
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Values '*' or 'all' can't be used with other domains");
}
@Test
public void rpcHttpCorsOriginsWildcardWithAnotherDomainMustFailAsFlags() {
parseCommand("--rpc-http-cors-origins=http://domain1.com", "--rpc-http-cors-origins=*");
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Values '*' or 'all' can't be used with other domains");
}
@Test
public void rpcHttpCorsOriginsInvalidRegexShouldFail() {
final String[] origins = {"**"};
parseCommand("--rpc-http-cors-origins", String.join(",", origins));
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Domain values result in invalid regex pattern");
}
@Test
public void rpcHttpCorsOriginsEmptyValueFails() {
parseCommand("--rpc-http-cors-origins=");
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Domain cannot be empty string or null string.");
}
@Test
public void rpcApisPropertyWithInvalidEntryMustDisplayError() {
parseCommand("--rpc-http-api", "BOB");
Mockito.verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
// PicoCLI uses longest option name for message when option has multiple names, so here plural.
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Invalid value for option '--rpc-http-api': invalid entries found [BOB]");
}
@Test
public void rpcApisPropertyWithPluginNamespaceAreValid() {
rpcEndpointServiceImpl.registerRPCEndpoint(
"bob", "method", (Function<PluginRpcRequest, Object>) request -> "nothing");
parseCommand("--rpc-http-api", "BOB");
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis())
.containsExactlyInAnyOrder("BOB");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpMaxRequestContentLengthOptionMustBeUsed() {
final int rpcHttpMaxRequestContentLength = 1;
parseCommand(
"--rpc-http-max-request-content-length", Long.toString(rpcHttpMaxRequestContentLength));
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxRequestContentLength())
.isEqualTo(rpcHttpMaxRequestContentLength);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void rpcHttpMaxBatchSizeOptionMustBeUsed() {
final int rpcHttpMaxBatchSize = 1;
parseCommand("--rpc-http-max-batch-size", Integer.toString(rpcHttpMaxBatchSize));
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxBatchSize())
.isEqualTo(rpcHttpMaxBatchSize);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void portInUseReportsError() throws IOException {
final ServerSocket serverSocket = new ServerSocket(8545);
parseCommandWithPortCheck("--rpc-http-enabled");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains("Port(s) '[8545]' already in use. Check for other processes using the port(s).");
serverSocket.close();
}
@Test
public void assertThatCheckPortClashRejectsAsExpected() throws Exception {
// use WS port for HTTP
final int port = 8546;
parseCommand("--rpc-http-enabled", "--rpc-http-port", String.valueOf(port), "--rpc-ws-enabled");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains(
"Port number '8546' has been specified multiple times. Please review the supplied configuration.");
}
@Test
public void assertThatCheckPortClashAcceptsAsExpected() throws Exception {
// use WS port for HTTP
final int port = 8546;
parseCommand("--rpc-http-enabled", "--rpc-http-port", String.valueOf(port));
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void httpAuthenticationWithoutRequiredConfiguredOptionsMustFail() {
parseCommand("--rpc-http-enabled", "--rpc-http-authentication-enabled");
verifyNoInteractions(mockRunnerBuilder);
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains(
"Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file");
}
@Test
public void httpAuthenticationAlgorithIsConfigured() {
parseCommand("--rpc-http-authentication-jwt-algorithm", "ES256");
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getAuthenticationAlgorithm())
.isEqualTo(JwtAlgorithm.ES256);
}
@Test
public void httpAuthenticationPublicKeyIsConfigured() throws IOException {
final Path publicKey = Files.createTempFile("public_key", "");
parseCommand("--rpc-http-authentication-jwt-public-key-file", publicKey.toString());
verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(jsonRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath())
.isEqualTo(publicKey.toString());
}
}

@ -246,4 +246,17 @@ public class RpcWebsocketOptionsTest extends CommandTestAbstract {
.contains( .contains(
"Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file"); "Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file");
} }
@Test
public void rpcWsRpcEnabledPropertyDefaultIsFalse() {
parseCommand();
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
assertThat(wsRpcConfigArgumentCaptor.getValue().isEnabled()).isFalse();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
} }

@ -28,8 +28,14 @@ import org.hyperledger.besu.datatypes.Address;
import java.time.Clock; import java.time.Clock;
import java.util.Collection; import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** This is the full data set, or context, required for many of the aspects of BFT workflows. */ /** This is the full data set, or context, required for many of the aspects of BFT workflows. */
public class BftFinalState { public class BftFinalState {
private static final Logger LOG = LoggerFactory.getLogger(BftFinalState.class);
private final ValidatorProvider validatorProvider; private final ValidatorProvider validatorProvider;
private final NodeKey nodeKey; private final NodeKey nodeKey;
private final Address localAddress; private final Address localAddress;
@ -126,7 +132,9 @@ public class BftFinalState {
* @return the boolean * @return the boolean
*/ */
public boolean isLocalNodeValidator() { public boolean isLocalNodeValidator() {
return getValidators().contains(localAddress); final boolean isValidator = getValidators().contains(localAddress);
LOG.debug(isValidator ? "Local node is a validator" : "Local node is a non-validator");
return isValidator;
} }
/** /**

@ -91,8 +91,6 @@ public class ForkchoiceEvent {
+ safeBlockHash + safeBlockHash
+ ", finalizedBlockHash=" + ", finalizedBlockHash="
+ finalizedBlockHash + finalizedBlockHash
+ ", safeBlockHash="
+ safeBlockHash
+ '}'; + '}';
} }
} }

@ -61,7 +61,7 @@ public class UndoScalar<T> implements Undoable {
} }
/** /**
* Has this scalar had any change since the inital value * Has this scalar had any change since the initial value
* *
* @return true if there are any changes to undo * @return true if there are any changes to undo
*/ */

@ -69,7 +69,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) { tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought" description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files files = sourceSets.main.allJava.files
knownHash = 'ZsovOR0oPfomcLP4b+HjikWzM0Tx6sCwi68mf5qwZf4=' knownHash = 'VpNy2KuAtEUc9hPguNivbjwy2YM3vIF444RCREJojqY='
} }
check.dependsOn('checkAPIChanges') check.dependsOn('checkAPIChanges')

@ -23,7 +23,7 @@ import java.util.Map;
import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256; import org.apache.tuweni.units.bigints.UInt256;
/** Accumulator interface tor provding trie updates for creating TrieLogs. */ /** Accumulator interface for providing trie updates for creating TrieLogs. */
public interface TrieLogAccumulator { public interface TrieLogAccumulator {
/** /**

Loading…
Cancel
Save