[PIE-1809] Clean up genesis parsing (#1809)

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
mbaxter 5 years ago committed by GitHub
parent 75b754e36b
commit 63ffdb5f02
  1. 1
      config/build.gradle
  2. 13
      config/src/main/java/tech/pegasys/pantheon/config/CliqueConfigOptions.java
  3. 45
      config/src/main/java/tech/pegasys/pantheon/config/ConfigUtil.java
  4. 12
      config/src/main/java/tech/pegasys/pantheon/config/EthashConfigOptions.java
  5. 31
      config/src/main/java/tech/pegasys/pantheon/config/GenesisAllocation.java
  6. 81
      config/src/main/java/tech/pegasys/pantheon/config/GenesisConfigFile.java
  7. 44
      config/src/main/java/tech/pegasys/pantheon/config/IbftConfigOptions.java
  8. 60
      config/src/main/java/tech/pegasys/pantheon/config/JsonGenesisConfigOptions.java
  9. 243
      config/src/main/java/tech/pegasys/pantheon/config/JsonUtil.java
  10. 12
      config/src/test/java/tech/pegasys/pantheon/config/CliqueConfigOptionsTest.java
  11. 39
      config/src/test/java/tech/pegasys/pantheon/config/GenesisConfigFileTest.java
  12. 11
      config/src/test/java/tech/pegasys/pantheon/config/GenesisConfigOptionsTest.java
  13. 12
      config/src/test/java/tech/pegasys/pantheon/config/IbftConfigOptionsTest.java
  14. 545
      config/src/test/java/tech/pegasys/pantheon/config/JsonUtilTest.java
  15. 3
      consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/blockcreation/CliqueMinerExecutorTest.java
  16. 21
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/GenesisState.java
  17. 14
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolScheduleTest.java
  18. 6
      ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeSmartContractPermissioningControllerTest.java
  19. 56
      pantheon/src/main/java/tech/pegasys/pantheon/cli/subcommands/operator/OperatorSubCommand.java
  20. 30
      pantheon/src/test/java/tech/pegasys/pantheon/cli/operator/OperatorSubCommandTest.java
  21. 44
      pantheon/src/test/resources/operator/config_import_keys_invalid_keys.json

@ -28,7 +28,6 @@ jar {
dependencies { dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.google.guava:guava' implementation 'com.google.guava:guava'
implementation 'io.vertx:vertx-core'
implementation 'org.apache.logging.log4j:log4j-api' implementation 'org.apache.logging.log4j:log4j-api'
runtime 'org.apache.logging.log4j:log4j-core' runtime 'org.apache.logging.log4j:log4j-core'

@ -14,28 +14,29 @@ package tech.pegasys.pantheon.config;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import io.vertx.core.json.JsonObject;
public class CliqueConfigOptions { public class CliqueConfigOptions {
public static final CliqueConfigOptions DEFAULT = new CliqueConfigOptions(new JsonObject()); public static final CliqueConfigOptions DEFAULT =
new CliqueConfigOptions(JsonUtil.createEmptyObjectNode());
private static final long DEFAULT_EPOCH_LENGTH = 30_000; private static final long DEFAULT_EPOCH_LENGTH = 30_000;
private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 15; private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 15;
private final JsonObject cliqueConfigRoot; private final ObjectNode cliqueConfigRoot;
CliqueConfigOptions(final JsonObject cliqueConfigRoot) { CliqueConfigOptions(final ObjectNode cliqueConfigRoot) {
this.cliqueConfigRoot = cliqueConfigRoot; this.cliqueConfigRoot = cliqueConfigRoot;
} }
public long getEpochLength() { public long getEpochLength() {
return cliqueConfigRoot.getLong("epochlength", DEFAULT_EPOCH_LENGTH); return JsonUtil.getLong(cliqueConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH);
} }
public int getBlockPeriodSeconds() { public int getBlockPeriodSeconds() {
return cliqueConfigRoot.getInteger("blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS); return JsonUtil.getInt(cliqueConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
} }
Map<String, Object> asMap() { Map<String, Object> asMap() {

@ -1,45 +0,0 @@
/*
* 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.config;
import java.math.BigInteger;
import java.util.Optional;
import java.util.OptionalLong;
import io.vertx.core.json.JsonObject;
public class ConfigUtil {
public static OptionalLong getOptionalLong(final JsonObject jsonObject, final String key) {
return jsonObject.containsKey(key)
? OptionalLong.of(jsonObject.getLong(key))
: OptionalLong.empty();
}
public static Optional<BigInteger> getOptionalBigInteger(
final JsonObject jsonObject, final String key) {
return jsonObject.containsKey(key)
? Optional.ofNullable(getBigInteger(jsonObject, key))
: Optional.empty();
}
private static BigInteger getBigInteger(final JsonObject jsonObject, final String key) {
final Number value = (Number) jsonObject.getMap().get(key);
if (value == null) {
return null;
} else if (value instanceof BigInteger) {
return (BigInteger) value;
} else {
return BigInteger.valueOf(value.longValue());
}
}
}

@ -15,20 +15,22 @@ package tech.pegasys.pantheon.config;
import java.util.Map; import java.util.Map;
import java.util.OptionalLong; import java.util.OptionalLong;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import io.vertx.core.json.JsonObject;
public class EthashConfigOptions { public class EthashConfigOptions {
public static final EthashConfigOptions DEFAULT = new EthashConfigOptions(new JsonObject());; public static final EthashConfigOptions DEFAULT =
private final JsonObject ethashConfigRoot; new EthashConfigOptions(JsonUtil.createEmptyObjectNode());
EthashConfigOptions(final JsonObject ethashConfigRoot) { private final ObjectNode ethashConfigRoot;
EthashConfigOptions(final ObjectNode ethashConfigRoot) {
this.ethashConfigRoot = ethashConfigRoot; this.ethashConfigRoot = ethashConfigRoot;
} }
public OptionalLong getFixedDifficulty() { public OptionalLong getFixedDifficulty() {
return ConfigUtil.getOptionalLong(ethashConfigRoot, "fixeddifficulty"); return JsonUtil.getLong(ethashConfigRoot, "fixeddifficulty");
} }
Map<String, Object> asMap() { Map<String, Object> asMap() {

@ -12,15 +12,16 @@
*/ */
package tech.pegasys.pantheon.config; package tech.pegasys.pantheon.config;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import io.vertx.core.json.JsonObject; import com.fasterxml.jackson.databind.node.ObjectNode;
public class GenesisAllocation { public class GenesisAllocation {
private final String address; private final String address;
private final JsonObject data; private final ObjectNode data;
GenesisAllocation(final String address, final JsonObject data) { GenesisAllocation(final String address, final ObjectNode data) {
this.address = address; this.address = address;
this.data = data; this.data = data;
} }
@ -30,22 +31,30 @@ public class GenesisAllocation {
} }
public String getBalance() { public String getBalance() {
return data.getString("balance", "0"); return JsonUtil.getValueAsString(data, "balance", "0");
} }
public String getCode() { public String getCode() {
return data.getString("code"); return JsonUtil.getString(data, "code", null);
} }
public String getNonce() { public String getNonce() {
return data.getString("nonce", "0"); return JsonUtil.getValueAsString(data, "nonce", "0");
} }
public String getVersion() { public String getVersion() {
return data.getString("version"); return JsonUtil.getValueAsString(data, "version", null);
} }
public Map<String, Object> getStorage() { public Map<String, String> getStorage() {
return data.getJsonObject("storage", new JsonObject()).getMap(); final Map<String, String> map = new HashMap<>();
JsonUtil.getObjectNode(data, "storage")
.orElse(JsonUtil.createEmptyObjectNode())
.fields()
.forEachRemaining(
(entry) -> {
map.put(entry.getKey(), entry.getValue().asText());
});
return map;
} }
} }

@ -15,22 +15,23 @@ package tech.pegasys.pantheon.config;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Streams;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import io.vertx.core.json.JsonObject;
public class GenesisConfigFile { public class GenesisConfigFile {
public static final GenesisConfigFile DEFAULT = new GenesisConfigFile(new JsonObject()); public static final GenesisConfigFile DEFAULT =
new GenesisConfigFile(JsonUtil.createEmptyObjectNode());
private final JsonObject configRoot; private final ObjectNode configRoot;
private GenesisConfigFile(final JsonObject config) { private GenesisConfigFile(final ObjectNode config) {
this.configRoot = config; this.configRoot = config;
} }
@ -52,26 +53,36 @@ public class GenesisConfigFile {
} }
} }
public static GenesisConfigFile fromConfig(final String config) { public static GenesisConfigFile fromConfig(final String jsonString) {
return fromConfig(new JsonObject(config)); // TODO: Should we disable comments?
final boolean allowComments = true;
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonString, allowComments);
return fromConfig(rootNode);
} }
public static GenesisConfigFile fromConfig(final JsonObject config) { public static GenesisConfigFile fromConfig(final ObjectNode config) {
return new GenesisConfigFile(normalizeKeys(config)); return new GenesisConfigFile(normalizeKeys(config));
} }
public GenesisConfigOptions getConfigOptions() { public GenesisConfigOptions getConfigOptions() {
return new JsonGenesisConfigOptions(configRoot.getJsonObject("config")); ObjectNode config =
JsonUtil.getObjectNode(configRoot, "config").orElse(JsonUtil.createEmptyObjectNode());
return new JsonGenesisConfigOptions(config);
} }
public Stream<GenesisAllocation> streamAllocations() { public Stream<GenesisAllocation> streamAllocations() {
final JsonObject allocations = configRoot.getJsonObject("alloc", new JsonObject()); return JsonUtil.getObjectNode(configRoot, "alloc").stream()
return allocations.fieldNames().stream() .flatMap(
.map(key -> new GenesisAllocation(key, allocations.getJsonObject(key))); allocations ->
Streams.stream(allocations.fieldNames())
.map(
key ->
new GenesisAllocation(
key, JsonUtil.getObjectNode(allocations, key).get())));
} }
public String getParentHash() { public String getParentHash() {
return configRoot.getString("parenthash", ""); return JsonUtil.getString(configRoot, "parenthash", "");
} }
public String getDifficulty() { public String getDifficulty() {
@ -79,7 +90,7 @@ public class GenesisConfigFile {
} }
public String getExtraData() { public String getExtraData() {
return configRoot.getString("extradata", ""); return JsonUtil.getString(configRoot, "extradata", "");
} }
public long getGasLimit() { public long getGasLimit() {
@ -87,27 +98,27 @@ public class GenesisConfigFile {
} }
public String getMixHash() { public String getMixHash() {
return configRoot.getString("mixhash", ""); return JsonUtil.getString(configRoot, "mixhash", "");
} }
public String getNonce() { public String getNonce() {
return configRoot.getString("nonce", "0x0"); return JsonUtil.getValueAsString(configRoot, "nonce", "0x0");
} }
public Optional<String> getCoinbase() { public Optional<String> getCoinbase() {
return Optional.ofNullable(configRoot.getString("coinbase")); return JsonUtil.getString(configRoot, "coinbase");
} }
public long getTimestamp() { public long getTimestamp() {
return parseLong("timestamp", configRoot.getString("timestamp", "0x0")); return parseLong("timestamp", JsonUtil.getValueAsString(configRoot, "timestamp", "0x0"));
} }
private String getRequiredString(final String key) { private String getRequiredString(final String key) {
if (!configRoot.containsKey(key)) { if (!configRoot.has(key)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
String.format("Invalid genesis block configuration, missing value for '%s'", key)); String.format("Invalid genesis block configuration, missing value for '%s'", key));
} }
return configRoot.getString(key); return configRoot.get(key).asText();
} }
private long parseLong(final String name, final String value) { private long parseLong(final String name, final String value) {
@ -123,28 +134,24 @@ public class GenesisConfigFile {
} }
} }
/* Converts the {@link JsonObject} describing the Genesis Block to a {@link Map}. This method /* Converts all to lowercase for easier lookup since the keys in a 'genesis.json' file are assumed
* converts all nested {@link JsonObject} to {@link Map} as well. Also, note that all keys are
* converted to lowercase for easier lookup since the keys in a 'genesis.json' file are assumed
* case insensitive. * case insensitive.
*/ */
@SuppressWarnings("unchecked") private static ObjectNode normalizeKeys(final ObjectNode genesis) {
private static JsonObject normalizeKeys(final JsonObject genesis) { final ObjectNode normalized = JsonUtil.createEmptyObjectNode();
final Map<String, Object> normalized = new HashMap<>();
genesis genesis
.getMap() .fields()
.forEach( .forEachRemaining(
(key, value) -> { entry -> {
final String key = entry.getKey();
final JsonNode value = entry.getValue();
final String normalizedKey = key.toLowerCase(Locale.US); final String normalizedKey = key.toLowerCase(Locale.US);
if (value instanceof JsonObject) { if (value instanceof ObjectNode) {
normalized.put(normalizedKey, normalizeKeys((JsonObject) value)); normalized.set(normalizedKey, normalizeKeys((ObjectNode) value));
} else if (value instanceof Map) {
normalized.put(
normalizedKey, normalizeKeys(new JsonObject((Map<String, Object>) value)));
} else { } else {
normalized.put(normalizedKey, value); normalized.set(normalizedKey, value);
} }
}); });
return new JsonObject(normalized); return normalized;
} }
} }

@ -14,12 +14,13 @@ package tech.pegasys.pantheon.config;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import io.vertx.core.json.JsonObject;
public class IbftConfigOptions { public class IbftConfigOptions {
public static final IbftConfigOptions DEFAULT = new IbftConfigOptions(new JsonObject()); public static final IbftConfigOptions DEFAULT =
new IbftConfigOptions(JsonUtil.createEmptyObjectNode());
private static final long DEFAULT_EPOCH_LENGTH = 30_000; private static final long DEFAULT_EPOCH_LENGTH = 30_000;
private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 1; private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 1;
@ -32,69 +33,70 @@ public class IbftConfigOptions {
private static final int DEFAULT_FUTURE_MESSAGES_LIMIT = 1000; private static final int DEFAULT_FUTURE_MESSAGES_LIMIT = 1000;
private static final int DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE = 10; private static final int DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE = 10;
private final JsonObject ibftConfigRoot; private final ObjectNode ibftConfigRoot;
IbftConfigOptions(final JsonObject ibftConfigRoot) { IbftConfigOptions(final ObjectNode ibftConfigRoot) {
this.ibftConfigRoot = ibftConfigRoot; this.ibftConfigRoot = ibftConfigRoot;
} }
public long getEpochLength() { public long getEpochLength() {
return ibftConfigRoot.getLong("epochlength", DEFAULT_EPOCH_LENGTH); return JsonUtil.getLong(ibftConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH);
} }
public int getBlockPeriodSeconds() { public int getBlockPeriodSeconds() {
return ibftConfigRoot.getInteger("blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS); return JsonUtil.getInt(ibftConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
} }
public int getRequestTimeoutSeconds() { public int getRequestTimeoutSeconds() {
return ibftConfigRoot.getInteger("requesttimeoutseconds", DEFAULT_ROUND_EXPIRY_SECONDS); return JsonUtil.getInt(ibftConfigRoot, "requesttimeoutseconds", DEFAULT_ROUND_EXPIRY_SECONDS);
} }
public int getGossipedHistoryLimit() { public int getGossipedHistoryLimit() {
return ibftConfigRoot.getInteger("gossipedhistorylimit", DEFAULT_GOSSIPED_HISTORY_LIMIT); return JsonUtil.getInt(ibftConfigRoot, "gossipedhistorylimit", DEFAULT_GOSSIPED_HISTORY_LIMIT);
} }
public int getMessageQueueLimit() { public int getMessageQueueLimit() {
return ibftConfigRoot.getInteger("messagequeuelimit", DEFAULT_MESSAGE_QUEUE_LIMIT); return JsonUtil.getInt(ibftConfigRoot, "messagequeuelimit", DEFAULT_MESSAGE_QUEUE_LIMIT);
} }
public int getDuplicateMessageLimit() { public int getDuplicateMessageLimit() {
return ibftConfigRoot.getInteger("duplicatemessagelimit", DEFAULT_DUPLICATE_MESSAGE_LIMIT); return JsonUtil.getInt(
ibftConfigRoot, "duplicatemessagelimit", DEFAULT_DUPLICATE_MESSAGE_LIMIT);
} }
public int getFutureMessagesLimit() { public int getFutureMessagesLimit() {
return ibftConfigRoot.getInteger("futuremessageslimit", DEFAULT_FUTURE_MESSAGES_LIMIT); return JsonUtil.getInt(ibftConfigRoot, "futuremessageslimit", DEFAULT_FUTURE_MESSAGES_LIMIT);
} }
public int getFutureMessagesMaxDistance() { public int getFutureMessagesMaxDistance() {
return ibftConfigRoot.getInteger( return JsonUtil.getInt(
"futuremessagesmaxdistance", DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE); ibftConfigRoot, "futuremessagesmaxdistance", DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE);
} }
Map<String, Object> asMap() { Map<String, Object> asMap() {
final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder(); final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
if (ibftConfigRoot.containsKey("epochlength")) { if (ibftConfigRoot.has("epochlength")) {
builder.put("epochLength", getEpochLength()); builder.put("epochLength", getEpochLength());
} }
if (ibftConfigRoot.containsKey("blockperiodseconds")) { if (ibftConfigRoot.has("blockperiodseconds")) {
builder.put("blockPeriodSeconds", getBlockPeriodSeconds()); builder.put("blockPeriodSeconds", getBlockPeriodSeconds());
} }
if (ibftConfigRoot.containsKey("requesttimeoutseconds")) { if (ibftConfigRoot.has("requesttimeoutseconds")) {
builder.put("requestTimeoutSeconds", getRequestTimeoutSeconds()); builder.put("requestTimeoutSeconds", getRequestTimeoutSeconds());
} }
if (ibftConfigRoot.containsKey("gossipedhistorylimit")) { if (ibftConfigRoot.has("gossipedhistorylimit")) {
builder.put("gossipedHistoryLimit", getGossipedHistoryLimit()); builder.put("gossipedHistoryLimit", getGossipedHistoryLimit());
} }
if (ibftConfigRoot.containsKey("messagequeuelimit")) { if (ibftConfigRoot.has("messagequeuelimit")) {
builder.put("messageQueueLimit", getMessageQueueLimit()); builder.put("messageQueueLimit", getMessageQueueLimit());
} }
if (ibftConfigRoot.containsKey("duplicatemessagelimit")) { if (ibftConfigRoot.has("duplicatemessagelimit")) {
builder.put("duplicateMessageLimit", getDuplicateMessageLimit()); builder.put("duplicateMessageLimit", getDuplicateMessageLimit());
} }
if (ibftConfigRoot.containsKey("futuremessageslimit")) { if (ibftConfigRoot.has("futuremessageslimit")) {
builder.put("futureMessagesLimit", getFutureMessagesLimit()); builder.put("futureMessagesLimit", getFutureMessagesLimit());
} }
if (ibftConfigRoot.containsKey("futuremessagesmaxdistance")) { if (ibftConfigRoot.has("futuremessagesmaxdistance")) {
builder.put("futureMessagesMaxDistance", getFutureMessagesMaxDistance()); builder.put("futureMessagesMaxDistance", getFutureMessagesMaxDistance());
} }
return builder.build(); return builder.build();

@ -12,7 +12,7 @@
*/ */
package tech.pegasys.pantheon.config; package tech.pegasys.pantheon.config;
import static tech.pegasys.pantheon.config.ConfigUtil.getOptionalBigInteger; import static java.util.Objects.isNull;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Map; import java.util.Map;
@ -20,8 +20,8 @@ import java.util.Optional;
import java.util.OptionalInt; import java.util.OptionalInt;
import java.util.OptionalLong; import java.util.OptionalLong;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import io.vertx.core.json.JsonObject;
public class JsonGenesisConfigOptions implements GenesisConfigOptions { public class JsonGenesisConfigOptions implements GenesisConfigOptions {
@ -29,62 +29,62 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
private static final String IBFT_LEGACY_CONFIG_KEY = "ibft"; private static final String IBFT_LEGACY_CONFIG_KEY = "ibft";
private static final String IBFT2_CONFIG_KEY = "ibft2"; private static final String IBFT2_CONFIG_KEY = "ibft2";
private static final String CLIQUE_CONFIG_KEY = "clique"; private static final String CLIQUE_CONFIG_KEY = "clique";
private final JsonObject configRoot; private final ObjectNode configRoot;
public static JsonGenesisConfigOptions fromJsonObject(final JsonObject configRoot) { public static JsonGenesisConfigOptions fromJsonObject(final ObjectNode configRoot) {
return new JsonGenesisConfigOptions(configRoot); return new JsonGenesisConfigOptions(configRoot);
} }
JsonGenesisConfigOptions(final JsonObject configRoot) { JsonGenesisConfigOptions(final ObjectNode maybeConfig) {
this.configRoot = configRoot != null ? configRoot : new JsonObject(); this.configRoot = isNull(maybeConfig) ? JsonUtil.createEmptyObjectNode() : maybeConfig;
} }
@Override @Override
public boolean isEthHash() { public boolean isEthHash() {
return configRoot.containsKey(ETHASH_CONFIG_KEY); return configRoot.has(ETHASH_CONFIG_KEY);
} }
@Override @Override
public boolean isIbftLegacy() { public boolean isIbftLegacy() {
return configRoot.containsKey(IBFT_LEGACY_CONFIG_KEY); return configRoot.has(IBFT_LEGACY_CONFIG_KEY);
} }
@Override @Override
public boolean isClique() { public boolean isClique() {
return configRoot.containsKey(CLIQUE_CONFIG_KEY); return configRoot.has(CLIQUE_CONFIG_KEY);
} }
@Override @Override
public boolean isIbft2() { public boolean isIbft2() {
return configRoot.containsKey(IBFT2_CONFIG_KEY); return configRoot.has(IBFT2_CONFIG_KEY);
} }
@Override @Override
public IbftConfigOptions getIbftLegacyConfigOptions() { public IbftConfigOptions getIbftLegacyConfigOptions() {
return isIbftLegacy() return JsonUtil.getObjectNode(configRoot, IBFT_LEGACY_CONFIG_KEY)
? new IbftConfigOptions(configRoot.getJsonObject(IBFT_LEGACY_CONFIG_KEY)) .map(IbftConfigOptions::new)
: IbftConfigOptions.DEFAULT; .orElse(IbftConfigOptions.DEFAULT);
} }
@Override @Override
public IbftConfigOptions getIbft2ConfigOptions() { public IbftConfigOptions getIbft2ConfigOptions() {
return isIbft2() return JsonUtil.getObjectNode(configRoot, IBFT2_CONFIG_KEY)
? new IbftConfigOptions(configRoot.getJsonObject(IBFT2_CONFIG_KEY)) .map(IbftConfigOptions::new)
: IbftConfigOptions.DEFAULT; .orElse(IbftConfigOptions.DEFAULT);
} }
@Override @Override
public CliqueConfigOptions getCliqueConfigOptions() { public CliqueConfigOptions getCliqueConfigOptions() {
return isClique() return JsonUtil.getObjectNode(configRoot, CLIQUE_CONFIG_KEY)
? new CliqueConfigOptions(configRoot.getJsonObject(CLIQUE_CONFIG_KEY)) .map(CliqueConfigOptions::new)
: CliqueConfigOptions.DEFAULT; .orElse(CliqueConfigOptions.DEFAULT);
} }
@Override @Override
public EthashConfigOptions getEthashConfigOptions() { public EthashConfigOptions getEthashConfigOptions() {
return isEthHash() return JsonUtil.getObjectNode(configRoot, ETHASH_CONFIG_KEY)
? new EthashConfigOptions(configRoot.getJsonObject(ETHASH_CONFIG_KEY)) .map(EthashConfigOptions::new)
: EthashConfigOptions.DEFAULT; .orElse(EthashConfigOptions.DEFAULT);
} }
@Override @Override
@ -133,21 +133,17 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
@Override @Override
public Optional<BigInteger> getChainId() { public Optional<BigInteger> getChainId() {
return getOptionalBigInteger(configRoot, "chainid"); return JsonUtil.getValueAsString(configRoot, "chainid").map(BigInteger::new);
} }
@Override @Override
public OptionalInt getContractSizeLimit() { public OptionalInt getContractSizeLimit() {
return configRoot.containsKey("contractsizelimit") return JsonUtil.getInt(configRoot, "contractsizelimit");
? OptionalInt.of(configRoot.getInteger("contractsizelimit"))
: OptionalInt.empty();
} }
@Override @Override
public OptionalInt getEvmStackSize() { public OptionalInt getEvmStackSize() {
return configRoot.containsKey("evmstacksize") return JsonUtil.getInt(configRoot, "evmstacksize");
? OptionalInt.of(configRoot.getInteger("evmstacksize"))
: OptionalInt.empty();
} }
@Override @Override
@ -165,8 +161,8 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
.ifPresent( .ifPresent(
l -> { l -> {
builder.put("eip150Block", l); builder.put("eip150Block", l);
if (configRoot.containsKey("eip150hash")) { if (configRoot.has("eip150hash")) {
builder.put("eip150Hash", configRoot.getString("eip150hash")); builder.put("eip150Hash", configRoot.get("eip150hash").asText());
} }
}); });
getSpuriousDragonBlockNumber() getSpuriousDragonBlockNumber()
@ -197,6 +193,6 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
} }
private OptionalLong getOptionalLong(final String key) { private OptionalLong getOptionalLong(final String key) {
return ConfigUtil.getOptionalLong(configRoot, key); return JsonUtil.getLong(configRoot, key);
} }
} }

@ -0,0 +1,243 @@
/*
* 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.config;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class JsonUtil {
/**
* Get the string representation of the value at {@code key}. For example, a numeric value like 5
* will be returned as "5".
*
* @param node The {@code ObjectNode} from which the value will be extracted.
* @param key The key corresponding to the value to extract.
* @return The value at the given key as a string if it exists.
*/
public static Optional<String> getValueAsString(final ObjectNode node, final String key) {
return getValue(node, key).map(JsonNode::asText);
}
/**
* Get the string representation of the value at {@code key}. For example, a numeric value like 5
* will be returned as "5".
*
* @param node The {@code ObjectNode} from which the value will be extracted.
* @param key The key corresponding to the value to extract.
* @param defaultValue The value to return if no value is found at {@code key}.
* @return The value at the given key as a string if it exists, otherwise {@code defaultValue}
*/
public static String getValueAsString(
final ObjectNode node, final String key, final String defaultValue) {
return getValueAsString(node, key).orElse(defaultValue);
}
/**
* Returns textual (string) value at {@code key}. See {@link #getValueAsString} for retrieving
* non-textual values in string form.
*
* @param node The {@code ObjectNode} from which the value will be extracted.
* @param key The key corresponding to the value to extract.
* @return The textual value at {@code key} if it exists.
*/
public static Optional<String> getString(final ObjectNode node, final String key) {
return getValue(node, key)
.filter(jsonNode -> validateType(jsonNode, JsonNodeType.STRING))
.map(JsonNode::asText);
}
/**
* Returns textual (string) value at {@code key}. See {@link #getValueAsString} for retrieving
* non-textual values in string form.
*
* @param node The {@code ObjectNode} from which the value will be extracted.
* @param key The key corresponding to the value to extract.
* @param defaultValue The value to return if no value is found at {@code key}.
* @return The textual value at {@code key} if it exists, otherwise {@code defaultValue}
*/
public static String getString(
final ObjectNode node, final String key, final String defaultValue) {
return getString(node, key).orElse(defaultValue);
}
public static OptionalInt getInt(final ObjectNode node, final String key) {
return getValue(node, key)
.filter(jsonNode -> validateType(jsonNode, JsonNodeType.NUMBER))
.filter(JsonUtil::validateInt)
.map(JsonNode::asInt)
.map(OptionalInt::of)
.orElse(OptionalInt.empty());
}
public static int getInt(final ObjectNode node, final String key, final int defaultValue) {
return getInt(node, key).orElse(defaultValue);
}
public static OptionalLong getLong(final ObjectNode json, final String key) {
return getValue(json, key)
.filter(jsonNode -> validateType(jsonNode, JsonNodeType.NUMBER))
.filter(JsonUtil::validateLong)
.map(JsonNode::asLong)
.map(OptionalLong::of)
.orElse(OptionalLong.empty());
}
public static long getLong(final ObjectNode json, final String key, final long defaultValue) {
return getLong(json, key).orElse(defaultValue);
}
public static Optional<Boolean> getBoolean(final ObjectNode node, final String key) {
return getValue(node, key)
.filter(jsonNode -> validateType(jsonNode, JsonNodeType.BOOLEAN))
.map(JsonNode::asBoolean);
}
public static boolean getBoolean(
final ObjectNode node, final String key, final boolean defaultValue) {
return getBoolean(node, key).orElse(defaultValue);
}
public static ObjectNode createEmptyObjectNode() {
ObjectMapper mapper = getObjectMapper();
return mapper.createObjectNode();
}
public static ObjectNode objectNodeFromMap(final Map<String, Object> map) {
return (ObjectNode) getObjectMapper().valueToTree(map);
}
public static ObjectNode objectNodeFromString(final String jsonData) {
return objectNodeFromString(jsonData, false);
}
public static ObjectNode objectNodeFromString(
final String jsonData, final boolean allowComments) {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(Feature.ALLOW_COMMENTS, allowComments);
try {
final JsonNode jsonNode = objectMapper.readTree(jsonData);
validateType(jsonNode, JsonNodeType.OBJECT);
return (ObjectNode) jsonNode;
} catch (IOException e) {
// Reading directly from a string should not raise an IOException, just catch and rethrow
throw new RuntimeException(e);
}
}
public static String getJson(final Object objectNode) throws JsonProcessingException {
return getJson(objectNode, true);
}
public static String getJson(final Object objectNode, final boolean prettyPrint)
throws JsonProcessingException {
ObjectMapper mapper = getObjectMapper();
if (prettyPrint) {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode);
} else {
return mapper.writeValueAsString(objectNode);
}
}
public static ObjectMapper getObjectMapper() {
return new ObjectMapper();
}
public static Optional<ObjectNode> getObjectNode(final ObjectNode json, final String fieldKey) {
return getObjectNode(json, fieldKey, true);
}
public static Optional<ObjectNode> getObjectNode(
final ObjectNode json, final String fieldKey, final boolean strict) {
final JsonNode obj = json.get(fieldKey);
if (obj == null || obj.isNull()) {
return Optional.empty();
}
if (!obj.isObject()) {
if (strict) {
validateType(obj, JsonNodeType.OBJECT);
} else {
return Optional.empty();
}
}
return Optional.of((ObjectNode) obj);
}
public static Optional<ArrayNode> getArrayNode(final ObjectNode json, final String fieldKey) {
return getArrayNode(json, fieldKey, true);
}
public static Optional<ArrayNode> getArrayNode(
final ObjectNode json, final String fieldKey, final boolean strict) {
final JsonNode obj = json.get(fieldKey);
if (obj == null || obj.isNull()) {
return Optional.empty();
}
if (!obj.isArray()) {
if (strict) {
validateType(obj, JsonNodeType.ARRAY);
} else {
return Optional.empty();
}
}
return Optional.of((ArrayNode) obj);
}
private static Optional<JsonNode> getValue(final ObjectNode node, final String key) {
JsonNode jsonNode = node.get(key);
if (jsonNode == null || jsonNode.isNull()) {
return Optional.empty();
}
return Optional.of(jsonNode);
}
private static boolean validateType(final JsonNode node, final JsonNodeType expectedType) {
if (node.getNodeType() != expectedType) {
final String errorMessage =
String.format(
"Expected %s value but got %s",
expectedType.toString().toLowerCase(), node.getNodeType().toString().toLowerCase());
throw new IllegalArgumentException(errorMessage);
}
return true;
}
private static boolean validateLong(final JsonNode node) {
if (!node.canConvertToLong()) {
throw new IllegalArgumentException("Cannot convert value to long: " + node.toString());
}
return true;
}
private static boolean validateInt(final JsonNode node) {
if (!node.canConvertToInt()) {
throw new IllegalArgumentException("Cannot convert value to integer: " + node.toString());
}
return true;
}
}

@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.util.Map; import java.util.Map;
import io.vertx.core.json.JsonObject; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test; import org.junit.Test;
public class CliqueConfigOptionsTest { public class CliqueConfigOptionsTest {
@ -63,9 +63,11 @@ public class CliqueConfigOptionsTest {
} }
private CliqueConfigOptions fromConfigOptions(final Map<String, Object> cliqueConfigOptions) { private CliqueConfigOptions fromConfigOptions(final Map<String, Object> cliqueConfigOptions) {
return GenesisConfigFile.fromConfig( final ObjectNode rootNode = JsonUtil.createEmptyObjectNode();
new JsonObject(singletonMap("config", singletonMap("clique", cliqueConfigOptions)))) final ObjectNode configNode = JsonUtil.createEmptyObjectNode();
.getConfigOptions() final ObjectNode options = JsonUtil.objectNodeFromMap(cliqueConfigOptions);
.getCliqueConfigOptions(); configNode.set("clique", options);
rootNode.set("config", configNode);
return GenesisConfigFile.fromConfig(rootNode).getConfigOptions().getCliqueConfigOptions();
} }
} }

@ -14,10 +14,10 @@ package tech.pegasys.pantheon.config;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
@ -148,21 +148,42 @@ public class GenesisConfigFileTest {
+ " \"balance\": \"1000\"" + " \"balance\": \"1000\""
+ " }," + " },"
+ " \"f17f52151EbEF6C7334FAD080c5704D77216b732\": {" + " \"f17f52151EbEF6C7334FAD080c5704D77216b732\": {"
+ " \"balance\": \"90000000000000000000000\"" + " \"balance\": \"90000000000000000000000\","
+ " \"storage\": {"
+ " \"0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a4\": \"0x937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0\",\n"
+ " \"0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a3\": \"0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012\""
+ " }"
+ " }" + " }"
+ " }" + " }"
+ "}"); + "}");
final Map<String, String> allocations = final Map<String, GenesisAllocation> allocations =
config config
.streamAllocations() .streamAllocations()
.collect( .collect(Collectors.toMap(GenesisAllocation::getAddress, Function.identity()));
Collectors.toMap(GenesisAllocation::getAddress, GenesisAllocation::getBalance)); assertThat(allocations.keySet())
assertThat(allocations)
.containsOnly( .containsOnly(
entry("fe3b557e8fb62b89f4916b721be55ceb828dbd73", "0xad78ebc5ac6200000"), "fe3b557e8fb62b89f4916b721be55ceb828dbd73",
entry("627306090abab3a6e1400e9345bc60c78a8bef57", "1000"), "627306090abab3a6e1400e9345bc60c78a8bef57",
entry("f17f52151ebef6c7334fad080c5704d77216b732", "90000000000000000000000")); "f17f52151ebef6c7334fad080c5704d77216b732");
final GenesisAllocation alloc1 = allocations.get("fe3b557e8fb62b89f4916b721be55ceb828dbd73");
final GenesisAllocation alloc2 = allocations.get("627306090abab3a6e1400e9345bc60c78a8bef57");
final GenesisAllocation alloc3 = allocations.get("f17f52151ebef6c7334fad080c5704d77216b732");
assertThat(alloc1.getBalance()).isEqualTo("0xad78ebc5ac6200000");
assertThat(alloc2.getBalance()).isEqualTo("1000");
assertThat(alloc3.getBalance()).isEqualTo("90000000000000000000000");
assertThat(alloc3.getStorage().size()).isEqualTo(2);
assertThat(
alloc3
.getStorage()
.get("0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a4"))
.isEqualTo("0x937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0");
assertThat(
alloc3
.getStorage()
.get("0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a3"))
.isEqualTo("0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012");
} }
@Test @Test

