/*
* 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.TestUtils;
import org.ethereum.core.Block;
import org.ethereum.core.BlockHeader;
import org.ethereum.core.BlockHeaderWrapper;
import org.ethereum.db.ByteArrayWrapper;
import org.ethereum.util.FastByteComparisons;
import org.junit.Test;
import java.util.*;
/**
* Created by Anton Nashatyrev on 30.05.2016.
*/
public class SyncQueueImplTest {
byte[] peer0 = new byte[32];
private static final int DEFAULT_REQUEST_LEN = 192;
@Test
public void test1() {
List<Block> randomChain = TestUtils.getRandomChain(new byte[32], 0, 1024);
SyncQueueImpl syncQueue = new SyncQueueImpl(randomChain.subList(0, 32));
SyncQueueIfc.HeadersRequest headersRequest = syncQueue.requestHeaders(DEFAULT_REQUEST_LEN, 1, Integer.MAX_VALUE).iterator().next();
System.out.println(headersRequest);
syncQueue.addHeaders(createHeadersFromBlocks(TestUtils.getRandomChain(randomChain.get(16).getHash(), 17, 64), peer0));
syncQueue.addHeaders(createHeadersFromBlocks(randomChain.subList(32, 1024), peer0));
}
@Test
public void test2() {
List<Block> randomChain = TestUtils.getRandomChain(new byte[32], 0, 1024);
Peer[] peers = new Peer[10];
peers[0] = new Peer(randomChain);
for (int i = 1; i < peers.length; i++) {
peers[i] = new Peer(TestUtils.getRandomChain(TestUtils.randomBytes(32), 1, 1024));
}
}
@Test
public void testHeadersSplit() {
// 1, 2, 3, 4, 5
SyncQueueImpl.HeadersRequestImpl headersRequest = new SyncQueueImpl.HeadersRequestImpl(1, 5, false);
List<SyncQueueIfc.HeadersRequest> requests = headersRequest.split(2);
assert requests.size() == 3;
// 1, 2
assert requests.get(0).getStart() == 1;
assert requests.get(0).getCount() == 2;
// 3, 4
assert requests.get(1).getStart() == 3;
assert requests.get(1).getCount() == 2;
// 5
assert requests.get(2).getStart() == 5;
assert requests.get(2).getCount() == 1;
}
@Test
public void testReverseHeaders1() {
List<Block> randomChain = TestUtils.getRandomChain(new byte[32], 0, 699);
List<Block> randomChain1 = TestUtils.getRandomChain(new byte[32], 0, 699);
Peer[] peers = new Peer[]{new Peer(randomChain), new Peer(randomChain, false), new Peer(randomChain1)};
SyncQueueReverseImpl syncQueue = new SyncQueueReverseImpl(randomChain.get(randomChain.size() - 1).getHash(), true);
List<BlockHeaderWrapper> result = new ArrayList<>();
int peerIdx = 1;
Random rnd = new Random();
int cnt = 0;
while (cnt < 1000) {
System.out.println("Cnt: " + cnt++);
Collection<SyncQueueIfc.HeadersRequest> headersRequests = syncQueue.requestHeaders(20, 5, Integer.MAX_VALUE);
if (headersRequests == null) break;
for (SyncQueueIfc.HeadersRequest request : headersRequests) {
System.out.println("Req: " + request);
List<BlockHeader> headers = rnd.nextBoolean() ? peers[peerIdx].getHeaders(request)
: peers[peerIdx].getRandomHeaders(10);
// List<BlockHeader> headers = peers[0].getHeaders(request);
peerIdx = (peerIdx + 1) % peers.length;
List<BlockHeaderWrapper> ret = syncQueue.addHeaders(createHeadersFromHeaders(headers, peer0));
result.addAll(ret);
System.out.println("Result length: " + result.size());
}
}
List<BlockHeaderWrapper> extraHeaders =
syncQueue.addHeaders(createHeadersFromHeaders(peers[0].getRandomHeaders(10), peer0));
assert extraHeaders.isEmpty();
assert cnt != 1000;
assert result.size() == randomChain.size() - 1;
for (int i = 0; i < result.size() - 1; i++) {
assert Arrays.equals(result.get(i + 1).getHash(), result.get(i).getHeader().getParentHash());
}
assert Arrays.equals(randomChain.get(0).getHash(), result.get(result.size() - 1).getHeader().getParentHash());
}
@Test
public void testReverseHeaders2() {
List<Block> randomChain = TestUtils.getRandomChain(new byte[32], 0, 194);
Peer[] peers = new Peer[]{new Peer(randomChain), new Peer(randomChain)};
SyncQueueReverseImpl syncQueue = new SyncQueueReverseImpl(randomChain.get(randomChain.size() - 1).getHash(), true);
List<BlockHeaderWrapper> result = new ArrayList<>();
int peerIdx = 1;
int cnt = 0;
while (cnt < 100) {
System.out.println("Cnt: " + cnt++);
Collection<SyncQueueIfc.HeadersRequest> headersRequests = syncQueue.requestHeaders(192, 10, Integer.MAX_VALUE);
if (headersRequests == null) break;
for (SyncQueueIfc.HeadersRequest request : headersRequests) {
System.out.println("Req: " + request);
List<BlockHeader> headers = peers[peerIdx].getHeaders(request);
// Removing genesis header, which we will not get from real peers
Iterator<BlockHeader> it = headers.iterator();
while (it.hasNext()) {
if (FastByteComparisons.equal(it.next().getHash(), randomChain.get(0).getHash())) it.remove();
}
peerIdx = (peerIdx + 1) % 2;
List<BlockHeaderWrapper> ret = syncQueue.addHeaders(createHeadersFromHeaders(headers, peer0));
result.addAll(ret);
System.out.println("Result length: " + result.size());
}
}
assert cnt != 100;
assert result.size() == randomChain.size() - 1; // - genesis
for (int i = 0; i < result.size() - 1; i++) {
assert Arrays.equals(result.get(i + 1).getHash(), result.get(i).getHeader().getParentHash());
}
assert Arrays.equals(randomChain.get(0).getHash(), result.get(result.size() - 1).getHeader().getParentHash());
}
@Test
public void testLongLongestChain() {
List<Block> randomChain = TestUtils.getRandomAltChain(new byte[32], 0, 10500, 3);
SyncQueueImpl syncQueue = new SyncQueueImpl(randomChain);
assert syncQueue.getLongestChain().size() == 10500;
}
@Test
public void testWideLongestChain() {
List<Block> randomChain = TestUtils.getRandomAltChain(new byte[32], 0, 100, 100);
SyncQueueImpl syncQueue = new SyncQueueImpl(randomChain);
assert syncQueue.getLongestChain().size() == 100;
}
@Test
public void testGapedLongestChain() {
List<Block> randomChain = TestUtils.getRandomAltChain(new byte[32], 0, 100, 5);
Iterator<Block> it = randomChain.iterator();
while (it.hasNext()) {
if (it.next().getHeader().getNumber() == 15) it.remove();
}
SyncQueueImpl syncQueue = new SyncQueueImpl(randomChain);
assert syncQueue.getLongestChain().size() == 15; // 0 .. 14
}
@Test
public void testFirstBlockGapedLongestChain() {
List<Block> randomChain = TestUtils.getRandomAltChain(new byte[32], 0, 100, 5);
Iterator<Block> it = randomChain.iterator();
while (it.hasNext()) {
if (it.next().getHeader().getNumber() == 1) it.remove();
}
SyncQueueImpl syncQueue = new SyncQueueImpl(randomChain);
assert syncQueue.getLongestChain().size() == 1; // 0
}
@Test(expected = AssertionError.class)
public void testZeroBlockGapedLongestChain() {
List<Block> randomChain = TestUtils.getRandomAltChain(new byte[32], 0, 100, 5);
Iterator<Block> it = randomChain.iterator();
while (it.hasNext()) {
if (it.next().getHeader().getNumber() == 0) it.remove();
}
SyncQueueImpl syncQueue = new SyncQueueImpl(randomChain);
syncQueue.getLongestChain().size();
}
@Test
public void testNoParentGapeLongestChain() {
List<Block> randomChain = TestUtils.getRandomAltChain(new byte[32], 0, 100, 5);
// Moving #15 blocks to the end to be sure it didn't trick SyncQueue
Iterator<Block> it = randomChain.iterator();
List<Block> blockSaver = new ArrayList<>();
while (it.hasNext()) {
Block block = it.next();
if (block.getHeader().getNumber() == 15) {
blockSaver.add(block);
it.remove();
}
}
randomChain.addAll(blockSaver);
SyncQueueImpl syncQueue = new SyncQueueImpl(randomChain);
// We still have linked chain
assert syncQueue.getLongestChain().size() == 100;
List<Block> randomChain2 = TestUtils.getRandomAltChain(new byte[32], 0, 100, 5);
Iterator<Block> it2 = randomChain2.iterator();
List<Block> blockSaver2 = new ArrayList<>();
while (it2.hasNext()) {
Block block = it2.next();
if (block.getHeader().getNumber() == 15) {
blockSaver2.add(block);
}
}
// Removing #15 blocks
for (int i = 0; i < 5; ++i) {
randomChain.remove(randomChain.size() - 1);
}
// Adding wrong #15 blocks
assert blockSaver2.size() == 5;
randomChain.addAll(blockSaver2);
assert new SyncQueueImpl(randomChain).getLongestChain().size() == 15; // 0 .. 14
}
public void test2Impl(List<Block> mainChain, List<Block> initChain, Peer[] peers) {
List<Block> randomChain = TestUtils.getRandomChain(new byte[32], 0, 1024);
final Block[] maxExportedBlock = new Block[] {randomChain.get(31)};
final Map<ByteArrayWrapper, Block> exportedBlocks = new HashMap<>();
for (Block block : randomChain.subList(0, 32)) {
exportedBlocks.put(new ByteArrayWrapper(block.getHash()), block);
}
SyncQueueImpl syncQueue = new SyncQueueImpl(randomChain.subList(0, 32)) {
@Override
protected void exportNewBlock(Block block) {
exportedBlocks.put(new ByteArrayWrapper(block.getHash()), block);
if (!exportedBlocks.containsKey(new ByteArrayWrapper(block.getParentHash()))) {
throw new RuntimeException("No parent for " + block);
}
if (block.getNumber() > maxExportedBlock[0].getNumber()) {
maxExportedBlock[0] = block;
}
}
};
Random rnd = new Random();
int i = 0;
for (; i < 1000; i++) {
SyncQueueIfc.HeadersRequest headersRequest = syncQueue.requestHeaders(DEFAULT_REQUEST_LEN, 1, Integer.MAX_VALUE).iterator().next();
List<BlockHeader> headers = peers[rnd.nextInt(peers.length)].getHeaders(headersRequest.getStart(), headersRequest.getCount(), headersRequest.isReverse());
syncQueue.addHeaders(createHeadersFromHeaders(headers, peer0));
SyncQueueIfc.BlocksRequest blocksRequest = syncQueue.requestBlocks(rnd.nextInt(128 + 1));
List<Block> blocks = peers[rnd.nextInt(peers.length)].getBlocks(blocksRequest.getBlockHeaders());
syncQueue.addBlocks(blocks);
if (maxExportedBlock[0].getNumber() == randomChain.get(randomChain.size() - 1).getNumber()) {
break;
}
}
if (i == 1000) throw new RuntimeException("Exported only till block: " + maxExportedBlock[0]);
}
private static class Peer {
Map<ByteArrayWrapper, Block> blocks = new HashMap<>();
List<Block> chain;
boolean returnGenesis;
public Peer(List<Block> chain) {
this(chain, true);
}
public Peer(List<Block> chain, boolean returnGenesis) {
this.returnGenesis = returnGenesis;
this.chain = chain;
for (Block block : chain) {
blocks.put(new ByteArrayWrapper(block.getHash()), block);
}
}
public List<BlockHeader> getHeaders(long startBlockNum, int count, boolean reverse) {
return getHeaders(startBlockNum, count, reverse, 0);
}
public List<BlockHeader> getHeaders(SyncQueueIfc.HeadersRequest req) {
if (req.getHash() == null) {
return getHeaders(req.getStart(), req.getCount(), req.isReverse(), req.getStep());
} else {
Block block = blocks.get(new ByteArrayWrapper(req.getHash()));
if (block == null) return Collections.emptyList();
return getHeaders(block.getNumber(), req.getCount(), req.isReverse(), req.getStep());
}
}
public List<BlockHeader> getRandomHeaders(int count) {
List<BlockHeader> ret = new ArrayList<>();
Random rnd = new Random();
for (int i = 0; i < count; i++) {
ret.add(chain.get(rnd.nextInt(chain.size())).getHeader());
}
return ret;
}
public List<BlockHeader> getHeaders(long startBlockNum, int count, boolean reverse, int step) {
step = step == 0 ? 1 : step;
List<BlockHeader> ret = new ArrayList<>();
int i = (int) startBlockNum;
for(; count-- > 0 && i >= (returnGenesis ? 0 : 1) && i <= chain.get(chain.size() - 1).getNumber();
i += reverse ? -step : step) {
ret.add(chain.get(i).getHeader());
}
// step = step == 0 ? 1 : step;
//
// if (reverse) {
// startBlockNum = startBlockNum - (count - 1 ) * step;
// }
//
// startBlockNum = Math.max(startBlockNum, chain.get(0).getNumber());
// startBlockNum = Math.min(startBlockNum, chain.get(chain.size() - 1).getNumber());
// long endBlockNum = startBlockNum + (count - 1) * step;
// endBlockNum = Math.max(endBlockNum, chain.get(0).getNumber());
// endBlockNum = Math.min(endBlockNum, chain.get(chain.size() - 1).getNumber());
// List<BlockHeader> ret = new ArrayList<>();
// int startIdx = (int) (startBlockNum - chain.get(0).getNumber());
// for (int i = startIdx; i < startIdx + (endBlockNum - startBlockNum + 1); i+=step) {
// ret.add(chain.get(i).getHeader());
// }
return ret;
}
public List<Block> getBlocks(Collection<BlockHeaderWrapper> hashes) {
List<Block> ret = new ArrayList<>();
for (BlockHeaderWrapper hash : hashes) {
Block block = blocks.get(new ByteArrayWrapper(hash.getHash()));
if (block != null) ret.add(block);
}
return ret;
}
}
private List<BlockHeaderWrapper> createHeadersFromHeaders(List<BlockHeader> headers, byte[] peer) {
List<BlockHeaderWrapper> ret = new ArrayList<>();
for (BlockHeader header : headers) {
ret.add(new BlockHeaderWrapper(header, peer));
}
return ret;
}
private List<BlockHeaderWrapper> createHeadersFromBlocks(List<Block> blocks, byte[] peer) {
List<BlockHeaderWrapper> ret = new ArrayList<>();
for (Block block : blocks) {
ret.add(new BlockHeaderWrapper(block.getHeader(), peer));
}
return ret;
}
}