/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.core.genesis; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.io.ByteStreams; import org.ethereum.config.BlockchainNetConfig; import org.ethereum.config.SystemProperties; import org.ethereum.core.AccountState; import org.ethereum.core.Genesis; import org.ethereum.crypto.HashUtil; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.trie.SecureTrie; import org.ethereum.trie.Trie; import org.ethereum.util.ByteUtil; import org.ethereum.util.Utils; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; import static org.ethereum.core.Genesis.ZERO_HASH_2048; import static org.ethereum.crypto.HashUtil.EMPTY_LIST_HASH; import static org.ethereum.util.ByteUtil.*; import static org.ethereum.core.BlockHeader.NONCE_LENGTH; import static org.ethereum.core.Genesis.PremineAccount; public class GenesisLoader { /** * Load genesis from passed location or from classpath `genesis` directory */ public static GenesisJson loadGenesisJson(SystemProperties config, ClassLoader classLoader) throws RuntimeException { final String genesisFile = config.getProperty("genesisFile", null); final String genesisResource = config.genesisInfo(); // #1 try to find genesis at passed location if (genesisFile != null) { try (InputStream is = new FileInputStream(new File(genesisFile))) { return loadGenesisJson(is); } catch (Exception e) { showLoadError("Problem loading genesis file from " + genesisFile, genesisFile, genesisResource); } } // #2 fall back to old genesis location at `src/main/resources/genesis` directory InputStream is = classLoader.getResourceAsStream("genesis/" + genesisResource); if (is != null) { try { return loadGenesisJson(is); } catch (Exception e) { showLoadError("Problem loading genesis file from resource directory", genesisFile, genesisResource); } } else { showLoadError("Genesis file was not found in resource directory", genesisFile, genesisResource); } return null; } private static void showLoadError(String message, String genesisFile, String genesisResource) { Utils.showErrorAndExit( message, "Config option 'genesisFile': " + genesisFile, "Config option 'genesis': " + genesisResource); } public static Genesis parseGenesis(BlockchainNetConfig blockchainNetConfig, GenesisJson genesisJson) throws RuntimeException { try { Genesis genesis = createBlockForJson(genesisJson); genesis.setPremine(generatePreMine(blockchainNetConfig, genesisJson.getAlloc())); byte[] rootHash = generateRootHash(genesis.getPremine()); genesis.setStateRoot(rootHash); return genesis; } catch (Exception e) { e.printStackTrace(); Utils.showErrorAndExit("Problem parsing genesis", e.getMessage()); } return null; } /** * Method used much in tests. */ public static Genesis loadGenesis(InputStream resourceAsStream) { GenesisJson genesisJson = loadGenesisJson(resourceAsStream); return parseGenesis(SystemProperties.getDefault().getBlockchainConfig(), genesisJson); } public static GenesisJson loadGenesisJson(InputStream genesisJsonIS) throws RuntimeException { String json = null; try { json = new String(ByteStreams.toByteArray(genesisJsonIS)); ObjectMapper mapper = new ObjectMapper() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); GenesisJson genesisJson = mapper.readValue(json, GenesisJson.class); return genesisJson; } catch (Exception e) { Utils.showErrorAndExit("Problem parsing genesis: "+ e.getMessage(), json); throw new RuntimeException(e.getMessage(), e); } } private static Genesis createBlockForJson(GenesisJson genesisJson) { byte[] nonce = prepareNonce(ByteUtil.hexStringToBytes(genesisJson.nonce)); byte[] difficulty = hexStringToBytesValidate(genesisJson.difficulty, 32, true); byte[] mixHash = hexStringToBytesValidate(genesisJson.mixhash, 32, false); byte[] coinbase = hexStringToBytesValidate(genesisJson.coinbase, 20, false); byte[] timestampBytes = hexStringToBytesValidate(genesisJson.timestamp, 8, true); long timestamp = ByteUtil.byteArrayToLong(timestampBytes); byte[] parentHash = hexStringToBytesValidate(genesisJson.parentHash, 32, false); byte[] extraData = hexStringToBytesValidate(genesisJson.extraData, 32, true); byte[] gasLimitBytes = hexStringToBytesValidate(genesisJson.gasLimit, 8, true); long gasLimit = ByteUtil.byteArrayToLong(gasLimitBytes); return new Genesis(parentHash, EMPTY_LIST_HASH, coinbase, ZERO_HASH_2048, difficulty, 0, gasLimit, 0, timestamp, extraData, mixHash, nonce); } private static byte[] hexStringToBytesValidate(String hex, int bytes, boolean notGreater) { byte[] ret = ByteUtil.hexStringToBytes(hex); if (notGreater) { if (ret.length > bytes) { throw new RuntimeException("Wrong value length: " + hex + ", expected length < " + bytes + " bytes"); } } else { if (ret.length != bytes) { throw new RuntimeException("Wrong value length: " + hex + ", expected length " + bytes + " bytes"); } } return ret; } /** * Prepares nonce to be correct length * @param nonceUnchecked unchecked, user-provided nonce * @return correct nonce * @throws RuntimeException when nonce is too long */ private static byte[] prepareNonce(byte[] nonceUnchecked) { if (nonceUnchecked.length > 8) { throw new RuntimeException(String.format("Invalid nonce, should be %s length", NONCE_LENGTH)); } else if (nonceUnchecked.length == 8) { return nonceUnchecked; } byte[] nonce = new byte[NONCE_LENGTH]; int diff = NONCE_LENGTH - nonceUnchecked.length; for (int i = diff; i < NONCE_LENGTH; ++i) { nonce[i] = nonceUnchecked[i - diff]; } return nonce; } private static Map<ByteArrayWrapper, PremineAccount> generatePreMine(BlockchainNetConfig blockchainNetConfig, Map<String, GenesisJson.AllocatedAccount> allocs){ final Map<ByteArrayWrapper, PremineAccount> premine = new HashMap<>(); for (String key : allocs.keySet()){ final byte[] address = hexStringToBytes(key); final GenesisJson.AllocatedAccount alloc = allocs.get(key); final PremineAccount state = new PremineAccount(); AccountState accountState = new AccountState( blockchainNetConfig.getCommonConstants().getInitialNonce(), parseHexOrDec(alloc.balance)); if (alloc.nonce != null) { accountState = accountState.withNonce(parseHexOrDec(alloc.nonce)); } if (alloc.code != null) { final byte[] codeBytes = hexStringToBytes(alloc.code); accountState = accountState.withCodeHash(HashUtil.sha3(codeBytes)); state.code = codeBytes; } state.accountState = accountState; premine.put(wrap(address), state); } return premine; } /** * @param rawValue either hex started with 0x or dec * return BigInteger */ private static BigInteger parseHexOrDec(String rawValue) { if (rawValue != null) { return rawValue.startsWith("0x") ? bytesToBigInteger(hexStringToBytes(rawValue)) : new BigInteger(rawValue); } else { return BigInteger.ZERO; } } public static byte[] generateRootHash(Map<ByteArrayWrapper, PremineAccount> premine){ Trie<byte[]> state = new SecureTrie((byte[]) null); for (ByteArrayWrapper key : premine.keySet()) { state.put(key.getData(), premine.get(key).accountState.getEncoded()); } return state.getRootHash(); } }