@ -17,10 +17,9 @@ import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import io.vertx.core.json.JsonObject; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test; import org.junit.Test;
public class GenesisConfigOptionsTest { public class GenesisConfigOptionsTest {
@ -150,8 +149,10 @@ public class GenesisConfigOptionsTest {
assertThat(config.getHomesteadBlockNumber()).isEmpty(); assertThat(config.getHomesteadBlockNumber()).isEmpty();
} }
private GenesisConfigOptions fromConfigOptions(final Map<String, Object> options) { private GenesisConfigOptions fromConfigOptions(final Map<String, Object> configOptions) {
return GenesisConfigFile.fromConfig(new JsonObject(Collections.singletonMap("config", options))) final ObjectNode rootNode = JsonUtil.createEmptyObjectNode();
.getConfigOptions(); final ObjectNode options = JsonUtil.objectNodeFromMap(configOptions);
rootNode.set("config", options);
return GenesisConfigFile.fromConfig(rootNode).getConfigOptions();
} }
} }

@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.util.Map; import java.util.Map;
import io.vertx.core.json.JsonObject; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test; import org.junit.Test;
public class IbftConfigOptionsTest { public class IbftConfigOptionsTest {
@ -179,9 +179,11 @@ public class IbftConfigOptionsTest {
} }
private IbftConfigOptions fromConfigOptions(final Map<String, Object> ibftConfigOptions) { private IbftConfigOptions fromConfigOptions(final Map<String, Object> ibftConfigOptions) {
return GenesisConfigFile.fromConfig( final ObjectNode rootNode = JsonUtil.createEmptyObjectNode();
new JsonObject(singletonMap("config", singletonMap("ibft", ibftConfigOptions)))) final ObjectNode configNode = JsonUtil.createEmptyObjectNode();
.getConfigOptions() final ObjectNode options = JsonUtil.objectNodeFromMap(ibftConfigOptions);
.getIbftLegacyConfigOptions(); configNode.set("ibft", options);
rootNode.set("config", configNode);
return GenesisConfigFile.fromConfig(rootNode).getConfigOptions().getIbftLegacyConfigOptions();
} }
} }

