@ -14,6 +14,7 @@ package tech.pegasys.pantheon.ethereum.eth.sync.worldstate;
import static java.util.Collections.singletonList ;
import static org.assertj.core.api.Assertions.assertThat ;
import static org.assertj.core.api.Assertions.assertThatThrownBy ;
import static org.mockito.Mockito.mock ;
import static org.mockito.Mockito.verify ;
import static tech.pegasys.pantheon.ethereum.eth.sync.worldstate.NodeDataRequest.createAccountDataRequest ;
@ -27,9 +28,11 @@ import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage;
import tech.pegasys.pantheon.services.kvstore.InMemoryKeyValueStorage ;
import tech.pegasys.pantheon.services.tasks.CachingTaskCollection ;
import tech.pegasys.pantheon.services.tasks.InMemoryTaskQueue ;
import tech.pegasys.pantheon.testutil.TestClock ;
import tech.pegasys.pantheon.util.bytes.BytesValue ;
import java.util.concurrent.CompletableFuture ;
import java.util.concurrent.ExecutionException ;
import org.junit.Before ;
import org.junit.Test ;
@ -39,6 +42,7 @@ public class WorldDownloadStateTest {
private static final BytesValue ROOT_NODE_DATA = BytesValue . of ( 1 , 2 , 3 , 4 ) ;
private static final Hash ROOT_NODE_HASH = Hash . hash ( ROOT_NODE_DATA ) ;
private static final int MAX_REQUESTS_WITHOUT_PROGRESS = 10 ;
private static final long MIN_MILLIS_BEFORE_STALLING = 50_000 ;
private final WorldStateStorage worldStateStorage =
new KeyValueStorageWorldStateStorage ( new InMemoryKeyValueStorage ( ) ) ;
@ -50,8 +54,10 @@ public class WorldDownloadStateTest {
private final WorldStateDownloadProcess worldStateDownloadProcess =
mock ( WorldStateDownloadProcess . class ) ;
private final TestClock clock = new TestClock ( ) ;
private final WorldDownloadState downloadState =
new WorldDownloadState ( pendingRequests , MAX_REQUESTS_WITHOUT_PROGRESS ) ;
new WorldDownloadState (
pendingRequests , MAX_REQUESTS_WITHOUT_PROGRESS , MIN_MILLIS_BEFORE_STALLING , clock ) ;
private final CompletableFuture < Void > future = downloadState . getDownloadFuture ( ) ;
@ -121,6 +127,7 @@ public class WorldDownloadStateTest {
downloadState . requestComplete ( false ) ;
downloadState . requestComplete ( true ) ;
clock . stepMillis ( MIN_MILLIS_BEFORE_STALLING + 1 ) ;
for ( int i = 0 ; i < MAX_REQUESTS_WITHOUT_PROGRESS - 1 ; i + + ) {
downloadState . requestComplete ( false ) ;
@ -128,11 +135,50 @@ public class WorldDownloadStateTest {
}
downloadState . requestComplete ( false ) ;
assertThat ( downloadState . getDownloadFuture ( ) ) . isCompletedExceptionally ( ) ;
assertWorldStateStalled ( downloadState ) ;
}
@Test
public void shouldNotAddRequestsAfterDownloadIsStalled ( ) {
public void shouldNotBeStalledWhenMaxRequestsReachedUntilMinimumTimeAlsoReached ( ) {
for ( int i = 0 ; i < MAX_REQUESTS_WITHOUT_PROGRESS ; i + + ) {
downloadState . requestComplete ( false ) ;
assertThat ( downloadState . getDownloadFuture ( ) ) . isNotDone ( ) ;
}
// Exceeding the requests without progress limit doesn't trigger stalled state
downloadState . requestComplete ( false ) ;
assertThat ( downloadState . getDownloadFuture ( ) ) . isNotDone ( ) ;
// Until the minimum time has elapsed, then the next request with no progress marks as stalled
clock . stepMillis ( MIN_MILLIS_BEFORE_STALLING + 1 ) ;
downloadState . requestComplete ( false ) ;
assertWorldStateStalled ( downloadState ) ;
}
@Test
public void shouldNotBeStalledIfMinimumTimeIsReachedButMaximumRequestsIsNot ( ) {
clock . stepMillis ( MIN_MILLIS_BEFORE_STALLING + 1 ) ;
downloadState . requestComplete ( false ) ;
assertThat ( downloadState . getDownloadFuture ( ) ) . isNotDone ( ) ;
}
@Test
public void shouldResetTimeSinceProgressWhenProgressIsMade ( ) {
// Enough time has progressed but the next request makes progress so we are not stalled.
clock . stepMillis ( MIN_MILLIS_BEFORE_STALLING + 1 ) ;
downloadState . requestComplete ( true ) ;
assertThat ( downloadState . getDownloadFuture ( ) ) . isNotDone ( ) ;
// We then reach the max number of requests without progress but the timer should have reset
for ( int i = 0 ; i < MAX_REQUESTS_WITHOUT_PROGRESS ; i + + ) {
downloadState . requestComplete ( false ) ;
assertThat ( downloadState . getDownloadFuture ( ) ) . isNotDone ( ) ;
}
assertThat ( downloadState . getDownloadFuture ( ) ) . isNotDone ( ) ;
}
@Test
public void shouldNotAddRequestsAfterDownloadIsCompleted ( ) {
downloadState . checkCompletion ( worldStateStorage , header ) ;
downloadState . enqueueRequests ( singletonList ( createAccountDataRequest ( Hash . EMPTY_TRIE_HASH ) ) ) ;
@ -140,4 +186,12 @@ public class WorldDownloadStateTest {
assertThat ( pendingRequests . isEmpty ( ) ) . isTrue ( ) ;
}
private void assertWorldStateStalled ( final WorldDownloadState state ) {
final CompletableFuture < Void > future = state . getDownloadFuture ( ) ;
assertThat ( future ) . isCompletedExceptionally ( ) ;
assertThatThrownBy ( future : : get )
. isInstanceOf ( ExecutionException . class )
. hasRootCauseInstanceOf ( StalledDownloadException . class ) ;
}
}