|
|
|
@ -17,10 +17,11 @@ |
|
|
|
|
|
|
|
|
|
package org.hyperledger.besu.ethereum.eth.sync.backwardsync; |
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; |
|
|
|
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; |
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
|
|
|
|
import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; |
|
|
|
|
import static org.mockito.ArgumentMatchers.any; |
|
|
|
|
import static org.mockito.ArgumentMatchers.eq; |
|
|
|
|
import static org.mockito.Mockito.doReturn; |
|
|
|
|
import static org.mockito.Mockito.spy; |
|
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
@ -64,6 +65,7 @@ import org.junit.jupiter.api.BeforeEach; |
|
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
|
import org.junit.jupiter.api.extension.ExtendWith; |
|
|
|
|
import org.mockito.Answers; |
|
|
|
|
import org.mockito.ArgumentCaptor; |
|
|
|
|
import org.mockito.Mock; |
|
|
|
|
import org.mockito.Mockito; |
|
|
|
|
import org.mockito.Spy; |
|
|
|
@ -75,11 +77,12 @@ import org.mockito.quality.Strictness; |
|
|
|
|
@MockitoSettings(strictness = Strictness.LENIENT) |
|
|
|
|
public class BackwardSyncContextTest { |
|
|
|
|
|
|
|
|
|
public static final int REMOTE_HEIGHT = 50; |
|
|
|
|
public static final int LOCAL_HEIGHT = 25; |
|
|
|
|
public static final int REMOTE_HEIGHT = 50; |
|
|
|
|
public static final int UNCLE_HEIGHT = 25 - 3; |
|
|
|
|
|
|
|
|
|
public static final int NUM_OF_RETRIES = 100; |
|
|
|
|
public static final int TEST_MAX_BAD_CHAIN_EVENT_ENTRIES = 25; |
|
|
|
|
|
|
|
|
|
private BackwardSyncContext context; |
|
|
|
|
|
|
|
|
@ -173,7 +176,8 @@ public class BackwardSyncContextTest { |
|
|
|
|
ethContext, |
|
|
|
|
syncState, |
|
|
|
|
backwardChain, |
|
|
|
|
NUM_OF_RETRIES)); |
|
|
|
|
NUM_OF_RETRIES, |
|
|
|
|
TEST_MAX_BAD_CHAIN_EVENT_ENTRIES)); |
|
|
|
|
doReturn(true).when(context).isReady(); |
|
|
|
|
doReturn(2).when(context).getBatchSize(); |
|
|
|
|
} |
|
|
|
@ -347,6 +351,72 @@ public class BackwardSyncContextTest { |
|
|
|
|
block, Collections.emptyList(), List.of(childBlockHeader, grandChildBlockHeader)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
|
public void shouldEmitBadChainEventWithIncludedBlockHeadersLimitedToMaxBadChainEventsSize() { |
|
|
|
|
Block block = Mockito.mock(Block.class); |
|
|
|
|
BlockHeader blockHeader = Mockito.mock(BlockHeader.class); |
|
|
|
|
when(block.getHash()).thenReturn(Hash.fromHexStringLenient("0x42")); |
|
|
|
|
when(blockHeader.getHash()).thenReturn(Hash.fromHexStringLenient("0x42")); |
|
|
|
|
BadChainListener badChainListener = Mockito.mock(BadChainListener.class); |
|
|
|
|
context.subscribeBadChainListener(badChainListener); |
|
|
|
|
|
|
|
|
|
backwardChain.clear(); |
|
|
|
|
|
|
|
|
|
for (int i = REMOTE_HEIGHT; i >= 0; i--) { |
|
|
|
|
backwardChain.prependAncestorsHeader(remoteBlockchain.getBlockByNumber(i).get().getHeader()); |
|
|
|
|
} |
|
|
|
|
backwardChain.prependAncestorsHeader(blockHeader); |
|
|
|
|
|
|
|
|
|
doReturn(blockValidator).when(context).getBlockValidatorForBlock(any()); |
|
|
|
|
BlockProcessingResult result = new BlockProcessingResult("custom error"); |
|
|
|
|
doReturn(result).when(blockValidator).validateAndProcessBlock(any(), any(), any(), any()); |
|
|
|
|
|
|
|
|
|
assertThatThrownBy(() -> context.saveBlock(block)) |
|
|
|
|
.isInstanceOf(BackwardSyncException.class) |
|
|
|
|
.hasMessageContaining("custom error"); |
|
|
|
|
|
|
|
|
|
final ArgumentCaptor<List<BlockHeader>> badBlockHeaderDescendants = |
|
|
|
|
ArgumentCaptor.forClass(List.class); |
|
|
|
|
verify(badChainListener) |
|
|
|
|
.onBadChain(eq(block), eq(Collections.emptyList()), badBlockHeaderDescendants.capture()); |
|
|
|
|
assertThat(badBlockHeaderDescendants.getValue()).hasSize(TEST_MAX_BAD_CHAIN_EVENT_ENTRIES); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
|
@Test |
|
|
|
|
public void shouldEmitBadChainEventWithIncludedBlocksLimitedToMaxBadChainEventsSize() { |
|
|
|
|
Block block = Mockito.mock(Block.class); |
|
|
|
|
BlockHeader blockHeader = Mockito.mock(BlockHeader.class); |
|
|
|
|
when(block.getHash()).thenReturn(Hash.fromHexStringLenient("0x42")); |
|
|
|
|
when(blockHeader.getHash()).thenReturn(Hash.fromHexStringLenient("0x42")); |
|
|
|
|
BadChainListener badChainListener = Mockito.mock(BadChainListener.class); |
|
|
|
|
context.subscribeBadChainListener(badChainListener); |
|
|
|
|
|
|
|
|
|
backwardChain.clear(); |
|
|
|
|
for (int i = REMOTE_HEIGHT; i >= 0; i--) { |
|
|
|
|
backwardChain.prependAncestorsHeader(remoteBlockchain.getBlockByNumber(i).get().getHeader()); |
|
|
|
|
} |
|
|
|
|
backwardChain.prependAncestorsHeader(blockHeader); |
|
|
|
|
|
|
|
|
|
for (int i = REMOTE_HEIGHT; i >= 0; i--) { |
|
|
|
|
backwardChain.appendTrustedBlock(remoteBlockchain.getBlockByNumber(i).get()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
doReturn(blockValidator).when(context).getBlockValidatorForBlock(any()); |
|
|
|
|
BlockProcessingResult result = new BlockProcessingResult("custom error"); |
|
|
|
|
doReturn(result).when(blockValidator).validateAndProcessBlock(any(), any(), any(), any()); |
|
|
|
|
|
|
|
|
|
assertThatThrownBy(() -> context.saveBlock(block)) |
|
|
|
|
.isInstanceOf(BackwardSyncException.class) |
|
|
|
|
.hasMessageContaining("custom error"); |
|
|
|
|
|
|
|
|
|
final ArgumentCaptor<List<Block>> badBlockDescendants = ArgumentCaptor.forClass(List.class); |
|
|
|
|
verify(badChainListener) |
|
|
|
|
.onBadChain(eq(block), badBlockDescendants.capture(), eq(Collections.emptyList())); |
|
|
|
|
assertThat(badBlockDescendants.getValue()).hasSize(TEST_MAX_BAD_CHAIN_EVENT_ENTRIES); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
public void shouldFailAfterMaxNumberOfRetries() { |
|
|
|
|
doReturn(CompletableFuture.failedFuture(new Exception())) |
|
|
|
|