gt:         for (BlockInfo blockInfo : blocks)
context: /*
   * 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){
  
  