mirror of https://github.com/hyperledger/besu
commit
36ee9397c5
@ -0,0 +1,41 @@ |
||||
/* |
||||
* 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.config; |
||||
|
||||
/** Enum for profile names. Each profile corresponds to a configuration file. */ |
||||
public enum ProfileName { |
||||
/** The 'DEV' profile. Corresponds to the 'profiles/dev.toml' configuration file. */ |
||||
DEV("profiles/dev.toml"); |
||||
|
||||
private final String configFile; |
||||
|
||||
/** |
||||
* Constructs a new ProfileName. |
||||
* |
||||
* @param configFile the configuration file corresponding to the profile |
||||
*/ |
||||
ProfileName(final String configFile) { |
||||
this.configFile = configFile; |
||||
} |
||||
|
||||
/** |
||||
* Gets the configuration file corresponding to the profile. |
||||
* |
||||
* @return the configuration file |
||||
*/ |
||||
public String getConfigFile() { |
||||
return configFile; |
||||
} |
||||
} |
@ -0,0 +1,266 @@ |
||||
/* |
||||
* 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 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.websocket.WebSocketConfiguration.DEFAULT_WEBSOCKET_PORT; |
||||
|
||||
import org.hyperledger.besu.cli.DefaultCommandValues; |
||||
import org.hyperledger.besu.cli.custom.RpcAuthFileValidator; |
||||
import org.hyperledger.besu.cli.util.CommandLineUtils; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; |
||||
|
||||
import java.io.File; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.function.Predicate; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import com.google.common.base.Strings; |
||||
import org.slf4j.Logger; |
||||
import picocli.CommandLine; |
||||
|
||||
/** This class represents the WebSocket options for the RPC. */ |
||||
public class RpcWebsocketOptions { |
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-authentication-jwt-algorithm"}, |
||||
description = |
||||
"Encryption algorithm used for Websockets JWT public key. Possible values are ${COMPLETION-CANDIDATES}" |
||||
+ " (default: ${DEFAULT-VALUE})", |
||||
arity = "1") |
||||
private final JwtAlgorithm rpcWebsocketsAuthenticationAlgorithm = |
||||
DefaultCommandValues.DEFAULT_JWT_ALGORITHM; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-enabled"}, |
||||
description = "Set to start the JSON-RPC WebSocket service (default: ${DEFAULT-VALUE})") |
||||
private final Boolean isRpcWsEnabled = false; |
||||
|
||||
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-host"}, |
||||
paramLabel = DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP, |
||||
description = "Host for JSON-RPC WebSocket service to listen on (default: ${DEFAULT-VALUE})", |
||||
arity = "1") |
||||
private String rpcWsHost; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-port"}, |
||||
paramLabel = DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP, |
||||
description = "Port for JSON-RPC WebSocket service to listen on (default: ${DEFAULT-VALUE})", |
||||
arity = "1") |
||||
private final Integer rpcWsPort = DEFAULT_WEBSOCKET_PORT; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-max-frame-size"}, |
||||
description = |
||||
"Maximum size in bytes for JSON-RPC WebSocket frames (default: ${DEFAULT-VALUE}). If this limit is exceeded, the websocket will be disconnected.", |
||||
arity = "1") |
||||
private final Integer rpcWsMaxFrameSize = DefaultCommandValues.DEFAULT_WS_MAX_FRAME_SIZE; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-max-active-connections"}, |
||||
description = |
||||
"Maximum number of WebSocket connections allowed for JSON-RPC (default: ${DEFAULT-VALUE}). Once this limit is reached, incoming connections will be rejected.", |
||||
arity = "1") |
||||
private final Integer rpcWsMaxConnections = DefaultCommandValues.DEFAULT_WS_MAX_CONNECTIONS; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-api", "--rpc-ws-apis"}, |
||||
paramLabel = "<api name>", |
||||
split = " {0,1}, {0,1}", |
||||
arity = "1..*", |
||||
description = |
||||
"Comma separated list of APIs to enable on JSON-RPC WebSocket service (default: ${DEFAULT-VALUE})") |
||||
private final List<String> rpcWsApis = DEFAULT_RPC_APIS; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-api-methods-no-auth", "--rpc-ws-api-method-no-auth"}, |
||||
paramLabel = "<api name>", |
||||
split = " {0,1}, {0,1}", |
||||
arity = "1..*", |
||||
description = |
||||
"Comma separated list of RPC methods to exclude from RPC authentication services, RPC WebSocket authentication must be enabled") |
||||
private final List<String> rpcWsApiMethodsNoAuth = new ArrayList<String>(); |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-authentication-enabled"}, |
||||
description = |
||||
"Require authentication for the JSON-RPC WebSocket service (default: ${DEFAULT-VALUE})") |
||||
private final Boolean isRpcWsAuthenticationEnabled = false; |
||||
|
||||
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-authentication-credentials-file"}, |
||||
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, |
||||
description = |
||||
"Storage file for JSON-RPC WebSocket authentication credentials (default: ${DEFAULT-VALUE})", |
||||
arity = "1") |
||||
private String rpcWsAuthenticationCredentialsFile = null; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--rpc-ws-authentication-jwt-public-key-file"}, |
||||
paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, |
||||
description = "JWT public key file for JSON-RPC WebSocket authentication", |
||||
arity = "1") |
||||
private final File rpcWsAuthenticationPublicKeyFile = null; |
||||
|
||||
/** |
||||
* Validates the WebSocket 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) { |
||||
checkOptionDependencies(logger, commandLine); |
||||
|
||||
if (!rpcWsApis.stream().allMatch(configuredApis)) { |
||||
final List<String> invalidWsApis = new ArrayList<>(rpcWsApis); |
||||
invalidWsApis.removeAll(VALID_APIS); |
||||
throw new CommandLine.ParameterException( |
||||
commandLine, |
||||
"Invalid value for option '--rpc-ws-api': invalid entries found " + invalidWsApis); |
||||
} |
||||
|
||||
final boolean validWsApiMethods = |
||||
rpcWsApiMethodsNoAuth.stream().allMatch(RpcMethod::rpcMethodExists); |
||||
|
||||
if (!validWsApiMethods) { |
||||
throw new CommandLine.ParameterException( |
||||
commandLine, |
||||
"Invalid value for option '--rpc-ws-api-methods-no-auth', options must be valid RPC methods"); |
||||
} |
||||
|
||||
if (isRpcWsAuthenticationEnabled |
||||
&& rpcWsAuthenticationCredentialsFile(commandLine) == null |
||||
&& rpcWsAuthenticationPublicKeyFile == null) { |
||||
throw new CommandLine.ParameterException( |
||||
commandLine, |
||||
"Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Checks the dependencies of the WebSocket options. |
||||
* |
||||
* @param logger Logger instance |
||||
* @param commandLine CommandLine instance |
||||
*/ |
||||
private void checkOptionDependencies(final Logger logger, final CommandLine commandLine) { |
||||
CommandLineUtils.checkOptionDependencies( |
||||
logger, |
||||
commandLine, |
||||
"--rpc-ws-enabled", |
||||
!isRpcWsEnabled, |
||||
List.of( |
||||
"--rpc-ws-api", |
||||
"--rpc-ws-apis", |
||||
"--rpc-ws-api-method-no-auth", |
||||
"--rpc-ws-api-methods-no-auth", |
||||
"--rpc-ws-host", |
||||
"--rpc-ws-port", |
||||
"--rpc-ws-max-frame-size", |
||||
"--rpc-ws-max-active-connections", |
||||
"--rpc-ws-authentication-enabled", |
||||
"--rpc-ws-authentication-credentials-file", |
||||
"--rpc-ws-authentication-public-key-file", |
||||
"--rpc-ws-authentication-jwt-algorithm")); |
||||
|
||||
if (isRpcWsAuthenticationEnabled) { |
||||
CommandLineUtils.checkOptionDependencies( |
||||
logger, |
||||
commandLine, |
||||
"--rpc-ws-authentication-public-key-file", |
||||
rpcWsAuthenticationPublicKeyFile == null, |
||||
List.of("--rpc-ws-authentication-jwt-algorithm")); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates a WebSocket configuration based on the WebSocket options. |
||||
* |
||||
* @param hostsAllowlist List of allowed hosts |
||||
* @param defaultHostAddress Default host address |
||||
* @param wsTimoutSec WebSocket timeout in seconds |
||||
* @return WebSocketConfiguration instance |
||||
*/ |
||||
public WebSocketConfiguration webSocketConfiguration( |
||||
final List<String> hostsAllowlist, final String defaultHostAddress, final Long wsTimoutSec) { |
||||
final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); |
||||
webSocketConfiguration.setEnabled(isRpcWsEnabled); |
||||
webSocketConfiguration.setHost( |
||||
Strings.isNullOrEmpty(rpcWsHost) ? defaultHostAddress : rpcWsHost); |
||||
webSocketConfiguration.setPort(rpcWsPort); |
||||
webSocketConfiguration.setMaxFrameSize(rpcWsMaxFrameSize); |
||||
webSocketConfiguration.setMaxActiveConnections(rpcWsMaxConnections); |
||||
webSocketConfiguration.setRpcApis(rpcWsApis); |
||||
webSocketConfiguration.setRpcApisNoAuth( |
||||
rpcWsApiMethodsNoAuth.stream().distinct().collect(Collectors.toList())); |
||||
webSocketConfiguration.setAuthenticationEnabled(isRpcWsAuthenticationEnabled); |
||||
webSocketConfiguration.setAuthenticationCredentialsFile(rpcWsAuthenticationCredentialsFile); |
||||
webSocketConfiguration.setHostsAllowlist(hostsAllowlist); |
||||
webSocketConfiguration.setAuthenticationPublicKeyFile(rpcWsAuthenticationPublicKeyFile); |
||||
webSocketConfiguration.setAuthenticationAlgorithm(rpcWebsocketsAuthenticationAlgorithm); |
||||
webSocketConfiguration.setTimeoutSec(wsTimoutSec); |
||||
return webSocketConfiguration; |
||||
} |
||||
|
||||
/** |
||||
* Validates the authentication credentials file for the WebSocket. |
||||
* |
||||
* @param commandLine CommandLine instance |
||||
* @return Filename of the authentication credentials file |
||||
*/ |
||||
private String rpcWsAuthenticationCredentialsFile(final CommandLine commandLine) { |
||||
final String filename = rpcWsAuthenticationCredentialsFile; |
||||
|
||||
if (filename != null) { |
||||
RpcAuthFileValidator.validate(commandLine, filename, "WS"); |
||||
} |
||||
return filename; |
||||
} |
||||
|
||||
/** |
||||
* Returns the list of APIs for the WebSocket. |
||||
* |
||||
* @return List of APIs |
||||
*/ |
||||
public List<String> getRpcWsApis() { |
||||
return rpcWsApis; |
||||
} |
||||
|
||||
/** |
||||
* Checks if the WebSocket service is enabled. |
||||
* |
||||
* @return Boolean indicating if the WebSocket service is enabled |
||||
*/ |
||||
public Boolean isRpcWsEnabled() { |
||||
return isRpcWsEnabled; |
||||
} |
||||
|
||||
/** |
||||
* Returns the port for the WebSocket service. |
||||
* |
||||
* @return Port number |
||||
*/ |
||||
public Integer getRpcWsPort() { |
||||
return rpcWsPort; |
||||
} |
||||
} |
@ -0,0 +1,125 @@ |
||||
/* |
||||
* 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.util; |
||||
|
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import picocli.CommandLine; |
||||
|
||||
/** |
||||
* Abstract class for finding configuration resources. This class provides a common structure for |
||||
* classes that need to find configuration resources based on command line options and environment |
||||
* variables. |
||||
* |
||||
* @param <T> the type of configuration resource this finder will return |
||||
*/ |
||||
public abstract class AbstractConfigurationFinder<T> { |
||||
|
||||
/** |
||||
* Returns the name of the configuration option. |
||||
* |
||||
* @return the name of the configuration option |
||||
*/ |
||||
protected abstract String getConfigOptionName(); |
||||
|
||||
/** |
||||
* Returns the name of the environment variable for the configuration. |
||||
* |
||||
* @return the name of the environment variable for the configuration |
||||
*/ |
||||
protected abstract String getConfigEnvName(); |
||||
|
||||
/** |
||||
* Finds the configuration resource based on command line options and environment variables. |
||||
* |
||||
* @param environment the environment variables |
||||
* @param parseResult the command line parse result |
||||
* @return an Optional containing the configuration resource, or an empty Optional if no |
||||
* configuration resource was found |
||||
*/ |
||||
public Optional<T> findConfiguration( |
||||
final Map<String, String> environment, final CommandLine.ParseResult parseResult) { |
||||
final CommandLine commandLine = parseResult.commandSpec().commandLine(); |
||||
if (isConfigSpecifiedInBothSources(environment, parseResult)) { |
||||
throwExceptionForBothSourcesSpecified(environment, parseResult, commandLine); |
||||
} |
||||
if (parseResult.hasMatchedOption(getConfigOptionName())) { |
||||
return getFromOption(parseResult, commandLine); |
||||
} |
||||
if (environment.containsKey(getConfigEnvName())) { |
||||
return getFromEnvironment(environment, commandLine); |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
/** |
||||
* Gets the configuration resource from the command line option. |
||||
* |
||||
* @param parseResult the command line parse result |
||||
* @param commandLine the command line |
||||
* @return an Optional containing the configuration resource, or an empty Optional if the |
||||
* configuration resource was not specified in the command line option |
||||
*/ |
||||
protected abstract Optional<T> getFromOption( |
||||
final CommandLine.ParseResult parseResult, final CommandLine commandLine); |
||||
|
||||
/** |
||||
* Gets the configuration resource from the environment variable. |
||||
* |
||||
* @param environment the environment variables |
||||
* @param commandLine the command line |
||||
* @return an Optional containing the configuration resource, or an empty Optional if the |
||||
* configuration resource was not specified in the environment variable |
||||
*/ |
||||
protected abstract Optional<T> getFromEnvironment( |
||||
final Map<String, String> environment, final CommandLine commandLine); |
||||
|
||||
/** |
||||
* Checks if the configuration resource is specified in both command line options and environment |
||||
* variables. |
||||
* |
||||
* @param environment the environment variables |
||||
* @param parseResult the command line parse result |
||||
* @return true if the configuration resource is specified in both places, false otherwise |
||||
*/ |
||||
public boolean isConfigSpecifiedInBothSources( |
||||
final Map<String, String> environment, final CommandLine.ParseResult parseResult) { |
||||
return parseResult.hasMatchedOption(getConfigOptionName()) |
||||
&& environment.containsKey(getConfigEnvName()); |
||||
} |
||||
|
||||
/** |
||||
* Throws an exception if the configuration resource is specified in both command line options and |
||||
* environment variables. |
||||
* |
||||
* @param environment the environment variables |
||||
* @param parseResult the command line parse result |
||||
* @param commandLine the command line |
||||
*/ |
||||
public void throwExceptionForBothSourcesSpecified( |
||||
final Map<String, String> environment, |
||||
final CommandLine.ParseResult parseResult, |
||||
final CommandLine commandLine) { |
||||
throw new CommandLine.ParameterException( |
||||
commandLine, |
||||
String.format( |
||||
"Both %s=%s and %s %s specified. Please specify only one.", |
||||
getConfigEnvName(), |
||||
getConfigOptionName(), |
||||
environment.get(getConfigEnvName()), |
||||
parseResult.matchedOption(getConfigOptionName()).stringValues())); |
||||
} |
||||
} |
@ -0,0 +1,100 @@ |
||||
/* |
||||
* 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.util; |
||||
|
||||
import static org.hyperledger.besu.cli.DefaultCommandValues.CONFIG_FILE_OPTION_NAME; |
||||
|
||||
import java.io.File; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import picocli.CommandLine; |
||||
|
||||
/** |
||||
* Class for finding configuration files. This class extends the AbstractConfigurationFinder and |
||||
* provides methods for finding configuration files based on command line options and environment |
||||
* variables. |
||||
*/ |
||||
public class ConfigFileFinder extends AbstractConfigurationFinder<File> { |
||||
private static final String CONFIG_FILE_ENV_NAME = "BESU_CONFIG_FILE"; |
||||
|
||||
/** |
||||
* Returns the name of the configuration option. |
||||
* |
||||
* @return the name of the configuration option |
||||
*/ |
||||
@Override |
||||
protected String getConfigOptionName() { |
||||
return CONFIG_FILE_OPTION_NAME; |
||||
} |
||||
|
||||
/** |
||||
* Returns the name of the environment variable for the configuration. |
||||
* |
||||
* @return the name of the environment variable for the configuration |
||||
*/ |
||||
@Override |
||||
protected String getConfigEnvName() { |
||||
return CONFIG_FILE_ENV_NAME; |
||||
} |
||||
|
||||
/** |
||||
* Gets the configuration file from the command line option. |
||||
* |
||||
* @param parseResult the command line parse result |
||||
* @param commandLine the command line |
||||
* @return an Optional containing the configuration file, or an empty Optional if the |
||||
* configuration file was not specified in the command line option |
||||
*/ |
||||
@Override |
||||
public Optional<File> getFromOption( |
||||
final CommandLine.ParseResult parseResult, final CommandLine commandLine) { |
||||
final CommandLine.Model.OptionSpec configFileOption = |
||||
parseResult.matchedOption(CONFIG_FILE_OPTION_NAME); |
||||
try { |
||||
File file = configFileOption.getter().get(); |
||||
if (!file.exists()) { |
||||
throw new CommandLine.ParameterException( |
||||
commandLine, |
||||
String.format("Unable to read TOML configuration, file not found: %s", file)); |
||||
} |
||||
return Optional.of(file); |
||||
} catch (final Exception e) { |
||||
throw new CommandLine.ParameterException(commandLine, e.getMessage(), e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Gets the configuration file from the environment variable. |
||||
* |
||||
* @param environment the environment variables |
||||
* @param commandLine the command line |
||||
* @return an Optional containing the configuration file, or an empty Optional if the |
||||
* configuration file was not specified in the environment variable |
||||
*/ |
||||
@Override |
||||
public Optional<File> getFromEnvironment( |
||||
final Map<String, String> environment, final CommandLine commandLine) { |
||||
final File toml = new File(environment.get(CONFIG_FILE_ENV_NAME)); |
||||
if (!toml.exists()) { |
||||
throw new CommandLine.ParameterException( |
||||
commandLine, |
||||
String.format( |
||||
"TOML file %s specified in environment variable %s not found", |
||||
CONFIG_FILE_ENV_NAME, environment.get(CONFIG_FILE_ENV_NAME))); |
||||
} |
||||
return Optional.of(toml); |
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
/* |
||||
* 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.util; |
||||
|
||||
import static org.hyperledger.besu.cli.DefaultCommandValues.PROFILE_OPTION_NAME; |
||||
|
||||
import org.hyperledger.besu.cli.config.ProfileName; |
||||
|
||||
import java.io.InputStream; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import picocli.CommandLine; |
||||
|
||||
/** |
||||
* Class for finding profile configurations. This class extends the AbstractConfigurationFinder and |
||||
* provides methods for finding profile configurations based on command line options and environment |
||||
* variables. Each profile corresponds to a TOML configuration file that contains settings for |
||||
* various options. The profile to use can be specified with the '--profile' command line option or |
||||
* the 'BESU_PROFILE' environment variable. |
||||
*/ |
||||
public class ProfileFinder extends AbstractConfigurationFinder<InputStream> { |
||||
private static final String PROFILE_ENV_NAME = "BESU_PROFILE"; |
||||
|
||||
@Override |
||||
protected String getConfigOptionName() { |
||||
return PROFILE_OPTION_NAME; |
||||
} |
||||
|
||||
@Override |
||||
protected String getConfigEnvName() { |
||||
return PROFILE_ENV_NAME; |
||||
} |
||||
|
||||
@Override |
||||
public Optional<InputStream> getFromOption( |
||||
final CommandLine.ParseResult parseResult, final CommandLine commandLine) { |
||||
try { |
||||
return getProfile(parseResult.matchedOption(PROFILE_OPTION_NAME).getter().get(), commandLine); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Optional<InputStream> getFromEnvironment( |
||||
final Map<String, String> environment, final CommandLine commandLine) { |
||||
return getProfile(ProfileName.valueOf(environment.get(PROFILE_ENV_NAME)), commandLine); |
||||
} |
||||
|
||||
private static Optional<InputStream> getProfile( |
||||
final ProfileName profileName, final CommandLine commandLine) { |
||||
return Optional.of(getTomlFile(commandLine, profileName.getConfigFile())); |
||||
} |
||||
|
||||
private static InputStream getTomlFile(final CommandLine commandLine, final String file) { |
||||
InputStream resourceUrl = ProfileFinder.class.getClassLoader().getResourceAsStream(file); |
||||
if (resourceUrl == null) { |
||||
throw new CommandLine.ParameterException( |
||||
commandLine, String.format("TOML file %s not found", file)); |
||||
} |
||||
return resourceUrl; |
||||
} |
||||
} |
@ -0,0 +1,306 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static java.util.Arrays.asList; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.cli.config.NetworkName.DEV; |
||||
import static org.hyperledger.besu.cli.config.NetworkName.MAINNET; |
||||
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ETH; |
||||
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.WEB3; |
||||
import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.MAINNET_BOOTSTRAP_NODES; |
||||
import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.MAINNET_DISCOVERY_URL; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.eq; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
import org.hyperledger.besu.cli.config.EthNetworkConfig; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; |
||||
import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; |
||||
import org.hyperledger.besu.ethereum.eth.sync.SyncMode; |
||||
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; |
||||
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; |
||||
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; |
||||
import org.hyperledger.besu.plugin.data.EnodeURL; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.math.BigInteger; |
||||
import java.net.URL; |
||||
import java.nio.file.Path; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import com.google.common.io.Resources; |
||||
import io.vertx.core.json.JsonObject; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.io.TempDir; |
||||
import org.mockito.ArgumentCaptor; |
||||
|
||||
public class CascadingDefaultProviderTest extends CommandTestAbstract { |
||||
private static final int GENESIS_CONFIG_TEST_CHAINID = 3141592; |
||||
private static final String VALID_NODE_ID = |
||||
"6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"; |
||||
private static final JsonObject GENESIS_VALID_JSON = |
||||
(new JsonObject()) |
||||
.put("config", (new JsonObject()).put("chainId", GENESIS_CONFIG_TEST_CHAINID)); |
||||
|
||||
/** |
||||
* Test if the default values are overridden if the key is present in the configuration file. The |
||||
* test checks if the configuration file correctly overrides the default values for various |
||||
* settings, such as the JSON-RPC configuration, GraphQL configuration, WebSocket configuration, |
||||
* and metrics configuration. |
||||
*/ |
||||
@Test |
||||
public void overrideDefaultValuesIfKeyIsPresentInConfigFile(final @TempDir File dataFolder) |
||||
throws IOException { |
||||
final URL configFile = this.getClass().getResource("/complete_config.toml"); |
||||
final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); |
||||
final String updatedConfig = |
||||
Resources.toString(configFile, UTF_8) |
||||
.replace("/opt/besu/genesis.json", escapeTomlString(genesisFile.toString())) |
||||
.replace( |
||||
"data-path=\"/opt/besu\"", |
||||
"data-path=\"" + escapeTomlString(dataFolder.getPath()) + "\""); |
||||
|
||||
final Path toml = createTempFile("toml", updatedConfig.getBytes(UTF_8)); |
||||
|
||||
final List<String> expectedApis = asList(ETH.name(), WEB3.name()); |
||||
|
||||
final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); |
||||
jsonRpcConfiguration.setEnabled(false); |
||||
jsonRpcConfiguration.setHost("5.6.7.8"); |
||||
jsonRpcConfiguration.setPort(5678); |
||||
jsonRpcConfiguration.setCorsAllowedDomains(Collections.emptyList()); |
||||
jsonRpcConfiguration.setRpcApis(expectedApis); |
||||
jsonRpcConfiguration.setMaxActiveConnections(1000); |
||||
|
||||
final GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration.createDefault(); |
||||
graphQLConfiguration.setEnabled(false); |
||||
graphQLConfiguration.setHost("6.7.8.9"); |
||||
graphQLConfiguration.setPort(6789); |
||||
|
||||
final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); |
||||
webSocketConfiguration.setEnabled(false); |
||||
webSocketConfiguration.setHost("9.10.11.12"); |
||||
webSocketConfiguration.setPort(9101); |
||||
webSocketConfiguration.setRpcApis(expectedApis); |
||||
|
||||
final MetricsConfiguration metricsConfiguration = |
||||
MetricsConfiguration.builder().enabled(false).host("8.6.7.5").port(309).build(); |
||||
|
||||
parseCommand("--config-file", toml.toString()); |
||||
|
||||
verify(mockRunnerBuilder).discovery(eq(false)); |
||||
verify(mockRunnerBuilder).ethNetworkConfig(ethNetworkConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).p2pAdvertisedHost(eq("1.2.3.4")); |
||||
verify(mockRunnerBuilder).p2pListenPort(eq(1234)); |
||||
verify(mockRunnerBuilder).jsonRpcConfiguration(eq(jsonRpcConfiguration)); |
||||
verify(mockRunnerBuilder).graphQLConfiguration(eq(graphQLConfiguration)); |
||||
verify(mockRunnerBuilder).webSocketConfiguration(eq(webSocketConfiguration)); |
||||
verify(mockRunnerBuilder).metricsConfiguration(eq(metricsConfiguration)); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
final List<EnodeURL> nodes = |
||||
asList( |
||||
EnodeURLImpl.fromString("enode://" + VALID_NODE_ID + "@192.168.0.1:4567"), |
||||
EnodeURLImpl.fromString("enode://" + VALID_NODE_ID + "@192.168.0.1:4567"), |
||||
EnodeURLImpl.fromString("enode://" + VALID_NODE_ID + "@192.168.0.1:4567")); |
||||
assertThat(ethNetworkConfigArgumentCaptor.getValue().getBootNodes()).isEqualTo(nodes); |
||||
|
||||
final EthNetworkConfig networkConfig = |
||||
new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(MAINNET)) |
||||
.setNetworkId(BigInteger.valueOf(42)) |
||||
.setGenesisConfig(encodeJsonGenesis(GENESIS_VALID_JSON)) |
||||
.setBootNodes(nodes) |
||||
.setDnsDiscoveryUrl(null) |
||||
.build(); |
||||
verify(mockControllerBuilder).dataDirectory(eq(dataFolder.toPath())); |
||||
verify(mockControllerBuilderFactory).fromEthNetworkConfig(eq(networkConfig), any(), any()); |
||||
verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); |
||||
|
||||
assertThat(syncConfigurationCaptor.getValue().getSyncMode()).isEqualTo(SyncMode.FAST); |
||||
assertThat(syncConfigurationCaptor.getValue().getFastSyncMinimumPeerCount()).isEqualTo(13); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
/** |
||||
* Test if the default values are not overridden if the key is not present in the configuration |
||||
* file. The test checks if the default values for various settings remain unchanged when the |
||||
* corresponding keys are not present in the configuration file. |
||||
*/ |
||||
@Test |
||||
public void noOverrideDefaultValuesIfKeyIsNotPresentInConfigFile() { |
||||
final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); |
||||
|
||||
parseCommand("--config-file", configFile); |
||||
final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); |
||||
|
||||
final GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration.createDefault(); |
||||
|
||||
final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); |
||||
|
||||
final MetricsConfiguration metricsConfiguration = MetricsConfiguration.builder().build(); |
||||
|
||||
verify(mockRunnerBuilder).discovery(eq(true)); |
||||
verify(mockRunnerBuilder) |
||||
.ethNetworkConfig( |
||||
new EthNetworkConfig( |
||||
EthNetworkConfig.jsonConfig(MAINNET), |
||||
MAINNET.getNetworkId(), |
||||
MAINNET_BOOTSTRAP_NODES, |
||||
MAINNET_DISCOVERY_URL)); |
||||
verify(mockRunnerBuilder).p2pAdvertisedHost(eq("127.0.0.1")); |
||||
verify(mockRunnerBuilder).p2pListenPort(eq(30303)); |
||||
verify(mockRunnerBuilder).jsonRpcConfiguration(eq(jsonRpcConfiguration)); |
||||
verify(mockRunnerBuilder).graphQLConfiguration(eq(graphQLConfiguration)); |
||||
verify(mockRunnerBuilder).webSocketConfiguration(eq(webSocketConfiguration)); |
||||
verify(mockRunnerBuilder).metricsConfiguration(eq(metricsConfiguration)); |
||||
verify(mockRunnerBuilder).build(); |
||||
verify(mockControllerBuilder).build(); |
||||
verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); |
||||
|
||||
final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); |
||||
assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); |
||||
assertThat(syncConfig.getFastSyncMinimumPeerCount()).isEqualTo(5); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
/** |
||||
* Test if the environment variable overrides the value from the configuration file. The test |
||||
* checks if the value of the miner's coinbase address set through an environment variable |
||||
* correctly overrides the value specified in the configuration file. |
||||
*/ |
||||
@Test |
||||
public void envVariableOverridesValueFromConfigFile() { |
||||
final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); |
||||
final String expectedCoinbase = "0x0000000000000000000000000000000000000004"; |
||||
setEnvironmentVariable("BESU_MINER_COINBASE", expectedCoinbase); |
||||
parseCommand("--config-file", configFile); |
||||
|
||||
verify(mockControllerBuilder) |
||||
.miningParameters( |
||||
ImmutableMiningParameters.builder() |
||||
.mutableInitValues( |
||||
ImmutableMiningParameters.MutableInitValues.builder() |
||||
.coinbase(Address.fromHexString(expectedCoinbase)) |
||||
.build()) |
||||
.build()); |
||||
} |
||||
|
||||
/** |
||||
* Test if the command line option overrides the environment variable and configuration. The test |
||||
* checks if the value of the miner's coinbase address set through a command line option correctly |
||||
* overrides the value specified in the environment variable and the configuration file. |
||||
*/ |
||||
@Test |
||||
public void cliOptionOverridesEnvVariableAndConfig() { |
||||
final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); |
||||
final String expectedCoinbase = "0x0000000000000000000000000000000000000006"; |
||||
setEnvironmentVariable("BESU_MINER_COINBASE", "0x0000000000000000000000000000000000000004"); |
||||
parseCommand("--config-file", configFile, "--miner-coinbase", expectedCoinbase); |
||||
|
||||
verify(mockControllerBuilder) |
||||
.miningParameters( |
||||
ImmutableMiningParameters.builder() |
||||
.mutableInitValues( |
||||
ImmutableMiningParameters.MutableInitValues.builder() |
||||
.coinbase(Address.fromHexString(expectedCoinbase)) |
||||
.build()) |
||||
.build()); |
||||
} |
||||
|
||||
/** |
||||
* Test if the profile option sets the correct defaults. The test checks if the 'dev' profile |
||||
* correctly sets the network ID to the expected value. |
||||
*/ |
||||
@Test |
||||
public void profileOptionShouldSetCorrectDefaults() { |
||||
final ArgumentCaptor<EthNetworkConfig> networkArg = |
||||
ArgumentCaptor.forClass(EthNetworkConfig.class); |
||||
|
||||
parseCommand("--profile", "dev"); |
||||
verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); |
||||
verify(mockControllerBuilder).build(); |
||||
|
||||
final EthNetworkConfig config = networkArg.getValue(); |
||||
assertThat(config.getNetworkId()).isEqualTo(DEV.getNetworkId()); |
||||
} |
||||
|
||||
/** |
||||
* Test if the command line option overrides the profile configuration. The test checks if the |
||||
* network ID set through a command line option correctly overrides the value specified in the |
||||
* 'dev' profile. |
||||
*/ |
||||
@Test |
||||
public void cliOptionOverridesProfileConfiguration() { |
||||
final ArgumentCaptor<EthNetworkConfig> networkArg = |
||||
ArgumentCaptor.forClass(EthNetworkConfig.class); |
||||
|
||||
parseCommand("--profile", "dev", "--network", "MAINNET"); |
||||
verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); |
||||
verify(mockControllerBuilder).build(); |
||||
|
||||
final EthNetworkConfig config = networkArg.getValue(); |
||||
assertThat(config.getNetworkId()).isEqualTo(MAINNET.getNetworkId()); |
||||
} |
||||
|
||||
/** |
||||
* Test if the configuration file overrides the profile configuration. The test checks if the |
||||
* network ID specified in the configuration file correctly overrides the value specified in the |
||||
* 'dev' profile. |
||||
*/ |
||||
@Test |
||||
public void configFileOverridesProfileConfiguration() { |
||||
final ArgumentCaptor<EthNetworkConfig> networkArg = |
||||
ArgumentCaptor.forClass(EthNetworkConfig.class); |
||||
|
||||
final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); |
||||
parseCommand("--profile", "dev", "--config-file", configFile); |
||||
verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); |
||||
verify(mockControllerBuilder).build(); |
||||
|
||||
final EthNetworkConfig config = networkArg.getValue(); |
||||
assertThat(config.getNetworkId()).isEqualTo(MAINNET.getNetworkId()); |
||||
} |
||||
|
||||
/** |
||||
* Test if the environment variable overrides the profile configuration. The test checks if the |
||||
* network ID set through an environment variable correctly overrides the value specified in the |
||||
* 'dev' profile. |
||||
*/ |
||||
@Test |
||||
public void environmentVariableOverridesProfileConfiguration() { |
||||
final ArgumentCaptor<EthNetworkConfig> networkArg = |
||||
ArgumentCaptor.forClass(EthNetworkConfig.class); |
||||
setEnvironmentVariable("BESU_NETWORK", "MAINNET"); |
||||
parseCommand("--profile", "dev"); |
||||
verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); |
||||
verify(mockControllerBuilder).build(); |
||||
|
||||
final EthNetworkConfig config = networkArg.getValue(); |
||||
assertThat(config.getNetworkId()).isEqualTo(MAINNET.getNetworkId()); |
||||
} |
||||
} |
@ -0,0 +1,249 @@ |
||||
/* |
||||
* 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.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoInteractions; |
||||
|
||||
import org.hyperledger.besu.cli.CommandTestAbstract; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
|
||||
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 RpcWebsocketOptionsTest extends CommandTestAbstract { |
||||
|
||||
@Test |
||||
public void rpcWsApiPropertyMustBeUsed() { |
||||
final CommandTestAbstract.TestBesuCommand command = |
||||
parseCommand("--rpc-ws-enabled", "--rpc-ws-api", "ETH, NET"); |
||||
|
||||
assertThat(command).isNotNull(); |
||||
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().getRpcApis()) |
||||
.containsExactlyInAnyOrder(ETH.name(), NET.name()); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsHostAndPortOptionMustBeUsed() { |
||||
final String host = "1.2.3.4"; |
||||
final int port = 1234; |
||||
parseCommand("--rpc-ws-enabled", "--rpc-ws-host", host, "--rpc-ws-port", String.valueOf(port)); |
||||
|
||||
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); |
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsMaxFrameSizePropertyMustBeUsed() { |
||||
final int maxFrameSize = 65535; |
||||
parseCommand("--rpc-ws-max-frame-size", String.valueOf(maxFrameSize)); |
||||
|
||||
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().getMaxFrameSize()).isEqualTo(maxFrameSize); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsMaxActiveConnectionsPropertyMustBeUsed() { |
||||
final int maxConnections = 99; |
||||
parseCommand("--rpc-ws-max-active-connections", String.valueOf(maxConnections)); |
||||
|
||||
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().getMaxActiveConnections()) |
||||
.isEqualTo(maxConnections); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsRpcEnabledPropertyMustBeUsed() { |
||||
parseCommand("--rpc-ws-enabled"); |
||||
|
||||
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().isEnabled()).isTrue(); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void webSocketAuthenticationAlgorithmIsConfigured() { |
||||
parseCommand("--rpc-ws-authentication-jwt-algorithm", "ES256"); |
||||
|
||||
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().getAuthenticationAlgorithm()) |
||||
.isEqualTo(JwtAlgorithm.ES256); |
||||
} |
||||
|
||||
@Test |
||||
public void wsAuthenticationPublicKeyIsConfigured() throws IOException { |
||||
final Path publicKey = Files.createTempFile("public_key", ""); |
||||
parseCommand("--rpc-ws-authentication-jwt-public-key-file", publicKey.toString()); |
||||
|
||||
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath()) |
||||
.isEqualTo(publicKey.toString()); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsHostAndMayBeLocalhost() { |
||||
final String host = "localhost"; |
||||
parseCommand("--rpc-ws-enabled", "--rpc-ws-host", host); |
||||
|
||||
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsHostAndMayBeIPv6() { |
||||
final String host = "2600:DB8::8545"; |
||||
parseCommand("--rpc-ws-enabled", "--rpc-ws-host", host); |
||||
|
||||
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(wsRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsNoAuthApiMethodsCannotBeInvalid() { |
||||
parseCommand("--rpc-ws-enabled", "--rpc-ws-api-methods-no-auth", "invalid"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)) |
||||
.contains( |
||||
"Invalid value for option '--rpc-ws-api-methods-no-auth', options must be valid RPC methods"); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsApisPropertyWithInvalidEntryMustDisplayError() { |
||||
parseCommand("--rpc-ws-api", "ETH,BOB,TEST"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8).trim()) |
||||
.contains("Invalid value for option '--rpc-ws-api': invalid entries found [BOB, TEST]"); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsOptionsRequiresServiceToBeEnabled() { |
||||
parseCommand( |
||||
"--rpc-ws-api", |
||||
"ETH,NET", |
||||
"--rpc-ws-host", |
||||
"0.0.0.0", |
||||
"--rpc-ws-port", |
||||
"1234", |
||||
"--rpc-ws-max-active-connections", |
||||
"77", |
||||
"--rpc-ws-max-frame-size", |
||||
"65535"); |
||||
|
||||
verifyOptionsConstraintLoggerCall( |
||||
"--rpc-ws-enabled", |
||||
"--rpc-ws-host", |
||||
"--rpc-ws-port", |
||||
"--rpc-ws-api", |
||||
"--rpc-ws-max-active-connections", |
||||
"--rpc-ws-max-frame-size"); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void rpcWsOptionsRequiresServiceToBeEnabledToml() throws IOException { |
||||
final Path toml = |
||||
createTempFile( |
||||
"toml", |
||||
"rpc-ws-api=[\"ETH\", \"NET\"]\n" |
||||
+ "rpc-ws-host=\"0.0.0.0\"\n" |
||||
+ "rpc-ws-port=1234\n" |
||||
+ "rpc-ws-max-active-connections=77\n" |
||||
+ "rpc-ws-max-frame-size=65535\n"); |
||||
|
||||
parseCommand("--config-file", toml.toString()); |
||||
|
||||
verifyOptionsConstraintLoggerCall( |
||||
"--rpc-ws-enabled", |
||||
"--rpc-ws-host", |
||||
"--rpc-ws-port", |
||||
"--rpc-ws-api", |
||||
"--rpc-ws-max-active-connections", |
||||
"--rpc-ws-max-frame-size"); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void wsAuthenticationWithoutRequiredConfiguredOptionsMustFail() { |
||||
parseCommand("--rpc-ws-enabled", "--rpc-ws-authentication-enabled"); |
||||
|
||||
verifyNoInteractions(mockRunnerBuilder); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)) |
||||
.contains( |
||||
"Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file"); |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
network="DEV" |
@ -0,0 +1,79 @@ |
||||
/* |
||||
* 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.ethereum.api.jsonrpc.internal.methods; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; |
||||
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
|
||||
import java.util.function.Supplier; |
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.CsvSource; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
||||
@ExtendWith(MockitoExtension.class) |
||||
public class TraceFilterTest { |
||||
|
||||
private TraceFilter method; |
||||
|
||||
@Mock Supplier<BlockTracer> blockTracerSupplier; |
||||
@Mock ProtocolSchedule protocolSchedule; |
||||
@Mock BlockchainQueries blockchainQueries; |
||||
|
||||
@ParameterizedTest |
||||
@CsvSource({ |
||||
"0, 1001, 1000", "0, 5000, 1000", "1, 1002, 1000", "1, 6002, 1000", "1000, 3000, 1000", |
||||
"0, 501, 500", "0, 5000, 500", "1, 502, 500", "1, 6002, 500", "1000, 3000, 500" |
||||
}) |
||||
public void shouldFailIfParamsExceedMaxRange( |
||||
final long fromBlock, final long toBlock, final long maxFilterRange) { |
||||
final FilterParameter filterParameter = |
||||
new FilterParameter( |
||||
new BlockParameter(fromBlock), |
||||
new BlockParameter(toBlock), |
||||
null, |
||||
null, |
||||
null, |
||||
null, |
||||
null, |
||||
null, |
||||
null); |
||||
|
||||
JsonRpcRequestContext request = |
||||
new JsonRpcRequestContext( |
||||
new JsonRpcRequest("2.0", "trace_filter", new Object[] {filterParameter})); |
||||
|
||||
method = |
||||
new TraceFilter(blockTracerSupplier, protocolSchedule, blockchainQueries, maxFilterRange); |
||||
|
||||
final JsonRpcResponse response = method.response(request); |
||||
assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); |
||||
|
||||
final JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response; |
||||
assertThat(errorResponse.getErrorType()).isEqualTo(RpcErrorType.EXCEEDS_RPC_MAX_BLOCK_RANGE); |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue