mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-09 21:17:54 -05:00
[PIE-1809] Clean up genesis parsing (#1809)
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
This commit is contained in:
@@ -28,7 +28,6 @@ jar {
|
||||
dependencies {
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
implementation 'com.google.guava:guava'
|
||||
implementation 'io.vertx:vertx-core'
|
||||
implementation 'org.apache.logging.log4j:log4j-api'
|
||||
|
||||
runtime 'org.apache.logging.log4j:log4j-core'
|
||||
|
||||
@@ -14,28 +14,29 @@ package tech.pegasys.pantheon.config;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
public long getEpochLength() {
|
||||
return cliqueConfigRoot.getLong("epochlength", DEFAULT_EPOCH_LENGTH);
|
||||
return JsonUtil.getLong(cliqueConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH);
|
||||
}
|
||||
|
||||
public int getBlockPeriodSeconds() {
|
||||
return cliqueConfigRoot.getInteger("blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
|
||||
return JsonUtil.getInt(cliqueConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
|
||||
}
|
||||
|
||||
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.OptionalLong;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
public class EthashConfigOptions {
|
||||
|
||||
public static final EthashConfigOptions DEFAULT = new EthashConfigOptions(new JsonObject());;
|
||||
private final JsonObject ethashConfigRoot;
|
||||
public static final EthashConfigOptions DEFAULT =
|
||||
new EthashConfigOptions(JsonUtil.createEmptyObjectNode());
|
||||
|
||||
EthashConfigOptions(final JsonObject ethashConfigRoot) {
|
||||
private final ObjectNode ethashConfigRoot;
|
||||
|
||||
EthashConfigOptions(final ObjectNode ethashConfigRoot) {
|
||||
this.ethashConfigRoot = ethashConfigRoot;
|
||||
}
|
||||
|
||||
public OptionalLong getFixedDifficulty() {
|
||||
return ConfigUtil.getOptionalLong(ethashConfigRoot, "fixeddifficulty");
|
||||
return JsonUtil.getLong(ethashConfigRoot, "fixeddifficulty");
|
||||
}
|
||||
|
||||
Map<String, Object> asMap() {
|
||||
|
||||
@@ -12,15 +12,16 @@
|
||||
*/
|
||||
package tech.pegasys.pantheon.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public class GenesisAllocation {
|
||||
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.data = data;
|
||||
}
|
||||
@@ -30,22 +31,30 @@ public class GenesisAllocation {
|
||||
}
|
||||
|
||||
public String getBalance() {
|
||||
return data.getString("balance", "0");
|
||||
return JsonUtil.getValueAsString(data, "balance", "0");
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return data.getString("code");
|
||||
return JsonUtil.getString(data, "code", null);
|
||||
}
|
||||
|
||||
public String getNonce() {
|
||||
return data.getString("nonce", "0");
|
||||
return JsonUtil.getValueAsString(data, "nonce", "0");
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return data.getString("version");
|
||||
return JsonUtil.getValueAsString(data, "version", null);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStorage() {
|
||||
return data.getJsonObject("storage", new JsonObject()).getMap();
|
||||
public Map<String, String> getStorage() {
|
||||
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 java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
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 io.vertx.core.json.JsonObject;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -52,26 +53,36 @@ public class GenesisConfigFile {
|
||||
}
|
||||
}
|
||||
|
||||
public static GenesisConfigFile fromConfig(final String config) {
|
||||
return fromConfig(new JsonObject(config));
|
||||
public static GenesisConfigFile fromConfig(final String jsonString) {
|
||||
// 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));
|
||||
}
|
||||
|
||||
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() {
|
||||
final JsonObject allocations = configRoot.getJsonObject("alloc", new JsonObject());
|
||||
return allocations.fieldNames().stream()
|
||||
.map(key -> new GenesisAllocation(key, allocations.getJsonObject(key)));
|
||||
return JsonUtil.getObjectNode(configRoot, "alloc").stream()
|
||||
.flatMap(
|
||||
allocations ->
|
||||
Streams.stream(allocations.fieldNames())
|
||||
.map(
|
||||
key ->
|
||||
new GenesisAllocation(
|
||||
key, JsonUtil.getObjectNode(allocations, key).get())));
|
||||
}
|
||||
|
||||
public String getParentHash() {
|
||||
return configRoot.getString("parenthash", "");
|
||||
return JsonUtil.getString(configRoot, "parenthash", "");
|
||||
}
|
||||
|
||||
public String getDifficulty() {
|
||||
@@ -79,7 +90,7 @@ public class GenesisConfigFile {
|
||||
}
|
||||
|
||||
public String getExtraData() {
|
||||
return configRoot.getString("extradata", "");
|
||||
return JsonUtil.getString(configRoot, "extradata", "");
|
||||
}
|
||||
|
||||
public long getGasLimit() {
|
||||
@@ -87,27 +98,27 @@ public class GenesisConfigFile {
|
||||
}
|
||||
|
||||
public String getMixHash() {
|
||||
return configRoot.getString("mixhash", "");
|
||||
return JsonUtil.getString(configRoot, "mixhash", "");
|
||||
}
|
||||
|
||||
public String getNonce() {
|
||||
return configRoot.getString("nonce", "0x0");
|
||||
return JsonUtil.getValueAsString(configRoot, "nonce", "0x0");
|
||||
}
|
||||
|
||||
public Optional<String> getCoinbase() {
|
||||
return Optional.ofNullable(configRoot.getString("coinbase"));
|
||||
return JsonUtil.getString(configRoot, "coinbase");
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return parseLong("timestamp", configRoot.getString("timestamp", "0x0"));
|
||||
return parseLong("timestamp", JsonUtil.getValueAsString(configRoot, "timestamp", "0x0"));
|
||||
}
|
||||
|
||||
private String getRequiredString(final String key) {
|
||||
if (!configRoot.containsKey(key)) {
|
||||
if (!configRoot.has(key)) {
|
||||
throw new IllegalArgumentException(
|
||||
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) {
|
||||
@@ -123,28 +134,24 @@ public class GenesisConfigFile {
|
||||
}
|
||||
}
|
||||
|
||||
/* Converts the {@link JsonObject} describing the Genesis Block to a {@link Map}. This method
|
||||
* 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
|
||||
/* Converts all to lowercase for easier lookup since the keys in a 'genesis.json' file are assumed
|
||||
* case insensitive.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static JsonObject normalizeKeys(final JsonObject genesis) {
|
||||
final Map<String, Object> normalized = new HashMap<>();
|
||||
private static ObjectNode normalizeKeys(final ObjectNode genesis) {
|
||||
final ObjectNode normalized = JsonUtil.createEmptyObjectNode();
|
||||
genesis
|
||||
.getMap()
|
||||
.forEach(
|
||||
(key, value) -> {
|
||||
.fields()
|
||||
.forEachRemaining(
|
||||
entry -> {
|
||||
final String key = entry.getKey();
|
||||
final JsonNode value = entry.getValue();
|
||||
final String normalizedKey = key.toLowerCase(Locale.US);
|
||||
if (value instanceof JsonObject) {
|
||||
normalized.put(normalizedKey, normalizeKeys((JsonObject) value));
|
||||
} else if (value instanceof Map) {
|
||||
normalized.put(
|
||||
normalizedKey, normalizeKeys(new JsonObject((Map<String, Object>) value)));
|
||||
if (value instanceof ObjectNode) {
|
||||
normalized.set(normalizedKey, normalizeKeys((ObjectNode) value));
|
||||
} 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 com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
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 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_MAX_DISTANCE = 10;
|
||||
|
||||
private final JsonObject ibftConfigRoot;
|
||||
private final ObjectNode ibftConfigRoot;
|
||||
|
||||
IbftConfigOptions(final JsonObject ibftConfigRoot) {
|
||||
IbftConfigOptions(final ObjectNode ibftConfigRoot) {
|
||||
this.ibftConfigRoot = ibftConfigRoot;
|
||||
}
|
||||
|
||||
public long getEpochLength() {
|
||||
return ibftConfigRoot.getLong("epochlength", DEFAULT_EPOCH_LENGTH);
|
||||
return JsonUtil.getLong(ibftConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH);
|
||||
}
|
||||
|
||||
public int getBlockPeriodSeconds() {
|
||||
return ibftConfigRoot.getInteger("blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
|
||||
return JsonUtil.getInt(ibftConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
|
||||
}
|
||||
|
||||
public int getRequestTimeoutSeconds() {
|
||||
return ibftConfigRoot.getInteger("requesttimeoutseconds", DEFAULT_ROUND_EXPIRY_SECONDS);
|
||||
return JsonUtil.getInt(ibftConfigRoot, "requesttimeoutseconds", DEFAULT_ROUND_EXPIRY_SECONDS);
|
||||
}
|
||||
|
||||
public int getGossipedHistoryLimit() {
|
||||
return ibftConfigRoot.getInteger("gossipedhistorylimit", DEFAULT_GOSSIPED_HISTORY_LIMIT);
|
||||
return JsonUtil.getInt(ibftConfigRoot, "gossipedhistorylimit", DEFAULT_GOSSIPED_HISTORY_LIMIT);
|
||||
}
|
||||
|
||||
public int getMessageQueueLimit() {
|
||||
return ibftConfigRoot.getInteger("messagequeuelimit", DEFAULT_MESSAGE_QUEUE_LIMIT);
|
||||
return JsonUtil.getInt(ibftConfigRoot, "messagequeuelimit", DEFAULT_MESSAGE_QUEUE_LIMIT);
|
||||
}
|
||||
|
||||
public int getDuplicateMessageLimit() {
|
||||
return ibftConfigRoot.getInteger("duplicatemessagelimit", DEFAULT_DUPLICATE_MESSAGE_LIMIT);
|
||||
return JsonUtil.getInt(
|
||||
ibftConfigRoot, "duplicatemessagelimit", DEFAULT_DUPLICATE_MESSAGE_LIMIT);
|
||||
}
|
||||
|
||||
public int getFutureMessagesLimit() {
|
||||
return ibftConfigRoot.getInteger("futuremessageslimit", DEFAULT_FUTURE_MESSAGES_LIMIT);
|
||||
return JsonUtil.getInt(ibftConfigRoot, "futuremessageslimit", DEFAULT_FUTURE_MESSAGES_LIMIT);
|
||||
}
|
||||
|
||||
public int getFutureMessagesMaxDistance() {
|
||||
return ibftConfigRoot.getInteger(
|
||||
"futuremessagesmaxdistance", DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE);
|
||||
return JsonUtil.getInt(
|
||||
ibftConfigRoot, "futuremessagesmaxdistance", DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE);
|
||||
}
|
||||
|
||||
Map<String, Object> asMap() {
|
||||
final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
|
||||
if (ibftConfigRoot.containsKey("epochlength")) {
|
||||
if (ibftConfigRoot.has("epochlength")) {
|
||||
builder.put("epochLength", getEpochLength());
|
||||
}
|
||||
if (ibftConfigRoot.containsKey("blockperiodseconds")) {
|
||||
if (ibftConfigRoot.has("blockperiodseconds")) {
|
||||
builder.put("blockPeriodSeconds", getBlockPeriodSeconds());
|
||||
}
|
||||
if (ibftConfigRoot.containsKey("requesttimeoutseconds")) {
|
||||
if (ibftConfigRoot.has("requesttimeoutseconds")) {
|
||||
builder.put("requestTimeoutSeconds", getRequestTimeoutSeconds());
|
||||
}
|
||||
if (ibftConfigRoot.containsKey("gossipedhistorylimit")) {
|
||||
if (ibftConfigRoot.has("gossipedhistorylimit")) {
|
||||
builder.put("gossipedHistoryLimit", getGossipedHistoryLimit());
|
||||
}
|
||||
if (ibftConfigRoot.containsKey("messagequeuelimit")) {
|
||||
if (ibftConfigRoot.has("messagequeuelimit")) {
|
||||
builder.put("messageQueueLimit", getMessageQueueLimit());
|
||||
}
|
||||
if (ibftConfigRoot.containsKey("duplicatemessagelimit")) {
|
||||
if (ibftConfigRoot.has("duplicatemessagelimit")) {
|
||||
builder.put("duplicateMessageLimit", getDuplicateMessageLimit());
|
||||
}
|
||||
if (ibftConfigRoot.containsKey("futuremessageslimit")) {
|
||||
if (ibftConfigRoot.has("futuremessageslimit")) {
|
||||
builder.put("futureMessagesLimit", getFutureMessagesLimit());
|
||||
}
|
||||
if (ibftConfigRoot.containsKey("futuremessagesmaxdistance")) {
|
||||
if (ibftConfigRoot.has("futuremessagesmaxdistance")) {
|
||||
builder.put("futureMessagesMaxDistance", getFutureMessagesMaxDistance());
|
||||
}
|
||||
return builder.build();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
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.util.Map;
|
||||
@@ -20,8 +20,8 @@ import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
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 IBFT2_CONFIG_KEY = "ibft2";
|
||||
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);
|
||||
}
|
||||
|
||||
JsonGenesisConfigOptions(final JsonObject configRoot) {
|
||||
this.configRoot = configRoot != null ? configRoot : new JsonObject();
|
||||
JsonGenesisConfigOptions(final ObjectNode maybeConfig) {
|
||||
this.configRoot = isNull(maybeConfig) ? JsonUtil.createEmptyObjectNode() : maybeConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEthHash() {
|
||||
return configRoot.containsKey(ETHASH_CONFIG_KEY);
|
||||
return configRoot.has(ETHASH_CONFIG_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIbftLegacy() {
|
||||
return configRoot.containsKey(IBFT_LEGACY_CONFIG_KEY);
|
||||
return configRoot.has(IBFT_LEGACY_CONFIG_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClique() {
|
||||
return configRoot.containsKey(CLIQUE_CONFIG_KEY);
|
||||
return configRoot.has(CLIQUE_CONFIG_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIbft2() {
|
||||
return configRoot.containsKey(IBFT2_CONFIG_KEY);
|
||||
return configRoot.has(IBFT2_CONFIG_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IbftConfigOptions getIbftLegacyConfigOptions() {
|
||||
return isIbftLegacy()
|
||||
? new IbftConfigOptions(configRoot.getJsonObject(IBFT_LEGACY_CONFIG_KEY))
|
||||
: IbftConfigOptions.DEFAULT;
|
||||
return JsonUtil.getObjectNode(configRoot, IBFT_LEGACY_CONFIG_KEY)
|
||||
.map(IbftConfigOptions::new)
|
||||
.orElse(IbftConfigOptions.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IbftConfigOptions getIbft2ConfigOptions() {
|
||||
return isIbft2()
|
||||
? new IbftConfigOptions(configRoot.getJsonObject(IBFT2_CONFIG_KEY))
|
||||
: IbftConfigOptions.DEFAULT;
|
||||
return JsonUtil.getObjectNode(configRoot, IBFT2_CONFIG_KEY)
|
||||
.map(IbftConfigOptions::new)
|
||||
.orElse(IbftConfigOptions.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CliqueConfigOptions getCliqueConfigOptions() {
|
||||
return isClique()
|
||||
? new CliqueConfigOptions(configRoot.getJsonObject(CLIQUE_CONFIG_KEY))
|
||||
: CliqueConfigOptions.DEFAULT;
|
||||
return JsonUtil.getObjectNode(configRoot, CLIQUE_CONFIG_KEY)
|
||||
.map(CliqueConfigOptions::new)
|
||||
.orElse(CliqueConfigOptions.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EthashConfigOptions getEthashConfigOptions() {
|
||||
return isEthHash()
|
||||
? new EthashConfigOptions(configRoot.getJsonObject(ETHASH_CONFIG_KEY))
|
||||
: EthashConfigOptions.DEFAULT;
|
||||
return JsonUtil.getObjectNode(configRoot, ETHASH_CONFIG_KEY)
|
||||
.map(EthashConfigOptions::new)
|
||||
.orElse(EthashConfigOptions.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,21 +133,17 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
|
||||
|
||||
@Override
|
||||
public Optional<BigInteger> getChainId() {
|
||||
return getOptionalBigInteger(configRoot, "chainid");
|
||||
return JsonUtil.getValueAsString(configRoot, "chainid").map(BigInteger::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt getContractSizeLimit() {
|
||||
return configRoot.containsKey("contractsizelimit")
|
||||
? OptionalInt.of(configRoot.getInteger("contractsizelimit"))
|
||||
: OptionalInt.empty();
|
||||
return JsonUtil.getInt(configRoot, "contractsizelimit");
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt getEvmStackSize() {
|
||||
return configRoot.containsKey("evmstacksize")
|
||||
? OptionalInt.of(configRoot.getInteger("evmstacksize"))
|
||||
: OptionalInt.empty();
|
||||
return JsonUtil.getInt(configRoot, "evmstacksize");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,8 +161,8 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
|
||||
.ifPresent(
|
||||
l -> {
|
||||
builder.put("eip150Block", l);
|
||||
if (configRoot.containsKey("eip150hash")) {
|
||||
builder.put("eip150Hash", configRoot.getString("eip150hash"));
|
||||
if (configRoot.has("eip150hash")) {
|
||||
builder.put("eip150Hash", configRoot.get("eip150hash").asText());
|
||||
}
|
||||
});
|
||||
getSpuriousDragonBlockNumber()
|
||||
@@ -197,6 +193,6 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
|
||||
}
|
||||
|
||||
private OptionalLong getOptionalLong(final String key) {
|
||||
return ConfigUtil.getOptionalLong(configRoot, key);
|
||||
return JsonUtil.getLong(configRoot, key);
|
||||
}
|
||||
}
|
||||
|
||||
243
config/src/main/java/tech/pegasys/pantheon/config/JsonUtil.java
Normal file
243
config/src/main/java/tech/pegasys/pantheon/config/JsonUtil.java
Normal file
@@ -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 io.vertx.core.json.JsonObject;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CliqueConfigOptionsTest {
|
||||
@@ -63,9 +63,11 @@ public class CliqueConfigOptionsTest {
|
||||
}
|
||||
|
||||
private CliqueConfigOptions fromConfigOptions(final Map<String, Object> cliqueConfigOptions) {
|
||||
return GenesisConfigFile.fromConfig(
|
||||
new JsonObject(singletonMap("config", singletonMap("clique", cliqueConfigOptions))))
|
||||
.getConfigOptions()
|
||||
.getCliqueConfigOptions();
|
||||
final ObjectNode rootNode = JsonUtil.createEmptyObjectNode();
|
||||
final ObjectNode configNode = JsonUtil.createEmptyObjectNode();
|
||||
final ObjectNode options = JsonUtil.objectNodeFromMap(cliqueConfigOptions);
|
||||
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.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
@@ -148,21 +148,42 @@ public class GenesisConfigFileTest {
|
||||
+ " \"balance\": \"1000\""
|
||||
+ " },"
|
||||
+ " \"f17f52151EbEF6C7334FAD080c5704D77216b732\": {"
|
||||
+ " \"balance\": \"90000000000000000000000\""
|
||||
+ " \"balance\": \"90000000000000000000000\","
|
||||
+ " \"storage\": {"
|
||||
+ " \"0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a4\": \"0x937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0\",\n"
|
||||
+ " \"0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a3\": \"0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012\""
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ "}");
|
||||
|
||||
final Map<String, String> allocations =
|
||||
final Map<String, GenesisAllocation> allocations =
|
||||
config
|
||||
.streamAllocations()
|
||||
.collect(
|
||||
Collectors.toMap(GenesisAllocation::getAddress, GenesisAllocation::getBalance));
|
||||
assertThat(allocations)
|
||||
.collect(Collectors.toMap(GenesisAllocation::getAddress, Function.identity()));
|
||||
assertThat(allocations.keySet())
|
||||
.containsOnly(
|
||||
entry("fe3b557e8fb62b89f4916b721be55ceb828dbd73", "0xad78ebc5ac6200000"),
|
||||
entry("627306090abab3a6e1400e9345bc60c78a8bef57", "1000"),
|
||||
entry("f17f52151ebef6c7334fad080c5704d77216b732", "90000000000000000000000"));
|
||||
"fe3b557e8fb62b89f4916b721be55ceb828dbd73",
|
||||
"627306090abab3a6e1400e9345bc60c78a8bef57",
|
||||
"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
|
||||
|
||||
@@ -17,10 +17,9 @@ import static java.util.Collections.singletonMap;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GenesisConfigOptionsTest {
|
||||
@@ -150,8 +149,10 @@ public class GenesisConfigOptionsTest {
|
||||
assertThat(config.getHomesteadBlockNumber()).isEmpty();
|
||||
}
|
||||
|
||||
private GenesisConfigOptions fromConfigOptions(final Map<String, Object> options) {
|
||||
return GenesisConfigFile.fromConfig(new JsonObject(Collections.singletonMap("config", options)))
|
||||
.getConfigOptions();
|
||||
private GenesisConfigOptions fromConfigOptions(final Map<String, Object> configOptions) {
|
||||
final ObjectNode rootNode = JsonUtil.createEmptyObjectNode();
|
||||
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 io.vertx.core.json.JsonObject;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.junit.Test;
|
||||
|
||||
public class IbftConfigOptionsTest {
|
||||
@@ -179,9 +179,11 @@ public class IbftConfigOptionsTest {
|
||||
}
|
||||
|
||||
private IbftConfigOptions fromConfigOptions(final Map<String, Object> ibftConfigOptions) {
|
||||
return GenesisConfigFile.fromConfig(
|
||||
new JsonObject(singletonMap("config", singletonMap("ibft", ibftConfigOptions))))
|
||||
.getConfigOptions()
|
||||
.getIbftLegacyConfigOptions();
|
||||
final ObjectNode rootNode = JsonUtil.createEmptyObjectNode();
|
||||
final ObjectNode configNode = JsonUtil.createEmptyObjectNode();
|
||||
final ObjectNode options = JsonUtil.objectNodeFromMap(ibftConfigOptions);
|
||||
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 com.google.common.collect.Lists;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -56,7 +55,7 @@ public class CliqueMinerExecutorTest {
|
||||
|
||||
private static final int EPOCH_LENGTH = 10;
|
||||
private static final GenesisConfigOptions GENESIS_CONFIG_OPTIONS =
|
||||
GenesisConfigFile.fromConfig(new JsonObject()).getConfigOptions();
|
||||
GenesisConfigFile.fromConfig("{}").getConfigOptions();
|
||||
private final KeyPair proposerKeyPair = KeyPair.generate();
|
||||
private final Random random = new Random(21341234L);
|
||||
private Address localAddress;
|
||||
|
||||
@@ -242,7 +242,7 @@ public final class GenesisState {
|
||||
final String hexNonce,
|
||||
final String hexAddress,
|
||||
final String balance,
|
||||
final Map<String, Object> storage,
|
||||
final Map<String, String> storage,
|
||||
final String hexCode,
|
||||
final String version) {
|
||||
this.nonce = withNiceErrorMessage("nonce", hexNonce, GenesisState::parseUnsignedLong);
|
||||
@@ -264,14 +264,19 @@ public final class GenesisState {
|
||||
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<>();
|
||||
storage.forEach(
|
||||
(key, value) ->
|
||||
parsedStorage.put(
|
||||
withNiceErrorMessage("storage key", key, UInt256::fromHexString),
|
||||
withNiceErrorMessage(
|
||||
"storage value", String.valueOf(value), UInt256::fromHexString)));
|
||||
storage
|
||||
.entrySet()
|
||||
.forEach(
|
||||
(entry) -> {
|
||||
final UInt256 key =
|
||||
withNiceErrorMessage("storage key", entry.getKey(), UInt256::fromHexString);
|
||||
final UInt256 value =
|
||||
withNiceErrorMessage("storage value", entry.getValue(), UInt256::fromHexString);
|
||||
parsedStorage.put(key, value);
|
||||
});
|
||||
|
||||
return parsedStorage;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import tech.pegasys.pantheon.config.GenesisConfigFile;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import com.google.common.io.Resources;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -47,18 +46,16 @@ public class MainnetProtocolScheduleTest {
|
||||
|
||||
@Test
|
||||
public void shouldOnlyUseFrontierWhenEmptyJsonConfigIsUsed() {
|
||||
final JsonObject json = new JsonObject("{}");
|
||||
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(Long.MAX_VALUE).getName()).isEqualTo("Frontier");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromConfigWithSettings() {
|
||||
final JsonObject json =
|
||||
new JsonObject(
|
||||
"{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 19, \"chainId\":1234}}");
|
||||
final String json =
|
||||
"{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 19, \"chainId\":1234}}";
|
||||
final ProtocolSchedule<Void> sched =
|
||||
MainnetProtocolSchedule.fromConfig(GenesisConfigFile.fromConfig(json).getConfigOptions());
|
||||
Assertions.assertThat(sched.getByBlockNumber(1).getName()).isEqualTo("Frontier");
|
||||
@@ -75,9 +72,8 @@ public class MainnetProtocolScheduleTest {
|
||||
|
||||
@Test
|
||||
public void outOfOrderConstantinoplesFail() {
|
||||
final JsonObject json =
|
||||
new JsonObject(
|
||||
"{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 17, \"chainId\":1234}}");
|
||||
final String json =
|
||||
"{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 17, \"chainId\":1234}}";
|
||||
Assertions.assertThatExceptionOfType(RuntimeException.class)
|
||||
.describedAs(
|
||||
"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 tech.pegasys.pantheon.config.GenesisConfigFile;
|
||||
import tech.pegasys.pantheon.config.JsonUtil;
|
||||
import tech.pegasys.pantheon.ethereum.chain.GenesisState;
|
||||
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
|
||||
import tech.pegasys.pantheon.ethereum.core.Address;
|
||||
@@ -36,6 +37,7 @@ import tech.pegasys.pantheon.metrics.PantheonMetricCategory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.io.Resources;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -55,8 +57,10 @@ public class NodeSmartContractPermissioningControllerTest {
|
||||
|
||||
final String emptyContractFile =
|
||||
Resources.toString(this.getClass().getResource(resourceName), UTF_8);
|
||||
|
||||
final ObjectNode jsonData = JsonUtil.objectNodeFromString(emptyContractFile, true);
|
||||
final GenesisState genesisState =
|
||||
GenesisState.fromConfig(GenesisConfigFile.fromConfig(emptyContractFile), protocolSchedule);
|
||||
GenesisState.fromConfig(GenesisConfigFile.fromConfig(jsonData), protocolSchedule);
|
||||
|
||||
final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock());
|
||||
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.config.JsonGenesisConfigOptions;
|
||||
import tech.pegasys.pantheon.config.JsonUtil;
|
||||
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
|
||||
import tech.pegasys.pantheon.crypto.SECP256K1;
|
||||
import tech.pegasys.pantheon.ethereum.core.Address;
|
||||
@@ -39,9 +40,10 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
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 io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import picocli.CommandLine.Command;
|
||||
@@ -129,10 +131,10 @@ public class OperatorSubCommand implements Runnable {
|
||||
@ParentCommand
|
||||
private OperatorSubCommand parentCommand; // Picocli injects reference to parent command
|
||||
|
||||
private JsonObject operatorConfig;
|
||||
private JsonObject genesisConfig;
|
||||
private JsonObject blockchainConfig;
|
||||
private JsonObject nodesConfig;
|
||||
private ObjectNode operatorConfig;
|
||||
private ObjectNode genesisConfig;
|
||||
private ObjectNode blockchainConfig;
|
||||
private ObjectNode nodesConfig;
|
||||
private boolean generateNodesKeys;
|
||||
private List<Address> addressesForGenesisExtraData = new ArrayList<>();
|
||||
private Path keysDirectory;
|
||||
@@ -171,19 +173,25 @@ public class OperatorSubCommand implements Runnable {
|
||||
/** Imports public keys from input configuration. */
|
||||
private void importPublicKeysFromConfig() {
|
||||
LOG.info("Importing public keys from configuration.");
|
||||
JsonArray keys = nodesConfig.getJsonArray("keys");
|
||||
keys.stream().forEach(this::importPublicKey);
|
||||
JsonUtil.getArrayNode(nodesConfig, "keys")
|
||||
.ifPresent(keys -> keys.forEach(this::importPublicKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
final SECP256K1.PublicKey publicKey =
|
||||
SECP256K1.PublicKey.create(BytesValue.fromHexString((String) publicKeyObject));
|
||||
SECP256K1.PublicKey.create(BytesValue.fromHexString(publicKeyText));
|
||||
writeKeypair(publicKey, null);
|
||||
LOG.info("Public key imported from configuration.({})", publicKey.toString());
|
||||
} catch (IOException e) {
|
||||
@@ -193,7 +201,7 @@ public class OperatorSubCommand implements Runnable {
|
||||
|
||||
/** Generates nodes keypairs. */
|
||||
private void generateNodesKeys() {
|
||||
final int nodesCount = nodesConfig.getInteger("count");
|
||||
final int nodesCount = JsonUtil.getInt(nodesConfig, "count", 0);
|
||||
LOG.info("Generating {} nodes keys.", nodesCount);
|
||||
IntStream.range(0, nodesCount).forEach(this::generateNodeKeypair);
|
||||
}
|
||||
@@ -241,8 +249,9 @@ public class OperatorSubCommand implements Runnable {
|
||||
* @throws IOException
|
||||
*/
|
||||
private void processExtraData() {
|
||||
final ObjectNode configNode = JsonUtil.getObjectNode(genesisConfig, "config").orElse(null);
|
||||
final JsonGenesisConfigOptions genesisConfigOptions =
|
||||
JsonGenesisConfigOptions.fromJsonObject(genesisConfig.getJsonObject("config"));
|
||||
JsonGenesisConfigOptions.fromJsonObject(configNode);
|
||||
if (genesisConfigOptions.isIbft2()) {
|
||||
LOG.info("Generating IBFT extra data.");
|
||||
final String extraData =
|
||||
@@ -265,11 +274,18 @@ public class OperatorSubCommand implements Runnable {
|
||||
private void parseConfig() throws IOException {
|
||||
final String configString =
|
||||
Resources.toString(configurationFile.toPath().toUri().toURL(), UTF_8);
|
||||
operatorConfig = new JsonObject(configString);
|
||||
genesisConfig = operatorConfig.getJsonObject("genesis");
|
||||
blockchainConfig = operatorConfig.getJsonObject("blockchain");
|
||||
nodesConfig = blockchainConfig.getJsonObject("nodes");
|
||||
generateNodesKeys = nodesConfig.getBoolean("generate", false);
|
||||
final ObjectNode root = JsonUtil.objectNodeFromString(configString);
|
||||
operatorConfig = root;
|
||||
genesisConfig =
|
||||
JsonUtil.getObjectNode(operatorConfig, "genesis")
|
||||
.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
|
||||
*/
|
||||
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.");
|
||||
Files.write(
|
||||
directory.toPath().resolve(fileName),
|
||||
genesis.encodePrettily().getBytes(UTF_8),
|
||||
JsonUtil.getJson(genesis).getBytes(UTF_8),
|
||||
StandardOpenOption.CREATE_NEW);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static java.util.Arrays.stream;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
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.junit.Assert.assertTrue;
|
||||
import static tech.pegasys.pantheon.cli.operator.OperatorSubCommandTest.Cmd.cmd;
|
||||
@@ -152,15 +153,36 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
|
||||
asList("key.pub", "priv.test"));
|
||||
}
|
||||
|
||||
@Test(expected = CommandLine.ExecutionException.class)
|
||||
public void shouldFailIfDuplicateFiles() throws IOException {
|
||||
runCmdAndCheckOutput(
|
||||
cmd("--private-key-file-name", "dup.test", "--public-key-file-name", "dup.test"),
|
||||
"/operator/config_generate_keys.json",
|
||||
tmpOutputDirectoryPath,
|
||||
"genesis.json",
|
||||
true,
|
||||
asList("key.pub", "priv.test"));
|
||||
@Test
|
||||
public void shouldFailIfDuplicateFiles() {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
runCmdAndCheckOutput(
|
||||
cmd(
|
||||
"--private-key-file-name",
|
||||
"dup.test",
|
||||
"--public-key-file-name",
|
||||
"dup.test"),
|
||||
"/operator/config_generate_keys.json",
|
||||
tmpOutputDirectoryPath,
|
||||
"genesis.json",
|
||||
true,
|
||||
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)
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user