[PAN-2325] Fix IndexOutOfBoundsException in DetermineCommonAncestorTask (#1038)

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
mbaxter 6 years ago committed by GitHub
parent 0c8f771cec
commit 21cea1b9a2
  1. 13
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/Blockchain.java
  2. 13
      ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/BlockDataGenerator.java
  3. 2
      ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java
  4. 28
      ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTask.java
  5. 11
      ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/RespondingEthPeer.java
  6. 294
      ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java

@ -63,6 +63,19 @@ public interface Blockchain {
return new Block(header, body); return new Block(header, body);
} }
default Block getGenesisBlock() {
final Hash genesisHash =
getBlockHashByNumber(BlockHeader.GENESIS_BLOCK_NUMBER)
.orElseThrow(() -> new IllegalStateException("Missing genesis block."));
final BlockHeader header =
getBlockHeader(genesisHash)
.orElseThrow(() -> new IllegalStateException("Missing genesis block."));
final BlockBody body =
getBlockBody(genesisHash)
.orElseThrow(() -> new IllegalStateException("Missing genesis block."));
return new Block(header, body);
}
/** /**
* Checks whether the block corresponding to the given hash is on the canonical chain. * Checks whether the block corresponding to the given hash is on the canonical chain.
* *

@ -137,6 +137,19 @@ public class BlockDataGenerator {
return blockSequence(count, worldState, Collections.emptyList(), Collections.emptyList()); return blockSequence(count, worldState, Collections.emptyList(), Collections.emptyList());
} }
public List<Block> blockSequence(final Block previousBlock, final int count) {
final WorldStateArchive worldState = createInMemoryWorldStateArchive();
Hash parentHash = previousBlock.getHeader().getHash();
long blockNumber = previousBlock.getHeader().getNumber() + 1;
return blockSequence(
count,
blockNumber,
parentHash,
worldState,
Collections.emptyList(),
Collections.emptyList());
}
public List<Block> blockSequence( public List<Block> blockSequence(
final int count, final int count,
final WorldStateArchive worldStateArchive, final WorldStateArchive worldStateArchive,

@ -220,7 +220,7 @@ public class SynchronizerConfiguration {
private Range<Long> blockPropagationRange = Range.closed(-10L, 30L); private Range<Long> blockPropagationRange = Range.closed(-10L, 30L);
private long downloaderChangeTargetThresholdByHeight = 20L; private long downloaderChangeTargetThresholdByHeight = 20L;
private UInt256 downloaderChangeTargetThresholdByTd = UInt256.of(1_000_000_000L); private UInt256 downloaderChangeTargetThresholdByTd = UInt256.of(1_000_000_000L);
private int downloaderHeaderRequestSize = 10; private int downloaderHeaderRequestSize = 200;
private int downloaderCheckpointTimeoutsPermitted = 5; private int downloaderCheckpointTimeoutsPermitted = 5;
private int downloaderChainSegmentTimeoutsPermitted = 5; private int downloaderChainSegmentTimeoutsPermitted = 5;
private int downloaderChainSegmentSize = 200; private int downloaderChainSegmentSize = 200;

@ -31,11 +31,17 @@ import com.google.common.annotations.VisibleForTesting;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
public class DetermineCommonAncestorTask<C> extends AbstractEthTask<BlockHeader> { /**
* Finds the common ancestor with the given peer. It is assumed that the peer will at least share
* the same genesis block with this node. Running this task against a peer with a non-matching
* genesis block will result in undefined behavior: the task may complete exceptionally or in some
* cases this node's genesis block will be returned.
*/
public class DetermineCommonAncestorTask extends AbstractEthTask<BlockHeader> {
private static final Logger LOG = LogManager.getLogger(); private static final Logger LOG = LogManager.getLogger();
private final EthContext ethContext; private final EthContext ethContext;
private final ProtocolSchedule<C> protocolSchedule; private final ProtocolSchedule<?> protocolSchedule;
private final ProtocolContext<C> protocolContext; private final ProtocolContext<?> protocolContext;
private final EthPeer peer; private final EthPeer peer;
private final int headerRequestSize; private final int headerRequestSize;
private final MetricsSystem metricsSystem; private final MetricsSystem metricsSystem;
@ -46,8 +52,8 @@ public class DetermineCommonAncestorTask<C> extends AbstractEthTask<BlockHeader>
private boolean initialQuery = true; private boolean initialQuery = true;
private DetermineCommonAncestorTask( private DetermineCommonAncestorTask(
final ProtocolSchedule<C> protocolSchedule, final ProtocolSchedule<?> protocolSchedule,
final ProtocolContext<C> protocolContext, final ProtocolContext<?> protocolContext,
final EthContext ethContext, final EthContext ethContext,
final EthPeer peer, final EthPeer peer,
final int headerRequestSize, final int headerRequestSize,
@ -66,14 +72,14 @@ public class DetermineCommonAncestorTask<C> extends AbstractEthTask<BlockHeader>
protocolContext.getBlockchain().getBlockHeader(BlockHeader.GENESIS_BLOCK_NUMBER).get(); protocolContext.getBlockchain().getBlockHeader(BlockHeader.GENESIS_BLOCK_NUMBER).get();
} }
public static <C> DetermineCommonAncestorTask<C> create( public static DetermineCommonAncestorTask create(
final ProtocolSchedule<C> protocolSchedule, final ProtocolSchedule<?> protocolSchedule,
final ProtocolContext<C> protocolContext, final ProtocolContext<?> protocolContext,
final EthContext ethContext, final EthContext ethContext,
final EthPeer peer, final EthPeer peer,
final int headerRequestSize, final int headerRequestSize,
final MetricsSystem metricsSystem) { final MetricsSystem metricsSystem) {
return new DetermineCommonAncestorTask<>( return new DetermineCommonAncestorTask(
protocolSchedule, protocolContext, ethContext, peer, headerRequestSize, metricsSystem); protocolSchedule, protocolContext, ethContext, peer, headerRequestSize, metricsSystem);
} }
@ -144,6 +150,10 @@ public class DetermineCommonAncestorTask<C> extends AbstractEthTask<BlockHeader>
final AbstractPeerTask.PeerTaskResult<List<BlockHeader>> headersResult) { final AbstractPeerTask.PeerTaskResult<List<BlockHeader>> headersResult) {
initialQuery = false; initialQuery = false;
final List<BlockHeader> headers = headersResult.getResult(); final List<BlockHeader> headers = headersResult.getResult();
if (headers.isEmpty()) {
// Nothing to do
return CompletableFuture.completedFuture(null);
}
final OptionalInt maybeAncestorNumber = final OptionalInt maybeAncestorNumber =
BlockchainUtil.findHighestKnownBlockIndex(protocolContext.getBlockchain(), headers, false); BlockchainUtil.findHighestKnownBlockIndex(protocolContext.getBlockchain(), headers, false);

@ -32,6 +32,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage; import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage;
import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256; import tech.pegasys.pantheon.util.uint.UInt256;
@ -82,6 +83,16 @@ public class RespondingEthPeer {
respondOnce(responder, Arrays.asList(peers)); respondOnce(responder, Arrays.asList(peers));
} }
public boolean disconnect(final DisconnectReason reason) {
if (ethPeer.isDisconnected()) {
return false;
}
ethPeer.disconnect(reason);
ethProtocolManager.handleDisconnect(getPeerConnection(), reason, true);
return true;
}
public MockPeerConnection getPeerConnection() { public MockPeerConnection getPeerConnection() {
return peerConnection; return peerConnection;
} }

