/*
* 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.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 {
startNumber = maxNum + 1;
}
while (ret.size() <= maxRequests && getHeadersCount() <= maxTotHeaderCount) {
HeadersRequestImpl nextReq = getNextReq(startNumber, count);
if (nextReq.getEnd() > minNum + maxTotHeaderCount) break;
ret.add(nextReq);
startNumber = nextReq.getEnd();
}
return ret;
}
private HeadersRequestImpl getNextReq(long startFrom, int maxCount) {
while(headers.containsKey(startFrom)) startFrom++;
if (endBlockNumber != null && maxCount > endBlockNumber - startFrom + 1) {
maxCount = (int) (endBlockNumber - startFrom + 1);
}
return new HeadersRequestImpl(startFrom, maxCount, false);
}
@Override
public synchronized List<BlockHeaderWrapper> addHeaders(Collection<BlockHeaderWrapper> headers) {
for (BlockHeaderWrapper header : headers) {
addHeader(header);
}
trimChain();
return null;
}
@Override
public synchronized int getHeadersCount() {
return (int) (maxNum - minNum);
}
@Override
public synchronized BlocksRequest requestBlocks(int maxSize) {
BlocksRequest ret = new BlocksRequestImpl();
outer:
for (long i = minNum; i <= maxNum; i++) {
Map<ByteArrayWrapper, HeaderElement> gen = headers.get(i);
if (gen != null) {
for (HeaderElement element : gen.values()) {
if (element.block == null) {
ret.getBlockHeaders().add(element.header);
if (ret.getBlockHeaders().size() >= maxSize) break outer;
}
}
}
}
return ret;
}
HeaderElement findHeaderElement(BlockHeader bh) {
Map<ByteArrayWrapper, HeaderElement> genHeaders = headers.get(bh.getNumber());
if (genHeaders == null) return null;
return genHeaders.get(new ByteArrayWrapper(bh.getHash()));
}
private HeaderElement addBlock(Block block) {
HeaderElement headerElement = findHeaderElement(block.getHeader());
if (headerElement != null) {
headerElement.block = block;
}
return headerElement;
}
@Override
public synchronized List<Block> addBlocks(Collection<Block> blocks) {
for (Block block : blocks) {
addBlock(block);
}
return exportBlocks();
}
private List<Block> exportBlocks() {
List<Block> ret = new ArrayList<>();
for (long i = minNum; i <= maxNum; i++) {
Map<ByteArrayWrapper, HeaderElement> gen = headers.get(i);
if (gen == null) break;
boolean hasAny = false;
for (HeaderElement element : gen.values()) {
HeaderElement parent = element.getParent();
if (element.block != null && (i == minNum || parent != null && parent.exported)) {
if (!element.exported) {
exportNewBlock(element.block);
ret.add(element.block);
element.exported = true;
}
hasAny = true;
}
}
if (!hasAny) break;
}
trimExported();
return ret;
}
protected void exportNewBlock(Block block) {
}
public synchronized List<Block> pollBlocks() {
return null;
}
interface Visitor<T> {
T visit(HeaderElement el, List<T> childrenRes);
}
class ChildVisitor<T> {
private Visitor<T> handler;
boolean downUp = true;
public ChildVisitor(Functional.Function<HeaderElement, List<T>> handler) {
// this.handler = handler;
}
public T traverse(HeaderElement el) {
List<T> childrenRet = new ArrayList<>();
for (HeaderElement child : el.getChildren()) {
T res = traverse(child);
childrenRet.add(res);
}
return handler.visit(el, childrenRet);
}
}
}