@ -16,10 +16,9 @@
package org.hyperledger.besu.cli.subcommands.storage ;
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.hyperledger.besu.ethereum.worldstate.DataStorageFormat.BONSAI ;
import static org.junit.jupiter.api.Assertions.assertArrayEquals ;
import static org.junit.jupiter.api.Assertions.assertEquals ;
import static org.junit.jupiter.api.Assertions.assertThrows ;
import static org.mockito.ArgumentMatchers.any ;
import static org.mockito.Mockito.when ;
@ -37,6 +36,7 @@ import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration;
import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration ;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem ;
import java.io.FileNotFoundException ;
import java.io.IOException ;
import java.nio.file.Files ;
import java.nio.file.Path ;
@ -58,13 +58,19 @@ class TrieLogHelperTest {
private static final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider ( ) ;
private static BonsaiWorldStateKeyValueStorage inMemoryWorldState ;
private TrieLogHelper trieLogHelper ;
@Mock private MutableBlockchain blockchain ;
private static class NonValidatingTrieLogHelper extends TrieLogHelper {
@Override
void validatePruneConfiguration ( final DataStorageConfiguration config ) { }
}
@Mock private MutableBlockchain blockchain ;
static BlockHeader blockHeader1 ;
static BlockHeader blockHeader2 ;
static BlockHeader blockHeader3 ;
static BlockHeader blockHeader4 ;
static BlockHeader blockHeader5 ;
@BeforeEach
@ -99,6 +105,8 @@ class TrieLogHelperTest {
. getTrieLogStorageTransaction ( )
. put ( blockHeader5 . getHash ( ) . toArrayUnsafe ( ) , createTrieLog ( blockHeader5 ) ) ;
updater . getTrieLogStorageTransaction ( ) . commit ( ) ;
trieLogHelper = new NonValidatingTrieLogHelper ( ) ;
}
private static byte [ ] createTrieLog ( final BlockHeader blockHeader ) {
@ -122,12 +130,11 @@ class TrieLogHelperTest {
DataStorageConfiguration dataStorageConfiguration =
ImmutableDataStorageConfiguration . builder ( )
. dataStorageFormat ( BONSAI )
. bonsaiMaxLayersToLoad ( 2 L)
. bonsaiMaxLayersToLoad ( 3 L)
. unstable (
ImmutableDataStorageConfiguration . Unstable . builder ( )
. bonsaiTrieLogRetentionThreshold ( 3 )
. build ( )
. withBonsaiTrieLogRetentionThreshold ( 3 ) )
. bonsaiLimitTrieLogsEnabled ( true )
. build ( ) )
. build ( ) ;
mockBlockchainBase ( ) ;
@ -136,73 +143,75 @@ class TrieLogHelperTest {
when ( blockchain . getBlockHeader ( 3 ) ) . thenReturn ( Optional . of ( blockHeader3 ) ) ;
// assert trie logs that will be pruned exist before prune call
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader1 ) ) ;
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader2 ) ) ;
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader3 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader1 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader2 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader3 ) ) ;
T rieLogHelper. prune ( dataStorageConfiguration , inMemoryWorldState , blockchain , dataDir ) ;
t rieLogHelper. prune ( dataStorageConfiguration , inMemoryWorldState , blockchain , dataDir ) ;
// assert pruned trie logs are not in the DB
assertEquals ( inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) , Optional . empty ( ) ) ;
assertEquals ( inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) , Optional . empty ( ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) ) . isEqualTo ( Optional . empty ( ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) ) . isEqualTo ( Optional . empty ( ) ) ;
// assert retained trie logs are in the DB
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader3 ) ) ;
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader4 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader4 ) ) ;
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader5 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader5 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader3 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader4 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader4 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader5 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader5 ) ) ;
}
@Test
public void cantPruneIfNoFinalizedIsFound ( final @TempDir Path dataDir ) {
public void canno tPruneIfNoFinalizedIsFound ( ) {
DataStorageConfiguration dataStorageConfiguration =
ImmutableDataStorageConfiguration . builder ( )
. dataStorageFormat ( BONSAI )
. bonsaiMaxLayersToLoad ( 2L )
. unstable (
ImmutableDataStorageConfiguration . Unstable . builder ( )
. bonsaiTrieLogRetentionThreshold ( 2 )
. build ( )
. withBonsaiTrieLogRetentionThreshold ( 2 ) )
. bonsaiLimitTrieLogsEnabled ( true )
. build ( ) )
. build ( ) ;
when ( blockchain . getChainHeadBlockNumber ( ) ) . thenReturn ( 5L ) ;
when ( blockchain . getFinalized ( ) ) . thenReturn ( Optional . empty ( ) ) ;
assertThrows (
RuntimeException . class ,
( ) - >
TrieLogHelper . prune ( dataStorageConfiguration , inMemoryWorldState , blockchain , dataDir ) ) ;
assertThatThrownBy (
( ) - >
trieLogHelper . prune (
dataStorageConfiguration , inMemoryWorldState , blockchain , Path . of ( "" ) ) )
. isInstanceOf ( RuntimeException . class )
. hasMessage ( "No finalized block present, can't safely run trie log prune" ) ;
}
@Test
public void cantPruneIfUserRetainsMoreLayerThanExistingChainLength ( final @TempDir Path dataDir ) {
public void canno tPruneIfUserRetainsMoreLayers ThanExistingChainLength ( ) {
DataStorageConfiguration dataStorageConfiguration =
ImmutableDataStorageConfiguration . builder ( )
. dataStorageFormat ( BONSAI )
. bonsaiMaxLayersToLoad ( 2 L)
. bonsaiMaxLayersToLoad ( 10 L)
. unstable (
ImmutableDataStorageConfiguration . Unstable . builder ( )
. bonsaiTrieLogRetentionThreshold ( 10 )
. build ( )
. withBonsaiTrieLogRetentionThreshold ( 10 ) )
. bonsaiLimitTrieLogsEnabled ( true )
. build ( ) )
. build ( ) ;
when ( blockchain . getChainHeadBlockNumber ( ) ) . thenReturn ( 5L ) ;
assertThrows (
IllegalArgumentException . class ,
( ) - >
TrieLogHelper . prune ( dataStorageConfiguration , inMemoryWorldState , blockchain , dataDir ) ) ;
assertThatThrownBy (
( ) - >
trieLogHelper . prune (
dataStorageConfiguration , inMemoryWorldState , blockchain , Path . of ( "" ) ) )
. isInstanceOf ( IllegalArgumentException . class )
. hasMessage ( "Trying to retain more trie logs than chain length (5), skipping pruning" ) ;
}
@Test
public void cantPruneIfUserRequiredFurtherThanFinalized ( final @TempDir Path dataDir ) {
public void canno tPruneIfUserRequiredFurtherThanFinalized ( final @TempDir Path dataDir ) {
DataStorageConfiguration dataStorageConfiguration =
ImmutableDataStorageConfiguration . builder ( )
@ -210,17 +219,84 @@ class TrieLogHelperTest {
. bonsaiMaxLayersToLoad ( 2L )
. unstable (
ImmutableDataStorageConfiguration . Unstable . builder ( )
. bonsaiTrieLogRetentionThreshold ( 2 )
. build ( )
. withBonsaiTrieLogRetentionThreshold ( 2 ) )
. bonsaiLimitTrieLogsEnabled ( true )
. build ( ) )
. build ( ) ;
mockBlockchainBase ( ) ;
assertThrows (
IllegalArgumentException . class ,
( ) - >
TrieLogHelper . prune ( dataStorageConfiguration , inMemoryWorldState , blockchain , dataDir ) ) ;
assertThatThrownBy (
( ) - >
trieLogHelper . prune (
dataStorageConfiguration , inMemoryWorldState , blockchain , dataDir ) )
. isInstanceOf ( IllegalArgumentException . class )
. hasMessage (
"Trying to prune more layers than the finalized block height, skipping pruning" ) ;
}
@Test
public void trieLogRetentionLimitShouldBeAboveMinimum ( ) {
DataStorageConfiguration dataStorageConfiguration =
ImmutableDataStorageConfiguration . builder ( )
. dataStorageFormat ( BONSAI )
. bonsaiMaxLayersToLoad ( 511L )
. unstable (
ImmutableDataStorageConfiguration . Unstable . builder ( )
. bonsaiLimitTrieLogsEnabled ( true )
. build ( ) )
. build ( ) ;
TrieLogHelper helper = new TrieLogHelper ( ) ;
assertThatThrownBy (
( ) - >
helper . prune ( dataStorageConfiguration , inMemoryWorldState , blockchain , Path . of ( "" ) ) )
. isInstanceOf ( RuntimeException . class )
. hasMessage ( "--bonsai-historical-block-limit minimum value is 512" ) ;
}
@Test
public void trieLogPruningWindowSizeShouldBePositive ( ) {
DataStorageConfiguration dataStorageConfiguration =
ImmutableDataStorageConfiguration . builder ( )
. dataStorageFormat ( BONSAI )
. bonsaiMaxLayersToLoad ( 512L )
. unstable (
ImmutableDataStorageConfiguration . Unstable . builder ( )
. bonsaiLimitTrieLogsEnabled ( true )
. bonsaiTrieLogPruningWindowSize ( 0 )
. build ( ) )
. build ( ) ;
TrieLogHelper helper = new TrieLogHelper ( ) ;
assertThatThrownBy (
( ) - >
helper . prune ( dataStorageConfiguration , inMemoryWorldState , blockchain , Path . of ( "" ) ) )
. isInstanceOf ( RuntimeException . class )
. hasMessage ( "--Xbonsai-trie-logs-pruning-window-size=0 must be greater than 0" ) ;
}
@Test
public void trieLogPruningWindowSizeShouldBeAboveRetentionLimit ( ) {
DataStorageConfiguration dataStorageConfiguration =
ImmutableDataStorageConfiguration . builder ( )
. dataStorageFormat ( BONSAI )
. bonsaiMaxLayersToLoad ( 512L )
. unstable (
ImmutableDataStorageConfiguration . Unstable . builder ( )
. bonsaiLimitTrieLogsEnabled ( true )
. bonsaiTrieLogPruningWindowSize ( 512 )
. build ( ) )
. build ( ) ;
TrieLogHelper helper = new TrieLogHelper ( ) ;
assertThatThrownBy (
( ) - >
helper . prune ( dataStorageConfiguration , inMemoryWorldState , blockchain , Path . of ( "" ) ) )
. isInstanceOf ( RuntimeException . class )
. hasMessage (
"--Xbonsai-trie-logs-pruning-window-size=512 must be greater than --bonsai-historical-block-limit=512" ) ;
}
@Test
@ -229,77 +305,81 @@ class TrieLogHelperTest {
DataStorageConfiguration dataStorageConfiguration =
ImmutableDataStorageConfiguration . builder ( )
. dataStorageFormat ( BONSAI )
. bonsaiMaxLayersToLoad ( 2 L)
. bonsaiMaxLayersToLoad ( 3 L)
. unstable (
ImmutableDataStorageConfiguration . Unstable . builder ( )
. bonsaiTrieLogRetentionThreshold ( 2 )
. build ( )
. withBonsaiTrieLogRetentionThreshold ( 2 ) )
. bonsaiLimitTrieLogsEnabled ( true )
. build ( ) )
. build ( ) ;
assertThrows (
RuntimeException . class ,
( ) - >
TrieLogHelper . prune (
dataStorageConfiguration ,
inMemoryWorldState ,
blockchain ,
dataDir . resolve ( "unknownPath" ) ) ) ;
mockBlockchainBase ( ) ;
when ( blockchain . getBlockHeader ( 5 ) ) . thenReturn ( Optional . of ( blockHeader5 ) ) ;
when ( blockchain . getBlockHeader ( 4 ) ) . thenReturn ( Optional . of ( blockHeader4 ) ) ;
when ( blockchain . getBlockHeader ( 3 ) ) . thenReturn ( Optional . of ( blockHeader3 ) ) ;
assertThatThrownBy (
( ) - >
trieLogHelper . prune (
dataStorageConfiguration ,
inMemoryWorldState ,
blockchain ,
dataDir . resolve ( "unknownPath" ) ) )
. isInstanceOf ( RuntimeException . class )
. hasCauseExactlyInstanceOf ( FileNotFoundException . class ) ;
// assert all trie logs are still in the DB
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader1 ) ) ;
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader2 ) ) ;
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader3 ) ) ;
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader4 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader4 ) ) ;
assertArrayEquals (
inMemoryWorldState . getTrieLog ( blockHeader5 . getHash ( ) ) . get ( ) , createTrieLog ( blockHeader5 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader1 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader2 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader3 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader4 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader4 ) ) ;
assertThat ( inMemoryWorldState . getTrieLog ( blockHeader5 . getHash ( ) ) . get ( ) )
. isEqualTo ( createTrieLog ( blockHeader5 ) ) ;
}
@Test
public void exportedTrieMatchesDbTrieLog ( final @TempDir Path dataDir ) throws IOException {
T rieLogHelper. exportTrieLog (
t rieLogHelper. exportTrieLog (
inMemoryWorldState ,
singletonList ( blockHeader1 . getHash ( ) ) ,
dataDir . resolve ( "trie-log-dump" ) ) ;
var trieLog =
TrieLogHelper . readTrieLogsAsRlpFromFile ( dataDir . resolve ( "trie-log-dump" ) . toString ( ) )
trieLogHelper
. readTrieLogsAsRlpFromFile ( dataDir . resolve ( "trie-log-dump" ) . toString ( ) )
. entrySet ( )
. stream ( )
. findFirst ( )
. get ( ) ;
assertArrayEquals ( trieLog . getKey ( ) , blockHeader1 . getHash ( ) . toArrayUnsafe ( ) ) ;
assertArrayEquals (
trieLog . getValue ( ) , inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ) ;
assertThat ( trieLog . getKey ( ) ) . isEqualTo ( blockHeader1 . getHash ( ) . toArrayUnsafe ( ) ) ;
assertThat ( trieLog . getValue ( ) )
. isEqualTo ( inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ) ;
}
@Test
public void exportedMultipleTriesMatchDbTrieLogs ( final @TempDir Path dataDir ) throws IOException {
T rieLogHelper. exportTrieLog (
t rieLogHelper. exportTrieLog (
inMemoryWorldState ,
List . of ( blockHeader1 . getHash ( ) , blockHeader2 . getHash ( ) , blockHeader3 . getHash ( ) ) ,
dataDir . resolve ( "trie-log-dump" ) ) ;
var trieLogs =
TrieLogHelper . readTrieLogsAsRlpFromFile ( dataDir . resolve ( "trie-log-dump" ) . toString ( ) )
trieLogHelper
. readTrieLogsAsRlpFromFile ( dataDir . resolve ( "trie-log-dump" ) . toString ( ) )
. entrySet ( )
. stream ( )
. collect ( Collectors . toMap ( e - > Bytes . wrap ( e . getKey ( ) ) , Map . Entry : : getValue ) ) ;
assertArrayEquals (
trieLogs . get ( blockHeader1 . getHash ( ) ) ,
inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ) ;
assertArrayEquals (
trieLogs . get ( blockHeader2 . getHash ( ) ) ,
inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) ) ;
assertArrayEquals (
trieLogs . get ( blockHeader3 . getHash ( ) ) ,
inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) ) ;
assertThat ( trieLogs . get ( blockHeader1 . getHash ( ) ) )
. isEqualTo ( inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ) ;
assertThat ( trieLogs . get ( blockHeader2 . getHash ( ) ) )
. isEqualTo ( inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) ) ;
assertThat ( trieLogs . get ( blockHeader3 . getHash ( ) ) )
. isEqualTo ( inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) ) ;
}
@Test
@ -309,22 +389,21 @@ class TrieLogHelperTest {
new BonsaiWorldStateKeyValueStorage (
tempStorageProvider , new NoOpMetricsSystem ( ) , DataStorageConfiguration . DEFAULT_CONFIG ) ;
T rieLogHelper. exportTrieLog (
t rieLogHelper. exportTrieLog (
inMemoryWorldState ,
singletonList ( blockHeader1 . getHash ( ) ) ,
dataDir . resolve ( "trie-log-dump" ) ) ;
var trieLog =
T rieLogHelper. readTrieLogsAsRlpFromFile ( dataDir . resolve ( "trie-log-dump" ) . toString ( ) ) ;
t rieLogHelper. readTrieLogsAsRlpFromFile ( dataDir . resolve ( "trie-log-dump" ) . toString ( ) ) ;
var updater = inMemoryWorldState2 . updater ( ) ;
trieLog . forEach ( ( k , v ) - > updater . getTrieLogStorageTransaction ( ) . put ( k , v ) ) ;
updater . getTrieLogStorageTransaction ( ) . commit ( ) ;
assertArrayEquals (
inMemoryWorldState2 . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ,
inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ) ;
assertThat ( inMemoryWorldState2 . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) )
. isEqualTo ( inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ) ;
}
@Test
@ -334,27 +413,24 @@ class TrieLogHelperTest {
new BonsaiWorldStateKeyValueStorage (
tempStorageProvider , new NoOpMetricsSystem ( ) , DataStorageConfiguration . DEFAULT_CONFIG ) ;
T rieLogHelper. exportTrieLog (
t rieLogHelper. exportTrieLog (
inMemoryWorldState ,
List . of ( blockHeader1 . getHash ( ) , blockHeader2 . getHash ( ) , blockHeader3 . getHash ( ) ) ,
dataDir . resolve ( "trie-log-dump" ) ) ;
var trieLog =
T rieLogHelper. readTrieLogsAsRlpFromFile ( dataDir . resolve ( "trie-log-dump" ) . toString ( ) ) ;
t rieLogHelper. readTrieLogsAsRlpFromFile ( dataDir . resolve ( "trie-log-dump" ) . toString ( ) ) ;
var updater = inMemoryWorldState2 . updater ( ) ;
trieLog . forEach ( ( k , v ) - > updater . getTrieLogStorageTransaction ( ) . put ( k , v ) ) ;
updater . getTrieLogStorageTransaction ( ) . commit ( ) ;
assertArrayEquals (
inMemoryWorldState2 . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ,
inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ) ;
assertArrayEquals (
inMemoryWorldState2 . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) ,
inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) ) ;
assertArrayEquals (
inMemoryWorldState2 . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) ,
inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) ) ;
assertThat ( inMemoryWorldState2 . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) )
. isEqualTo ( inMemoryWorldState . getTrieLog ( blockHeader1 . getHash ( ) ) . get ( ) ) ;
assertThat ( inMemoryWorldState2 . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) )
. isEqualTo ( inMemoryWorldState . getTrieLog ( blockHeader2 . getHash ( ) ) . get ( ) ) ;
assertThat ( inMemoryWorldState2 . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) )
. isEqualTo ( inMemoryWorldState . getTrieLog ( blockHeader3 . getHash ( ) ) . get ( ) ) ;
}
}