diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/MiningParameters.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/MiningParameters.java
index 3b7799512b..9aa9fafc46 100644
--- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/MiningParameters.java
+++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/MiningParameters.java
@@ -14,8 +14,11 @@ package tech.pegasys.pantheon.ethereum.core;
import tech.pegasys.pantheon.util.bytes.BytesValue;
+import java.util.Objects;
import java.util.Optional;
+import com.google.common.base.MoreObjects;
+
public class MiningParameters {
private final Optional
coinbase;
@@ -49,4 +52,34 @@ public class MiningParameters {
public Boolean isMiningEnabled() {
return enabled;
}
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final MiningParameters that = (MiningParameters) o;
+ return Objects.equals(coinbase, that.coinbase)
+ && Objects.equals(minTransactionGasPrice, that.minTransactionGasPrice)
+ && Objects.equals(extraData, that.extraData)
+ && Objects.equals(enabled, that.enabled);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(coinbase, minTransactionGasPrice, extraData, enabled);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("coinbase", coinbase)
+ .add("minTransactionGasPrice", minTransactionGasPrice)
+ .add("extraData", extraData)
+ .add("enabled", enabled)
+ .toString();
+ }
}
diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java b/pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java
index 2ab80980f7..edca09d11b 100644
--- a/pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java
+++ b/pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java
@@ -43,7 +43,8 @@ public final class Pantheon {
new SynchronizerConfiguration.Builder(),
EthereumWireProtocolConfiguration.builder(),
new RocksDbConfiguration.Builder(),
- new PantheonPluginContextImpl());
+ new PantheonPluginContextImpl(),
+ System.getenv());
pantheonCommand.parse(
new RunLast().andExit(SUCCESS_EXIT_CODE),
diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/CascadingDefaultProvider.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/CascadingDefaultProvider.java
new file mode 100644
index 0000000000..9cadbe46ad
--- /dev/null
+++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/CascadingDefaultProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.cli;
+
+import static java.util.Arrays.asList;
+
+import java.util.List;
+
+import picocli.CommandLine.IDefaultValueProvider;
+import picocli.CommandLine.Model.ArgSpec;
+
+public class CascadingDefaultProvider implements IDefaultValueProvider {
+
+ private final List defaultValueProviders;
+
+ public CascadingDefaultProvider(final IDefaultValueProvider... defaultValueProviders) {
+ this.defaultValueProviders = asList(defaultValueProviders);
+ }
+
+ @Override
+ public String defaultValue(final ArgSpec argSpec) throws Exception {
+ for (final IDefaultValueProvider provider : defaultValueProviders) {
+ final String defaultValue = provider.defaultValue(argSpec);
+ if (defaultValue != null) {
+ return defaultValue;
+ }
+ }
+ return null;
+ }
+}
diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/ConfigOptionSearchAndRunHandler.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/ConfigOptionSearchAndRunHandler.java
index 03409c4980..e51997abd1 100644
--- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/ConfigOptionSearchAndRunHandler.java
+++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/ConfigOptionSearchAndRunHandler.java
@@ -15,10 +15,12 @@ package tech.pegasys.pantheon.cli;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import picocli.CommandLine;
import picocli.CommandLine.AbstractParseResultHandler;
import picocli.CommandLine.ExecutionException;
+import picocli.CommandLine.IDefaultValueProvider;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.ParseResult;
@@ -28,16 +30,19 @@ class ConfigOptionSearchAndRunHandler extends AbstractParseResultHandler> resultHandler;
private final CommandLine.IExceptionHandler2> exceptionHandler;
private final String configFileOptionName;
+ private final Map environment;
private final boolean isDocker;
ConfigOptionSearchAndRunHandler(
final AbstractParseResultHandler> resultHandler,
final CommandLine.IExceptionHandler2> exceptionHandler,
final String configFileOptionName,
+ final Map environment,
final boolean isDocker) {
this.resultHandler = resultHandler;
this.exceptionHandler = exceptionHandler;
this.configFileOptionName = configFileOptionName;
+ this.environment = environment;
this.isDocker = isDocker;
// use the same output as the regular options handler to ensure that outputs are all going
// in the same place. No need to do this for the exception handler as we reuse it directly.
@@ -55,9 +60,11 @@ class ConfigOptionSearchAndRunHandler extends AbstractParseResultHandler environment;
+
+ public EnvironmentVariableDefaultProvider(final Map environment) {
+ this.environment = environment;
+ }
+
+ @Override
+ public String defaultValue(final ArgSpec argSpec) {
+ return envVarNames((OptionSpec) argSpec)
+ .map(environment::get)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+ }
+
+ private Stream envVarNames(final OptionSpec spec) {
+ return Arrays.stream(spec.names())
+ .filter(name -> name.startsWith("--")) // Only long options are allowed
+ .map(
+ name ->
+ ENV_VAR_PREFIX
+ + name.substring("--".length()).replace('-', '_').toUpperCase(Locale.US));
+ }
+}
diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
index adf68d6d65..5ed6432c44 100644
--- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
+++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
@@ -95,6 +95,7 @@ import java.time.Clock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
@@ -145,6 +146,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
private final RunnerBuilder runnerBuilder;
private final PantheonController.Builder controllerBuilderFactory;
private final PantheonPluginContextImpl pantheonPluginContext;
+ private final Map environment;
protected KeyLoader getKeyLoader() {
return KeyPairUtil::loadKeyPair;
@@ -607,7 +609,8 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
final SynchronizerConfiguration.Builder synchronizerConfigurationBuilder,
final EthereumWireProtocolConfiguration.Builder ethereumWireConfigurationBuilder,
final RocksDbConfiguration.Builder rocksDbConfigurationBuilder,
- final PantheonPluginContextImpl pantheonPluginContext) {
+ final PantheonPluginContextImpl pantheonPluginContext,
+ final Map environment) {
this.logger = logger;
this.blockImporter = blockImporter;
this.runnerBuilder = runnerBuilder;
@@ -616,6 +619,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
this.ethereumWireConfigurationBuilder = ethereumWireConfigurationBuilder;
this.rocksDbConfigurationBuilder = rocksDbConfigurationBuilder;
this.pantheonPluginContext = pantheonPluginContext;
+ this.environment = environment;
}
private StandaloneCommand standaloneCommands;
@@ -680,7 +684,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
// and eventually it will run regular parsing of the remaining options.
final ConfigOptionSearchAndRunHandler configParsingHandler =
new ConfigOptionSearchAndRunHandler(
- resultHandler, exceptionHandler, CONFIG_FILE_OPTION_NAME, isDocker);
+ resultHandler, exceptionHandler, CONFIG_FILE_OPTION_NAME, environment, isDocker);
commandLine.parseWithHandlers(configParsingHandler, exceptionHandler, args);
}
diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java
index f3c162d378..896021b72e 100644
--- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java
+++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java
@@ -49,6 +49,8 @@ import java.io.InputStream;
import java.io.PrintStream;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -75,6 +77,7 @@ public abstract class CommandTestAbstract {
protected final ByteArrayOutputStream commandErrorOutput = new ByteArrayOutputStream();
private final PrintStream errPrintStream = new PrintStream(commandErrorOutput);
+ private final HashMap environment = new HashMap<>();
@Mock RunnerBuilder mockRunnerBuilder;
@Mock Runner mockRunner;
@@ -187,6 +190,10 @@ public abstract class CommandTestAbstract {
commandErrorOutput.close();
}
+ protected void setEnvironemntVariable(final String name, final String value) {
+ environment.put(name, value);
+ }
+
protected CommandLine.Model.CommandSpec parseCommand(final String... args) {
return parseCommand(System.in, args);
}
@@ -215,7 +222,8 @@ public abstract class CommandTestAbstract {
mockEthereumWireProtocolConfigurationBuilder,
mockRocksDbConfBuilder,
keyLoader,
- mockPantheonPluginContext);
+ mockPantheonPluginContext,
+ environment);
// parse using Ansi.OFF to be able to assert on non formatted output results
pantheonCommand.parse(
@@ -245,7 +253,8 @@ public abstract class CommandTestAbstract {
final EthereumWireProtocolConfiguration.Builder mockEthereumConfigurationMockBuilder,
final RocksDbConfiguration.Builder mockRocksDbConfBuilder,
final KeyLoader keyLoader,
- final PantheonPluginContextImpl pantheonPluginContext) {
+ final PantheonPluginContextImpl pantheonPluginContext,
+ final Map environment) {
super(
mockLogger,
mockBlockImporter,
@@ -254,7 +263,8 @@ public abstract class CommandTestAbstract {
mockSyncConfBuilder,
mockEthereumConfigurationMockBuilder,
mockRocksDbConfBuilder,
- pantheonPluginContext);
+ pantheonPluginContext,
+ environment);
this.keyLoader = keyLoader;
}
}
diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/ConfigOptionSearchAndRunHandlerTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/ConfigOptionSearchAndRunHandlerTest.java
index 68b66ed61d..7167f43941 100644
--- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/ConfigOptionSearchAndRunHandlerTest.java
+++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/ConfigOptionSearchAndRunHandlerTest.java
@@ -12,6 +12,7 @@
*/
package tech.pegasys.pantheon.cli;
+import static java.util.Collections.emptyMap;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
@@ -63,7 +64,7 @@ public class ConfigOptionSearchAndRunHandlerTest {
new DefaultExceptionHandler>().useErr(errPrintStream).useAnsi(Ansi.OFF);
private final ConfigOptionSearchAndRunHandler configParsingHandler =
new ConfigOptionSearchAndRunHandler(
- resultHandler, exceptionHandler, CONFIG_FILE_OPTION_NAME, false);
+ resultHandler, exceptionHandler, CONFIG_FILE_OPTION_NAME, emptyMap(), false);
@Mock ParseResult mockParseResult;
@Mock CommandLine mockCommandLine;
diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/EnvironmentVariableDefaultProviderTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/EnvironmentVariableDefaultProviderTest.java
new file mode 100644
index 0000000000..75fa4f57d0
--- /dev/null
+++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/EnvironmentVariableDefaultProviderTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.cli;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+import picocli.CommandLine.Model.OptionSpec;
+
+public class EnvironmentVariableDefaultProviderTest {
+
+ private final Map environment = new HashMap<>();
+
+ private final EnvironmentVariableDefaultProvider provider =
+ new EnvironmentVariableDefaultProvider(environment);
+
+ @Test
+ public void shouldReturnNullWhenEnvironmentVariableIsNotSet() {
+ assertThat(provider.defaultValue(OptionSpec.builder("--no-env-var-set").build())).isNull();
+ }
+
+ @Test
+ public void shouldReturnValueWhenEnvironmentVariableIsSet() {
+ environment.put("PANTHEON_ENV_VAR_SET", "abc");
+ assertThat(provider.defaultValue(OptionSpec.builder("--env-var-set").build())).isEqualTo("abc");
+ }
+
+ @Test
+ public void shouldReturnValueWhenEnvironmentVariableIsSetForAlternateName() {
+ environment.put("PANTHEON_ENV_VAR_SET", "abc");
+ assertThat(provider.defaultValue(OptionSpec.builder("--env-var", "--env-var-set").build()))
+ .isEqualTo("abc");
+ }
+
+ @Test
+ public void shouldNotReturnValueForShortOptions() {
+ environment.put("PANTHEON_H", "abc");
+ assertThat(provider.defaultValue(OptionSpec.builder("-h").build())).isNull();
+ }
+}
diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java
index 7873d67da6..f09a98f8cb 100644
--- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java
+++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java
@@ -732,6 +732,42 @@ public class PantheonCommandTest extends CommandTestAbstract {
assertThat(commandErrorOutput.toString()).isEmpty();
}
+ @Test
+ public void envVariableOverridesValueFromConfigFile() {
+ assumeTrue(isFullInstantiation());
+
+ final String configFile = this.getClass().getResource("/partial_config.toml").getFile();
+ final String expectedCoinbase = "0x0000000000000000000000000000000000000004";
+ setEnvironemntVariable("PANTHEON_MINER_COINBASE", expectedCoinbase);
+ parseCommand("--config-file", configFile);
+
+ verify(mockControllerBuilder)
+ .miningParameters(
+ new MiningParameters(
+ Address.fromHexString(expectedCoinbase),
+ DefaultCommandValues.DEFAULT_MIN_TRANSACTION_GAS_PRICE,
+ DefaultCommandValues.DEFAULT_EXTRA_DATA,
+ false));
+ }
+
+ @Test
+ public void cliOptionOverridesEnvVariableAndConfig() {
+ assumeTrue(isFullInstantiation());
+
+ final String configFile = this.getClass().getResource("/partial_config.toml").getFile();
+ final String expectedCoinbase = "0x0000000000000000000000000000000000000006";
+ setEnvironemntVariable("PANTHEON_MINER_COINBASE", "0x0000000000000000000000000000000000000004");
+ parseCommand("--config-file", configFile, "--miner-coinbase", expectedCoinbase);
+
+ verify(mockControllerBuilder)
+ .miningParameters(
+ new MiningParameters(
+ Address.fromHexString(expectedCoinbase),
+ DefaultCommandValues.DEFAULT_MIN_TRANSACTION_GAS_PRICE,
+ DefaultCommandValues.DEFAULT_EXTRA_DATA,
+ false));
+ }
+
@Test
public void configOptionDisabledUnderDocker() {
System.setProperty("pantheon.docker", "true");