@ -0,0 +1,545 @@
/*
* 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.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.TreeMap;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test;
public class JsonUtilTest {
private ObjectMapper mapper = new ObjectMapper();
@Test
public void getLong_nonExistentKey() {
final ObjectNode node = mapper.createObjectNode();
final OptionalLong result = JsonUtil.getLong(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getLong_nullValue() {
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final OptionalLong result = JsonUtil.getLong(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getLong_validValue() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", Long.MAX_VALUE);
final OptionalLong result = JsonUtil.getLong(node, "test");
assertThat(result).hasValue(Long.MAX_VALUE);
}
@Test
public void getLong_overflowingValue() {
final String overflowingValue = Long.toString(Long.MAX_VALUE, 10) + "100";
final String jsonStr = "{\"test\": " + overflowingValue + " }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getLong(rootNode, "test"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Cannot convert value to long: " + overflowingValue);
}
@Test
public void getLong_wrongType() {
final String jsonStr = "{\"test\": \"bla\" }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getLong(rootNode, "test"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected number value but got string");
}
@Test
public void getLong_nullValue_withDefault() {
final long defaultValue = 11;
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final long result = JsonUtil.getLong(node, "test", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getLong_nonExistentKey_withDefault() {
final long defaultValue = 11;
final ObjectNode node = mapper.createObjectNode();
final long result = JsonUtil.getLong(node, "test", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getLong_validValue_withDefault() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", Long.MAX_VALUE);
final long result = JsonUtil.getLong(node, "test", 11);
assertThat(result).isEqualTo(Long.MAX_VALUE);
}
@Test
public void getLong_overflowingValue_withDefault() {
final String overflowingValue = Long.toString(Long.MAX_VALUE, 10) + "100";
final String jsonStr = "{\"test\": " + overflowingValue + " }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getLong(rootNode, "test", 11))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Cannot convert value to long: " + overflowingValue);
}
@Test
public void getLong_wrongType_withDefault() {
final String jsonStr = "{\"test\": \"bla\" }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getLong(rootNode, "test", 11))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected number value but got string");
}
@Test
public void getInt_nonExistentKey() {
final ObjectNode node = mapper.createObjectNode();
final OptionalInt result = JsonUtil.getInt(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getInt_nullValue() {
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final OptionalInt result = JsonUtil.getInt(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getInt_validValue() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", Integer.MAX_VALUE);
final OptionalInt result = JsonUtil.getInt(node, "test");
assertThat(result).hasValue(Integer.MAX_VALUE);
}
@Test
public void getInt_overflowingValue() {
final String overflowingValue = Integer.toString(Integer.MAX_VALUE, 10) + "100";
final String jsonStr = "{\"test\": " + overflowingValue + " }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getInt(rootNode, "test"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Cannot convert value to integer: " + overflowingValue);
}
@Test
public void getInt_wrongType() {
final String jsonStr = "{\"test\": \"bla\" }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getInt(rootNode, "test"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected number value but got string");
}
@Test
public void getInt_nullValue_withDefault() {
final int defaultValue = 11;
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final int result = JsonUtil.getInt(node, "test", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getInt_nonExistentKey_withDefault() {
final int defaultValue = 11;
final ObjectNode node = mapper.createObjectNode();
final int result = JsonUtil.getInt(node, "test", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getInt_validValue_withDefault() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", Integer.MAX_VALUE);
final int result = JsonUtil.getInt(node, "test", 11);
assertThat(result).isEqualTo(Integer.MAX_VALUE);
}
@Test
public void getInt_overflowingValue_withDefault() {
final String overflowingValue = Integer.toString(Integer.MAX_VALUE, 10) + "100";
final String jsonStr = "{\"test\": " + overflowingValue + " }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getInt(rootNode, "test", 11))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Cannot convert value to integer: " + overflowingValue);
}
@Test
public void getInt_wrongType_withDefault() {
final String jsonStr = "{\"test\": \"bla\" }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getInt(rootNode, "test", 11))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected number value but got string");
}
@Test
public void getString_nonExistentKey() {
final ObjectNode node = mapper.createObjectNode();
final Optional<String> result = JsonUtil.getString(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getString_nullValue() {
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final Optional<String> result = JsonUtil.getString(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getString_validValue() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", "bla");
final Optional<String> result = JsonUtil.getString(node, "test");
assertThat(result).hasValue("bla");
}
@Test
public void getString_wrongType() {
final String jsonStr = "{\"test\": 123 }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getString(rootNode, "test"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected string value but got number");
}
@Test
public void getString_nullValue_withDefault() {
final String defaultValue = "bla";
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final String result = JsonUtil.getString(node, "test", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getString_nonExistentKey_withDefault() {
final String defaultValue = "bla";
final ObjectNode node = mapper.createObjectNode();
final String result = JsonUtil.getString(node, "test", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getString_validValue_withDefault() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", "bla");
final String result = JsonUtil.getString(node, "test", "11");
assertThat(result).isEqualTo("bla");
}
@Test
public void getValueAsString_nonExistentKey() {
final ObjectNode node = mapper.createObjectNode();
final Optional<String> result = JsonUtil.getValueAsString(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getValueAsString_nullValue() {
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final Optional<String> result = JsonUtil.getValueAsString(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getValueAsString_stringValue() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", "bla");
final Optional<String> result = JsonUtil.getValueAsString(node, "test");
assertThat(result).hasValue("bla");
}
@Test
public void getValueAsString_nonStringValue() {
final String jsonStr = "{\"test\": 123 }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
final Optional<String> result = JsonUtil.getValueAsString(rootNode, "test");
assertThat(result).hasValue("123");
}
@Test
public void getValueAsString_nullValue_withDefault() {
final String defaultValue = "bla";
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final String result = JsonUtil.getValueAsString(node, "test", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getValueAsString_nonExistentKey_withDefault() {
final String defaultValue = "bla";
final ObjectNode node = mapper.createObjectNode();
final String result = JsonUtil.getValueAsString(node, "test", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getValueAsString_stringValue_withDefault() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", "bla");
final String result = JsonUtil.getValueAsString(node, "test", "11");
assertThat(result).isEqualTo("bla");
}
@Test
public void getValueAsString_nonStringValue_withDefault() {
final String jsonStr = "{\"test\": 123 }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
final String result = JsonUtil.getValueAsString(rootNode, "test", "11");
assertThat(result).isEqualTo("123");
}
// Boolean
@Test
public void getBoolean_nonExistentKey() {
final ObjectNode node = mapper.createObjectNode();
final Optional<Boolean> result = JsonUtil.getBoolean(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getBoolean_nullValue() {
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final Optional<Boolean> result = JsonUtil.getBoolean(node, "test");
assertThat(result).isEmpty();
}
@Test
public void getBoolean_validValue() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", true);
final Optional<Boolean> result = JsonUtil.getBoolean(node, "test");
assertThat(result).hasValue(true);
}
@Test
public void getBoolean_wrongType() {
final String jsonStr = "{\"test\": 123 }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getBoolean(rootNode, "test"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected boolean value but got number");
}
@Test
public void getBoolean_nullValue_withDefault() {
final ObjectNode node = mapper.createObjectNode();
node.set("test", null);
final Boolean result = JsonUtil.getBoolean(node, "test", false);
assertThat(result).isEqualTo(false);
}
@Test
public void getBoolean_nonExistentKey_withDefault() {
final ObjectNode node = mapper.createObjectNode();
final Boolean result = JsonUtil.getBoolean(node, "test", true);
assertThat(result).isEqualTo(true);
}
@Test
public void getBoolean_validValue_withDefault() {
final ObjectNode node = mapper.createObjectNode();
node.put("test", false);
final Boolean result = JsonUtil.getBoolean(node, "test", true);
assertThat(result).isEqualTo(false);
}
@Test
public void getBoolean_wrongType_withDefault() {
final String jsonStr = "{\"test\": 123 }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getBoolean(rootNode, "test", true))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected boolean value but got number");
}
@Test
public void objectNodeFromMap() {
final Map<String, Object> map = new TreeMap<>();
map.put("a", 1);
map.put("b", 2);
final Map<String, Object> subMap = new TreeMap<>();
subMap.put("c", "bla");
subMap.put("d", 2L);
map.put("subtree", subMap);
ObjectNode node = JsonUtil.objectNodeFromMap(map);
assertThat(node.get("a").asInt()).isEqualTo(1);
assertThat(node.get("b").asInt()).isEqualTo(2);
assertThat(node.get("subtree").get("c").asText()).isEqualTo("bla");
assertThat(node.get("subtree").get("d").asLong()).isEqualTo(2L);
}
@Test
public void objectNodeFromString() {
final String jsonStr = "{\"a\":1, \"b\":2}";
final ObjectNode result = JsonUtil.objectNodeFromString(jsonStr);
assertThat(result.get("a").asInt()).isEqualTo(1);
assertThat(result.get("b").asInt()).isEqualTo(2);
}
@Test
public void objectNodeFromString_withComments_commentsDisabled() {
final String jsonStr = "// Comment\n{\"a\":1, \"b\":2}";
assertThatThrownBy(() -> JsonUtil.objectNodeFromString(jsonStr, false))
.hasCauseInstanceOf(JsonParseException.class)
.hasMessageContaining("Unexpected character ('/'");
}
@Test
public void objectNodeFromString_withComments_commentsEnabled() {
final String jsonStr = "// Comment\n{\"a\":1, \"b\":2}";
final ObjectNode result = JsonUtil.objectNodeFromString(jsonStr, true);
assertThat(result.get("a").asInt()).isEqualTo(1);
assertThat(result.get("b").asInt()).isEqualTo(2);
}
@Test
public void getJson() throws JsonProcessingException {
final String jsonStr = "{\"a\":1, \"b\":2}";
final ObjectNode objectNode = JsonUtil.objectNodeFromString(jsonStr);
final String resultUgly = JsonUtil.getJson(objectNode, false);
final String resultPretty = JsonUtil.getJson(objectNode, true);
assertThat(resultUgly).isEqualToIgnoringWhitespace(jsonStr);
assertThat(resultPretty).isEqualToIgnoringWhitespace(jsonStr);
// Pretty printed value should have more whitespace and contain returns
assertThat(resultPretty.length()).isGreaterThan(resultUgly.length());
assertThat(resultPretty).contains("\n");
assertThat(resultUgly).doesNotContain("\n");
}
@Test
public void getObjectNode_validValue() {
final String jsonStr = "{\"test\": {\"a\":1, \"b\":2} }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
final Optional<ObjectNode> maybeTestNode = JsonUtil.getObjectNode(rootNode, "test");
assertThat(maybeTestNode).isNotEmpty();
final ObjectNode testNode = maybeTestNode.get();
assertThat(testNode.get("a").asInt()).isEqualTo(1);
assertThat(testNode.get("b").asInt()).isEqualTo(2);
}
@Test
public void getObjectNode_nullValue() {
final String jsonStr = "{\"test\": null }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
final Optional<ObjectNode> maybeTestNode = JsonUtil.getObjectNode(rootNode, "test");
assertThat(maybeTestNode).isEmpty();
}
@Test
public void getObjectNode_nonExistentKey() {
final String jsonStr = "{}";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
final Optional<ObjectNode> maybeTestNode = JsonUtil.getObjectNode(rootNode, "test");
assertThat(maybeTestNode).isEmpty();
}
@Test
public void getObjectNode_wrongNodeType() {
final String jsonStr = "{\"test\": \"abc\" }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getObjectNode(rootNode, "test"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected object value but got string");
}
@Test
public void getArrayNode_validValue() {
final String jsonStr = "{\"test\": [\"a\", \"b\"] }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
final Optional<ArrayNode> maybeTestNode = JsonUtil.getArrayNode(rootNode, "test");
assertThat(maybeTestNode).isNotEmpty();
final ArrayNode testNode = maybeTestNode.get();
assertThat(testNode.get(0).asText()).isEqualTo("a");
assertThat(testNode.get(1).asText()).isEqualTo("b");
}
@Test
public void getArrayNode_nullValue() {
final String jsonStr = "{\"test\": null }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
final Optional<ArrayNode> maybeTestNode = JsonUtil.getArrayNode(rootNode, "test");
assertThat(maybeTestNode).isEmpty();
}
@Test
public void getArrayNode_nonExistentKey() {
final String jsonStr = "{}";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
final Optional<ArrayNode> maybeTestNode = JsonUtil.getArrayNode(rootNode, "test");
assertThat(maybeTestNode).isEmpty();
}
@Test
public void getArrayNode_wrongNodeType() {
final String jsonStr = "{\"test\": \"abc\" }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThatThrownBy(() -> JsonUtil.getArrayNode(rootNode, "test"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected array value but got string");
}
}

@ -48,7 +48,6 @@ import java.util.Random;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import io.vertx.core.json.JsonObject;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -56,7 +55,7 @@ public class CliqueMinerExecutorTest {
private static final int EPOCH_LENGTH = 10; private static final int EPOCH_LENGTH = 10;
private static final GenesisConfigOptions GENESIS_CONFIG_OPTIONS = private static final GenesisConfigOptions GENESIS_CONFIG_OPTIONS =
GenesisConfigFile.fromConfig(new JsonObject()).getConfigOptions(); GenesisConfigFile.fromConfig("{}").getConfigOptions();
private final KeyPair proposerKeyPair = KeyPair.generate(); private final KeyPair proposerKeyPair = KeyPair.generate();
private final Random random = new Random(21341234L); private final Random random = new Random(21341234L);
private Address localAddress; private Address localAddress;

@ -242,7 +242,7 @@ public final class GenesisState {
final String hexNonce, final String hexNonce,
final String hexAddress, final String hexAddress,
final String balance, final String balance,
final Map<String, Object> storage, final Map<String, String> storage,
final String hexCode, final String hexCode,
final String version) { final String version) {
this.nonce = withNiceErrorMessage("nonce", hexNonce, GenesisState::parseUnsignedLong); this.nonce = withNiceErrorMessage("nonce", hexNonce, GenesisState::parseUnsignedLong);
@ -264,14 +264,19 @@ public final class GenesisState {
return Wei.of(val); return Wei.of(val);
} }
private Map<UInt256, UInt256> parseStorage(final Map<String, Object> storage) { private Map<UInt256, UInt256> parseStorage(final Map<String, String> storage) {
final Map<UInt256, UInt256> parsedStorage = new HashMap<>(); final Map<UInt256, UInt256> parsedStorage = new HashMap<>();
storage.forEach( storage
(key, value) -> .entrySet()
parsedStorage.put( .forEach(
withNiceErrorMessage("storage key", key, UInt256::fromHexString), (entry) -> {
withNiceErrorMessage( final UInt256 key =
"storage value", String.valueOf(value), UInt256::fromHexString))); withNiceErrorMessage("storage key", entry.getKey(), UInt256::fromHexString);
final UInt256 value =
withNiceErrorMessage("storage value", entry.getValue(), UInt256::fromHexString);
parsedStorage.put(key, value);
});
return parsedStorage; return parsedStorage;
} }

@ -17,7 +17,6 @@ import tech.pegasys.pantheon.config.GenesisConfigFile;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import io.vertx.core.json.JsonObject;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.Test; import org.junit.Test;
@ -47,18 +46,16 @@ public class MainnetProtocolScheduleTest {
@Test @Test
public void shouldOnlyUseFrontierWhenEmptyJsonConfigIsUsed() { public void shouldOnlyUseFrontierWhenEmptyJsonConfigIsUsed() {
final JsonObject json = new JsonObject("{}");
final ProtocolSchedule<Void> sched = final ProtocolSchedule<Void> sched =
MainnetProtocolSchedule.fromConfig(GenesisConfigFile.fromConfig(json).getConfigOptions()); MainnetProtocolSchedule.fromConfig(GenesisConfigFile.fromConfig("{}").getConfigOptions());
Assertions.assertThat(sched.getByBlockNumber(1L).getName()).isEqualTo("Frontier"); Assertions.assertThat(sched.getByBlockNumber(1L).getName()).isEqualTo("Frontier");
Assertions.assertThat(sched.getByBlockNumber(Long.MAX_VALUE).getName()).isEqualTo("Frontier"); Assertions.assertThat(sched.getByBlockNumber(Long.MAX_VALUE).getName()).isEqualTo("Frontier");
} }
@Test @Test
public void createFromConfigWithSettings() { public void createFromConfigWithSettings() {
final JsonObject json = final String json =
new JsonObject( "{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 19, \"chainId\":1234}}";
"{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 19, \"chainId\":1234}}");
final ProtocolSchedule<Void> sched = final ProtocolSchedule<Void> sched =
MainnetProtocolSchedule.fromConfig(GenesisConfigFile.fromConfig(json).getConfigOptions()); MainnetProtocolSchedule.fromConfig(GenesisConfigFile.fromConfig(json).getConfigOptions());
Assertions.assertThat(sched.getByBlockNumber(1).getName()).isEqualTo("Frontier"); Assertions.assertThat(sched.getByBlockNumber(1).getName()).isEqualTo("Frontier");
@ -75,9 +72,8 @@ public class MainnetProtocolScheduleTest {
@Test @Test
public void outOfOrderConstantinoplesFail() { public void outOfOrderConstantinoplesFail() {
final JsonObject json = final String json =
new JsonObject( "{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 17, \"chainId\":1234}}";
"{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 17, \"chainId\":1234}}");
Assertions.assertThatExceptionOfType(RuntimeException.class) Assertions.assertThatExceptionOfType(RuntimeException.class)
.describedAs( .describedAs(
"Genesis Config Error: 'ConstantinopleFix' is scheduled for block 17 but it must be on or after block 18.") "Genesis Config Error: 'ConstantinopleFix' is scheduled for block 17 but it must be on or after block 18.")

@ -22,6 +22,7 @@ import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.create
import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive;
import tech.pegasys.pantheon.config.GenesisConfigFile; import tech.pegasys.pantheon.config.GenesisConfigFile;
import tech.pegasys.pantheon.config.JsonUtil;
import tech.pegasys.pantheon.ethereum.chain.GenesisState; import tech.pegasys.pantheon.ethereum.chain.GenesisState;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Address;
@ -36,6 +37,7 @@ import tech.pegasys.pantheon.metrics.PantheonMetricCategory;
import java.io.IOException; import java.io.IOException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -55,8 +57,10 @@ public class NodeSmartContractPermissioningControllerTest {
final String emptyContractFile = final String emptyContractFile =
Resources.toString(this.getClass().getResource(resourceName), UTF_8); Resources.toString(this.getClass().getResource(resourceName), UTF_8);
final ObjectNode jsonData = JsonUtil.objectNodeFromString(emptyContractFile, true);
final GenesisState genesisState = final GenesisState genesisState =
GenesisState.fromConfig(GenesisConfigFile.fromConfig(emptyContractFile), protocolSchedule); GenesisState.fromConfig(GenesisConfigFile.fromConfig(jsonData), protocolSchedule);
final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock()); final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock());
final WorldStateArchive worldArchive = createInMemoryWorldStateArchive(); final WorldStateArchive worldArchive = createInMemoryWorldStateArchive();

@ -21,6 +21,7 @@ import static tech.pegasys.pantheon.cli.subcommands.operator.OperatorSubCommand.
import tech.pegasys.pantheon.cli.PantheonCommand; import tech.pegasys.pantheon.cli.PantheonCommand;
import tech.pegasys.pantheon.config.JsonGenesisConfigOptions; import tech.pegasys.pantheon.config.JsonGenesisConfigOptions;
import tech.pegasys.pantheon.config.JsonUtil;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Address;
@ -39,9 +40,10 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import picocli.CommandLine.Command; import picocli.CommandLine.Command;
@ -129,10 +131,10 @@ public class OperatorSubCommand implements Runnable {
@ParentCommand @ParentCommand
private OperatorSubCommand parentCommand; // Picocli injects reference to parent command private OperatorSubCommand parentCommand; // Picocli injects reference to parent command
private JsonObject operatorConfig; private ObjectNode operatorConfig;
private JsonObject genesisConfig; private ObjectNode genesisConfig;
private JsonObject blockchainConfig; private ObjectNode blockchainConfig;
private JsonObject nodesConfig; private ObjectNode nodesConfig;
private boolean generateNodesKeys; private boolean generateNodesKeys;
private List<Address> addressesForGenesisExtraData = new ArrayList<>(); private List<Address> addressesForGenesisExtraData = new ArrayList<>();
private Path keysDirectory; private Path keysDirectory;
@ -171,19 +173,25 @@ public class OperatorSubCommand implements Runnable {
/** Imports public keys from input configuration. */ /** Imports public keys from input configuration. */
private void importPublicKeysFromConfig() { private void importPublicKeysFromConfig() {
LOG.info("Importing public keys from configuration."); LOG.info("Importing public keys from configuration.");
JsonArray keys = nodesConfig.getJsonArray("keys"); JsonUtil.getArrayNode(nodesConfig, "keys")
keys.stream().forEach(this::importPublicKey); .ifPresent(keys -> keys.forEach(this::importPublicKey));
} }
/** /**
* Imports a single public key. * Imports a single public key.
* *
* @param publicKeyObject The public key. * @param publicKeyJson The public key.
*/ */
private void importPublicKey(final Object publicKeyObject) { private void importPublicKey(final JsonNode publicKeyJson) {
if (publicKeyJson.getNodeType() != JsonNodeType.STRING) {
throw new IllegalArgumentException(
"Invalid key json of type: " + publicKeyJson.getNodeType());
}
String publicKeyText = publicKeyJson.asText();
try { try {
final SECP256K1.PublicKey publicKey = final SECP256K1.PublicKey publicKey =
SECP256K1.PublicKey.create(BytesValue.fromHexString((String) publicKeyObject)); SECP256K1.PublicKey.create(BytesValue.fromHexString(publicKeyText));
writeKeypair(publicKey, null); writeKeypair(publicKey, null);
LOG.info("Public key imported from configuration.({})", publicKey.toString()); LOG.info("Public key imported from configuration.({})", publicKey.toString());
} catch (IOException e) { } catch (IOException e) {
@ -193,7 +201,7 @@ public class OperatorSubCommand implements Runnable {
/** Generates nodes keypairs. */ /** Generates nodes keypairs. */
private void generateNodesKeys() { private void generateNodesKeys() {
final int nodesCount = nodesConfig.getInteger("count"); final int nodesCount = JsonUtil.getInt(nodesConfig, "count", 0);
LOG.info("Generating {} nodes keys.", nodesCount); LOG.info("Generating {} nodes keys.", nodesCount);
IntStream.range(0, nodesCount).forEach(this::generateNodeKeypair); IntStream.range(0, nodesCount).forEach(this::generateNodeKeypair);
} }
@ -241,8 +249,9 @@ public class OperatorSubCommand implements Runnable {
* @throws IOException * @throws IOException
*/ */
private void processExtraData() { private void processExtraData() {
final ObjectNode configNode = JsonUtil.getObjectNode(genesisConfig, "config").orElse(null);
final JsonGenesisConfigOptions genesisConfigOptions = final JsonGenesisConfigOptions genesisConfigOptions =
JsonGenesisConfigOptions.fromJsonObject(genesisConfig.getJsonObject("config")); JsonGenesisConfigOptions.fromJsonObject(configNode);
if (genesisConfigOptions.isIbft2()) { if (genesisConfigOptions.isIbft2()) {
LOG.info("Generating IBFT extra data."); LOG.info("Generating IBFT extra data.");
final String extraData = final String extraData =
@ -265,11 +274,18 @@ public class OperatorSubCommand implements Runnable {
private void parseConfig() throws IOException { private void parseConfig() throws IOException {
final String configString = final String configString =
Resources.toString(configurationFile.toPath().toUri().toURL(), UTF_8); Resources.toString(configurationFile.toPath().toUri().toURL(), UTF_8);
operatorConfig = new JsonObject(configString); final ObjectNode root = JsonUtil.objectNodeFromString(configString);
genesisConfig = operatorConfig.getJsonObject("genesis"); operatorConfig = root;
blockchainConfig = operatorConfig.getJsonObject("blockchain"); genesisConfig =
nodesConfig = blockchainConfig.getJsonObject("nodes"); JsonUtil.getObjectNode(operatorConfig, "genesis")
generateNodesKeys = nodesConfig.getBoolean("generate", false); .orElse(JsonUtil.createEmptyObjectNode());
blockchainConfig =
JsonUtil.getObjectNode(operatorConfig, "blockchain")
.orElse(JsonUtil.createEmptyObjectNode());
nodesConfig =
JsonUtil.getObjectNode(blockchainConfig, "nodes")
.orElse(JsonUtil.createEmptyObjectNode());
generateNodesKeys = JsonUtil.getBoolean(nodesConfig, "generate", false);
} }
/** /**
@ -301,11 +317,11 @@ public class OperatorSubCommand implements Runnable {
* @throws IOException * @throws IOException
*/ */
private void writeGenesisFile( private void writeGenesisFile(
final File directory, final String fileName, final JsonObject genesis) throws IOException { final File directory, final String fileName, final ObjectNode genesis) throws IOException {
LOG.info("Writing genesis file."); LOG.info("Writing genesis file.");
Files.write( Files.write(
directory.toPath().resolve(fileName), directory.toPath().resolve(fileName),
genesis.encodePrettily().getBytes(UTF_8), JsonUtil.getJson(genesis).getBytes(UTF_8),
StandardOpenOption.CREATE_NEW); StandardOpenOption.CREATE_NEW);
} }
} }

@ -21,6 +21,7 @@ import static java.util.Arrays.stream;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.contentOf; import static org.assertj.core.api.Assertions.contentOf;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static tech.pegasys.pantheon.cli.operator.OperatorSubCommandTest.Cmd.cmd; import static tech.pegasys.pantheon.cli.operator.OperatorSubCommandTest.Cmd.cmd;
@ -152,15 +153,36 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
asList("key.pub", "priv.test")); asList("key.pub", "priv.test"));
} }
@Test(expected = CommandLine.ExecutionException.class) @Test
public void shouldFailIfDuplicateFiles() throws IOException { public void shouldFailIfDuplicateFiles() {
assertThatThrownBy(
() ->
runCmdAndCheckOutput( runCmdAndCheckOutput(
cmd("--private-key-file-name", "dup.test", "--public-key-file-name", "dup.test"), cmd(
"--private-key-file-name",
"dup.test",
"--public-key-file-name",
"dup.test"),
"/operator/config_generate_keys.json", "/operator/config_generate_keys.json",
tmpOutputDirectoryPath, tmpOutputDirectoryPath,
"genesis.json", "genesis.json",
true, true,
asList("key.pub", "priv.test")); asList("key.pub", "priv.test")))
.isInstanceOf(CommandLine.ExecutionException.class);
}
@Test
public void shouldFailIfPublicKeysAreWrongType() {
assertThatThrownBy(
() ->
runCmdAndCheckOutput(
cmd(),
"/operator/config_import_keys_invalid_keys.json",
tmpOutputDirectoryPath,
"genesis.json",
false,
singletonList("key.pub")))
.isInstanceOf(CommandLine.ExecutionException.class);
} }
@Test(expected = CommandLine.ExecutionException.class) @Test(expected = CommandLine.ExecutionException.class)

@ -0,0 +1,44 @@
{
"genesis": {
"config": {
"chainId": 2017,
"constantinoplefixblock": 0,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"ibft2": {
}
},
"nonce": "0x0",
"timestamp": "0x5b3c3d18",
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"2a813d7db3de19b07f92268b6d4125ed295cbe00": {
"balance": "0x446c3b15f9926687d2c40534fdb542000000000000"
}
}
},
"blockchain": {
"nodes": {
"keys": [
{"invalidObj": "0xb295c4242fb40c6e8ac7b831c916846050f191adc560b8098ba6ad513079571ec1be6e5e1a715857a13a91963097962e048c36c5863014b59e8f67ed3f667680"},
"0x6295c4242fb40c6e8ac7b831c916846050f191adc560b8098ba6ad513079571ec1be6e5e1a715857a13a91963097962e048c36c5863014b59e8f67ed3f667680"
]
}
}
}
Loading…
Cancel
Save