@ -12,6 +12,7 @@
*/ */
package tech.pegasys.pantheon.ethereum.eth.sync.tasks; package tech.pegasys.pantheon.ethereum.eth.sync.tasks;
import static com.google.common.base.Preconditions.checkArgument;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
@ -24,6 +25,7 @@ import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.create
import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive;
import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator; import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator;
@ -45,7 +47,6 @@ import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import tech.pegasys.pantheon.util.ExceptionUtils; import tech.pegasys.pantheon.util.ExceptionUtils;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -57,20 +58,20 @@ import org.junit.Test;
public class DetermineCommonAncestorTaskTest { public class DetermineCommonAncestorTaskTest {
private final ProtocolSchedule<Void> protocolSchedule = MainnetProtocolSchedule.create(); private final ProtocolSchedule<?> protocolSchedule = MainnetProtocolSchedule.create();
private final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); private final BlockDataGenerator blockDataGenerator = new BlockDataGenerator();
private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private final MetricsSystem metricsSystem = new NoOpMetricsSystem();
private MutableBlockchain localBlockchain;
private final int defaultHeaderRequestSize = 10; private final int defaultHeaderRequestSize = 10;
Block genesisBlock; private MutableBlockchain localBlockchain;
private Block localGenesisBlock;
private EthProtocolManager ethProtocolManager; private EthProtocolManager ethProtocolManager;
private EthContext ethContext; private EthContext ethContext;
private ProtocolContext<Void> protocolContext; private ProtocolContext<?> protocolContext;
@Before @Before
public void setup() { public void setup() {
genesisBlock = blockDataGenerator.genesisBlock(); localGenesisBlock = blockDataGenerator.genesisBlock();
localBlockchain = createInMemoryBlockchain(genesisBlock); localBlockchain = createInMemoryBlockchain(localGenesisBlock);
final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive();
ethProtocolManager = EthProtocolManagerTestUtil.create(localBlockchain, worldStateArchive); ethProtocolManager = EthProtocolManagerTestUtil.create(localBlockchain, worldStateArchive);
ethContext = ethProtocolManager.ethContext(); ethContext = ethProtocolManager.ethContext();
@ -78,36 +79,13 @@ public class DetermineCommonAncestorTaskTest {
} }
@Test @Test
public void shouldThrowExceptionNoCommonBlock() { public void shouldFailIfPeerDisconnects() {
// Populate local chain final Block block = blockDataGenerator.nextBlock(localBlockchain.getChainHeadBlock());
for (long i = 1; i <= 9; i++) { localBlockchain.appendBlock(block, blockDataGenerator.receipts(block));
final BlockDataGenerator.BlockOptions options00 =
new BlockDataGenerator.BlockOptions()
.setBlockNumber(i)
.setParentHash(localBlockchain.getBlockHashByNumber(i - 1).get());
final Block block00 = blockDataGenerator.block(options00);
final List<TransactionReceipt> receipts00 = blockDataGenerator.receipts(block00);
localBlockchain.appendBlock(block00, receipts00);
}
// Populate remote chain
final Block remoteGenesisBlock = blockDataGenerator.genesisBlock();
final MutableBlockchain remoteBlockchain = createInMemoryBlockchain(remoteGenesisBlock);
for (long i = 1; i <= 9; i++) {
final BlockDataGenerator.BlockOptions options01 =
new BlockDataGenerator.BlockOptions()
.setDifficulty(UInt256.ONE)
.setBlockNumber(i)
.setParentHash(remoteBlockchain.getBlockHashByNumber(i - 1).get());
final Block block01 = blockDataGenerator.block(options01);
final List<TransactionReceipt> receipts01 = blockDataGenerator.receipts(block01);
remoteBlockchain.appendBlock(block01, receipts01);
}
final RespondingEthPeer.Responder responder =
RespondingEthPeer.blockchainResponder(remoteBlockchain);
final RespondingEthPeer respondingEthPeer = final RespondingEthPeer respondingEthPeer =
EthProtocolManagerTestUtil.createPeer(ethProtocolManager); EthProtocolManagerTestUtil.createPeer(ethProtocolManager);
final EthTask<BlockHeader> task = final EthTask<BlockHeader> task =
DetermineCommonAncestorTask.create( DetermineCommonAncestorTask.create(
protocolSchedule, protocolSchedule,
@ -117,34 +95,32 @@ public class DetermineCommonAncestorTaskTest {
defaultHeaderRequestSize, defaultHeaderRequestSize,
metricsSystem); metricsSystem);
final CompletableFuture<BlockHeader> future = task.run();
respondingEthPeer.respondWhile(responder, () -> !future.isDone());
final AtomicReference<Throwable> failure = new AtomicReference<>(); final AtomicReference<Throwable> failure = new AtomicReference<>();
final CompletableFuture<BlockHeader> future = task.run();
future.whenComplete( future.whenComplete(
(response, error) -> { (response, error) -> {
failure.set(error); failure.set(error);
}); });
// Disconnect the target peer
respondingEthPeer.disconnect(DisconnectReason.CLIENT_QUITTING);
assertThat(failure.get()).isNotNull(); assertThat(failure.get()).isNotNull();
final Throwable error = ExceptionUtils.rootCause(failure.get()); final Throwable error = ExceptionUtils.rootCause(failure.get());
assertThat(error) assertThat(error).isInstanceOf(EthTaskException.class);
.isInstanceOf(IllegalStateException.class) assertThat(((EthTaskException) error).reason()).isEqualTo(FailureReason.PEER_DISCONNECTED);
.hasMessageContaining("No common ancestor.");
} }
@Test @Test
public void shouldFailIfPeerDisconnects() { public void shouldHandleEmptyResponses() {
final Block block = blockDataGenerator.nextBlock(localBlockchain.getChainHeadBlock()); final Blockchain remoteBlockchain = setupLocalAndRemoteChains(11, 11, 5);
localBlockchain.appendBlock(block, blockDataGenerator.receipts(block));
final RespondingEthPeer.Responder responder = final RespondingEthPeer.Responder emptyResponder = RespondingEthPeer.emptyResponder();
RespondingEthPeer.blockchainResponder(localBlockchain); final RespondingEthPeer.Responder fullResponder =
RespondingEthPeer.blockchainResponder(remoteBlockchain);
final RespondingEthPeer respondingEthPeer = final RespondingEthPeer respondingEthPeer =
EthProtocolManagerTestUtil.createPeer(ethProtocolManager); EthProtocolManagerTestUtil.createPeer(ethProtocolManager);
// Disconnect the target peer
respondingEthPeer.getEthPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL);
final EthTask<BlockHeader> task = final EthTask<BlockHeader> task =
DetermineCommonAncestorTask.create( DetermineCommonAncestorTask.create(
protocolSchedule, protocolSchedule,
@ -154,23 +130,23 @@ public class DetermineCommonAncestorTaskTest {
defaultHeaderRequestSize, defaultHeaderRequestSize,
metricsSystem); metricsSystem);
// Execute task and wait for response // Empty response should be handled without any error
final AtomicReference<Throwable> failure = new AtomicReference<>();
final CompletableFuture<BlockHeader> future = task.run(); final CompletableFuture<BlockHeader> future = task.run();
respondingEthPeer.respondWhile(responder, () -> !future.isDone()); respondingEthPeer.respond(emptyResponder);
future.whenComplete( assertThat(future).isNotDone();
(response, error) -> {
failure.set(error);
});
assertThat(failure.get()).isNotNull(); // Task should continue on and complete when valid responses are received
final Throwable error = ExceptionUtils.rootCause(failure.get()); // Execute task and wait for response
assertThat(error).isInstanceOf(EthTaskException.class); respondingEthPeer.respondWhile(fullResponder, () -> !future.isDone());
assertThat(((EthTaskException) error).reason()).isEqualTo(FailureReason.PEER_DISCONNECTED);
assertThat(future).isDone();
assertThat(future).isNotCompletedExceptionally();
final BlockHeader expectedResult = remoteBlockchain.getBlockHeader(4).get();
assertThat(future).isCompletedWithValue(expectedResult);
} }
@Test @Test
public void shouldCorrectlyCalculateSkipIntervalAndCount() { public void calculateSkipInterval() {
final long maximumPossibleCommonAncestorNumber = 100; final long maximumPossibleCommonAncestorNumber = 100;
final long minimumPossibleCommonAncestorNumber = 0; final long minimumPossibleCommonAncestorNumber = 0;
final int headerRequestSize = 10; final int headerRequestSize = 10;
@ -184,96 +160,16 @@ public class DetermineCommonAncestorTaskTest {
assertThat(skipInterval).isEqualTo(9); assertThat(skipInterval).isEqualTo(9);
} }
@Test
public void shouldGracefullyHandleExecutionsForNoCommonAncestor() {
// Populate local chain
for (long i = 1; i <= 99; i++) {
final BlockDataGenerator.BlockOptions options00 =
new BlockDataGenerator.BlockOptions()
.setBlockNumber(i)
.setParentHash(localBlockchain.getBlockHashByNumber(i - 1).get());
final Block block00 = blockDataGenerator.block(options00);
final List<TransactionReceipt> receipts00 = blockDataGenerator.receipts(block00);
localBlockchain.appendBlock(block00, receipts00);
}
// Populate remote chain
final Block remoteGenesisBlock = blockDataGenerator.genesisBlock();
final MutableBlockchain remoteBlockchain = createInMemoryBlockchain(remoteGenesisBlock);
for (long i = 1; i <= 99; i++) {
final BlockDataGenerator.BlockOptions options01 =
new BlockDataGenerator.BlockOptions()
.setDifficulty(UInt256.ONE)
.setBlockNumber(i)
.setParentHash(remoteBlockchain.getBlockHashByNumber(i - 1).get());
final Block block01 = blockDataGenerator.block(options01);
final List<TransactionReceipt> receipts01 = blockDataGenerator.receipts(block01);
remoteBlockchain.appendBlock(block01, receipts01);
}
final RespondingEthPeer.Responder responder =
RespondingEthPeer.blockchainResponder(remoteBlockchain);
final RespondingEthPeer respondingEthPeer =
EthProtocolManagerTestUtil.createPeer(ethProtocolManager);
final DetermineCommonAncestorTask<Void> task =
DetermineCommonAncestorTask.create(
protocolSchedule,
protocolContext,
ethContext,
respondingEthPeer.getEthPeer(),
defaultHeaderRequestSize,
metricsSystem);
final DetermineCommonAncestorTask<Void> spy = spy(task);
// Execute task
final CompletableFuture<BlockHeader> future = spy.run();
respondingEthPeer.respondWhile(responder, () -> !future.isDone());
final AtomicReference<BlockHeader> result = new AtomicReference<>();
future.whenComplete(
(response, error) -> {
result.set(response);
});
Assertions.assertThat(result.get().getHash())
.isEqualTo(MainnetBlockHashFunction.createHash(genesisBlock.getHeader()));
verify(spy, times(2)).requestHeaders();
}
@Test @Test
public void shouldIssueConsistentNumberOfRequestsToPeer() { public void shouldIssueConsistentNumberOfRequestsToPeer() {
// Populate local chain final Blockchain remoteBlockchain = setupLocalAndRemoteChains(101, 101, 1);
for (long i = 1; i <= 100; i++) {
final BlockDataGenerator.BlockOptions options00 =
new BlockDataGenerator.BlockOptions()
.setBlockNumber(i)
.setParentHash(localBlockchain.getBlockHashByNumber(i - 1).get());
final Block block00 = blockDataGenerator.block(options00);
final List<TransactionReceipt> receipts00 = blockDataGenerator.receipts(block00);
localBlockchain.appendBlock(block00, receipts00);
}
// Populate remote chain
final MutableBlockchain remoteBlockchain = createInMemoryBlockchain(genesisBlock);
for (long i = 1; i <= 100; i++) {
final BlockDataGenerator.BlockOptions options01 =
new BlockDataGenerator.BlockOptions()
.setDifficulty(UInt256.ONE)
.setBlockNumber(i)
.setParentHash(remoteBlockchain.getBlockHashByNumber(i - 1).get());
final Block block01 = blockDataGenerator.block(options01);
final List<TransactionReceipt> receipts01 = blockDataGenerator.receipts(block01);
remoteBlockchain.appendBlock(block01, receipts01);
}
final RespondingEthPeer.Responder responder = final RespondingEthPeer.Responder responder =
RespondingEthPeer.blockchainResponder(remoteBlockchain); RespondingEthPeer.blockchainResponder(remoteBlockchain);
final RespondingEthPeer respondingEthPeer = final RespondingEthPeer respondingEthPeer =
EthProtocolManagerTestUtil.createPeer(ethProtocolManager); EthProtocolManagerTestUtil.createPeer(ethProtocolManager);
final DetermineCommonAncestorTask<Void> task = final DetermineCommonAncestorTask task =
DetermineCommonAncestorTask.create( DetermineCommonAncestorTask.create(
protocolSchedule, protocolSchedule,
protocolContext, protocolContext,
@ -281,7 +177,7 @@ public class DetermineCommonAncestorTaskTest {
respondingEthPeer.getEthPeer(), respondingEthPeer.getEthPeer(),
defaultHeaderRequestSize, defaultHeaderRequestSize,
metricsSystem); metricsSystem);
final DetermineCommonAncestorTask<Void> spy = spy(task); final DetermineCommonAncestorTask spy = spy(task);
// Execute task // Execute task
final CompletableFuture<BlockHeader> future = spy.run(); final CompletableFuture<BlockHeader> future = spy.run();
@ -294,66 +190,30 @@ public class DetermineCommonAncestorTaskTest {
}); });
Assertions.assertThat(result.get().getHash()) Assertions.assertThat(result.get().getHash())
.isEqualTo(MainnetBlockHashFunction.createHash(genesisBlock.getHeader())); .isEqualTo(MainnetBlockHashFunction.createHash(localGenesisBlock.getHeader()));
verify(spy, times(3)).requestHeaders(); verify(spy, times(3)).requestHeaders();
} }
@Test @Test
public void shouldShortCircuitOnHeaderInInitialRequest() { public void shouldShortCircuitOnHeaderInInitialRequest() {
final MutableBlockchain remoteBlockchain = createInMemoryBlockchain(genesisBlock); final Blockchain remoteBlockchain = setupLocalAndRemoteChains(100, 100, 96);
final BlockHeader commonHeader = localBlockchain.getBlockHeader(95).get();
Block commonBlock = null;
// Populate common chain
for (long i = 1; i <= 95; i++) {
final BlockDataGenerator.BlockOptions options =
new BlockDataGenerator.BlockOptions()
.setBlockNumber(i)
.setParentHash(localBlockchain.getBlockHashByNumber(i - 1).get());
commonBlock = blockDataGenerator.block(options);
final List<TransactionReceipt> receipts = blockDataGenerator.receipts(commonBlock);
localBlockchain.appendBlock(commonBlock, receipts);
remoteBlockchain.appendBlock(commonBlock, receipts);
}
// Populate local chain
for (long i = 96; i <= 99; i++) {
final BlockDataGenerator.BlockOptions options00 =
new BlockDataGenerator.BlockOptions()
.setBlockNumber(i)
.setParentHash(localBlockchain.getBlockHashByNumber(i - 1).get());
final Block block00 = blockDataGenerator.block(options00);
final List<TransactionReceipt> receipts00 = blockDataGenerator.receipts(block00);
localBlockchain.appendBlock(block00, receipts00);
}
// Populate remote chain
for (long i = 96; i <= 99; i++) {
final BlockDataGenerator.BlockOptions options01 =
new BlockDataGenerator.BlockOptions()
.setDifficulty(UInt256.ONE)
.setBlockNumber(i)
.setParentHash(remoteBlockchain.getBlockHashByNumber(i - 1).get());
final Block block01 = blockDataGenerator.block(options01);
final List<TransactionReceipt> receipts01 = blockDataGenerator.receipts(block01);
remoteBlockchain.appendBlock(block01, receipts01);
}
final RespondingEthPeer.Responder responder = final RespondingEthPeer.Responder responder =
RespondingEthPeer.blockchainResponder(remoteBlockchain); RespondingEthPeer.blockchainResponder(remoteBlockchain);
final RespondingEthPeer respondingEthPeer = final RespondingEthPeer respondingEthPeer =
EthProtocolManagerTestUtil.createPeer(ethProtocolManager); EthProtocolManagerTestUtil.createPeer(ethProtocolManager);
final DetermineCommonAncestorTask<Void> task = final DetermineCommonAncestorTask task =
DetermineCommonAncestorTask.create( DetermineCommonAncestorTask.create(
protocolSchedule, protocolSchedule,
protocolContext, protocolContext,
ethContext, ethContext,
respondingEthPeer.getEthPeer(), respondingEthPeer.getEthPeer(),
defaultHeaderRequestSize, 10,
metricsSystem); metricsSystem);
final DetermineCommonAncestorTask<Void> spy = spy(task); final DetermineCommonAncestorTask spy = spy(task);
// Execute task // Execute task
final CompletableFuture<BlockHeader> future = spy.run(); final CompletableFuture<BlockHeader> future = spy.run();
@ -366,7 +226,7 @@ public class DetermineCommonAncestorTaskTest {
}); });
Assertions.assertThat(result.get().getHash()) Assertions.assertThat(result.get().getHash())
.isEqualTo(MainnetBlockHashFunction.createHash(commonBlock.getHeader())); .isEqualTo(MainnetBlockHashFunction.createHash(commonHeader));
verify(spy, times(1)).requestHeaders(); verify(spy, times(1)).requestHeaders();
} }
@ -387,11 +247,73 @@ public class DetermineCommonAncestorTaskTest {
metricsSystem); metricsSystem);
final CompletableFuture<BlockHeader> result = task.run(); final CompletableFuture<BlockHeader> result = task.run();
assertThat(result).isCompletedWithValue(genesisBlock.getHeader()); assertThat(result).isCompletedWithValue(localGenesisBlock.getHeader());
// Make sure we didn't ask for any headers // Make sure we didn't ask for any headers
verify(peer, times(0)).getHeadersByHash(any(), anyInt(), anyInt(), anyBoolean()); verify(peer, times(0)).getHeadersByHash(any(), anyInt(), anyInt(), anyBoolean());
verify(peer, times(0)).getHeadersByNumber(anyLong(), anyInt(), anyInt(), anyBoolean()); verify(peer, times(0)).getHeadersByNumber(anyLong(), anyInt(), anyInt(), anyBoolean());
verify(peer, times(0)).send(any()); verify(peer, times(0)).send(any());
} }
/**
* @param localBlockCount The number of local blocks to create. Highest block will be: {@code
* localBlockHeight} - 1.
* @param remoteBlockCount The number of remote blocks to create. Highest block will be: {@code
* remoteBlockCount} - 1.
* @param blocksInCommon The number of blocks shared between local and remote. If a common
* ancestor exists, its block number will be: {@code blocksInCommon} - 1
* @return
*/
private Blockchain setupLocalAndRemoteChains(
final int localBlockCount, final int remoteBlockCount, final int blocksInCommon) {
checkArgument(localBlockCount >= 1);
checkArgument(remoteBlockCount >= 1);
checkArgument(blocksInCommon >= 0);
checkArgument(blocksInCommon <= Math.min(localBlockCount, remoteBlockCount));
final Block remoteGenesis =
(blocksInCommon > 0) ? localGenesisBlock : blockDataGenerator.genesisBlock();
MutableBlockchain remoteChain = createInMemoryBlockchain(remoteGenesis);
// Build common chain
if (blocksInCommon > 1) {
List<Block> commonBlocks =
blockDataGenerator.blockSequence(remoteGenesis, blocksInCommon - 1);
for (Block commonBlock : commonBlocks) {
List<TransactionReceipt> receipts = blockDataGenerator.receipts(commonBlock);
localBlockchain.appendBlock(commonBlock, receipts);
remoteChain.appendBlock(commonBlock, receipts);
}
}
// Build divergent local blocks
if (localBlockCount > blocksInCommon) {
Block localChainHead = localBlockchain.getChainHeadBlock();
final int currentHeight =
Math.toIntExact(
localBlockchain.getChainHeadBlockNumber() - BlockHeader.GENESIS_BLOCK_NUMBER + 1);
List<Block> localBlocks =
blockDataGenerator.blockSequence(localChainHead, localBlockCount - currentHeight);
for (Block localBlock : localBlocks) {
List<TransactionReceipt> receipts = blockDataGenerator.receipts(localBlock);
localBlockchain.appendBlock(localBlock, receipts);
}
}
// Build divergent remote blocks
if (remoteBlockCount > blocksInCommon) {
Block remoteChainHead = remoteChain.getChainHeadBlock();
final int currentHeight =
Math.toIntExact(
remoteChain.getChainHeadBlockNumber() - BlockHeader.GENESIS_BLOCK_NUMBER + 1);
List<Block> remoteBlocks =
blockDataGenerator.blockSequence(remoteChainHead, remoteBlockCount - currentHeight);
for (Block remoteBlock : remoteBlocks) {
List<TransactionReceipt> receipts = blockDataGenerator.receipts(remoteBlock);
remoteChain.appendBlock(remoteBlock, receipts);
}
}
return remoteChain;
}
} }

Loading…
Cancel
Save