/*
* 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.db;
import org.ethereum.core.Block;
import org.ethereum.core.BlockHeader;
import org.ethereum.datasource.DataSourceArray;
import org.ethereum.datasource.ObjectDataSource;
import org.ethereum.datasource.Serializer;
import org.ethereum.datasource.Source;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.FastByteComparisons;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPElement;
import org.ethereum.util.RLPList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import static java.math.BigInteger.ZERO;
import static org.ethereum.crypto.HashUtil.shortHash;
import static org.spongycastle.util.Arrays.areEqual;
public class IndexedBlockStore extends AbstractBlockstore{
private static final Logger logger = LoggerFactory.getLogger("general");
Source<byte[], byte[]> indexDS;
DataSourceArray<List<BlockInfo>> index;
Source<byte[], byte[]> blocksDS;
ObjectDataSource<Block> blocks;
public IndexedBlockStore(){
}
public void init(Source<byte[], byte[]> index, Source<byte[], byte[]> blocks) {
indexDS = index;
this.index = new DataSourceArray<>(
new ObjectDataSource<>(index, BLOCK_INFO_SERIALIZER, 512));
this.blocksDS = blocks;
this.blocks = new ObjectDataSource<>(blocks, new Serializer<Block, byte[]>() {
@Override
public byte[] serialize(Block block) {
return block.getEncoded();
}
@Override
public Block deserialize(byte[] bytes) {
return bytes == null ? null : new Block(bytes);
}
}, 512);
}
public synchronized Block getBestBlock(){
Long maxLevel = getMaxNumber();
if (maxLevel < 0) return null;
Block bestBlock = getChainBlockByNumber(maxLevel);
if (bestBlock != null) return bestBlock;
// That scenario can happen
// if there is a fork branch that is
// higher than main branch but has
// less TD than the main branch TD
while (bestBlock == null){
--maxLevel;
bestBlock = getChainBlockByNumber(maxLevel);
}
return bestBlock;
}
public synchronized byte[] getBlockHashByNumber(long blockNumber){
Block chainBlock = getChainBlockByNumber(blockNumber);
return chainBlock == null ? null : chainBlock.getHash(); // FIXME: can be improved by accessing the hash directly in the index
}
@Override
public synchronized void flush(){
blocks.flush();
index.flush();
blocksDS.flush();
indexDS.flush();
}
@Override
public synchronized void saveBlock(Block block, BigInteger cummDifficulty, boolean mainChain){
addInternalBlock(block, cummDifficulty, mainChain);
}
private void addInternalBlock(Block block, BigInteger cummDifficulty, boolean mainChain){
List<BlockInfo> blockInfos = block.getNumber() >= index.size() ? null : index.get((int) block.getNumber());
blockInfos = blockInfos == null ? new ArrayList<BlockInfo>() : blockInfos;
BlockInfo blockInfo = new BlockInfo();
blockInfo.setCummDifficulty(cummDifficulty);
blockInfo.setHash(block.getHash());
blockInfo.setMainChain(mainChain); // FIXME:maybe here I should force reset main chain for all uncles on that level
putBlockInfo(blockInfos, blockInfo);
index.set((int) block.getNumber(), blockInfos);
blocks.put(block.getHash(), block);
}
private void putBlockInfo(List<BlockInfo> blockInfos, BlockInfo blockInfo) {
for (int i = 0; i < blockInfos.size(); i++) {
BlockInfo curBlockInfo = blockInfos.get(i);
if (FastByteComparisons.equal(curBlockInfo.getHash(), blockInfo.getHash())) {
blockInfos.set(i, blockInfo);
return;
}
}
blockInfos.add(blockInfo);
}
public synchronized List<Block> getBlocksByNumber(long number){
List<Block> result = new ArrayList<>();
if (number >= index.size()) {
return result;
}
List<BlockInfo> blockInfos = index.get((int) number);
if (blockInfos == null) {
return result;
}
for (BlockInfo blockInfo : blockInfos){
byte[] hash = blockInfo.getHash();
Block block = blocks.get(hash);
result.add(block);
}
return result;
}
@Override
public synchronized Block getChainBlockByNumber(long number){
if (number >= index.size()){
return null;
}
List<BlockInfo> blockInfos = index.get((int) number);
if (blockInfos == null) {
return null;
}
for (BlockInfo blockInfo : blockInfos){
if (blockInfo.isMainChain()){
byte[] hash = blockInfo.getHash();
return blocks.get(hash);
}
}
return null;
}
@Override
public synchronized Block getBlockByHash(byte[] hash) {
return blocks.get(hash);
}
@Override
public synchronized boolean isBlockExist(byte[] hash) {
return blocks.get(hash) != null;
}
@Override
public synchronized BigInteger getTotalDifficultyForHash(byte[] hash){
Block block = this.getBlockByHash(hash);
if (block == null) return ZERO;
Long level = block.getNumber();
List<BlockInfo> blockInfos = index.get(level.intValue());
for (BlockInfo blockInfo : blockInfos)
if (areEqual(blockInfo.getHash(), hash)) {
return blockInfo.cummDifficulty;
}
return ZERO;
}
@Override
public synchronized BigInteger getTotalDifficulty(){
long maxNumber = getMaxNumber();
List<BlockInfo> blockInfos = index.get((int) maxNumber);
for (BlockInfo blockInfo : blockInfos){
if (blockInfo.isMainChain()){
return blockInfo.getCummDifficulty();
}
}
while (true){
--maxNumber;
List<BlockInfo> infos = getBlockInfoForLevel(maxNumber);
for (BlockInfo blockInfo : infos) {
if (blockInfo.isMainChain()) {
return blockInfo.getCummDifficulty();
}
}
}
}
public synchronized void updateTotDifficulties(long index) {
List<BlockInfo> level = getBlockInfoForLevel(index);
for (BlockInfo blockInfo : level) {
Block block = getBlockByHash(blockInfo.getHash());
List<BlockInfo> parentInfos = getBlockInfoForLevel(index - 1);
BlockInfo parentInfo = getBlockInfoForHash(parentInfos, block.getParentHash());
blockInfo.setCummDifficulty(parentInfo.getCummDifficulty().add(block.getDifficultyBI()));
}
this.index.set((int) index, level);
}
@Override
public synchronized long getMaxNumber(){
Long bestIndex = 0L;
if (index.size() > 0){
bestIndex = (long) index.size();
}
return bestIndex - 1L;
}
@Override
public synchronized List<byte[]> getListHashesEndWith(byte[] hash, long number){
List<Block> blocks = getListBlocksEndWith(hash, number);
List<byte[]> hashes = new ArrayList<>(blocks.size());
for (Block b : blocks) {
hashes.add(b.getHash());
}
return hashes;
}
@Override
public synchronized List<BlockHeader> getListHeadersEndWith(byte[] hash, long qty) {
List<Block> blocks = getListBlocksEndWith(hash, qty);
List<BlockHeader> headers = new ArrayList<>(blocks.size());
for (Block b : blocks) {
headers.add(b.getHeader());
}
return headers;
}
@Override
public synchronized List<Block> getListBlocksEndWith(byte[] hash, long qty) {
return getListBlocksEndWithInner(hash, qty);
}
private List<Block> getListBlocksEndWithInner(byte[] hash, long qty) {
Block block = this.blocks.get(hash);
if (block == null) return new ArrayList<>();
List<Block> blocks = new ArrayList<>((int) qty);
for (int i = 0; i < qty; ++i) {
blocks.add(block);
block = this.blocks.get(block.getParentHash());
if (block == null) break;
}
return blocks;
}
@Override
public synchronized void reBranch(Block forkBlock){
Block bestBlock = getBestBlock();
long maxLevel = Math.max(bestBlock.getNumber(), forkBlock.getNumber());
// 1. First ensure that you are one the save level
long currentLevel = maxLevel;
Block forkLine = forkBlock;
if (forkBlock.getNumber() > bestBlock.getNumber()){
while(currentLevel > bestBlock.getNumber()){
List<BlockInfo> blocks = getBlockInfoForLevel(currentLevel);
BlockInfo blockInfo = getBlockInfoForHash(blocks, forkLine.getHash());
if (blockInfo != null) {
blockInfo.setMainChain(true);
setBlockInfoForLevel(currentLevel, blocks);
}
forkLine = getBlockByHash(forkLine.getParentHash());
--currentLevel;
}
}
Block bestLine = bestBlock;
if (bestBlock.getNumber() > forkBlock.getNumber()){
while(currentLevel > forkBlock.getNumber()){
List<BlockInfo> blocks = getBlockInfoForLevel(currentLevel);
BlockInfo blockInfo = getBlockInfoForHash(blocks, bestLine.getHash());
if (blockInfo != null) {
blockInfo.setMainChain(false);
setBlockInfoForLevel(currentLevel, blocks);
}
bestLine = getBlockByHash(bestLine.getParentHash());
--currentLevel;
}
}
// 2. Loop back on each level until common block
while( !bestLine.isEqual(forkLine) ) {
List<BlockInfo> levelBlocks = getBlockInfoForLevel(currentLevel);
BlockInfo bestInfo = getBlockInfoForHash(levelBlocks, bestLine.getHash());
if (bestInfo != null) {
bestInfo.setMainChain(false);
setBlockInfoForLevel(currentLevel, levelBlocks);
}
BlockInfo forkInfo = getBlockInfoForHash(levelBlocks, forkLine.getHash());
if (forkInfo != null) {
forkInfo.setMainChain(true);
setBlockInfoForLevel(currentLevel, levelBlocks);
}
bestLine = getBlockByHash(bestLine.getParentHash());
forkLine = getBlockByHash(forkLine.getParentHash());
--currentLevel;
}
}
public synchronized List<byte[]> getListHashesStartWith(long number, long maxBlocks){
List<byte[]> result = new ArrayList<>();
int i;
for ( i = 0; i < maxBlocks; ++i){
List<BlockInfo> blockInfos = index.get((int) number);
if (blockInfos == null) break;
for (BlockInfo blockInfo : blockInfos)
if (blockInfo.isMainChain()){
result.add(blockInfo.getHash());
break;
}
++number;
}
maxBlocks -= i;
return result;
}
public static class BlockInfo implements Serializable {
byte[] hash;
BigInteger cummDifficulty;
boolean mainChain;
public byte[] getHash() {
return hash;
}
public void setHash(byte[] hash) {
this.hash = hash;
}
public BigInteger getCummDifficulty() {
return cummDifficulty;
}
public void setCummDifficulty(BigInteger cummDifficulty) {
this.cummDifficulty = cummDifficulty;
}
public boolean isMainChain() {
return mainChain;
}
public void setMainChain(boolean mainChain) {
this.mainChain = mainChain;
}
}
public static final Serializer<List<BlockInfo>, byte[]> BLOCK_INFO_SERIALIZER = new Serializer<List<BlockInfo>, byte[]>(){
@Override
public byte[] serialize(List<BlockInfo> value) {
List<byte[]> rlpBlockInfoList = new ArrayList<>();
for (BlockInfo blockInfo : value) {
byte[] hash = RLP.encodeElement(blockInfo.getHash());
// Encoding works correctly only with positive BigIntegers
if (blockInfo.getCummDifficulty() == null || blockInfo.getCummDifficulty().compareTo(BigInteger.ZERO) < 0) {
throw new RuntimeException("BlockInfo cummDifficulty should be positive BigInteger");
}
byte[] cummDiff = RLP.encodeBigInteger(blockInfo.getCummDifficulty());
byte[] isMainChain = RLP.encodeInt(blockInfo.isMainChain() ? 1 : 0);
rlpBlockInfoList.add(RLP.encodeList(hash, cummDiff, isMainChain));
}
byte[][] elements = rlpBlockInfoList.toArray(new byte[rlpBlockInfoList.size()][]);
return RLP.encodeList(elements);
}
@Override
public List<BlockInfo> deserialize(byte[] bytes) {
if (bytes == null) return null;
List<BlockInfo> blockInfoList = new ArrayList<>();
RLPList list = (RLPList) RLP.decode2(bytes).get(0);
for (RLPElement element : list) {
RLPList rlpBlock = (RLPList) element;
BlockInfo blockInfo = new BlockInfo();
byte[] rlpHash = rlpBlock.get(0).getRLPData();
blockInfo.setHash(rlpHash == null ? new byte[0] : rlpHash);
byte[] rlpCummDiff = rlpBlock.get(1).getRLPData();
blockInfo.setCummDifficulty(rlpCummDiff == null ? BigInteger.ZERO : ByteUtil.bytesToBigInteger(rlpCummDiff));
blockInfo.setMainChain(ByteUtil.byteArrayToInt(rlpBlock.get(2).getRLPData()) == 1);
blockInfoList.add(blockInfo);
}
return blockInfoList;
}
};
public synchronized void printChain(){
Long number = getMaxNumber();
for (int i = 0; i < number; ++i){
List<BlockInfo> levelInfos = index.get(i);
if (levelInfos != null) {
System.out.print(i);
for (BlockInfo blockInfo : levelInfos){
if (blockInfo.isMainChain())
System.out.print(" [" + shortHash(blockInfo.getHash()) + "] ");
else
System.out.print(" " + shortHash(blockInfo.getHash()) + " ");
}
System.out.println();
}
}
}
private synchronized List<BlockInfo> getBlockInfoForLevel(long level){
return index.get((int) level);
}
private synchronized void setBlockInfoForLevel(long level, List<BlockInfo> infos){
index.set((int) level, infos);
}
private static BlockInfo getBlockInfoForHash(List<BlockInfo> blocks, byte[] hash){
for (BlockInfo blockInfo : blocks)
if (areEqual(hash, blockInfo.getHash())) return blockInfo;
return null;
}
@Override
public synchronized void load() {
}
@Override
public synchronized void close() {
// logger.info("Closing IndexedBlockStore...");
// try {
// indexDS.close();
// } catch (Exception e) {
// logger.warn("Problems closing indexDS", e);
// }
// try {
// blocksDS.close();
// } catch (Exception e) {
// logger.warn("Problems closing blocksDS", e);
// }
}
}