@ -23,19 +23,25 @@ import org.hyperledger.besu.ethereum.trie.bonsai.cache.CachedWorldStorageManager
import org.hyperledger.besu.ethereum.trie.bonsai.cache.NoOpCachedWorldStorageManager ;
import org.hyperledger.besu.ethereum.trie.bonsai.storage.BonsaiPreImageProxy ;
import org.hyperledger.besu.ethereum.trie.bonsai.storage.BonsaiWorldStateKeyValueStorage ;
import org.hyperledger.besu.ethereum.trie.bonsai.trielog.NoOpTrieLogManager ;
import org.hyperledger.besu.ethereum.trie.bonsai.storage.BonsaiWorldStateLayerStorage ;
import org.hyperledger.besu.ethereum.trie.bonsai.trielog.TrieLogAddedEvent ;
import org.hyperledger.besu.ethereum.trie.bonsai.trielog.TrieLogManager ;
import org.hyperledger.besu.ethereum.trie.bonsai.worldview.BonsaiWorldState ;
import org.hyperledger.besu.ethereum.trie.bonsai.worldview.BonsaiWorldStateUpdateAccumulator ;
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration ;
import org.hyperledger.besu.evm.internal.EvmConfiguration ;
import org.hyperledger.besu.evm.worldstate.WorldUpdater ;
import org.hyperledger.besu.metrics.ObservableMetricsSystem ;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem ;
import org.hyperledger.besu.plugin.services.trielogs.TrieLog ;
import java.util.Map ;
import java.util.Optional ;
import java.util.stream.Stream ;
import com.fasterxml.jackson.annotation.JsonCreator ;
import com.google.common.cache.Cache ;
import com.google.common.cache.CacheBuilder ;
import org.apache.tuweni.bytes.Bytes ;
import org.apache.tuweni.bytes.Bytes32 ;
@ -98,6 +104,99 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState
// The test harness validates the root hash, no need to validate in-line for reference test
}
@Override
public void processExtraStateStorageFormatValidation ( final BlockHeader blockHeader ) {
if ( blockHeader ! = null ) {
final Hash parentStateRoot = getWorldStateRootHash ( ) ;
final BonsaiReferenceTestUpdateAccumulator originalUpdater =
( ( BonsaiReferenceTestUpdateAccumulator ) updater ( ) ) . createDetachedAccumulator ( ) ;
// validate trielog generation with persisted state
validateStateRolling ( parentStateRoot , originalUpdater , blockHeader , false ) ;
// validate trielog generation with frozen state
validateStateRolling ( parentStateRoot , originalUpdater , blockHeader , true ) ;
}
}
/ * *
* TrieLog is an important part of Bonsai , so it ' s important to verify the generation of the
* TrieLog by performing rollbacks and rollforwards .
*
* @param blockHeader header of the block to import
* /
private void validateStateRolling (
final Hash parentStateRoot ,
final BonsaiReferenceTestUpdateAccumulator originalUpdater ,
final BlockHeader blockHeader ,
final boolean isFrozenState ) {
// With Bonsai, a TrieLog is generated when the state is persisted. Therefore, we generate the
// TrieLog by triggering a state persist in order to closely match the real case scenario.
generateTrieLogFromState ( blockHeader , originalUpdater , isFrozenState ) ;
final TrieLog trieLogFromFrozenState =
trieLogManager
. getTrieLogLayer ( blockHeader . getBlockHash ( ) )
. orElseThrow ( ( ) - > new RuntimeException ( "trielog not found during test" ) ) ;
// trying rollback rollfoward with frozen state
validateTrieLog ( parentStateRoot , blockHeader , trieLogFromFrozenState ) ;
}
private void validateTrieLog (
final Hash parentStateRoot , final BlockHeader blockHeader , final TrieLog trieLog ) {
try ( var bonsaiWorldState = createBonsaiWorldState ( false ) ) {
BonsaiWorldStateUpdateAccumulator updaterForState =
( BonsaiWorldStateUpdateAccumulator ) bonsaiWorldState . updater ( ) ;
updaterForState . rollForward ( trieLog ) ;
updaterForState . commit ( ) ;
bonsaiWorldState . persist ( blockHeader ) ;
Hash generatedRootHash = bonsaiWorldState . rootHash ( ) ;
if ( ! bonsaiWorldState . rootHash ( ) . equals ( blockHeader . getStateRoot ( ) ) ) {
throw new RuntimeException (
"state root becomes invalid following a rollForward %s != %s"
. formatted ( blockHeader . getStateRoot ( ) , generatedRootHash ) ) ;
}
updaterForState = ( BonsaiWorldStateUpdateAccumulator ) bonsaiWorldState . updater ( ) ;
updaterForState . rollBack ( trieLog ) ;
updaterForState . commit ( ) ;
bonsaiWorldState . persist ( null ) ;
generatedRootHash = bonsaiWorldState . rootHash ( ) ;
if ( ! bonsaiWorldState . rootHash ( ) . equals ( parentStateRoot ) ) {
throw new RuntimeException (
"state root becomes invalid following a rollBackward %s != %s"
. formatted ( parentStateRoot , generatedRootHash ) ) ;
}
}
}
private void generateTrieLogFromState (
final BlockHeader blockHeader ,
final BonsaiReferenceTestUpdateAccumulator originalUpdater ,
final boolean isFrozen ) {
// generate trielog
BonsaiReferenceTestUpdateAccumulator updaterForState =
originalUpdater . createDetachedAccumulator ( ) ;
try ( var bonsaiWorldState = createBonsaiWorldState ( isFrozen ) ) {
bonsaiWorldState . setAccumulator ( updaterForState ) ;
updaterForState . commit ( ) ;
bonsaiWorldState . persist ( blockHeader ) ;
}
}
private BonsaiWorldState createBonsaiWorldState ( final boolean isFrozen ) {
BonsaiWorldState bonsaiWorldState =
new BonsaiWorldState (
new BonsaiWorldStateLayerStorage ( worldStateStorage ) ,
cachedMerkleTrieLoader ,
cachedWorldStorageManager ,
trieLogManager ,
evmConfiguration ) ;
if ( isFrozen ) {
bonsaiWorldState . freeze ( ) ; // freeze state
}
return bonsaiWorldState ;
}
@JsonCreator
public static BonsaiReferenceTestWorldState create (
final Map < String , ReferenceTestWorldState . AccountMock > accounts ) {
@ -110,7 +209,7 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState
final EvmConfiguration evmConfiguration ) {
final ObservableMetricsSystem metricsSystem = new NoOpMetricsSystem ( ) ;
final CachedMerkleTrieLoader cachedMerkleTrieLoader = new CachedMerkleTrieLoader ( metricsSystem ) ;
final TrieLogManager trieLogManager = new NoOp TrieLogManager( ) ;
final TrieLogManager trieLogManager = new ReferenceTestsInMemory TrieLogManager( ) ;
final BonsaiPreImageProxy preImageProxy =
new BonsaiPreImageProxy . BonsaiReferenceTestPreImageProxy ( ) ;
@ -149,6 +248,40 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState
return this . refTestStorage . streamAccounts ( this , startKeyHash , limit ) ;
}
static class ReferenceTestsInMemoryTrieLogManager extends TrieLogManager {
private final Cache < Hash , byte [ ] > trieLogCache =
CacheBuilder . newBuilder ( ) . maximumSize ( 5 ) . build ( ) ;
public ReferenceTestsInMemoryTrieLogManager ( ) {
super ( null , null , 0 , null ) ;
}
@Override
public synchronized void saveTrieLog (
final BonsaiWorldStateUpdateAccumulator localUpdater ,
final Hash forWorldStateRootHash ,
final BlockHeader forBlockHeader ,
final BonsaiWorldState forWorldState ) {
// notify trie log added observers, synchronously
TrieLog trieLog = trieLogFactory . create ( localUpdater , forBlockHeader ) ;
trieLogCache . put ( forBlockHeader . getHash ( ) , trieLogFactory . serialize ( trieLog ) ) ;
trieLogObservers . forEach ( o - > o . onTrieLogAdded ( new TrieLogAddedEvent ( trieLog ) ) ) ;
}
@Override
public long getMaxLayersToLoad ( ) {
return 0 ;
}
@Override
public Optional < TrieLog > getTrieLogLayer ( final Hash blockHash ) {
final byte [ ] trielog = trieLogCache . getIfPresent ( blockHash ) ;
trieLogCache . invalidate ( blockHash ) ; // remove trielog from the cache
return Optional . ofNullable ( trieLogFactory . deserialize ( trielog ) ) ;
}
}
@Override
protected Hash hashAndSavePreImage ( final Bytes value ) {
// by default do not save has preImages