/* * 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.longrun; import org.ethereum.config.CommonConfig; import org.ethereum.core.AccountState; import org.ethereum.core.Block; import org.ethereum.core.BlockHeader; import org.ethereum.core.BlockchainImpl; import org.ethereum.core.Bloom; import org.ethereum.core.Transaction; import org.ethereum.core.TransactionInfo; import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.DataSourceArray; import org.ethereum.datasource.Serializers; import org.ethereum.datasource.Source; import org.ethereum.datasource.SourceCodec; import org.ethereum.db.BlockStore; import org.ethereum.facade.Ethereum; import org.ethereum.trie.SecureTrie; import org.ethereum.trie.TrieImpl; import org.ethereum.util.FastByteComparisons; import org.ethereum.util.Value; import org.junit.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static org.ethereum.core.BlockchainImpl.calcReceiptsTrie; /** * Validation for all kind of blockchain data */ public class BlockchainValidation { private static final Logger testLogger = LoggerFactory.getLogger("TestLogger"); private static Integer getReferencedTrieNodes(final Source<byte[], byte[]> stateDS, final boolean includeAccounts, byte[] ... roots) { final AtomicInteger ret = new AtomicInteger(0); for (byte[] root : roots) { SecureTrie trie = new SecureTrie(stateDS, root); trie.scanTree(new TrieImpl.ScanAction() { @Override public void doOnNode(byte[] hash, TrieImpl.Node node) { ret.incrementAndGet(); } @Override public void doOnValue(byte[] nodeHash, TrieImpl.Node node, byte[] key, byte[] value) { if (includeAccounts) { AccountState accountState = new AccountState(value); if (!FastByteComparisons.equal(accountState.getCodeHash(), HashUtil.EMPTY_DATA_HASH)) { ret.incrementAndGet(); } if (!FastByteComparisons.equal(accountState.getStateRoot(), HashUtil.EMPTY_TRIE_HASH)) { ret.addAndGet(getReferencedTrieNodes(stateDS, false, accountState.getStateRoot())); } } } }); } return ret.get(); } public static void checkNodes(Ethereum ethereum, CommonConfig commonConfig, AtomicInteger fatalErrors) { try { Source<byte[], byte[]> stateDS = commonConfig.stateSource(); byte[] stateRoot = ethereum.getBlockchain().getBestBlock().getHeader().getStateRoot(); Integer rootsSize = getReferencedTrieNodes(stateDS, true, stateRoot); testLogger.info("Node validation successful"); testLogger.info("Non-unique node size: {}", rootsSize); } catch (Exception | AssertionError ex) { testLogger.error("Node validation error", ex); fatalErrors.incrementAndGet(); } } public static void checkHeaders(Ethereum ethereum, AtomicInteger fatalErrors) { int blockNumber = (int) ethereum.getBlockchain().getBestBlock().getHeader().getNumber(); byte[] lastParentHash = null; testLogger.info("Checking headers from best block: {}", blockNumber); try { while (blockNumber >= 0) { Block currentBlock = ethereum.getBlockchain().getBlockByNumber(blockNumber); if (lastParentHash != null) { assert FastByteComparisons.equal(currentBlock.getHash(), lastParentHash); } lastParentHash = currentBlock.getHeader().getParentHash(); assert lastParentHash != null; blockNumber--; } testLogger.info("Checking headers successful, ended on block: {}", blockNumber + 1); } catch (Exception | AssertionError ex) { testLogger.error(String.format("Block header validation error on block #%s", blockNumber), ex); fatalErrors.incrementAndGet(); } } public static void checkFastHeaders(Ethereum ethereum, CommonConfig commonConfig, AtomicInteger fatalErrors) { DataSourceArray<BlockHeader> headerStore = commonConfig.headerSource(); int blockNumber = headerStore.size() - 1; byte[] lastParentHash = null; try { testLogger.info("Checking fast headers from best block: {}", blockNumber); while (blockNumber > 0) { BlockHeader header = headerStore.get(blockNumber); if (lastParentHash != null) { assert FastByteComparisons.equal(header.getHash(), lastParentHash); } lastParentHash = header.getParentHash(); assert lastParentHash != null; blockNumber--; } Block genesis = ethereum.getBlockchain().getBlockByNumber(0); assert FastByteComparisons.equal(genesis.getHash(), lastParentHash); testLogger.info("Checking fast headers successful, ended on block: {}", blockNumber + 1); } catch (Exception | AssertionError ex) { testLogger.error(String.format("Fast header validation error on block #%s", blockNumber), ex); fatalErrors.incrementAndGet(); } } public static void checkBlocks(Ethereum ethereum, AtomicInteger fatalErrors) { Block currentBlock = ethereum.getBlockchain().getBestBlock(); int blockNumber = (int) currentBlock.getHeader().getNumber(); try { BlockStore blockStore = ethereum.getBlockchain().getBlockStore(); testLogger.info("Checking blocks from best block: {}", blockNumber); BigInteger curTotalDiff = blockStore.getTotalDifficultyForHash(currentBlock.getHash()); while (blockNumber > 0) { currentBlock = ethereum.getBlockchain().getBlockByNumber(blockNumber); // Validate uncles assert ((BlockchainImpl) ethereum.getBlockchain()).validateUncles(currentBlock); // Validate total difficulty Assert.assertTrue(String.format("Total difficulty, count %s == %s blockStore", curTotalDiff, blockStore.getTotalDifficultyForHash(currentBlock.getHash())), curTotalDiff.compareTo(blockStore.getTotalDifficultyForHash(currentBlock.getHash())) == 0); curTotalDiff = curTotalDiff.subtract(currentBlock.getDifficultyBI()); blockNumber--; } // Checking total difficulty for genesis currentBlock = ethereum.getBlockchain().getBlockByNumber(0); Assert.assertTrue(String.format("Total difficulty for genesis, count %s == %s blockStore", curTotalDiff, blockStore.getTotalDifficultyForHash(currentBlock.getHash())), curTotalDiff.compareTo(blockStore.getTotalDifficultyForHash(currentBlock.getHash())) == 0); Assert.assertTrue(String.format("Total difficulty, count %s == %s genesis", curTotalDiff, currentBlock.getDifficultyBI()), curTotalDiff.compareTo(currentBlock.getDifficultyBI()) == 0); testLogger.info("Checking blocks successful, ended on block: {}", blockNumber + 1); } catch (Exception | AssertionError ex) { testLogger.error(String.format("Block validation error on block #%s", blockNumber), ex); fatalErrors.incrementAndGet(); } } public static void checkTransactions(Ethereum ethereum, AtomicInteger fatalErrors) { int blockNumber = (int) ethereum.getBlockchain().getBestBlock().getHeader().getNumber(); testLogger.info("Checking block transactions from best block: {}", blockNumber); try { while (blockNumber > 0) { Block currentBlock = ethereum.getBlockchain().getBlockByNumber(blockNumber); List<TransactionReceipt> receipts = new ArrayList<>(); for (Transaction tx : currentBlock.getTransactionsList()) { TransactionInfo txInfo = ((BlockchainImpl) ethereum.getBlockchain()).getTransactionInfo(tx.getHash()); assert txInfo != null; receipts.add(txInfo.getReceipt()); } Bloom logBloom = new Bloom(); for (TransactionReceipt receipt : receipts) { logBloom.or(receipt.getBloomFilter()); } assert FastByteComparisons.equal(currentBlock.getLogBloom(), logBloom.getData()); assert FastByteComparisons.equal(currentBlock.getReceiptsRoot(), calcReceiptsTrie(receipts)); blockNumber--; } testLogger.info("Checking block transactions successful, ended on block: {}", blockNumber + 1); } catch (Exception | AssertionError ex) { testLogger.error(String.format("Transaction validation error on block #%s", blockNumber), ex); fatalErrors.incrementAndGet(); } } public static void fullCheck(Ethereum ethereum, CommonConfig commonConfig, AtomicInteger fatalErrors) { // nodes testLogger.info("Validating nodes: Start"); BlockchainValidation.checkNodes(ethereum, commonConfig, fatalErrors); testLogger.info("Validating nodes: End"); // headers testLogger.info("Validating block headers: Start"); BlockchainValidation.checkHeaders(ethereum, fatalErrors); testLogger.info("Validating block headers: End"); // blocks testLogger.info("Validating blocks: Start"); BlockchainValidation.checkBlocks(ethereum, fatalErrors); testLogger.info("Validating blocks: End"); // receipts testLogger.info("Validating transaction receipts: Start"); BlockchainValidation.checkTransactions(ethereum, fatalErrors); testLogger.info("Validating transaction receipts: End"); } }