gt:             startNumber = maxNum + 1;
context: package org.ethereum.sync;
  
  import org.ethereum.core.Block;
  import org.ethereum.core.BlockHeader;
  import org.ethereum.core.BlockHeaderWrapper;
  import org.ethereum.core.Blockchain;
  import org.ethereum.db.ByteArrayWrapper;
  import org.ethereum.util.ByteArrayMap;
  import org.ethereum.util.Functional;
  import org.spongycastle.util.encoders.Hex;
  
  import java.util.*;
  
  import static java.lang.Math.min;
  
  /**
   * Created by Anton Nashatyrev on 27.05.2016.
   */
  public class SyncQueueImpl implements SyncQueueIfc {
      static int MAX_CHAIN_LEN = 192;
  
      static class HeadersRequestImpl implements HeadersRequest {
          public HeadersRequestImpl(long start, int count, boolean reverse) {
              this.start = start;
              this.count = count;
              this.reverse = reverse;
          }
  
          public HeadersRequestImpl(byte[] hash, int count, boolean reverse) {
              this.hash = hash;
              this.count = count;
              this.reverse = reverse;
          }
  
          public HeadersRequestImpl(byte[] hash, int count, boolean reverse, int step) {
              this.hash = hash;
              this.count = count;
              this.reverse = reverse;
              this.step = step;
          }
  
  
          private long start;
          private byte[] hash;
          private int count;
  
          private boolean reverse;
          private int step = 0;
  
          @Override
          public List<HeadersRequest> split(int maxCount) {
              if (this.hash != null) return Collections.<HeadersRequest>singletonList(this);
              List<HeadersRequest> ret = new ArrayList<>();
              int remaining = count;
              while(remaining > 0) {
                  int reqSize = min(maxCount, remaining);
                  ret.add(new HeadersRequestImpl(start, reqSize, reverse));
                  remaining -= reqSize;
                  start = reverse ? start - reqSize : start + reqSize;
              }
              return ret;
          }
  
          @Override
          public String toString() {
              return "HeadersRequest{" +
                      (hash == null ? "start=" + getStart() : "hash=" + Hex.toHexString(hash).substring(0, 8))+
                      ", count=" + getCount() +
                      ", reverse=" + isReverse() +
                      ", step=" + getStep() +
                      '}';
          }
  
          @Override
          public long getStart() {
              return start;
          }
  
          public long getEnd() { return getStart() + getCount(); }
  
          @Override
          public byte[] getHash() {
              return hash;
          }
  
          @Override
          public int getCount() {
              return count;
          }
  
          @Override
          public boolean isReverse() {
              return reverse;
          }
  
          @Override
          public int getStep() {
              return step;
          }
      }
  
      static class BlocksRequestImpl implements BlocksRequest {
          private List<BlockHeaderWrapper> blockHeaders = new ArrayList<>();
  
          public BlocksRequestImpl() {
          }
  
          public BlocksRequestImpl(List<BlockHeaderWrapper> blockHeaders) {
              this.blockHeaders = blockHeaders;
          }
  
          @Override
          public List<BlocksRequest> split(int count) {
              List<BlocksRequest> ret = new ArrayList<>();
              int start = 0;
              while(start < getBlockHeaders().size()) {
                  count = min(getBlockHeaders().size() - start, count);
                  ret.add(new BlocksRequestImpl(getBlockHeaders().subList(start, start + count)));
                  start += count;
              }
              return ret;
          }
  
          @Override
          public List<BlockHeaderWrapper> getBlockHeaders() {
              return blockHeaders;
          }
      }
  
      class HeaderElement {
          BlockHeaderWrapper header;
          Block block;
          boolean exported;
  
          public HeaderElement(BlockHeaderWrapper header) {
              this.header = header;
          }
  
          public HeaderElement getParent() {
              Map<ByteArrayWrapper, HeaderElement> genHeaders = headers.get(header.getNumber() - 1);
              if (genHeaders == null) return null;
              return genHeaders.get(new ByteArrayWrapper(header.getHeader().getParentHash()));
          }
  
          public List<HeaderElement> getChildren() {
              List<HeaderElement> ret = new ArrayList<>();
              Map<ByteArrayWrapper, HeaderElement> childGenHeaders = headers.get(header.getNumber() + 1);
              if (childGenHeaders != null) {
                  for (HeaderElement child : childGenHeaders.values()) {
                      if (Arrays.equals(child.header.getHeader().getParentHash(), header.getHash())) {
                          ret.add(child);
                      }
                  }
              }
              return ret;
          }
      }
  
      Map<Long, Map<ByteArrayWrapper, HeaderElement>> headers = new HashMap<>();
  
      long minNum = Integer.MAX_VALUE;
      long maxNum = 0;
      long darkZoneNum = 0;
      Long endBlockNumber = null;
  
      Random rnd = new Random(); // ;)
  
      public SyncQueueImpl(List<Block> initBlocks) {
          init(initBlocks);
      }
  
      public SyncQueueImpl(Blockchain bc) {
          Block bestBlock = bc.getBestBlock();
          long start = bestBlock.getNumber() - MAX_CHAIN_LEN;
          start = start < 0 ? 0 : start;
          List<Block> initBlocks = new ArrayList<>();
          for (long i = start; i <= bestBlock.getNumber(); i++) {
              initBlocks.add(bc.getBlockByNumber(i));
          }
          init(initBlocks);
      }
  
      /**
       * Init with blockchain and download until endBlockNumber (included)
       * @param bc                Blockchain
       * @param endBlockNumber    last block to download
       */
      public SyncQueueImpl(Blockchain bc, Long endBlockNumber) {
          this(bc);
          this.endBlockNumber = endBlockNumber;
      }
  
  
  
      private void init(List<Block> initBlocks) {
          if (initBlocks.size() < MAX_CHAIN_LEN && initBlocks.get(0).getNumber() != 0) {
              throw new RuntimeException("Queue should be initialized with a chain of at least " + MAX_CHAIN_LEN + " size or with the first genesis block");
          }
          for (Block block : initBlocks) {
              addHeaderPriv(new BlockHeaderWrapper(block.getHeader(), null));
              addBlock(block).exported = true;
          }
          darkZoneNum = initBlocks.get(0).getNumber();
      }
  
      private void putGenHeaders(long num, Map<ByteArrayWrapper, HeaderElement> genHeaders) {
          minNum = min(minNum, num);
          maxNum = Math.max(maxNum, num);
          headers.put(num, genHeaders);
      }
  
      List<HeaderElement> getLongestChain() {
          Map<ByteArrayWrapper, HeaderElement> lastValidatedGen = headers.get(darkZoneNum);
          assert lastValidatedGen.size() == 1;
          HeaderElement lastHeader = lastValidatedGen.values().iterator().next();
  
          Map<byte[], HeaderElement> chainedParents = new ByteArrayMap<>();
          chainedParents.put(lastHeader.header.getHash(), lastHeader);
  
          for(long curNum = darkZoneNum + 1; ; curNum++) {
              // keep track of blocks chained to lastHeader until no children
              Map<byte[], HeaderElement> chainedBlocks = new ByteArrayMap<>();
              Map<ByteArrayWrapper, HeaderElement> curLevel = headers.get(curNum);
              if (curLevel == null) break;
              for (HeaderElement element : curLevel.values()) {
                  if (chainedParents.containsKey(element.header.getHeader().getParentHash())) {
                      chainedBlocks.put(element.header.getHash(), element);
                  }
              }
              if (chainedBlocks.isEmpty()) break;
              chainedParents = chainedBlocks;
          }
  
          // reconstruct the chain back from the last block in the longest path
          List<HeaderElement> ret = new ArrayList<>();
          for (HeaderElement el = chainedParents.values().iterator().next(); el != lastHeader.getParent(); el = el.getParent()) {
              ret.add(0, el);
          }
          return ret;
      }
  
      private boolean hasGaps() {
          List<HeaderElement> longestChain = getLongestChain();
          return longestChain.get(longestChain.size() - 1).header.getNumber() < maxNum;
      }
  
      private void trimChain() {
          List<HeaderElement> longestChain = getLongestChain();
          if (longestChain.size() > MAX_CHAIN_LEN) {
              long newTrimNum = getLongestChain().get(longestChain.size() - MAX_CHAIN_LEN).header.getNumber();
              for (int i = 0; darkZoneNum < newTrimNum; darkZoneNum++, i++) {
                  ByteArrayWrapper wHash = new ByteArrayWrapper(longestChain.get(i).header.getHash());
                  putGenHeaders(darkZoneNum, Collections.singletonMap(wHash, longestChain.get(i)));
              }
              darkZoneNum--;
          }
      }
  
      private void trimExported() {
          for (; minNum < darkZoneNum; minNum++) {
              Map<ByteArrayWrapper, HeaderElement> genHeaders = headers.get(minNum);
              assert genHeaders.size() == 1;
              HeaderElement headerElement = genHeaders.values().iterator().next();
              if (headerElement.exported) {
                  headers.remove(minNum);
              } else {
                  break;
              }
          }
      }
  
      private boolean addHeader(BlockHeaderWrapper header) {
          long num = header.getNumber();
          if (num <= darkZoneNum || num > maxNum + MAX_CHAIN_LEN * 128) {
              // dropping too distant headers
              return false;
          }
          return addHeaderPriv(header);
      }
  
      private boolean addHeaderPriv(BlockHeaderWrapper header) {
          long num = header.getNumber();
          Map<ByteArrayWrapper, HeaderElement> genHeaders = headers.get(num);
          if (genHeaders == null) {
              genHeaders = new HashMap<>();
              putGenHeaders(num, genHeaders);
          }
          ByteArrayWrapper wHash = new ByteArrayWrapper(header.getHash());
          HeaderElement headerElement = genHeaders.get(wHash);
          if (headerElement != null) return false;
  
          headerElement = new HeaderElement(header);
          genHeaders.put(wHash, headerElement);
  
          return true;
      }
  
      @Override
      public synchronized List<HeadersRequest> requestHeaders(int maxSize, int maxRequests, int maxTotalHeaders) {
          return requestHeadersImpl(maxSize, maxRequests, maxTotalHeaders);
      }
  
      private List<HeadersRequest> requestHeadersImpl(int count, int maxRequests, int maxTotHeaderCount) {
          List<HeadersRequest> ret = new ArrayList<>();
  
          long startNumber;
          if (hasGaps()) {
              List<HeaderElement> longestChain = getLongestChain();
              startNumber = longestChain.get(longestChain.size() - 1).header.getNumber();
              boolean reverse = rnd.nextBoolean();
              ret.add(new HeadersRequestImpl(startNumber, MAX_CHAIN_LEN, reverse));
              startNumber += reverse ? 1 : MAX_CHAIN_LEN;
  //            if (maxNum - startNumber > 2000) return ret;
          } else {
  