/*
* 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 com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.ethereum.config.CommonConfig;
import org.ethereum.config.SystemProperties;
import org.ethereum.crypto.HashUtil;
import org.ethereum.datasource.inmem.HashMapDB;
import org.ethereum.db.*;
import org.ethereum.trie.Trie;
import org.ethereum.trie.TrieImpl;
import org.ethereum.listener.EthereumListener;
import org.ethereum.listener.EthereumListenerAdapter;
import org.ethereum.manager.AdminInfo;
import org.ethereum.sync.SyncManager;
import org.ethereum.util.*;
import org.ethereum.validator.DependentBlockHeaderRule;
import org.ethereum.validator.ParentBlockHeaderValidator;
import org.ethereum.vm.program.invoke.ProgramInvokeFactory;
import org.ethereum.vm.program.invoke.ProgramInvokeFactoryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.*;
import static java.lang.Math.max;
import static java.lang.Runtime.getRuntime;
import static java.math.BigInteger.ONE;
import static java.math.BigInteger.ZERO;
import static java.util.Collections.emptyList;
import static org.ethereum.core.Denomination.SZABO;
import static org.ethereum.core.ImportResult.*;
import static org.ethereum.crypto.HashUtil.sha3;
import static org.ethereum.util.BIUtil.isMoreThan;
/**
* The Ethereum blockchain is in many ways similar to the Bitcoin blockchain,
* although it does have some differences.
* <p>
* The main difference between Ethereum and Bitcoin with regard to the blockchain architecture
* is that, unlike Bitcoin, Ethereum blocks contain a copy of both the transaction list
* and the most recent state. Aside from that, two other values, the block number and
* the difficulty, are also stored in the block.
* </p>
* The block validation algorithm in Ethereum is as follows:
* <ol>
* <li>Check if the previous block referenced exists and is valid.</li>
* <li>Check that the timestamp of the block is greater than that of the referenced previous block and less than 15 minutes into the future</li>
* <li>Check that the block number, difficulty, transaction root, uncle root and gas limit (various low-level Ethereum-specific concepts) are valid.</li>
* <li>Check that the proof of work on the block is valid.</li>
* <li>Let S[0] be the STATE_ROOT of the previous block.</li>
* <li>Let TX be the block's transaction list, with n transactions.
* For all in in 0...n-1, set S[i+1] = APPLY(S[i],TX[i]).
* If any applications returns an error, or if the total gas consumed in the block
* up until this point exceeds the GASLIMIT, return an error.</li>
* <li>Let S_FINAL be S[n], but adding the block reward paid to the miner.</li>
* <li>Check if S_FINAL is the same as the STATE_ROOT. If it is, the block is valid; otherwise, it is not valid.</li>
* </ol>
* See <a href="https://github.com/ethereum/wiki/wiki/White-Paper#blockchain-and-mining">Ethereum Whitepaper</a>
*
* @author Roman Mandeleil
* @author Nick Savers
* @since 20.05.2014
*/
@Component
public class BlockchainImpl implements Blockchain, org.ethereum.facade.Blockchain {
private static final Logger logger = LoggerFactory.getLogger("blockchain");
private static final Logger stateLogger = LoggerFactory.getLogger("state");
// to avoid using minGasPrice=0 from Genesis for the wallet
private static final long INITIAL_MIN_GAS_PRICE = 10 * SZABO.longValue();
private static final int MAGIC_REWARD_OFFSET = 8;
public static final byte[] EMPTY_LIST_HASH = sha3(RLP.encodeList(new byte[0]));
@Autowired @Qualifier("defaultRepository")
private Repository repository;
@Autowired
protected BlockStore blockStore;
@Autowired
private TransactionStore transactionStore;
private Block bestBlock;
private BigInteger totalDifficulty = ZERO;
@Autowired
private EthereumListener listener;
@Autowired
ProgramInvokeFactory programInvokeFactory;
@Autowired
private AdminInfo adminInfo;
@Autowired
private DependentBlockHeaderRule parentHeaderValidator;
@Autowired
private PendingState pendingState;
@Autowired
EventDispatchThread eventDispatchThread;
@Autowired
CommonConfig commonConfig = CommonConfig.getDefault();
@Autowired
SyncManager syncManager;
@Autowired
PruneManager pruneManager;
@Autowired
StateSource stateDataSource;
@Autowired
DbFlushManager dbFlushManager;
SystemProperties config = SystemProperties.getDefault();
private List<Chain> altChains = new ArrayList<>();
private List<Block> garbage = new ArrayList<>();
long exitOn = Long.MAX_VALUE;
public boolean byTest = false;
private boolean fork = false;
private byte[] minerCoinbase;
private byte[] minerExtraData;
private BigInteger BLOCK_REWARD;
private BigInteger INCLUSION_REWARD;
private int UNCLE_LIST_LIMIT;
private int UNCLE_GENERATION_LIMIT;
private Stack<State> stateStack = new Stack<>();
/** Tests only **/
public BlockchainImpl() {
}
@Autowired
public BlockchainImpl(final SystemProperties config) {
this.config = config;
initConst(config);
}
//todo: autowire over constructor
public BlockchainImpl(final BlockStore blockStore, final Repository repository) {
this.blockStore = blockStore;
this.repository = repository;
this.adminInfo = new AdminInfo();
this.listener = new EthereumListenerAdapter();
this.parentHeaderValidator = null;
this.transactionStore = new TransactionStore(new HashMapDB());
this.eventDispatchThread = EventDispatchThread.getDefault();
this.programInvokeFactory = new ProgramInvokeFactoryImpl();
initConst(SystemProperties.getDefault());
}
public BlockchainImpl withTransactionStore(TransactionStore transactionStore) {
this.transactionStore = transactionStore;
return this;
}
public BlockchainImpl withAdminInfo(AdminInfo adminInfo) {
this.adminInfo = adminInfo;
return this;
}
public BlockchainImpl withEthereumListener(EthereumListener listener) {
this.listener = listener;
return this;
}
public BlockchainImpl withSyncManager(SyncManager syncManager) {
this.syncManager = syncManager;
return this;
}
public BlockchainImpl withParentBlockHeaderValidator(ParentBlockHeaderValidator parentHeaderValidator) {
this.parentHeaderValidator = parentHeaderValidator;
return this;
}
private void initConst(SystemProperties config) {
minerCoinbase = config.getMinerCoinbase();
minerExtraData = config.getMineExtraData();
BLOCK_REWARD = config.getBlockchainConfig().getCommonConstants().getBLOCK_REWARD();
INCLUSION_REWARD = BLOCK_REWARD.divide(BigInteger.valueOf(32));
UNCLE_LIST_LIMIT = config.getBlockchainConfig().getCommonConstants().getUNCLE_LIST_LIMIT();
UNCLE_GENERATION_LIMIT = config.getBlockchainConfig().getCommonConstants().getUNCLE_GENERATION_LIMIT();
}
@Override
public byte[] getBestBlockHash() {
return getBestBlock().getHash();
}
@Override
public long getSize() {
return bestBlock.getNumber() + 1;
}
@Override
public Block getBlockByNumber(long blockNr) {
return blockStore.getChainBlockByNumber(blockNr);
}
@Override
public TransactionInfo getTransactionInfo(byte[] hash) {
List<TransactionInfo> infos = transactionStore.get(hash);
if (infos == null || infos.isEmpty())
return null;
TransactionInfo txInfo = null;
if (infos.size() == 1) {
txInfo = infos.get(0);
} else {
// pick up the receipt from the block on the main chain
for (TransactionInfo info : infos) {
Block block = blockStore.getBlockByHash(info.blockHash);
Block mainBlock = blockStore.getChainBlockByNumber(block.getNumber());
if (FastByteComparisons.equal(info.blockHash, mainBlock.getHash())) {
txInfo = info;
break;
}
}
}
if (txInfo == null) {
logger.warn("Can't find block from main chain for transaction " + Hex.toHexString(hash));
return null;
}
Transaction tx = this.getBlockByHash(txInfo.getBlockHash()).getTransactionsList().get(txInfo.getIndex());
txInfo.setTransaction(tx);
return txInfo;
}
@Override
public Block getBlockByHash(byte[] hash) {
return blockStore.getBlockByHash(hash);
}
@Override
public synchronized List<byte[]> getListOfHashesStartFrom(byte[] hash, int qty) {
return blockStore.getListHashesEndWith(hash, qty);
}
@Override
public synchronized List<byte[]> getListOfHashesStartFromBlock(long blockNumber, int qty) {
long bestNumber = bestBlock.getNumber();
if (blockNumber > bestNumber) {
return emptyList();
}
if (blockNumber + qty - 1 > bestNumber) {
qty = (int) (bestNumber - blockNumber + 1);
}
long endNumber = blockNumber + qty - 1;
Block block = getBlockByNumber(endNumber);
List<byte[]> hashes = blockStore.getListHashesEndWith(block.getHash(), qty);
// asc order of hashes is required in the response
Collections.reverse(hashes);
return hashes;
}
public static byte[] calcTxTrie(List<Transaction> transactions) {
Trie txsState = new TrieImpl();
if (transactions == null || transactions.isEmpty())
return HashUtil.EMPTY_TRIE_HASH;
for (int i = 0; i < transactions.size(); i++) {
txsState.put(RLP.encodeInt(i), transactions.get(i).getEncoded());
}
return txsState.getRootHash();
}
public Repository getRepository() {
return repository;
}
public Repository getRepositorySnapshot() {
return repository.getSnapshotTo(blockStore.getBestBlock().getStateRoot());
}
@Override
public BlockStore getBlockStore() {
return blockStore;
}
public ProgramInvokeFactory getProgramInvokeFactory() {
return programInvokeFactory;
}
private State pushState(byte[] bestBlockHash) {
State push = stateStack.push(new State());
this.bestBlock = blockStore.getBlockByHash(bestBlockHash);
totalDifficulty = blockStore.getTotalDifficultyForHash(bestBlockHash);
this.repository = this.repository.getSnapshotTo(this.bestBlock.getStateRoot());
return push;
}
private void popState() {
State state = stateStack.pop();
this.repository = repository.getSnapshotTo(state.root);
this.bestBlock = state.savedBest;
this.totalDifficulty = state.savedTD;
}
public void dropState() {
stateStack.pop();
}
private synchronized BlockSummary tryConnectAndFork(final Block block) {
State savedState = pushState(block.getParentHash());
this.fork = true;
final BlockSummary summary;
Repository repo;
try {
// FIXME: adding block with no option for flush
Block parentBlock = getBlockByHash(block.getParentHash());
repo = repository.getSnapshotTo(parentBlock.getStateRoot());
summary = add(repo, block);
if (summary == null) {
return null;
}
} catch (Throwable th) {
logger.error("Unexpected error: ", th);
return null;
} finally {
this.fork = false;
}
if (isMoreThan(this.totalDifficulty, savedState.savedTD)) {
logger.info("Rebranching: {} ~> {}", savedState.savedBest.getShortHash(), block.getShortHash());
// main branch become this branch
// cause we proved that total difficulty
// is greateer
blockStore.reBranch(block);
// The main repository rebranch
this.repository = repo;
// this.repository.syncToRoot(block.getStateRoot());
dropState();
} else {
// Stay on previous branch
popState();
}
return summary;
}
public synchronized ImportResult tryToConnect(final Block block) {
if (logger.isDebugEnabled())
logger.debug("Try connect block hash: {}, number: {}",
Hex.toHexString(block.getHash()).substring(0, 6),
block.getNumber());
if (blockStore.getMaxNumber() >= block.getNumber() &&
blockStore.isBlockExist(block.getHash())) {
if (logger.isDebugEnabled())
logger.debug("Block already exist hash: {}, number: {}",
Hex.toHexString(block.getHash()).substring(0, 6),
block.getNumber());
// retry of well known block
return EXIST;
}
final ImportResult ret;
// The simple case got the block
// to connect to the main chain
final BlockSummary summary;
if (bestBlock.isParentOf(block)) {
recordBlock(block);
// Repository repoSnap = repository.getSnapshotTo(bestBlock.getStateRoot());
summary = add(repository, block);
ret = summary == null ? INVALID_BLOCK : IMPORTED_BEST;
} else {
if (blockStore.isBlockExist(block.getParentHash())) {
BigInteger oldTotalDiff = getTotalDifficulty();
recordBlock(block);
summary = tryConnectAndFork(block);
ret = summary == null ? INVALID_BLOCK :
(isMoreThan(getTotalDifficulty(), oldTotalDiff) ? IMPORTED_BEST : IMPORTED_NOT_BEST);
} else {
summary = null;
ret = NO_PARENT;
}
}
if (ret.isSuccessful()) {
listener.onBlock(summary);
listener.trace(String.format("Block chain size: [ %d ]", this.getSize()));
if (ret == IMPORTED_BEST) {
eventDispatchThread.invokeLater(new Runnable() {
@Override
public void run() {
pendingState.processBest(block, summary.getReceipts());
}
});
}
}
return ret;
}
public synchronized Block createNewBlock(Block parent, List<Transaction> txs, List<BlockHeader> uncles) {
long time = System.currentTimeMillis() / 1000;
// adjust time to parent block this may happen due to system clocks difference
if (parent.getTimestamp() >= time) time = parent.getTimestamp() + 1;
return createNewBlock(parent, txs, uncles, time);
}
public synchronized Block createNewBlock(Block parent, List<Transaction> txs, List<BlockHeader> uncles, long time) {
final long blockNumber = parent.getNumber() + 1;
final byte[] extraData = config.getBlockchainConfig().getConfigForBlock(blockNumber).getExtraData(minerExtraData, blockNumber);
Block block = new Block(parent.getHash(),
EMPTY_LIST_HASH, // uncleHash
minerCoinbase,
new byte[0], // log bloom - from tx receipts
new byte[0], // difficulty computed right after block creation
blockNumber,
parent.getGasLimit(), // (add to config ?)
0, // gas used - computed after running all transactions
time, // block time
extraData, // extra data
new byte[0], // mixHash (to mine)
new byte[0], // nonce (to mine)
new byte[0], // receiptsRoot - computed after running all transactions
calcTxTrie(txs), // TransactionsRoot - computed after running all transactions
new byte[] {0}, // stateRoot - computed after running all transactions
txs,
null); // uncle list
for (BlockHeader uncle : uncles) {
block.addUncle(uncle);
}
block.getHeader().setDifficulty(ByteUtil.bigIntegerToBytes(block.getHeader().
calcDifficulty(config.getBlockchainConfig(), parent.getHeader())));
Repository track = repository.getSnapshotTo(parent.getStateRoot());
BlockSummary summary = applyBlock(track, block);
List<TransactionReceipt> receipts = summary.getReceipts();
block.setStateRoot(track.getRoot());
Bloom logBloom = new Bloom();
for (TransactionReceipt receipt : receipts) {
logBloom.or(receipt.getBloomFilter());
}
block.getHeader().setLogsBloom(logBloom.getData());
block.getHeader().setGasUsed(receipts.size() > 0 ? receipts.get(receipts.size() - 1).getCumulativeGasLong() : 0);
block.getHeader().setReceiptsRoot(calcReceiptsTrie(receipts));
return block;
}
@Override
public BlockSummary add(Block block) {
throw new RuntimeException("Not supported");
}
// @Override
public synchronized BlockSummary add(Repository repo, final Block block) {
BlockSummary summary = addImpl(repo, block);
if (summary == null) {
stateLogger.warn("Trying to reimport the block for debug...");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
BlockSummary summary1 = addImpl(repo.getSnapshotTo(getBestBlock().getStateRoot()), block);
stateLogger.warn("Second import trial " + (summary1 == null ? "FAILED" : "OK"));
if (summary1 != null) {
stateLogger.error("Inconsistent behavior, exiting...");
System.exit(-1);
}
}
return summary;
}
public synchronized BlockSummary addImpl(Repository repo, final Block block) {
if (exitOn < block.getNumber()) {
System.out.print("Exiting after block.number: " + bestBlock.getNumber());
dbFlushManager.flushSync();
System.exit(-1);
}
if (!isValid(repo, block)) {
logger.warn("Invalid block with number: {}", block.getNumber());
return null;
}
// Repository track = repo.startTracking();
byte[] origRoot = repo.getRoot();
if (block == null)
return null;
// keep chain continuity
// if (!Arrays.equals(bestBlock.getHash(),
// block.getParentHash())) return null;
if (block.getNumber() >= config.traceStartBlock() && config.traceStartBlock() != -1) {
AdvancedDeviceUtils.adjustDetailedTracing(config, block.getNumber());
}
BlockSummary summary = processBlock(repo, block);
final List<TransactionReceipt> receipts = summary.getReceipts();
// Sanity checks
if (!FastByteComparisons.equal(block.getReceiptsRoot(), calcReceiptsTrie(receipts))) {
logger.warn("Block's given Receipt Hash doesn't match: {} != {}", Hex.toHexString(block.getReceiptsRoot()), Hex.toHexString(calcReceiptsTrie(receipts)));
logger.warn("Calculated receipts: " + receipts);
repo.rollback();
summary = null;
}
if (!FastByteComparisons.equal(block.getLogBloom(), calcLogBloom(receipts))) {
logger.warn("Block's given logBloom Hash doesn't match: {} != {}", Hex.toHexString(block.getLogBloom()), Hex.toHexString(calcLogBloom(receipts)));
repo.rollback();
summary = null;
}
if (!FastByteComparisons.equal(block.getStateRoot(), repo.getRoot())) {
stateLogger.warn("BLOCK: State conflict or received invalid block. block: {} worldstate {} mismatch", block.getNumber(), Hex.toHexString(repo.getRoot()));
stateLogger.warn("Conflict block dump: {}", Hex.toHexString(block.getEncoded()));
// track.rollback();
// repository.rollback();
repository = repository.getSnapshotTo(origRoot);
// block is bad so 'rollback' the state root to the original state
// ((RepositoryImpl) repository).setRoot(origRoot);
// track.rollback();
// block is bad so 'rollback' the state root to the original state
// ((RepositoryImpl) repository).setRoot(origRoot);
if (config.exitOnBlockConflict()) {
adminInfo.lostConsensus();
System.out.println("CONFLICT: BLOCK #" + block.getNumber() + ", dump: " + Hex.toHexString(block.getEncoded()));
System.exit(1);
} else {
summary = null;
}
}
if (summary != null) {
repo.commit();
updateTotalDifficulty(block);
summary.setTotalDifficulty(getTotalDifficulty());
if (!byTest) {
dbFlushManager.commit(new Runnable() {
@Override
public void run() {
storeBlock(block, receipts);
repository.commit();
}
});
} else {
storeBlock(block, receipts);
}
}
return summary;
}
@Override
public void flush() {
// repository.flush();
// stateDataSource.flush();
// blockStore.flush();
// transactionStore.flush();
//
// repository = repository.getSnapshotTo(repository.getRoot());
//
// if (isMemoryBoundFlush()) {
// System.gc();
// }
}
private boolean needFlushByMemory(double maxMemoryPercents) {
return getRuntime().freeMemory() < (getRuntime().totalMemory() * (1 - maxMemoryPercents));
}
public static byte[] calcReceiptsTrie(List<TransactionReceipt> receipts) {
Trie receiptsTrie = new TrieImpl();
if (receipts == null || receipts.isEmpty())
return HashUtil.EMPTY_TRIE_HASH;
for (int i = 0; i < receipts.size(); i++) {
receiptsTrie.put(RLP.encodeInt(i), receipts.get(i).getReceiptTrieEncoded());
}
return receiptsTrie.getRootHash();
}
private byte[] calcLogBloom(List<TransactionReceipt> receipts) {
Bloom retBloomFilter = new Bloom();
if (receipts == null || receipts.isEmpty())
return retBloomFilter.getData();
for (int i = 0; i < receipts.size(); i++) {
retBloomFilter.or(receipts.get(i).getBloomFilter());
}
return retBloomFilter.getData();
}
public Block getParent(BlockHeader header) {
return blockStore.getBlockByHash(header.getParentHash());
}
public boolean isValid(BlockHeader header) {
if (parentHeaderValidator == null) return true;
Block parentBlock = getParent(header);
if (!parentHeaderValidator.validate(header, parentBlock.getHeader())) {
if (logger.isErrorEnabled())
parentHeaderValidator.logErrors(logger);
return false;
}
return true;
}
/**
* This mechanism enforces a homeostasis in terms of the time between blocks;
* a smaller period between the last two blocks results in an increase in the
* difficulty level and thus additional computation required, lengthening the
* likely next period. Conversely, if the period is too large, the difficulty,
* and expected time to the next block, is reduced.
*/
private boolean isValid(Repository repo, Block block) {
boolean isValid = true;
if (!block.isGenesis()) {
isValid = isValid(block.getHeader());
// Sanity checks
String trieHash = Hex.toHexString(block.getTxTrieRoot());
String trieListHash = Hex.toHexString(calcTxTrie(block.getTransactionsList()));
if (!trieHash.equals(trieListHash)) {
logger.warn("Block's given Trie Hash doesn't match: {} != {}", trieHash, trieListHash);
return false;
}
// if (!validateUncles(block)) return false;
List<Transaction> txs = block.getTransactionsList();
if (!txs.isEmpty()) {
// Repository parentRepo = repository;
// if (!Arrays.equals(bestBlock.getHash(), block.getParentHash())) {
// parentRepo = repository.getSnapshotTo(getBlockByHash(block.getParentHash()).getStateRoot());
// }
Map<ByteArrayWrapper, BigInteger> curNonce = new HashMap<>();
for (Transaction tx : txs) {
byte[] txSender = tx.getSender();
ByteArrayWrapper key = new ByteArrayWrapper(txSender);
BigInteger expectedNonce = curNonce.get(key);
if (expectedNonce == null) {
expectedNonce = repo.getNonce(txSender);
}
curNonce.put(key, expectedNonce.add(ONE));
BigInteger txNonce = new BigInteger(1, tx.getNonce());
if (!expectedNonce.equals(txNonce)) {
logger.warn("Invalid transaction: Tx nonce {} != expected nonce {} (parent nonce: {}): {}",
txNonce, expectedNonce, repo.getNonce(txSender), tx);
return false;
}
}
}
}
return isValid;
}
public boolean validateUncles(Block block) {
String unclesHash = Hex.toHexString(block.getHeader().getUnclesHash());
String unclesListHash = Hex.toHexString(HashUtil.sha3(block.getHeader().getUnclesEncoded(block.getUncleList())));
if (!unclesHash.equals(unclesListHash)) {
logger.warn("Block's given Uncle Hash doesn't match: {} != {}", unclesHash, unclesListHash);
return false;
}
if (block.getUncleList().size() > UNCLE_LIST_LIMIT) {
logger.warn("Uncle list to big: block.getUncleList().size() > UNCLE_LIST_LIMIT");
return false;
}
Set<ByteArrayWrapper> ancestors = getAncestors(blockStore, block, UNCLE_GENERATION_LIMIT + 1, false);
Set<ByteArrayWrapper> usedUncles = getUsedUncles(blockStore, block, false);
for (BlockHeader uncle : block.getUncleList()) {
// - They are valid headers (not necessarily valid blocks)
if (!isValid(uncle)) return false;
//if uncle's parent's number is not less than currentBlock - UNCLE_GEN_LIMIT, mark invalid
boolean isValid = !(getParent(uncle).getNumber() < (block.getNumber() - UNCLE_GENERATION_LIMIT));
if (!isValid) {
logger.warn("Uncle too old: generationGap must be under UNCLE_GENERATION_LIMIT");
return false;
}
ByteArrayWrapper uncleHash = new ByteArrayWrapper(uncle.getHash());
if (ancestors.contains(uncleHash)) {
logger.warn("Uncle is direct ancestor: " + Hex.toHexString(uncle.getHash()));
return false;
}
if (usedUncles.contains(uncleHash)) {
logger.warn("Uncle is not unique: " + Hex.toHexString(uncle.getHash()));
return false;
}
Block uncleParent = blockStore.getBlockByHash(uncle.getParentHash());
if (!ancestors.contains(new ByteArrayWrapper(uncleParent.getHash()))) {
logger.warn("Uncle has no common parent: " + Hex.toHexString(uncle.getHash()));
return false;
}
}
return true;
}
public static Set<ByteArrayWrapper> getAncestors(BlockStore blockStore, Block testedBlock, int limitNum, boolean isParentBlock) {
Set<ByteArrayWrapper> ret = new HashSet<>();
limitNum = (int) max(0, testedBlock.getNumber() - limitNum);
Block it = testedBlock;
if (!isParentBlock) {
it = blockStore.getBlockByHash(it.getParentHash());
}
while(it != null && it.getNumber() >= limitNum) {
ret.add(new ByteArrayWrapper(it.getHash()));
it = blockStore.getBlockByHash(it.getParentHash());
}
return ret;
}
public Set<ByteArrayWrapper> getUsedUncles(BlockStore blockStore, Block testedBlock, boolean isParentBlock) {
Set<ByteArrayWrapper> ret = new HashSet<>();
long limitNum = max(0, testedBlock.getNumber() - UNCLE_GENERATION_LIMIT);
Block it = testedBlock;
if (!isParentBlock) {
it = blockStore.getBlockByHash(it.getParentHash());
}
while(it.getNumber() > limitNum) {
for (BlockHeader uncle : it.getUncleList()) {
ret.add(new ByteArrayWrapper(uncle.getHash()));
}
it = blockStore.getBlockByHash(it.getParentHash());
}
return ret;
}
private BlockSummary processBlock(Repository track, Block block) {
if (!block.isGenesis() && !config.blockChainOnly()) {
return applyBlock(track, block);
}
else {
return new BlockSummary(block, new HashMap<byte[], BigInteger>(), new ArrayList<TransactionReceipt>(), new ArrayList<TransactionExecutionSummary>());
}
}
private BlockSummary applyBlock(Repository track, Block block) {
logger.debug("applyBlock: block: [{}] tx.list: [{}]", block.getNumber(), block.getTransactionsList().size());
config.getBlockchainConfig().getConfigForBlock(block.getNumber()).hardForkTransfers(block, track);
long saveTime = System.nanoTime();
int i = 1;
long totalGasUsed = 0;
List<TransactionReceipt> receipts = new ArrayList<>();
List<TransactionExecutionSummary> summaries = new ArrayList<>();
for (Transaction tx : block.getTransactionsList()) {
stateLogger.debug("apply block: [{}] tx: [{}] ", block.getNumber(), i);
Repository txTrack = track.startTracking();
TransactionExecutor executor = new TransactionExecutor(tx, block.getCoinbase(),
txTrack, blockStore, programInvokeFactory, block, listener, totalGasUsed)
.withCommonConfig(commonConfig);
executor.init();
executor.execute();
executor.go();
TransactionExecutionSummary summary = executor.finalization();
totalGasUsed += executor.getGasUsed();
txTrack.commit();
final TransactionReceipt receipt = executor.getReceipt();
receipt.setPostTxState(track.getRoot());
stateLogger.info("block: [{}] executed tx: [{}] \n state: [{}]", block.getNumber(), i,
Hex.toHexString(track.getRoot()));
stateLogger.info("[{}] ", receipt.toString());
if (stateLogger.isInfoEnabled())
stateLogger.info("tx[{}].receipt: [{}] ", i, Hex.toHexString(receipt.getEncoded()));
// TODO
// if (block.getNumber() >= config.traceStartBlock())
// repository.dumpState(block, totalGasUsed, i++, tx.getHash());
receipts.add(receipt);
if (summary != null) {
summaries.add(summary);
}
}
Map<byte[], BigInteger> rewards = addReward(track, block, summaries);
stateLogger.info("applied reward for block: [{}] \n state: [{}]",
block.getNumber(),
Hex.toHexString(track.getRoot()));
// TODO
// if (block.getNumber() >= config.traceStartBlock())
// repository.dumpState(block, totalGasUsed, 0, null);
long totalTime = System.nanoTime() - saveTime;
adminInfo.addBlockExecTime(totalTime);
logger.debug("block: num: [{}] hash: [{}], executed after: [{}]nano", block.getNumber(), block.getShortHash(), totalTime);
return new BlockSummary(block, rewards, receipts, summaries);
}
/**
* Add reward to block- and every uncle coinbase
* assuming the entire block is valid.
*
* @param block object containing the header and uncles
*/
private Map<byte[], BigInteger> addReward(Repository track, Block block, List<TransactionExecutionSummary> summaries) {
Map<byte[], BigInteger> rewards = new HashMap<>();
// Add extra rewards based on number of uncles
if (block.getUncleList().size() > 0) {
for (BlockHeader uncle : block.getUncleList()) {
BigInteger uncleReward = BLOCK_REWARD
.multiply(BigInteger.valueOf(MAGIC_REWARD_OFFSET + uncle.getNumber() - block.getNumber()))
.divide(BigInteger.valueOf(MAGIC_REWARD_OFFSET));
track.addBalance(uncle.getCoinbase(),uncleReward);
BigInteger existingUncleReward = rewards.get(uncle.getCoinbase());
if (existingUncleReward == null) {
rewards.put(uncle.getCoinbase(), uncleReward);
} else {
rewards.put(uncle.getCoinbase(), existingUncleReward.add(uncleReward));
}
}
}
BigInteger minerReward = BLOCK_REWARD.add(INCLUSION_REWARD.multiply(BigInteger.valueOf(block.getUncleList().size())));
BigInteger totalFees = BigInteger.ZERO;
for (TransactionExecutionSummary summary : summaries) {
totalFees = totalFees.add(summary.getFee());
}
rewards.put(block.getCoinbase(), minerReward.add(totalFees));
track.addBalance(block.getCoinbase(), minerReward); // fees are already given to the miner during tx execution
return rewards;
}
@Override
public synchronized void storeBlock(Block block, List<TransactionReceipt> receipts) {
if (fork)
blockStore.saveBlock(block, totalDifficulty, false);
else
blockStore.saveBlock(block, totalDifficulty, true);
for (int i = 0; i < receipts.size(); i++) {
transactionStore.put(new TransactionInfo(receipts.get(i), block.getHash(), i));
}
if (pruneManager != null) {
pruneManager.blockCommitted(block.getHeader());
}
logger.debug("Block saved: number: {}, hash: {}, TD: {}",
block.getNumber(), block.getShortHash(), totalDifficulty);
setBestBlock(block);
if (logger.isDebugEnabled())
logger.debug("block added to the blockChain: index: [{}]", block.getNumber());
if (block.getNumber() % 100 == 0)
logger.info("*** Last block added [ #{} ]", block.getNumber());
}
public boolean hasParentOnTheChain(Block block) {
return getParent(block.getHeader()) != null;
}
@Override
public List<Chain> getAltChains() {
return altChains;
}
@Override
public List<Block> getGarbage() {
return garbage;
}
public TransactionStore getTransactionStore() {
return transactionStore;
}
@Override
public void setBestBlock(Block block) {
bestBlock = block;
repository = repository.getSnapshotTo(block.getStateRoot());
}
@Override
public synchronized Block getBestBlock() {
// the method is synchronized since the bestBlock might be
// temporarily switched to the fork while importing non-best block
return bestBlock;
}
@Override
public synchronized void close() {
blockStore.close();
}
@Override
public BigInteger getTotalDifficulty() {
return totalDifficulty;
}
@Override
public synchronized void updateTotalDifficulty(Block block) {
totalDifficulty = totalDifficulty.add(block.getDifficultyBI());
logger.debug("TD: updated to {}", totalDifficulty);
}
@Override
public void setTotalDifficulty(BigInteger totalDifficulty) {
this.totalDifficulty = totalDifficulty;
}
private void recordBlock(Block block) {
if (!config.recordBlocks()) return;
String dumpDir = config.databaseDir() + "/" + config.dumpDir();
File dumpFile = new File(dumpDir + "/blocks-rec.dmp");
FileWriter fw = null;
BufferedWriter bw = null;
try {
dumpFile.getParentFile().mkdirs();
if (!dumpFile.exists()) dumpFile.createNewFile();
fw = new FileWriter(dumpFile.getAbsoluteFile(), true);
bw = new BufferedWriter(fw);
if (bestBlock.isGenesis()) {
bw.write(Hex.toHexString(bestBlock.getEncoded()));
bw.write("\n");
}
bw.write(Hex.toHexString(block.getEncoded()));
bw.write("\n");
} catch (IOException e) {
logger.error(e.getMessage(), e);
} finally {
try {
if (bw != null) bw.close();
if (fw != null) fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void updateBlockTotDifficulties(int startFrom) {
// no synchronization here not to lock instance for long period
while(true) {
synchronized (this) {
((IndexedBlockStore) blockStore).updateTotDifficulties(startFrom);
if (startFrom == bestBlock.getNumber()) {
totalDifficulty = blockStore.getTotalDifficultyForHash(bestBlock.getHash());
break;
}
startFrom++;
}
}
}
public void setRepository(Repository repository) {
this.repository = repository;
}
public void setProgramInvokeFactory(ProgramInvokeFactory factory) {
this.programInvokeFactory = factory;
}
public void setExitOn(long exitOn) {
this.exitOn = exitOn;
}
public void setMinerCoinbase(byte[] minerCoinbase) {
this.minerCoinbase = minerCoinbase;
}
@Override
public byte[] getMinerCoinbase() {
return minerCoinbase;
}
public void setMinerExtraData(byte[] minerExtraData) {
this.minerExtraData = minerExtraData;
}
public boolean isBlockExist(byte[] hash) {
return blockStore.isBlockExist(hash);
}
public void setParentHeaderValidator(DependentBlockHeaderRule parentHeaderValidator) {
this.parentHeaderValidator = parentHeaderValidator;
}
public void setPendingState(PendingState pendingState) {
this.pendingState = pendingState;
}
public PendingState getPendingState() {
return pendingState;
}
/**
* Returns up to limit headers found with following search parameters
* [Synchronized only in blockstore, not using any synchronized BlockchainImpl methods]
* @param identifier Identifier of start block, by number of by hash
* @param skip Number of blocks to skip between consecutive headers
* @param limit Maximum number of headers in return
* @param reverse Is search reverse or not
* @return {@link BlockHeader}'s list or empty list if none found
*/
@Override
public List<BlockHeader> getListOfHeadersStartFrom(BlockIdentifier identifier, int skip, int limit, boolean reverse) {
// Identifying block we'll move from
Block startBlock;
if (identifier.getHash() != null) {
startBlock = blockStore.getBlockByHash(identifier.getHash());
} else {
startBlock = blockStore.getChainBlockByNumber(identifier.getNumber());
}
// If nothing found or provided hash is not on main chain, return empty array
if (startBlock == null) {
return emptyList();
}
if (identifier.getHash() != null) {
Block mainChainBlock = blockStore.getChainBlockByNumber(startBlock.getNumber());
if (!startBlock.equals(mainChainBlock)) return emptyList();
}
List<BlockHeader> headers;
if (skip == 0) {
long bestNumber = blockStore.getBestBlock().getNumber();
headers = getContinuousHeaders(bestNumber, startBlock.getNumber(), limit, reverse);
} else {
headers = getGapedHeaders(startBlock, skip, limit, reverse);
}
return headers;
}
/**
* Finds up to limit blocks starting from blockNumber on main chain
* @param bestNumber Number of best block
* @param blockNumber Number of block to start search (included in return)
* @param limit Maximum number of headers in response
* @param reverse Order of search
* @return headers found by query or empty list if none
*/
private List<BlockHeader> getContinuousHeaders(long bestNumber, long blockNumber, int limit, boolean reverse) {
int qty = getQty(blockNumber, bestNumber, limit, reverse);
byte[] startHash = getStartHash(blockNumber, qty, reverse);
if (startHash == null) {
return emptyList();
}
List<BlockHeader> headers = blockStore.getListHeadersEndWith(startHash, qty);
// blocks come with falling numbers
if (!reverse) {
Collections.reverse(headers);
}
return headers;
}
/**
* Gets blocks from main chain with gaps between
* @param startBlock Block to start from (included in return)
* @param skip Number of blocks skipped between every header in return
* @param limit Maximum number of headers in return
* @param reverse Order of search
* @return headers found by query or empty list if none
*/
private List<BlockHeader> getGapedHeaders(Block startBlock, int skip, int limit, boolean reverse) {
List<BlockHeader> headers = new ArrayList<>();
headers.add(startBlock.getHeader());
int offset = skip + 1;
if (reverse) offset = -offset;
long currentNumber = startBlock.getNumber();
boolean finished = false;
while(!finished && headers.size() < limit) {
currentNumber += offset;
Block nextBlock = blockStore.getChainBlockByNumber(currentNumber);
if (nextBlock == null) {
finished = true;
} else {
headers.add(nextBlock.getHeader());
}
}
return headers;
}
private int getQty(long blockNumber, long bestNumber, int limit, boolean reverse) {
if (reverse) {
return blockNumber - limit + 1 < 0 ? (int) (blockNumber + 1) : limit;
} else {
if (blockNumber + limit - 1 > bestNumber) {
return (int) (bestNumber - blockNumber + 1);
} else {
return limit;
}
}
}
private byte[] getStartHash(long blockNumber, int qty, boolean reverse) {
long startNumber;
if (reverse) {
startNumber = blockNumber;
} else {
startNumber = blockNumber + qty - 1;
}
Block block = blockStore.getChainBlockByNumber(startNumber);
if (block == null) {
return null;
}
return block.getHash();
}
/**
* Returns list of block bodies by block hashes, stopping on first not found block
* [Synchronized only in blockstore, not using any synchronized BlockchainImpl methods]
* @param hashes List of hashes
* @return List of RLP encoded block bodies
*/
@Override
public List<byte[]> getListOfBodiesByHashes(List<byte[]> hashes) {
List<byte[]> bodies = new ArrayList<>(hashes.size());
for (byte[] hash : hashes) {
Block block = blockStore.getBlockByHash(hash);
if (block == null) break;
bodies.add(block.getEncodedBody());
}
return bodies;
}
private class State {
// Repository savedRepo = repository;
byte[] root = repository.getRoot();
Block savedBest = bestBlock;
BigInteger savedTD = totalDifficulty;
}
public void setPruneManager(PruneManager pruneManager) {
this.pruneManager = pruneManager;
}
}