Skip to content

Commit

Permalink
Journaled world state (#6023)
Browse files Browse the repository at this point in the history
Introduce a new Journaled World State Updater. Within a transaction it
keeps one copy of account and storage state, restoring previous 
revisions on reverts and exceptional halts. This updater only supports 
post-merge semantics with regard to empty accounts, namely that they do
not exist in world state.

Adds an EvmConfiguration option for stacked vs journaled updater, and
wire it in where needed. The staked updater is default mode, which is
the current behavior prior to this patch.

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
  • Loading branch information
shemnon authored Oct 31, 2023
1 parent 84dee29 commit 094c841
Show file tree
Hide file tree
Showing 65 changed files with 1,598 additions and 390 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3433,6 +3433,7 @@ private String generateConfigurationOverview() {
}

builder.setTxPoolImplementation(buildTransactionPoolConfiguration().getTxPoolImplementation());
builder.setWorldStateUpdateMode(unstableEvmOptions.toDomainObject().worldUpdaterMode());

builder.setPluginContext(besuComponent.getBesuPluginContext());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.hyperledger.besu.BesuInfo;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.services.BesuPluginContextImpl;
import org.hyperledger.besu.util.log.FramedLogMessage;
import org.hyperledger.besu.util.platform.PlatformDetector;
Expand Down Expand Up @@ -50,6 +51,7 @@ public class ConfigurationOverviewBuilder {
private String engineJwtFilePath;
private boolean isHighSpec = false;
private TransactionPoolConfiguration.Implementation txPoolImplementation;
private EvmConfiguration.WorldUpdaterMode worldStateUpdateMode;
private Map<String, String> environment;
private BesuPluginContextImpl besuPluginContext;

Expand Down Expand Up @@ -181,6 +183,18 @@ public ConfigurationOverviewBuilder setTxPoolImplementation(
return this;
}

/**
* Sets the world state updater mode
*
* @param worldStateUpdateMode the world state updater mode
* @return the builder
*/
public ConfigurationOverviewBuilder setWorldStateUpdateMode(
final EvmConfiguration.WorldUpdaterMode worldStateUpdateMode) {
this.worldStateUpdateMode = worldStateUpdateMode;
return this;
}

/**
* Sets the engine jwt file path.
*
Expand Down Expand Up @@ -257,6 +271,7 @@ public String build() {
}

lines.add("Using " + txPoolImplementation + " transaction pool implementation");
lines.add("Using " + worldStateUpdateMode + " worldstate update mode");

lines.add("");
lines.add("Host:");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import org.hyperledger.besu.cli.options.CLIOptions;
import org.hyperledger.besu.evm.internal.EvmConfiguration;

import java.util.Arrays;
import java.util.List;

import picocli.CommandLine;
Expand All @@ -29,6 +28,9 @@ public class EvmOptions implements CLIOptions<EvmConfiguration> {
/** The constant JUMPDEST_CACHE_WEIGHT. */
public static final String JUMPDEST_CACHE_WEIGHT = "--Xevm-jumpdest-cache-weight-kb";

/** The constant WORLDSTATE_UPDATE_MODE. */
public static final String WORLDSTATE_UPDATE_MODE = "--Xevm-worldstate-update-mode";

/**
* Create evm options.
*
Expand All @@ -51,13 +53,24 @@ public static EvmOptions create() {
private Long jumpDestCacheWeightKilobytes =
32_000L; // 10k contracts, (25k max contract size / 8 bit) + 32byte hash

@CommandLine.Option(
names = {WORLDSTATE_UPDATE_MODE},
description = "How to handle worldstate updates within a transaction",
fallbackValue = "STACKED",
defaultValue = "STACKED",
hidden = true,
arity = "1")
private EvmConfiguration.WorldUpdaterMode worldstateUpdateMode =
EvmConfiguration.WorldUpdaterMode
.STACKED; // Stacked Updater. Years of battle tested correctness.

@Override
public EvmConfiguration toDomainObject() {
return new EvmConfiguration(jumpDestCacheWeightKilobytes);
return new EvmConfiguration(jumpDestCacheWeightKilobytes, worldstateUpdateMode);
}

@Override
public List<String> getCLIOptions() {
return Arrays.asList(JUMPDEST_CACHE_WEIGHT);
return List.of(JUMPDEST_CACHE_WEIGHT, WORLDSTATE_UPDATE_MODE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -632,10 +632,7 @@ public BesuController build() {
if (chainPrunerConfiguration.getChainPruningEnabled()) {
protocolContext
.safeConsensusContext(MergeContext.class)
.ifPresent(
mergeContext -> {
mergeContext.setIsChainPruningEnabled(true);
});
.ifPresent(mergeContext -> mergeContext.setIsChainPruningEnabled(true));
final ChainDataPruner chainDataPruner = createChainPruner(blockchainStorage);
blockchain.observeBlockAdded(chainDataPruner);
LOG.info(
Expand Down Expand Up @@ -776,7 +773,6 @@ public BesuController build() {

final SubProtocolConfiguration subProtocolConfiguration =
createSubProtocolConfiguration(ethProtocolManager, maybeSnapProtocolManager);
;

final JsonRpcMethods additionalJsonRpcMethodFactory =
createAdditionalJsonRpcMethodFactory(protocolContext);
Expand Down Expand Up @@ -831,24 +827,21 @@ protected Synchronizer createSynchronizer(
final EthProtocolManager ethProtocolManager,
final PivotBlockSelector pivotBlockSelector) {

final DefaultSynchronizer toUse =
new DefaultSynchronizer(
syncConfig,
protocolSchedule,
protocolContext,
worldStateStorage,
ethProtocolManager.getBlockBroadcaster(),
maybePruner,
ethContext,
syncState,
dataDirectory,
storageProvider,
clock,
metricsSystem,
getFullSyncTerminationCondition(protocolContext.getBlockchain()),
pivotBlockSelector);

return toUse;
return new DefaultSynchronizer(
syncConfig,
protocolSchedule,
protocolContext,
worldStateStorage,
ethProtocolManager.getBlockBroadcaster(),
maybePruner,
ethContext,
syncState,
dataDirectory,
storageProvider,
clock,
metricsSystem,
getFullSyncTerminationCondition(protocolContext.getBlockchain()),
pivotBlockSelector);
}

private PivotBlockSelector createPivotSelector(
Expand Down Expand Up @@ -930,9 +923,8 @@ protected SubProtocolConfiguration createSubProtocolConfiguration(
final SubProtocolConfiguration subProtocolConfiguration =
new SubProtocolConfiguration().withSubProtocol(EthProtocol.get(), ethProtocolManager);
maybeSnapProtocolManager.ifPresent(
snapProtocolManager -> {
subProtocolConfiguration.withSubProtocol(SnapProtocol.get(), snapProtocolManager);
});
snapProtocolManager ->
subProtocolConfiguration.withSubProtocol(SnapProtocol.get(), snapProtocolManager));
return subProtocolConfiguration;
}

Expand Down Expand Up @@ -1071,22 +1063,21 @@ WorldStateArchive createWorldStateArchive(
final WorldStateStorage worldStateStorage,
final Blockchain blockchain,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
switch (dataStorageConfiguration.getDataStorageFormat()) {
case BONSAI:
return new BonsaiWorldStateProvider(
(BonsaiWorldStateKeyValueStorage) worldStateStorage,
blockchain,
Optional.of(dataStorageConfiguration.getBonsaiMaxLayersToLoad()),
cachedMerkleTrieLoader,
metricsSystem,
besuComponent.map(BesuComponent::getBesuPluginContext).orElse(null));

case FOREST:
default:
return switch (dataStorageConfiguration.getDataStorageFormat()) {
case BONSAI -> new BonsaiWorldStateProvider(
(BonsaiWorldStateKeyValueStorage) worldStateStorage,
blockchain,
Optional.of(dataStorageConfiguration.getBonsaiMaxLayersToLoad()),
cachedMerkleTrieLoader,
metricsSystem,
besuComponent.map(BesuComponent::getBesuPluginContext).orElse(null),
evmConfiguration);
case FOREST -> {
final WorldStatePreimageStorage preimageStorage =
storageProvider.createWorldStatePreimageStorage();
return new DefaultWorldStateArchive(worldStateStorage, preimageStorage);
}
yield new DefaultWorldStateArchive(worldStateStorage, preimageStorage, evmConfiguration);
}
};
}

private ChainDataPruner createChainPruner(final BlockchainStorage blockchainStorage) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LEGACY;
import static org.mockito.Mockito.mock;

import org.hyperledger.besu.evm.internal.EvmConfiguration;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -159,4 +161,25 @@ void setTxPoolImplementationLegacy() {
final String legacyTxPoolSelected = builder.build();
assertThat(legacyTxPoolSelected).contains("Using LEGACY transaction pool implementation");
}

@Test
void setWorldStateUpdateModeDefault() {
builder.setWorldStateUpdateMode(EvmConfiguration.DEFAULT.worldUpdaterMode());
final String layeredTxPoolSelected = builder.build();
assertThat(layeredTxPoolSelected).contains("Using STACKED worldstate update mode");
}

@Test
void setWorldStateUpdateModeStacked() {
builder.setWorldStateUpdateMode(EvmConfiguration.WorldUpdaterMode.STACKED);
final String layeredTxPoolSelected = builder.build();
assertThat(layeredTxPoolSelected).contains("Using STACKED worldstate update mode");
}

@Test
void setWorldStateUpdateModeJournaled() {
builder.setWorldStateUpdateMode(EvmConfiguration.WorldUpdaterMode.JOURNALED);
final String layeredTxPoolSelected = builder.build();
assertThat(layeredTxPoolSelected).contains("Using JOURNALED worldstate update mode");
}
}
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ allprojects {
options.encoding = 'UTF-8'
}

// IntelliJ workaround to allow repeated debugging of unchanged code
tasks.withType(JavaExec) {
if (it.name.contains(".")) {
outputs.upToDateWhen { false }
}
}

/*
* Pass some system properties provided on the gradle command line to test executions for
* convenience.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.evm.worldstate.StackedUpdater;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
Expand Down Expand Up @@ -208,8 +207,8 @@ public WorldUpdater getNextUpdater() {
// if we have no prior updater, it must be the first TX, so use the block's initial state
if (updater == null) {
updater = worldState.updater();
} else if (updater instanceof StackedUpdater) {
((StackedUpdater) updater).markTransactionBoundary();
} else {
updater.markTransactionBoundary();
}
updater = updater.updater();
return updater;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.evm.worldstate.StackedUpdater;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.util.List;
Expand Down Expand Up @@ -58,8 +57,8 @@ private BlockReplay.TransactionAction<TransactionTrace> prepareReplayAction(
// if we have no prior updater, it must be the first TX, so use the block's initial state
if (chainedUpdater == null) {
chainedUpdater = mutableWorldState.updater();
} else if (chainedUpdater instanceof StackedUpdater<?, ?> stackedUpdater) {
stackedUpdater.markTransactionBoundary();
} else {
chainedUpdater.markTransactionBoundary();
}
// create an updater for just this tx
chainedUpdater = chainedUpdater.updater();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.evm.tracing.OperationTracer;
import org.hyperledger.besu.evm.tracing.StandardJsonTracer;
import org.hyperledger.besu.evm.worldstate.StackedUpdater;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.io.File;
Expand Down Expand Up @@ -123,7 +122,7 @@ public List<String> traceTransactionToFile(
.map(parent -> calculateExcessBlobGasForParent(protocolSpec, parent))
.orElse(BlobGas.ZERO));
for (int i = 0; i < body.getTransactions().size(); i++) {
((StackedUpdater<?, ?>) stackedUpdater).markTransactionBoundary();
stackedUpdater.markTransactionBoundary();
final Transaction transaction = body.getTransactions().get(i);
if (selectedHash.isEmpty()
|| selectedHash.filter(isEqual(transaction.getHash())).isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.TracingUtils;
import org.hyperledger.besu.ethereum.debug.TraceFrame;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.util.Collections;
Expand All @@ -39,7 +39,7 @@ public class StateDiffGenerator {

public Stream<Trace> generateStateDiff(final TransactionTrace transactionTrace) {
final List<TraceFrame> traceFrames = transactionTrace.getTraceFrames();
if (traceFrames.size() < 1) {
if (traceFrames.isEmpty()) {
return Stream.empty();
}

Expand All @@ -60,9 +60,7 @@ public Stream<Trace> generateStateDiff(final TransactionTrace transactionTrace)
// calculate storage diff
final Map<String, DiffNode> storageDiff = new TreeMap<>();
for (final Map.Entry<UInt256, UInt256> entry :
((UpdateTrackingAccount<?>) updatedAccount)
.getUpdatedStorage()
.entrySet()) { // FIXME cast
((MutableAccount) updatedAccount).getUpdatedStorage().entrySet()) {
final UInt256 newValue = entry.getValue();
if (rootAccount == null) {
if (!UInt256.ZERO.equals(newValue)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.hyperledger.besu.ethereum.trie.MerkleTrie;
import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie;
import org.hyperledger.besu.ethereum.worldstate.Pruner.PruningPhase;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.worldstate.WorldState;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
Expand Down Expand Up @@ -59,7 +60,9 @@ public class PrunerIntegrationTest {
private final WorldStateStorage worldStateStorage = new WorldStateKeyValueStorage(stateStorage);
private final WorldStateArchive worldStateArchive =
new DefaultWorldStateArchive(
worldStateStorage, new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()));
worldStateStorage,
new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()),
EvmConfiguration.DEFAULT);
private final InMemoryKeyValueStorage markStorage = new InMemoryKeyValueStorage();
private final Block genesisBlock = gen.genesisBlock();
private final MutableBlockchain blockchain = createInMemoryBlockchain(genesisBlock);
Expand Down Expand Up @@ -226,7 +229,7 @@ private Set<Bytes> collectWorldStateNodes(final Hash stateRootHash, final Set<By
private void collectTrieNodes(final MerkleTrie<Bytes32, Bytes> trie, final Set<Bytes> collector) {
final Bytes32 rootHash = trie.getRootHash();
trie.visitAll(
(node) -> {
node -> {
if (node.isReferencedByHash() || node.getHash().equals(rootHash)) {
collector.add(node.getEncodedBytes());
}
Expand Down
Loading

0 comments on commit 094c841

Please sign in to comment.