/* * 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; import org.ethereum.crypto.HashUtil; import org.ethereum.trie.Trie; import org.ethereum.trie.TrieImpl; import org.ethereum.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.Arrays; import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import static org.ethereum.crypto.HashUtil.sha3; /** * The block in Ethereum is the collection of relevant pieces of information * (known as the blockheader), H, together with information corresponding to * the comprised transactions, R, and a set of other blockheaders U that are known * to have a parent equal to the present block’s parent’s parent * (such blocks are known as uncles). * * @author Roman Mandeleil * @author Nick Savers * @since 20.05.2014 */ public class Block { private static final Logger logger = LoggerFactory.getLogger("blockchain"); private BlockHeader header; /* Transactions */ private List<Transaction> transactionsList = new CopyOnWriteArrayList<>(); /* Uncles */ private List<BlockHeader> uncleList = new CopyOnWriteArrayList<>(); /* Private */ private byte[] rlpEncoded; private boolean parsed = false; /* Constructors */ private Block() { } public Block(byte[] rawData) { logger.debug("new from [" + Hex.toHexString(rawData) + "]"); this.rlpEncoded = rawData; } public Block(BlockHeader header, List<Transaction> transactionsList, List<BlockHeader> uncleList) { this(header.getParentHash(), header.getUnclesHash(), header.getCoinbase(), header.getLogsBloom(), header.getDifficulty(), header.getNumber(), header.getGasLimit(), header.getGasUsed(), header.getTimestamp(), header.getExtraData(), header.getMixHash(), header.getNonce(), header.getReceiptsRoot(), header.getTxTrieRoot(), header.getStateRoot(), transactionsList, uncleList); } public Block(byte[] parentHash, byte[] unclesHash, byte[] coinbase, byte[] logsBloom, byte[] difficulty, long number, byte[] gasLimit, long gasUsed, long timestamp, byte[] extraData, byte[] mixHash, byte[] nonce, byte[] receiptsRoot, byte[] transactionsRoot, byte[] stateRoot, List<Transaction> transactionsList, List<BlockHeader> uncleList) { this(parentHash, unclesHash, coinbase, logsBloom, difficulty, number, gasLimit, gasUsed, timestamp, extraData, mixHash, nonce, transactionsList, uncleList); this.header.setTransactionsRoot(BlockchainImpl.calcTxTrie(transactionsList)); if (!Hex.toHexString(transactionsRoot). equals(Hex.toHexString(this.header.getTxTrieRoot()))) logger.debug("Transaction root miss-calculate, block: {}", getNumber()); this.header.setStateRoot(stateRoot); this.header.setReceiptsRoot(receiptsRoot); } public Block(byte[] parentHash, byte[] unclesHash, byte[] coinbase, byte[] logsBloom, byte[] difficulty, long number, byte[] gasLimit, long gasUsed, long timestamp, byte[] extraData, byte[] mixHash, byte[] nonce, List<Transaction> transactionsList, List<BlockHeader> uncleList) { this.header = new BlockHeader(parentHash, unclesHash, coinbase, logsBloom, difficulty, number, gasLimit, gasUsed, timestamp, extraData, mixHash, nonce); this.transactionsList = transactionsList; if (this.transactionsList == null) { this.transactionsList = new CopyOnWriteArrayList<>(); } this.uncleList = uncleList; if (this.uncleList == null) { this.uncleList = new CopyOnWriteArrayList<>(); } this.parsed = true; } private synchronized void parseRLP() { if (parsed) return; RLPList params = RLP.decode2(rlpEncoded); RLPList block = (RLPList) params.get(0); // Parse Header RLPList header = (RLPList) block.get(0); this.header = new BlockHeader(header); // Parse Transactions RLPList txTransactions = (RLPList) block.get(1); this.parseTxs(this.header.getTxTrieRoot(), txTransactions, false); // Parse Uncles RLPList uncleBlocks = (RLPList) block.get(2); for (RLPElement rawUncle : uncleBlocks) { RLPList uncleHeader = (RLPList) rawUncle; BlockHeader blockData = new BlockHeader(uncleHeader); this.uncleList.add(blockData); } this.parsed = true; } public BlockHeader getHeader() { parseRLP(); return this.header; } public byte[] getHash() { parseRLP(); return this.header.getHash(); } public byte[] getParentHash() { parseRLP(); return this.header.getParentHash(); } public byte[] getUnclesHash() { parseRLP(); return this.header.getUnclesHash(); } public byte[] getCoinbase() { parseRLP(); return this.header.getCoinbase(); } public byte[] getStateRoot() { parseRLP(); return this.header.getStateRoot(); } public void setStateRoot(byte[] stateRoot) { parseRLP(); this.header.setStateRoot(stateRoot); } public byte[] getTxTrieRoot() { parseRLP(); return this.header.getTxTrieRoot(); } public byte[] getReceiptsRoot() { parseRLP(); return this.header.getReceiptsRoot(); } public byte[] getLogBloom() { parseRLP(); return this.header.getLogsBloom(); } public byte[] getDifficulty() { parseRLP(); return this.header.getDifficulty(); } public BigInteger getDifficultyBI() { parseRLP(); return this.header.getDifficultyBI(); } public BigInteger getCumulativeDifficulty() { parseRLP(); BigInteger calcDifficulty = new BigInteger(1, this.header.getDifficulty()); for (BlockHeader uncle : uncleList) { calcDifficulty = calcDifficulty.add(new BigInteger(1, uncle.getDifficulty())); } return calcDifficulty; } public long getTimestamp() { parseRLP(); return this.header.getTimestamp(); } public long getNumber() { parseRLP(); return this.header.getNumber(); } public byte[] getGasLimit() { parseRLP(); return this.header.getGasLimit(); } public long getGasUsed() { parseRLP(); return this.header.getGasUsed(); } public byte[] getExtraData() { parseRLP(); return this.header.getExtraData(); } public byte[] getMixHash() { parseRLP(); return this.header.getMixHash(); } public byte[] getNonce() { parseRLP(); return this.header.getNonce(); } public void setNonce(byte[] nonce) { this.header.setNonce(nonce); rlpEncoded = null; } public void setMixHash(byte[] hash) { this.header.setMixHash(hash); rlpEncoded = null; } public void setExtraData(byte[] data) { this.header.setExtraData(data); rlpEncoded = null; } public List<Transaction> getTransactionsList() { parseRLP(); return transactionsList; } public List<BlockHeader> getUncleList() { parseRLP(); return uncleList; } private StringBuffer toStringBuff = new StringBuffer(); // [parent_hash, uncles_hash, coinbase, state_root, tx_trie_root, // difficulty, number, minGasPrice, gasLimit, gasUsed, timestamp, // extradata, nonce] @Override public String toString() { parseRLP(); toStringBuff.setLength(0); toStringBuff.append(Hex.toHexString(this.getEncoded())).append("\n"); toStringBuff.append("BlockData [ "); toStringBuff.append("hash=").append(ByteUtil.toHexString(this.getHash())).append("\n"); toStringBuff.append(header.toString()); if (!getUncleList().isEmpty()) { toStringBuff.append("Uncles [\n"); for (BlockHeader uncle : getUncleList()) { toStringBuff.append(uncle.toString()); toStringBuff.append("\n"); } toStringBuff.append("]\n"); } else { toStringBuff.append("Uncles []\n"); } if (!getTransactionsList().isEmpty()) { toStringBuff.append("Txs [\n"); for (Transaction tx : getTransactionsList()) { toStringBuff.append(tx); toStringBuff.append("\n"); } toStringBuff.append("]\n"); } else { toStringBuff.append("Txs []\n"); } toStringBuff.append("]"); return toStringBuff.toString(); } public String toFlatString() { parseRLP(); toStringBuff.setLength(0); toStringBuff.append("BlockData ["); toStringBuff.append("hash=").append(ByteUtil.toHexString(this.getHash())); toStringBuff.append(header.toFlatString()); for (Transaction tx : getTransactionsList()) { toStringBuff.append("\n"); toStringBuff.append(tx.toString()); } toStringBuff.append("]"); return toStringBuff.toString(); } private byte[] parseTxs(RLPList txTransactions, boolean validate) { Trie<byte[]> txsState = new TrieImpl(); for (int i = 0; i < txTransactions.size(); i++) { RLPElement transactionRaw = txTransactions.get(i); Transaction tx = new Transaction(transactionRaw.getRLPData()); if (validate) tx.verify(); this.transactionsList.add(tx); txsState.put(RLP.encodeInt(i), transactionRaw.getRLPData()); } return txsState.getRootHash(); } private boolean parseTxs(byte[] expectedRoot, RLPList txTransactions, boolean validate) { byte[] rootHash = parseTxs(txTransactions, validate); String calculatedRoot = Hex.toHexString(rootHash); if (!calculatedRoot.equals(Hex.toHexString(expectedRoot))) { logger.debug("Transactions trie root validation failed for block #{}", this.header.getNumber()); return false; } return true; } /** * check if param block is son of this block * * @param block - possible a son of this * @return - true if this block is parent of param block */ public boolean isParentOf(Block block) { return Arrays.areEqual(this.getHash(), block.getParentHash()); } public boolean isGenesis() { return this.header.isGenesis(); } public boolean isEqual(Block block) { return Arrays.areEqual(this.getHash(), block.getHash()); } private byte[] getTransactionsEncoded() { byte[][] transactionsEncoded = new byte[transactionsList.size()][]; int i = 0; for (Transaction tx : transactionsList) { transactionsEncoded[i] = tx.getEncoded(); ++i; } return RLP.encodeList(transactionsEncoded); } private byte[] getUnclesEncoded() { byte[][] unclesEncoded = new byte[uncleList.size()][]; int i = 0; for (BlockHeader uncle : uncleList) { unclesEncoded[i] = uncle.getEncoded(); ++i; } return RLP.encodeList(unclesEncoded); } public void addUncle(BlockHeader uncle) { uncleList.add(uncle); this.getHeader().setUnclesHash(sha3(getUnclesEncoded())); rlpEncoded = null; } public byte[] getEncoded() { if (rlpEncoded == null) { byte[] header = this.header.getEncoded(); List<byte[]> block = getBodyElements(); block.add(0, header); byte[][] elements = block.toArray(new byte[block.size()][]); this.rlpEncoded = RLP.encodeList(elements); } return rlpEncoded; } public byte[] getEncodedWithoutNonce() { parseRLP(); return this.header.getEncodedWithoutNonce(); } public byte[] getEncodedBody() { List<byte[]> body = getBodyElements(); byte[][] elements = body.toArray(new byte[body.size()][]); return RLP.encodeList(elements); } private List<byte[]> getBodyElements() { parseRLP(); byte[] transactions = getTransactionsEncoded(); byte[] uncles = getUnclesEncoded(); List<byte[]> body = new ArrayList<>(); body.add(transactions); body.add(uncles); return body; } public String getShortHash() { parseRLP(); return Hex.toHexString(getHash()).substring(0, 6); } public String getShortDescr() { return "#" + getNumber() + " (" + Hex.toHexString(getHash()).substring(0,6) + " <~ " + Hex.toHexString(getParentHash()).substring(0,6) + ") Txs:" + getTransactionsList().size() + ", Unc: " + getUncleList().size(); } public static class Builder { private BlockHeader header; private byte[] body; public Builder withHeader(BlockHeader header) { this.header = header; return this; } public Builder withBody(byte[] body) { this.body = body; return this; } public Block create() { if (header == null || body == null) { return null; } Block block = new Block(); block.header = header; block.parsed = true; RLPList items = (RLPList) RLP.decode2(body).get(0); RLPList transactions = (RLPList) items.get(0); RLPList uncles = (RLPList) items.get(1); if (!block.parseTxs(header.getTxTrieRoot(), transactions, false)) { return null; } byte[] unclesHash = HashUtil.sha3(uncles.getRLPData()); if (!java.util.Arrays.equals(header.getUnclesHash(), unclesHash)) { return null; } for (RLPElement rawUncle : uncles) { RLPList uncleHeader = (RLPList) rawUncle; BlockHeader blockData = new BlockHeader(uncleHeader); block.uncleList.add(blockData); } return block; } } }