mirror of https://github.com/hyperledger/besu
Split parsing of genesis config from creating initial state (#209)
* Make GenesisConfigFile responsible for handling all the content in the genesis config file and rename GenesisConfig to GenesisState as it is now just responsible for creating the initial state at genesis. * In CliqueProtocolController, pass the network ID to EthProtocolManager instead of the chain ID and use downloader parallelism setting instead of network ID for the number of threads. Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
a44d696117
commit
09a3c61f72
@ -0,0 +1,33 @@ |
||||
/* |
||||
* Copyright 2018 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 io.vertx.core.json.JsonObject; |
||||
|
||||
public class GenesisAllocation { |
||||
private final String address; |
||||
private final JsonObject data; |
||||
|
||||
public GenesisAllocation(final String address, final JsonObject data) { |
||||
this.address = address; |
||||
this.data = data; |
||||
} |
||||
|
||||
public String getAddress() { |
||||
return address; |
||||
} |
||||
|
||||
public String getBalance() { |
||||
return data.getString("balance", "0"); |
||||
} |
||||
} |
@ -0,0 +1,137 @@ |
||||
/* |
||||
* Copyright 2018 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 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.google.common.io.Resources; |
||||
import io.vertx.core.json.JsonObject; |
||||
|
||||
public class GenesisConfigFile { |
||||
|
||||
public static final GenesisConfigFile DEFAULT = new GenesisConfigFile(new JsonObject()); |
||||
|
||||
private final JsonObject configRoot; |
||||
|
||||
private GenesisConfigFile(final JsonObject config) { |
||||
this.configRoot = config; |
||||
} |
||||
|
||||
public static GenesisConfigFile mainnet() { |
||||
try { |
||||
return fromConfig(Resources.toString(Resources.getResource("mainnet.json"), UTF_8)); |
||||
} catch (final IOException e) { |
||||
throw new IllegalStateException(e); |
||||
} |
||||
} |
||||
|
||||
public static GenesisConfigFile development() { |
||||
try { |
||||
return fromConfig(Resources.toString(Resources.getResource("dev.json"), UTF_8)); |
||||
} catch (final IOException e) { |
||||
throw new IllegalStateException(e); |
||||
} |
||||
} |
||||
|
||||
public static GenesisConfigFile fromConfig(final String config) { |
||||
return fromConfig(new JsonObject(config)); |
||||
} |
||||
|
||||
public static GenesisConfigFile fromConfig(final JsonObject config) { |
||||
return new GenesisConfigFile(normalizeKeys(config)); |
||||
} |
||||
|
||||
public GenesisConfigOptions getConfigOptions() { |
||||
return new GenesisConfigOptions(configRoot.getJsonObject("config")); |
||||
} |
||||
|
||||
public Stream<GenesisAllocation> getAllocations() { |
||||
final JsonObject allocations = configRoot.getJsonObject("alloc"); |
||||
return allocations |
||||
.fieldNames() |
||||
.stream() |
||||
.map(key -> new GenesisAllocation(key, allocations.getJsonObject(key))); |
||||
} |
||||
|
||||
public String getParentHash() { |
||||
return configRoot.getString("parenthash", ""); |
||||
} |
||||
|
||||
public String getDifficulty() { |
||||
return getRequiredString("difficulty"); |
||||
} |
||||
|
||||
public String getExtraData() { |
||||
return configRoot.getString("extradata", ""); |
||||
} |
||||
|
||||
public Long getGasLimit() { |
||||
return Long.decode(getRequiredString("gaslimit")); |
||||
} |
||||
|
||||
public String getMixHash() { |
||||
return configRoot.getString("mixhash", ""); |
||||
} |
||||
|
||||
public String getNonce() { |
||||
return configRoot.getString("nonce", ""); |
||||
} |
||||
|
||||
public Optional<String> getCoinbase() { |
||||
return Optional.ofNullable(configRoot.getString("coinbase")); |
||||
} |
||||
|
||||
public long getTimestamp() { |
||||
return Long.parseLong(configRoot.getString("timestamp", "0x0").substring(2), 16); |
||||
} |
||||
|
||||
private String getRequiredString(final String key) { |
||||
if (!configRoot.containsKey(key)) { |
||||
throw new IllegalArgumentException( |
||||
String.format("Invalid Genesis block configuration, missing value for '%s'", key)); |
||||
} |
||||
return configRoot.getString(key); |
||||
} |
||||
|
||||
/* 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 |
||||
* case insensitive. |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
private static JsonObject normalizeKeys(final JsonObject genesis) { |
||||
final Map<String, Object> normalized = new HashMap<>(); |
||||
genesis |
||||
.getMap() |
||||
.forEach( |
||||
(key, value) -> { |
||||
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))); |
||||
} else { |
||||
normalized.put(normalizedKey, value); |
||||
} |
||||
}); |
||||
return new JsonObject(normalized); |
||||
} |
||||
} |
@ -0,0 +1,176 @@ |
||||
/* |
||||
* Copyright 2018 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 static org.assertj.core.api.Assertions.entry; |
||||
|
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable; |
||||
import org.junit.Test; |
||||
|
||||
public class GenesisConfigFileTest { |
||||
|
||||
private static final int MAINNET_CHAIN_ID = 1; |
||||
private static final int DEVELOPMENT_CHAIN_ID = 2018; |
||||
private static final GenesisConfigFile EMPTY_CONFIG = GenesisConfigFile.fromConfig("{}"); |
||||
|
||||
@Test |
||||
public void shouldLoadMainnetConfigFile() { |
||||
final GenesisConfigFile config = GenesisConfigFile.mainnet(); |
||||
// Sanity check some basic properties to confirm this is the mainnet file.
|
||||
assertThat(config.getConfigOptions().isEthHash()).isTrue(); |
||||
assertThat(config.getConfigOptions().getChainId()).hasValue(MAINNET_CHAIN_ID); |
||||
assertThat(config.getAllocations().map(GenesisAllocation::getAddress)) |
||||
.contains( |
||||
"000d836201318ec6899a67540690382780743280", |
||||
"001762430ea9c3a26e5749afdb70da5f78ddbb8c", |
||||
"001d14804b399c6ef80e64576f657660804fec0b"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldLoadDevelopmentConfigFile() { |
||||
final GenesisConfigFile config = GenesisConfigFile.development(); |
||||
// Sanity check some basic properties to confirm this is the dev file.
|
||||
assertThat(config.getConfigOptions().isEthHash()).isTrue(); |
||||
assertThat(config.getConfigOptions().getChainId()).hasValue(DEVELOPMENT_CHAIN_ID); |
||||
assertThat(config.getAllocations().map(GenesisAllocation::getAddress)) |
||||
.contains( |
||||
"fe3b557e8fb62b89f4916b721be55ceb828dbd73", |
||||
"627306090abab3a6e1400e9345bc60c78a8bef57", |
||||
"f17f52151ebef6c7334fad080c5704d77216b732"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetParentHash() { |
||||
assertThat(configWithProperty("parentHash", "844633").getParentHash()).isEqualTo("844633"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldDefaultParentHashToEmptyString() { |
||||
assertThat(EMPTY_CONFIG.getParentHash()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetDifficulty() { |
||||
assertThat(configWithProperty("difficulty", "1234").getDifficulty()).isEqualTo("1234"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRequireDifficulty() { |
||||
assertInvalidConfiguration(EMPTY_CONFIG::getDifficulty); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetExtraData() { |
||||
assertThat(configWithProperty("extraData", "yay").getExtraData()).isEqualTo("yay"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldDefaultExtraDataToEmptyString() { |
||||
assertThat(EMPTY_CONFIG.getExtraData()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetGasLimit() { |
||||
assertThat(configWithProperty("gasLimit", "1000").getGasLimit()).isEqualTo(1000); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRequireGasLimit() { |
||||
assertInvalidConfiguration(EMPTY_CONFIG::getGasLimit); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetMixHash() { |
||||
assertThat(configWithProperty("mixHash", "asdf").getMixHash()).isEqualTo("asdf"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldDefaultMixHashToEmptyString() { |
||||
assertThat(EMPTY_CONFIG.getMixHash()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetNonce() { |
||||
assertThat(configWithProperty("nonce", "ABCD").getNonce()).isEqualTo("ABCD"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldDefaultNonceToEmptyString() { |
||||
assertThat(EMPTY_CONFIG.getNonce()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetCoinbase() { |
||||
assertThat(configWithProperty("coinbase", "abcd").getCoinbase()).contains("abcd"); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnEmptyWhenCoinbaseNotSpecified() { |
||||
assertThat(EMPTY_CONFIG.getCoinbase()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetTimestamp() { |
||||
assertThat(configWithProperty("timestamp", "0x10").getTimestamp()).isEqualTo(16L); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldDefaultTimestampToZero() { |
||||
assertThat(EMPTY_CONFIG.getTimestamp()).isZero(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetAllocations() { |
||||
final GenesisConfigFile config = |
||||
GenesisConfigFile.fromConfig( |
||||
"{" |
||||
+ " \"alloc\": {" |
||||
+ " \"fe3b557e8fb62b89f4916b721be55ceb828dbd73\": {" |
||||
+ " \"balance\": \"0xad78ebc5ac6200000\"" |
||||
+ " }," |
||||
+ " \"627306090abaB3A6e1400e9345bC60c78a8BEf57\": {" |
||||
+ " \"balance\": \"1000\"" |
||||
+ " }," |
||||
+ " \"f17f52151EbEF6C7334FAD080c5704D77216b732\": {" |
||||
+ " \"balance\": \"90000000000000000000000\"" |
||||
+ " }" |
||||
+ " }" |
||||
+ "}"); |
||||
|
||||
final Map<String, String> allocations = |
||||
config |
||||
.getAllocations() |
||||
.collect( |
||||
Collectors.toMap(GenesisAllocation::getAddress, GenesisAllocation::getBalance)); |
||||
assertThat(allocations) |
||||
.containsOnly( |
||||
entry("fe3b557e8fb62b89f4916b721be55ceb828dbd73", "0xad78ebc5ac6200000"), |
||||
entry("627306090abab3a6e1400e9345bc60c78a8bef57", "1000"), |
||||
entry("f17f52151ebef6c7334fad080c5704d77216b732", "90000000000000000000000")); |
||||
} |
||||
|
||||
private GenesisConfigFile configWithProperty(final String key, final String value) { |
||||
return GenesisConfigFile.fromConfig("{\"" + key + "\":\"" + value + "\"}"); |
||||
} |
||||
|
||||
private void assertInvalidConfiguration(final ThrowingCallable getter) { |
||||
assertThatThrownBy(getter) |
||||
.isInstanceOf(IllegalArgumentException.class) |
||||
.hasMessageContaining("Invalid Genesis block configuration"); |
||||
} |
||||
} |
@ -1,334 +0,0 @@ |
||||
/* |
||||
* Copyright 2018 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.ethereum.chain; |
||||
|
||||
import tech.pegasys.pantheon.config.GenesisConfigOptions; |
||||
import tech.pegasys.pantheon.ethereum.core.Address; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockBody; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeaderBuilder; |
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.core.LogsBloomFilter; |
||||
import tech.pegasys.pantheon.ethereum.core.MutableWorldState; |
||||
import tech.pegasys.pantheon.ethereum.core.Wei; |
||||
import tech.pegasys.pantheon.ethereum.core.WorldUpdater; |
||||
import tech.pegasys.pantheon.ethereum.development.DevelopmentProtocolSchedule; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ScheduleBasedBlockHashFunction; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.DefaultMutableWorldState; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.KeyValueStorageWorldStateStorage; |
||||
import tech.pegasys.pantheon.services.kvstore.InMemoryKeyValueStorage; |
||||
import tech.pegasys.pantheon.util.bytes.Bytes32; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
import tech.pegasys.pantheon.util.uint.UInt256; |
||||
|
||||
import java.io.IOException; |
||||
import java.math.BigInteger; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
import com.google.common.base.MoreObjects; |
||||
import com.google.common.io.Resources; |
||||
import io.vertx.core.json.JsonObject; |
||||
|
||||
public final class GenesisConfig<C> { |
||||
|
||||
private static final BlockBody BODY = |
||||
new BlockBody(Collections.emptyList(), Collections.emptyList()); |
||||
private static final String MAINNET_FILE = "mainnet.json"; |
||||
|
||||
private final Block block; |
||||
private final int chainId; |
||||
private final ProtocolSchedule<C> protocolSchedule; |
||||
private final List<GenesisAccount> genesisAccounts; |
||||
|
||||
private GenesisConfig( |
||||
final Block block, |
||||
final int chainId, |
||||
final ProtocolSchedule<C> protocolSchedule, |
||||
final List<GenesisAccount> genesisAccounts) { |
||||
this.block = block; |
||||
this.chainId = chainId; |
||||
this.protocolSchedule = protocolSchedule; |
||||
this.genesisAccounts = genesisAccounts; |
||||
} |
||||
|
||||
public static GenesisConfig<Void> mainnet() { |
||||
try { |
||||
final JsonObject config = |
||||
new JsonObject( |
||||
Resources.toString(Resources.getResource(MAINNET_FILE), StandardCharsets.UTF_8)); |
||||
final GenesisConfigOptions configOptions = GenesisConfigOptions.fromGenesisConfig(config); |
||||
return GenesisConfig.fromConfig(config, MainnetProtocolSchedule.fromConfig(configOptions)); |
||||
} catch (final IOException ex) { |
||||
throw new IllegalStateException(ex); |
||||
} |
||||
} |
||||
|
||||
public static GenesisConfig<Void> development() { |
||||
try { |
||||
final JsonObject config = |
||||
new JsonObject( |
||||
Resources.toString(Resources.getResource("dev.json"), StandardCharsets.UTF_8)); |
||||
return GenesisConfig.fromConfig( |
||||
config, DevelopmentProtocolSchedule.create(config.getJsonObject("config"))); |
||||
} catch (final IOException ex) { |
||||
throw new IllegalStateException(ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Construct a {@link GenesisConfig} from a JSON string. |
||||
* |
||||
* @param json A JSON string describing the genesis block |
||||
* @param protocolSchedule A protocol Schedule associated with |
||||
* @param <C> The consensus context type |
||||
* @return A new genesis block. |
||||
*/ |
||||
public static <C> GenesisConfig<C> fromJson( |
||||
final String json, final ProtocolSchedule<C> protocolSchedule) { |
||||
return fromConfig(new JsonObject(json), protocolSchedule); |
||||
} |
||||
|
||||
/** |
||||
* Construct a {@link GenesisConfig} from a JSON object. |
||||
* |
||||
* @param jsonConfig A {@link JsonObject} describing the genesis block. |
||||
* @param protocolSchedule A protocol Schedule associated with |
||||
* @param <C> The consensus context type |
||||
* @return A new genesis block. |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static <C> GenesisConfig<C> fromConfig( |
||||
final JsonObject jsonConfig, final ProtocolSchedule<C> protocolSchedule) { |
||||
final Map<String, Object> definition = toNormalizedMap(jsonConfig); |
||||
final List<GenesisAccount> genesisAccounts = |
||||
parseAllocations(definition).collect(Collectors.toList()); |
||||
final Block block = |
||||
new Block( |
||||
buildHeader(definition, calculateGenesisStateHash(genesisAccounts), protocolSchedule), |
||||
BODY); |
||||
final int chainId = |
||||
GenesisConfigOptions.fromGenesisConfig(jsonConfig) |
||||
.getChainId() |
||||
.orElse(MainnetProtocolSchedule.DEFAULT_CHAIN_ID); |
||||
return new GenesisConfig<>(block, chainId, protocolSchedule, genesisAccounts); |
||||
} |
||||
|
||||
public Block getBlock() { |
||||
return block; |
||||
} |
||||
|
||||
public int getChainId() { |
||||
return chainId; |
||||
} |
||||
|
||||
/** |
||||
* Writes the genesis block's world state to the given {@link MutableWorldState}. |
||||
* |
||||
* @param target WorldView to write genesis state to |
||||
*/ |
||||
public void writeStateTo(final MutableWorldState target) { |
||||
writeAccountsTo(target, genesisAccounts); |
||||
} |
||||
|
||||
private static void writeAccountsTo( |
||||
final MutableWorldState target, final List<GenesisAccount> genesisAccounts) { |
||||
final WorldUpdater updater = target.updater(); |
||||
genesisAccounts.forEach( |
||||
account -> updater.getOrCreate(account.address).setBalance(account.balance)); |
||||
updater.commit(); |
||||
target.persist(); |
||||
} |
||||
|
||||
public ProtocolSchedule<C> getProtocolSchedule() { |
||||
return protocolSchedule; |
||||
} |
||||
|
||||
private static Hash calculateGenesisStateHash(final List<GenesisAccount> genesisAccounts) { |
||||
final MutableWorldState worldState = |
||||
new DefaultMutableWorldState( |
||||
new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage())); |
||||
writeAccountsTo(worldState, genesisAccounts); |
||||
return worldState.rootHash(); |
||||
} |
||||
|
||||
private static <C> BlockHeader buildHeader( |
||||
final Map<String, Object> genesis, |
||||
final Hash genesisRootHash, |
||||
final ProtocolSchedule<C> protocolSchedule) { |
||||
|
||||
return BlockHeaderBuilder.create() |
||||
.parentHash(parseParentHash(genesis)) |
||||
.ommersHash(Hash.EMPTY_LIST_HASH) |
||||
.coinbase(parseCoinbase(genesis)) |
||||
.stateRoot(genesisRootHash) |
||||
.transactionsRoot(Hash.EMPTY_TRIE_HASH) |
||||
.receiptsRoot(Hash.EMPTY_TRIE_HASH) |
||||
.logsBloom(LogsBloomFilter.empty()) |
||||
.difficulty(parseDifficulty(genesis)) |
||||
.number(BlockHeader.GENESIS_BLOCK_NUMBER) |
||||
.gasLimit(parseGasLimit(genesis)) |
||||
.gasUsed(0L) |
||||
.timestamp(parseTimestamp(genesis)) |
||||
.extraData(parseExtraData(genesis)) |
||||
.mixHash(parseMixHash(genesis)) |
||||
.nonce(parseNonce(genesis)) |
||||
.blockHashFunction(ScheduleBasedBlockHashFunction.create(protocolSchedule)) |
||||
.buildBlockHeader(); |
||||
} |
||||
|
||||
/* 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 |
||||
* case insensitive. |
||||
*/ |
||||
private static Map<String, Object> toNormalizedMap(final JsonObject genesis) { |
||||
final Map<String, Object> normalized = new HashMap<>(); |
||||
genesis |
||||
.getMap() |
||||
.forEach( |
||||
(key, value) -> { |
||||
final String normalizedKey = key.toLowerCase(Locale.US); |
||||
if (value instanceof JsonObject) { |
||||
normalized.put(normalizedKey, toNormalizedMap((JsonObject) value)); |
||||
} else { |
||||
normalized.put(normalizedKey, value); |
||||
} |
||||
}); |
||||
return normalized; |
||||
} |
||||
|
||||
private static String getString( |
||||
final Map<String, Object> map, final String key, final String def) { |
||||
return entryAsString(map.getOrDefault(key.toLowerCase(Locale.US), def)); |
||||
} |
||||
|
||||
private static String getString(final Map<String, Object> map, final String key) { |
||||
final String keyy = key.toLowerCase(Locale.US); |
||||
if (!map.containsKey(keyy)) { |
||||
throw new IllegalArgumentException( |
||||
String.format("Invalid Genesis block configuration, missing value for '%s'", key)); |
||||
} |
||||
return entryAsString(map.get(keyy)); |
||||
} |
||||
|
||||
private static String entryAsString(final Object value) { |
||||
return ((CharSequence) value).toString(); |
||||
} |
||||
|
||||
private static long parseTimestamp(final Map<String, Object> genesis) { |
||||
return Long.parseLong(getString(genesis, "timestamp", "0x0").substring(2), 16); |
||||
} |
||||
|
||||
private static Address parseCoinbase(final Map<String, Object> genesis) { |
||||
final Address coinbase; |
||||
final String key = "coinbase"; |
||||
if (genesis.containsKey(key)) { |
||||
coinbase = Address.fromHexString(getString(genesis, key)); |
||||
} else { |
||||
coinbase = Address.wrap(BytesValue.wrap(new byte[Address.SIZE])); |
||||
} |
||||
return coinbase; |
||||
} |
||||
|
||||
private static Hash parseParentHash(final Map<String, Object> genesis) { |
||||
return Hash.wrap(Bytes32.fromHexString(getString(genesis, "parentHash", ""))); |
||||
} |
||||
|
||||
private static BytesValue parseExtraData(final Map<String, Object> genesis) { |
||||
return BytesValue.fromHexString(getString(genesis, "extraData", "")); |
||||
} |
||||
|
||||
private static UInt256 parseDifficulty(final Map<String, Object> genesis) { |
||||
return UInt256.fromHexString(getString(genesis, "difficulty")); |
||||
} |
||||
|
||||
private static long parseGasLimit(final Map<String, Object> genesis) { |
||||
return Long.decode(getString(genesis, "gasLimit")); |
||||
} |
||||
|
||||
private static Hash parseMixHash(final Map<String, Object> genesis) { |
||||
return Hash.wrap(Bytes32.fromHexString(getString(genesis, "mixHash", ""))); |
||||
} |
||||
|
||||
private static long parseNonce(final Map<String, Object> genesis) { |
||||
String nonce = getString(genesis, "nonce", "").toLowerCase(); |
||||
if (nonce.startsWith("0x")) { |
||||
nonce = nonce.substring(2); |
||||
} |
||||
return Long.parseUnsignedLong(nonce, 16); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private static Stream<GenesisAccount> parseAllocations(final Map<String, Object> genesis) { |
||||
final Map<String, Object> alloc = (Map<String, Object>) genesis.get("alloc"); |
||||
return alloc |
||||
.entrySet() |
||||
.stream() |
||||
.map( |
||||
entry -> { |
||||
final Address address = Address.fromHexString(entry.getKey()); |
||||
final String balance = getString((Map<String, Object>) entry.getValue(), "balance"); |
||||
return new GenesisAccount(address, balance); |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return MoreObjects.toStringHelper(this) |
||||
.add("block", block) |
||||
.add("chainId", chainId) |
||||
.add("protocolSchedule", protocolSchedule) |
||||
.add("genesisAccounts", genesisAccounts) |
||||
.toString(); |
||||
} |
||||
|
||||
private static final class GenesisAccount { |
||||
|
||||
final Address address; |
||||
final Wei balance; |
||||
|
||||
GenesisAccount(final Address address, final String balance) { |
||||
this.address = address; |
||||
this.balance = parseBalance(balance); |
||||
} |
||||
|
||||
private Wei parseBalance(final String balance) { |
||||
final BigInteger val; |
||||
if (balance.startsWith("0x")) { |
||||
val = new BigInteger(1, BytesValue.fromHexStringLenient(balance).extractArray()); |
||||
} else { |
||||
val = new BigInteger(balance); |
||||
} |
||||
|
||||
return Wei.of(val); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return MoreObjects.toStringHelper(this) |
||||
.add("address", address) |
||||
.add("balance", balance) |
||||
.toString(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,223 @@ |
||||
/* |
||||
* Copyright 2018 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.ethereum.chain; |
||||
|
||||
import tech.pegasys.pantheon.config.GenesisConfigFile; |
||||
import tech.pegasys.pantheon.ethereum.core.Address; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockBody; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeaderBuilder; |
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.core.LogsBloomFilter; |
||||
import tech.pegasys.pantheon.ethereum.core.MutableWorldState; |
||||
import tech.pegasys.pantheon.ethereum.core.Wei; |
||||
import tech.pegasys.pantheon.ethereum.core.WorldUpdater; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ScheduleBasedBlockHashFunction; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.DefaultMutableWorldState; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.KeyValueStorageWorldStateStorage; |
||||
import tech.pegasys.pantheon.services.kvstore.InMemoryKeyValueStorage; |
||||
import tech.pegasys.pantheon.util.bytes.Bytes32; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
import tech.pegasys.pantheon.util.uint.UInt256; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
import com.google.common.base.MoreObjects; |
||||
|
||||
public final class GenesisState { |
||||
|
||||
private static final BlockBody BODY = |
||||
new BlockBody(Collections.emptyList(), Collections.emptyList()); |
||||
|
||||
private final Block block; |
||||
private final List<GenesisAccount> genesisAccounts; |
||||
|
||||
private GenesisState(final Block block, final List<GenesisAccount> genesisAccounts) { |
||||
this.block = block; |
||||
this.genesisAccounts = genesisAccounts; |
||||
} |
||||
|
||||
/** |
||||
* Construct a {@link GenesisState} from a JSON string. |
||||
* |
||||
* @param json A JSON string describing the genesis block |
||||
* @param protocolSchedule A protocol Schedule associated with |
||||
* @param <C> The consensus context type |
||||
* @return A new {@link GenesisState}. |
||||
*/ |
||||
public static <C> GenesisState fromJson( |
||||
final String json, final ProtocolSchedule<C> protocolSchedule) { |
||||
return fromConfig(GenesisConfigFile.fromConfig(json), protocolSchedule); |
||||
} |
||||
|
||||
/** |
||||
* Construct a {@link GenesisState} from a JSON object. |
||||
* |
||||
* @param config A {@link GenesisConfigFile} describing the genesis block. |
||||
* @param protocolSchedule A protocol Schedule associated with |
||||
* @param <C> The consensus context type |
||||
* @return A new {@link GenesisState}. |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static <C> GenesisState fromConfig( |
||||
final GenesisConfigFile config, final ProtocolSchedule<C> protocolSchedule) { |
||||
final List<GenesisAccount> genesisAccounts = |
||||
parseAllocations(config).collect(Collectors.toList()); |
||||
final Block block = |
||||
new Block( |
||||
buildHeader(config, calculateGenesisStateHash(genesisAccounts), protocolSchedule), |
||||
BODY); |
||||
return new GenesisState(block, genesisAccounts); |
||||
} |
||||
|
||||
public Block getBlock() { |
||||
return block; |
||||
} |
||||
|
||||
/** |
||||
* Writes the genesis block's world state to the given {@link MutableWorldState}. |
||||
* |
||||
* @param target WorldView to write genesis state to |
||||
*/ |
||||
public void writeStateTo(final MutableWorldState target) { |
||||
writeAccountsTo(target, genesisAccounts); |
||||
} |
||||
|
||||
private static void writeAccountsTo( |
||||
final MutableWorldState target, final List<GenesisAccount> genesisAccounts) { |
||||
final WorldUpdater updater = target.updater(); |
||||
genesisAccounts.forEach( |
||||
account -> updater.getOrCreate(account.address).setBalance(account.balance)); |
||||
updater.commit(); |
||||
target.persist(); |
||||
} |
||||
|
||||
private static Hash calculateGenesisStateHash(final List<GenesisAccount> genesisAccounts) { |
||||
final MutableWorldState worldState = |
||||
new DefaultMutableWorldState( |
||||
new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage())); |
||||
writeAccountsTo(worldState, genesisAccounts); |
||||
return worldState.rootHash(); |
||||
} |
||||
|
||||
private static <C> BlockHeader buildHeader( |
||||
final GenesisConfigFile genesis, |
||||
final Hash genesisRootHash, |
||||
final ProtocolSchedule<C> protocolSchedule) { |
||||
|
||||
return BlockHeaderBuilder.create() |
||||
.parentHash(parseParentHash(genesis)) |
||||
.ommersHash(Hash.EMPTY_LIST_HASH) |
||||
.coinbase(parseCoinbase(genesis)) |
||||
.stateRoot(genesisRootHash) |
||||
.transactionsRoot(Hash.EMPTY_TRIE_HASH) |
||||
.receiptsRoot(Hash.EMPTY_TRIE_HASH) |
||||
.logsBloom(LogsBloomFilter.empty()) |
||||
.difficulty(parseDifficulty(genesis)) |
||||
.number(BlockHeader.GENESIS_BLOCK_NUMBER) |
||||
.gasLimit(genesis.getGasLimit()) |
||||
.gasUsed(0L) |
||||
.timestamp(genesis.getTimestamp()) |
||||
.extraData(parseExtraData(genesis)) |
||||
.mixHash(parseMixHash(genesis)) |
||||
.nonce(parseNonce(genesis)) |
||||
.blockHashFunction(ScheduleBasedBlockHashFunction.create(protocolSchedule)) |
||||
.buildBlockHeader(); |
||||
} |
||||
|
||||
private static Address parseCoinbase(final GenesisConfigFile genesis) { |
||||
return genesis |
||||
.getCoinbase() |
||||
.map(Address::fromHexString) |
||||
.orElseGet(() -> Address.wrap(BytesValue.wrap(new byte[Address.SIZE]))); |
||||
} |
||||
|
||||
private static Hash parseParentHash(final GenesisConfigFile genesis) { |
||||
return Hash.wrap(Bytes32.fromHexString(genesis.getParentHash())); |
||||
} |
||||
|
||||
private static BytesValue parseExtraData(final GenesisConfigFile genesis) { |
||||
return BytesValue.fromHexString(genesis.getExtraData()); |
||||
} |
||||
|
||||
private static UInt256 parseDifficulty(final GenesisConfigFile genesis) { |
||||
return UInt256.fromHexString(genesis.getDifficulty()); |
||||
} |
||||
|
||||
private static Hash parseMixHash(final GenesisConfigFile genesis) { |
||||
return Hash.wrap(Bytes32.fromHexString(genesis.getMixHash())); |
||||
} |
||||
|
||||
private static long parseNonce(final GenesisConfigFile genesis) { |
||||
String nonce = genesis.getNonce().toLowerCase(Locale.US); |
||||
if (nonce.startsWith("0x")) { |
||||
nonce = nonce.substring(2); |
||||
} |
||||
return Long.parseUnsignedLong(nonce, 16); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private static Stream<GenesisAccount> parseAllocations(final GenesisConfigFile genesis) { |
||||
return genesis |
||||
.getAllocations() |
||||
.map( |
||||
allocation -> |
||||
new GenesisAccount( |
||||
Address.fromHexString(allocation.getAddress()), allocation.getBalance())); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return MoreObjects.toStringHelper(this) |
||||
.add("block", block) |
||||
.add("genesisAccounts", genesisAccounts) |
||||
.toString(); |
||||
} |
||||
|
||||
private static final class GenesisAccount { |
||||
|
||||
final Address address; |
||||
final Wei balance; |
||||
|
||||
GenesisAccount(final Address address, final String balance) { |
||||
this.address = address; |
||||
this.balance = parseBalance(balance); |
||||
} |
||||
|
||||
private Wei parseBalance(final String balance) { |
||||
final BigInteger val; |
||||
if (balance.startsWith("0x")) { |
||||
val = new BigInteger(1, BytesValue.fromHexStringLenient(balance).extractArray()); |
||||
} else { |
||||
val = new BigInteger(balance); |
||||
} |
||||
|
||||
return Wei.of(val); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return MoreObjects.toStringHelper(this) |
||||
.add("address", address) |
||||
.add("balance", balance) |
||||
.toString(); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue