Java tutorial
/* * This file is part of RskJ * Copyright (C) 2017 RSK Labs Ltd. * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package co.rsk.blockchain.utils; import co.rsk.config.TestSystemProperties; import co.rsk.core.BlockDifficulty; import co.rsk.core.Coin; import co.rsk.core.DifficultyCalculator; import co.rsk.core.RskAddress; import co.rsk.core.bc.BlockChainImpl; import co.rsk.mine.MinimumGasPriceCalculator; import co.rsk.peg.PegTestUtils; import co.rsk.peg.simples.SimpleBlock; import co.rsk.peg.simples.SimpleRskTransaction; import co.rsk.trie.Trie; import co.rsk.trie.TrieImpl; import org.apache.commons.collections4.CollectionUtils; import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; import org.bouncycastle.util.encoders.Hex; import org.ethereum.core.*; import org.ethereum.core.genesis.InitialAddressState; import org.ethereum.crypto.HashUtil; import org.ethereum.util.RLP; import org.ethereum.util.RLPElement; import org.ethereum.util.RLPList; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.ethereum.core.Genesis.getZeroHash; import static org.ethereum.crypto.HashUtil.EMPTY_TRIE_HASH; /** * Created by ajlopez on 5/10/2016. */ public class BlockGenerator { private static final byte[] EMPTY_LIST_HASH = HashUtil.keccak256(RLP.encodeList()); private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private static final Block[] blockCache = new Block[5]; private final DifficultyCalculator difficultyCalculator; private int count = 0; private TestSystemProperties config; public BlockGenerator() { this(new TestSystemProperties()); } public BlockGenerator(TestSystemProperties config) { this.config = config; this.difficultyCalculator = new DifficultyCalculator(this.config); } public Genesis getGenesisBlock() { return getNewGenesisBlock(3141592, null, new byte[] { 2, 0, 0 }); } private Genesis getNewGenesisBlock(long initialGasLimit, Map<byte[], BigInteger> preMineMap, byte[] difficulty) { byte[] nonce = new byte[] { 0 }; byte[] mixHash = new byte[] { 0 }; /* Unimportant address. Because there is no subsidy ECKey ecKey; byte[] address; SecureRandom rand =new InsecureRandom(0); ecKey = new ECKey(rand); address = ecKey.getAddress(); */ byte[] coinbase = Hex.decode("e94aef644e428941ee0a3741f28d80255fddba7f"); long timestamp = 0; // predictable timeStamp byte[] parentHash = EMPTY_BYTE_ARRAY; byte[] extraData = EMPTY_BYTE_ARRAY; long gasLimit = initialGasLimit; byte[] bitcoinMergedMiningHeader = null; byte[] bitcoinMergedMiningMerkleProof = null; byte[] bitcoinMergedMiningCoinbaseTransaction = null; Genesis genesis = new Genesis(parentHash, EMPTY_LIST_HASH, coinbase, getZeroHash(), difficulty, 0, gasLimit, 0, timestamp, extraData, mixHash, nonce, bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, bitcoinMergedMiningCoinbaseTransaction, BigInteger.valueOf(100L).toByteArray()); if (preMineMap != null) { Map<RskAddress, InitialAddressState> preMineMap2 = generatePreMine(preMineMap); genesis.setPremine(preMineMap2); byte[] rootHash = generateRootHash(preMineMap2); genesis.setStateRoot(rootHash); } return genesis; } private byte[] generateRootHash(Map<RskAddress, InitialAddressState> premine) { Trie state = new TrieImpl(null, true); for (RskAddress addr : premine.keySet()) { state = state.put(addr.getBytes(), premine.get(addr).getAccountState().getEncoded()); } return state.getHash().getBytes(); } private Map<RskAddress, InitialAddressState> generatePreMine(Map<byte[], BigInteger> alloc) { Map<RskAddress, InitialAddressState> premine = new HashMap<>(); for (byte[] key : alloc.keySet()) { AccountState acctState = new AccountState(BigInteger.valueOf(0), new Coin(alloc.get(key))); premine.put(new RskAddress(key), new InitialAddressState(acctState, null)); } return premine; } public Block getBlock(int number) { if (blockCache[number] != null) { return blockCache[number]; } synchronized (blockCache) { for (int k = 0; k <= number; k++) { if (blockCache[k] == null) { if (k == 0) { blockCache[0] = this.getGenesisBlock(); } else { blockCache[k] = this.createChildBlock(blockCache[k - 1]); } } } return blockCache[number]; } } public Block createChildBlock(Block parent) { return createChildBlock(parent, 0); } public Block createChildBlock(Block parent, long fees, List<BlockHeader> uncles, byte[] difficulty) { List<Transaction> txs = new ArrayList<>(); byte[] unclesListHash = HashUtil.keccak256(BlockHeader.getUnclesEncodedEx(uncles)); return new Block(parent.getHash().getBytes(), // parent hash unclesListHash, // uncle hash parent.getCoinbase().getBytes(), ByteUtils.clone(new Bloom().getData()), difficulty, // difficulty parent.getNumber() + 1, parent.getGasLimit(), parent.getGasUsed(), parent.getTimestamp() + ++count, EMPTY_BYTE_ARRAY, // extraData EMPTY_BYTE_ARRAY, // mixHash BigInteger.ZERO.toByteArray(), // provisory nonce EMPTY_TRIE_HASH, // receipts root BlockChainImpl.calcTxTrie(txs), // transaction root ByteUtils.clone(parent.getStateRoot()), //EMPTY_TRIE_HASH, // state root txs, // transaction list uncles, // uncle list null, Coin.valueOf(fees)); // return createChildBlock(parent, 0); } public Block createChildBlock(Block parent, List<Transaction> txs, byte[] stateRoot) { return createChildBlock(parent, txs, stateRoot, parent.getCoinbase().getBytes()); } public Block createChildBlock(Block parent, List<Transaction> txs, byte[] stateRoot, byte[] coinbase) { Bloom logBloom = new Bloom(); if (txs == null) { txs = new ArrayList<>(); } return new Block(parent.getHash().getBytes(), // parent hash EMPTY_LIST_HASH, // uncle hash coinbase, // coinbase logBloom.getData(), // logs bloom parent.getDifficulty().getBytes(), // difficulty parent.getNumber() + 1, parent.getGasLimit(), parent.getGasUsed(), parent.getTimestamp() + ++count, EMPTY_BYTE_ARRAY, // extraData EMPTY_BYTE_ARRAY, // mixHash BigInteger.ZERO.toByteArray(), // provisory nonce EMPTY_TRIE_HASH, // receipts root BlockChainImpl.calcTxTrie(txs), // transaction root stateRoot, //EMPTY_TRIE_HASH, // state root txs, // transaction list null, // uncle list null, Coin.ZERO); } public Block createChildBlock(Block parent, int ntxs) { return createChildBlock(parent, ntxs, parent.getDifficulty().asBigInteger().longValue()); } public Block createChildBlock(Block parent, int ntxs, long difficulty) { List<Transaction> txs = new ArrayList<>(); for (int ntx = 0; ntx < ntxs; ntx++) { txs.add(new SimpleRskTransaction(null)); } List<BlockHeader> uncles = new ArrayList<>(); return createChildBlock(parent, txs, uncles, difficulty, null); } public Block createChildBlock(Block parent, List<Transaction> txs) { return createChildBlock(parent, txs, new ArrayList<>(), parent.getDifficulty().asBigInteger().longValue(), null); } public Block createChildBlock(Block parent, List<Transaction> txs, List<BlockHeader> uncles, long difficulty, BigInteger minGasPrice) { return createChildBlock(parent, txs, uncles, difficulty, minGasPrice, parent.getGasLimit()); } public Block createChildBlock(Block parent, List<Transaction> txs, List<BlockHeader> uncles, long difficulty, BigInteger minGasPrice, byte[] gasLimit) { if (txs == null) { txs = new ArrayList<>(); } if (uncles == null) { uncles = new ArrayList<>(); } byte[] unclesListHash = HashUtil.keccak256(BlockHeader.getUnclesEncodedEx(uncles)); BlockHeader newHeader = new BlockHeader(parent.getHash().getBytes(), unclesListHash, parent.getCoinbase().getBytes(), ByteUtils.clone(new Bloom().getData()), new byte[] { 1 }, parent.getNumber() + 1, gasLimit, 0, parent.getTimestamp() + ++count, new byte[] {}, new byte[] {}, new byte[] {}, new byte[] {}, (minGasPrice != null) ? minGasPrice.toByteArray() : null, CollectionUtils.size(uncles)); if (difficulty == 0) { newHeader.setDifficulty(difficultyCalculator.calcDifficulty(newHeader, parent.getHeader())); } else { newHeader.setDifficulty(new BlockDifficulty(BigInteger.valueOf(difficulty))); } newHeader.setTransactionsRoot(Block.getTxTrie(txs).getHash().getBytes()); newHeader.setStateRoot(ByteUtils.clone(parent.getStateRoot())); Block newBlock = new Block(newHeader, txs, uncles); return newBlock; } public Block createBlock(int number, int ntxs) { Bloom logBloom = new Bloom(); Block parent = getGenesisBlock(); List<Transaction> txs = new ArrayList<>(); for (int ntx = 0; ntx < ntxs; ntx++) { txs.add(new SimpleRskTransaction(null)); } Coin previousMGP = parent.getMinimumGasPrice() != null ? parent.getMinimumGasPrice() : Coin.valueOf(10L); Coin minimumGasPrice = new MinimumGasPriceCalculator().calculate(previousMGP, Coin.valueOf(100L)); return new Block(parent.getHash().getBytes(), // parent hash EMPTY_LIST_HASH, // uncle hash parent.getCoinbase().getBytes(), // coinbase logBloom.getData(), // logs bloom parent.getDifficulty().getBytes(), // difficulty number, parent.getGasLimit(), parent.getGasUsed(), parent.getTimestamp() + ++count, EMPTY_BYTE_ARRAY, // extraData EMPTY_BYTE_ARRAY, // mixHash BigInteger.ZERO.toByteArray(), // provisory nonce EMPTY_TRIE_HASH, // receipts root EMPTY_TRIE_HASH, // transaction receipts EMPTY_TRIE_HASH, // state root txs, // transaction list null, // uncle list minimumGasPrice.getBytes(), Coin.ZERO); } public Block createSimpleChildBlock(Block parent, int ntxs) { Bloom logBloom = new Bloom(); List<Transaction> txs = new ArrayList<>(); for (int ntx = 0; ntx < ntxs; ntx++) { txs.add(new SimpleRskTransaction(PegTestUtils.createHash3().getBytes())); } return new SimpleBlock(parent.getHash().getBytes(), // parent hash EMPTY_LIST_HASH, // uncle hash parent.getCoinbase().getBytes(), // coinbase logBloom.getData(), // logs bloom parent.getDifficulty().getBytes(), // difficulty parent.getNumber() + 1, parent.getGasLimit(), parent.getGasUsed(), parent.getTimestamp() + ++count, EMPTY_BYTE_ARRAY, // extraData EMPTY_BYTE_ARRAY, // mixHash BigInteger.ZERO.toByteArray(), // provisory nonce EMPTY_TRIE_HASH, // receipts root EMPTY_TRIE_HASH, // transaction receipts EMPTY_TRIE_HASH, // state root txs, // transaction list null // uncle list ); } public List<Block> getBlockChain(int size) { return getBlockChain(getGenesisBlock(), size); } public List<Block> getBlockChain(Block parent, int size, long difficulty) { return getBlockChain(parent, size, 0, false, difficulty); } public List<Block> getBlockChain(Block parent, int size) { return getBlockChain(parent, size, 0); } public List<Block> getMinedBlockChain(Block parent, int size) { return getBlockChain(parent, size, 0, false, true, null); } public List<Block> getSimpleBlockChain(Block parent, int size) { return getSimpleBlockChain(parent, size, 0); } public List<Block> getBlockChain(Block parent, int size, int ntxs) { return getBlockChain(parent, size, ntxs, false); } public List<Block> getBlockChain(Block parent, int size, int ntxs, boolean withUncles) { return getBlockChain(parent, size, ntxs, withUncles, null); } public List<Block> getBlockChain(Block parent, int size, int ntxs, boolean withUncles, Long difficulty) { return getBlockChain(parent, size, ntxs, false, false, difficulty); } public List<Block> getBlockChain(Block parent, int size, int ntxs, boolean withUncles, boolean withMining, Long difficulty) { List<Block> chain = new ArrayList<Block>(); List<BlockHeader> uncles = new ArrayList<>(); int chainSize = 0; while (chainSize < size) { List<Transaction> txs = new ArrayList<>(); for (int ntx = 0; ntx < ntxs; ntx++) { txs.add(new SimpleRskTransaction(null)); } if (difficulty == null) { difficulty = 0l; } Block newblock = createChildBlock(parent, txs, uncles, difficulty, BigInteger.valueOf(1)); if (withMining) { newblock = new BlockMiner(config).mineBlock(newblock); } chain.add(newblock); if (withUncles) { uncles = new ArrayList<>(); Block newuncle = createChildBlock(parent, ntxs); chain.add(newuncle); uncles.add(newuncle.getHeader()); newuncle = createChildBlock(parent, ntxs); chain.add(newuncle); uncles.add(newuncle.getHeader()); } parent = newblock; chainSize++; } return chain; } public List<Block> getSimpleBlockChain(Block parent, int size, int ntxs) { List<Block> chain = new ArrayList<Block>(); while (chain.size() < size) { Block newblock = createSimpleChildBlock(parent, ntxs); chain.add(newblock); parent = newblock; } return chain; } public Block getNewGenesisBlock(long initialGasLimit, Map<byte[], BigInteger> preMineMap) { return getNewGenesisBlock(initialGasLimit, preMineMap, new byte[] { 0 }); } private static byte[] nullReplace(byte[] e) { if (e == null) { return new byte[0]; } return e; } private static byte[] removeLastElement(byte[] rlpEncoded) { ArrayList<RLPElement> params = RLP.decode2(rlpEncoded); RLPList block = (RLPList) params.get(0); RLPList header = (RLPList) block.get(0); if (header.size() < 20) { return rlpEncoded; } header.remove(header.size() - 1); // remove last element header.remove(header.size() - 1); // remove second last element List<byte[]> newHeader = new ArrayList<>(); for (int i = 0; i < header.size(); i++) { byte[] e = nullReplace(header.get(i).getRLPData()); if ((e.length > 32) && (i == 15))// fix bad feePaid e = new byte[32]; newHeader.add(RLP.encodeElement(e)); } byte[][] newHeaderElements = newHeader.toArray(new byte[newHeader.size()][]); byte[] newEncodedHeader = RLP.encodeList(newHeaderElements); return RLP.encodeList(newEncodedHeader, // If you request the .getRLPData() of a list you DO get the encoding prefix. // very weird. nullReplace(block.get(1).getRLPData()), nullReplace(block.get(2).getRLPData())); } }