blob: a49b474fddd62cd8433867b0701f7643edb1425a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
* to You 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 org.apache.tuweni.eth.genesis;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.concurrent.AsyncCompletion;
import org.apache.tuweni.eth.AccountState;
import org.apache.tuweni.eth.Address;
import org.apache.tuweni.eth.Block;
import org.apache.tuweni.eth.BlockBody;
import org.apache.tuweni.eth.BlockHeader;
import org.apache.tuweni.eth.Hash;
import org.apache.tuweni.rlp.RLP;
import org.apache.tuweni.trie.MerklePatriciaTrie;
import org.apache.tuweni.units.bigints.UInt256;
import org.apache.tuweni.units.bigints.UInt64;
import org.apache.tuweni.units.ethereum.Gas;
import org.apache.tuweni.units.ethereum.Wei;
import java.io.IOException;
import java.math.BigInteger;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
/**
* Utility to read genesis config files and translate them to a block.
*/
public class GenesisFile {
private final Bytes nonce;
private final UInt256 difficulty;
private final Hash mixhash;
private final Address coinbase;
private final Instant timestamp;
private final Bytes extraData;
private final Gas gasLimit;
private final Map<Address, Wei> allocs;
private final int chainId;
private final List<Long> forks;
public GenesisFile(
String nonce,
String difficulty,
String mixhash,
String coinbase,
String timestamp,
String extraData,
String gasLimit,
String parentHash,
Map<String, String> allocs,
int chainId,
List<Long> forks) {
if (nonce == null) {
throw new IllegalArgumentException("nonce must be provided");
}
if (difficulty == null) {
throw new IllegalArgumentException("difficulty must be provided");
}
if (mixhash == null) {
throw new IllegalArgumentException("mixhash must be provided");
}
if (coinbase == null) {
throw new IllegalArgumentException("coinbase must be provided");
}
if (timestamp == null) {
throw new IllegalArgumentException("timestamp must be provided");
}
if (extraData == null) {
throw new IllegalArgumentException("extraData must be provided");
}
if (gasLimit == null) {
throw new IllegalArgumentException("gasLimit must be provided");
}
this.nonce = Bytes.fromHexString(nonce);
this.difficulty = UInt256.fromHexString(difficulty);
this.mixhash = Hash.fromHexString(mixhash);
this.coinbase = Address.fromHexString(coinbase);
this.timestamp = "0x0".equals(timestamp) ? Instant.ofEpochSecond(0)
: Instant.ofEpochSecond(Bytes.fromHexString(timestamp).toLong());
this.extraData = Bytes.fromHexString(extraData);
this.gasLimit = Gas.valueOf(Bytes.fromHexString(gasLimit).toLong());
this.allocs = new HashMap<>();
for (Map.Entry<String, String> entry : allocs.entrySet()) {
Address addr = null;
try {
addr = Address.fromHexString(entry.getKey());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid address " + entry.getKey(), e);
}
Wei value = null;
if (entry.getValue().startsWith("0x")) {
try {
value = Wei.valueOf(UInt256.fromHexString(entry.getValue()));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid balance " + entry.getValue(), e);
}
} else {
try {
value = Wei.valueOf(UInt256.valueOf(new BigInteger(entry.getValue())));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid balance " + entry.getValue(), e);
}
}
this.allocs.put(addr, value);
}
this.chainId = chainId;
this.forks = forks;
}
public static GenesisFile read(byte[] contents) throws IOException {
JsonFactory factory = new JsonFactory();
JsonParser parser = factory.createParser(contents);
int chainId = 0;
String nonce = null;
String difficulty = null;
String mixhash = null;
String coinbase = null;
String timestamp = null;
String extraData = null;
String gasLimit = null;
String parentHash = null;
Map<String, String> allocs = null;
Set<Long> collectedForks = new TreeSet<>();
while (!parser.isClosed()) {
JsonToken jsonToken = parser.nextToken();
if (JsonToken.FIELD_NAME.equals(jsonToken)) {
String fieldName = parser.getCurrentName();
parser.nextToken();
if ("nonce".equalsIgnoreCase(fieldName)) {
nonce = parser.getValueAsString();
} else if ("difficulty".equalsIgnoreCase(fieldName)) {
difficulty = parser.getValueAsString();
} else if ("mixHash".equalsIgnoreCase(fieldName)) {
mixhash = parser.getValueAsString();
} else if ("coinbase".equalsIgnoreCase(fieldName)) {
coinbase = parser.getValueAsString();
} else if ("gasLimit".equalsIgnoreCase(fieldName)) {
gasLimit = parser.getValueAsString();
} else if ("timestamp".equalsIgnoreCase(fieldName)) {
timestamp = parser.getValueAsString();
} else if ("extraData".equalsIgnoreCase(fieldName)) {
extraData = parser.getValueAsString();
} else if ("parentHash".equalsIgnoreCase(fieldName)) {
parentHash = parser.getValueAsString();
} else if ("alloc".equalsIgnoreCase(fieldName)) {
allocs = readAlloc(parser);
} else if ("chainId".equalsIgnoreCase(fieldName)) {
chainId = parser.getValueAsInt();
} else if (fieldName.contains("Block")) {
collectedForks.add(parser.getValueAsLong());
}
}
}
List<Long> forks = new ArrayList<>(collectedForks);
Collections.sort(forks);
return new GenesisFile(
nonce,
difficulty,
mixhash,
coinbase,
timestamp,
extraData,
gasLimit,
parentHash,
allocs,
chainId,
forks);
}
private static Map<String, String> readAlloc(JsonParser parser) throws IOException {
Map<String, String> allocs = new HashMap<>();
String name = null;
String value = null;
int depth = 1;
while (!parser.isClosed()) {
JsonToken jsonToken = parser.nextToken();
if (JsonToken.FIELD_NAME.equals(jsonToken)) {
String fieldName = parser.getCurrentName();
if ("balance".equals(fieldName)) {
parser.nextToken();
value = parser.getValueAsString();
allocs.put(name, value);
name = null;
} else {
if (depth == 1) {
name = parser.getValueAsString();
}
}
} else if (JsonToken.END_OBJECT.equals(jsonToken)) {
depth--;
if (depth == 0) {
return allocs;
}
} else if (JsonToken.START_OBJECT.equals(jsonToken)) {
depth++;
}
}
return allocs;
}
public Block toBlock() {
Hash emptyListHash = Hash.hash(RLP.encodeList(writer -> {
}));
Hash emptyHash = Hash.hash(RLP.encode(writer -> {
writer.writeValue(Bytes.EMPTY);
}));
Hash empty = Hash.hash(Bytes.EMPTY);
MerklePatriciaTrie<AccountState> stateTree = new MerklePatriciaTrie<>(AccountState::toBytes);
List<AsyncCompletion> futures = new ArrayList<>();
for (Map.Entry<Address, Wei> entry : allocs.entrySet()) {
AccountState accountState = new AccountState(UInt256.ZERO, entry.getValue(), emptyHash, empty);
futures.add(stateTree.putAsync(Hash.hash(entry.getKey()), accountState));
}
try {
AsyncCompletion.allOf(futures).join(10, TimeUnit.SECONDS);
} catch (InterruptedException | TimeoutException e) {
throw new RuntimeException("Creating the account states took more than 10 seconds.");
}
return new Block(
new BlockHeader(
null,
emptyListHash,
coinbase,
Hash.fromBytes(stateTree.rootHash()),
emptyHash,
emptyHash,
Bytes.wrap(new byte[256]),
difficulty,
UInt256.ZERO,
gasLimit,
Gas.valueOf(0L),
timestamp,
extraData,
mixhash,
UInt64.fromBytes(nonce)),
new BlockBody(new ArrayList<>(), new ArrayList<>()));
}
public Map<Address, Wei> getAllocations() {
return allocs;
}
public List<Long> getForks() {
return forks;
}
public int getChainId() {
return chainId;
}
}