Bonsai cleaning and robustness (#5123)

Bonsai-safe refactor

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>
Co-authored-by: garyschulte <garyschulte@gmail.com>
This commit is contained in:
matkt
2023-03-17 20:13:05 +01:00
committed by GitHub
parent e8cb17d694
commit 853c2f076c
77 changed files with 1954 additions and 3012 deletions

View File

@@ -30,9 +30,9 @@ import org.hyperledger.besu.ethereum.GasLimitCalculator;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.methods.JsonRpcMethods;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.chain.BlockchainStorage;
import org.hyperledger.besu.ethereum.chain.ChainDataPruner;
@@ -658,6 +658,7 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
createAdditionalJsonRpcMethodFactory(protocolContext);
final List<Closeable> closeables = new ArrayList<>();
closeables.add(protocolContext.getWorldStateArchive());
closeables.add(storageProvider);
if (privacyParameters.getPrivateStorageProvider() != null) {
closeables.add(privacyParameters.getPrivateStorageProvider());
@@ -934,17 +935,16 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
new SnapProtocolManager(peerValidators, ethPeers, snapMessages, worldStateArchive));
}
private WorldStateArchive createWorldStateArchive(
WorldStateArchive createWorldStateArchive(
final WorldStateStorage worldStateStorage,
final Blockchain blockchain,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
switch (dataStorageConfiguration.getDataStorageFormat()) {
case BONSAI:
return new BonsaiWorldStateArchive(
return new BonsaiWorldStateProvider(
(BonsaiWorldStateKeyValueStorage) worldStateStorage,
blockchain,
Optional.of(dataStorageConfiguration.getBonsaiMaxLayersToLoad()),
dataStorageConfiguration.useBonsaiSnapshots(),
cachedMerkleTrieLoader);
case FOREST:

View File

@@ -63,6 +63,7 @@ import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.metrics.ObservableMetricsSystem;
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration;
import org.hyperledger.besu.nat.NatMethod;
@@ -97,6 +98,7 @@ public final class RunnerBuilderTest {
@Mock BesuController besuController;
@Mock ProtocolSchedule protocolSchedule;
@Mock ProtocolContext protocolContext;
@Mock WorldStateArchive worldstateArchive;
@Mock Vertx vertx;
private NodeKey nodeKey;
@@ -124,7 +126,7 @@ public final class RunnerBuilderTest {
final Block block = mock(Block.class);
when(blockchain.getGenesisBlock()).thenReturn(block);
when(block.getHash()).thenReturn(Hash.ZERO);
when(protocolContext.getWorldStateArchive()).thenReturn(worldstateArchive);
when(besuController.getProtocolManager()).thenReturn(ethProtocolManager);
when(besuController.getSubProtocolConfiguration()).thenReturn(subProtocolConfiguration);
when(besuController.getProtocolContext()).thenReturn(protocolContext);

View File

@@ -16,8 +16,10 @@ package org.hyperledger.besu.controller;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -29,7 +31,10 @@ import org.hyperledger.besu.config.Keccak256ConfigOptions;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.GasLimitCalculator;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
@@ -43,11 +48,11 @@ import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration;
import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat;
import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration;
import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.metrics.ObservableMetricsSystem;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
import java.math.BigInteger;
@@ -61,6 +66,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -85,6 +91,7 @@ public class BesuControllerBuilderTest {
@Mock StorageProvider storageProvider;
@Mock GasLimitCalculator gasLimitCalculator;
@Mock WorldStateStorage worldStateStorage;
@Mock WorldStateArchive worldStateArchive;
@Mock BonsaiWorldStateKeyValueStorage bonsaiWorldStateStorage;
@Mock WorldStatePreimageStorage worldStatePreimageStorage;
@@ -132,14 +139,7 @@ public class BesuControllerBuilderTest {
when(worldStatePreimageStorage.updater())
.thenReturn(mock(WorldStatePreimageStorage.Updater.class));
when(worldStateStorage.updater()).thenReturn(mock(WorldStateStorage.Updater.class));
BonsaiWorldStateKeyValueStorage.BonsaiUpdater bonsaiUpdater =
mock(BonsaiWorldStateKeyValueStorage.BonsaiUpdater.class);
when(bonsaiUpdater.getTrieLogStorageTransaction())
.thenReturn(mock(KeyValueStorageTransaction.class));
when(bonsaiUpdater.getTrieBranchStorageTransaction())
.thenReturn(mock(KeyValueStorageTransaction.class));
when(bonsaiWorldStateStorage.updater()).thenReturn(bonsaiUpdater);
besuControllerBuilder = visitWithMockConfigs(new MainnetBesuControllerBuilder());
besuControllerBuilder = spy(visitWithMockConfigs(new MainnetBesuControllerBuilder()));
}
BesuControllerBuilder visitWithMockConfigs(final BesuControllerBuilder builder) {
@@ -162,6 +162,13 @@ public class BesuControllerBuilderTest {
@Test
public void shouldDisablePruningIfBonsaiIsEnabled() {
BonsaiWorldState mockWorldState = mock(BonsaiWorldState.class, Answers.RETURNS_DEEP_STUBS);
doReturn(worldStateArchive)
.when(besuControllerBuilder)
.createWorldStateArchive(
any(WorldStateStorage.class), any(Blockchain.class), any(CachedMerkleTrieLoader.class));
doReturn(mockWorldState).when(worldStateArchive).getMutable();
when(storageProvider.createWorldStateStorage(DataStorageFormat.BONSAI))
.thenReturn(bonsaiWorldStateStorage);
besuControllerBuilder

View File

@@ -131,8 +131,7 @@ public class BesuEventsImplTest {
.thenReturn(ValidationResult.valid());
when(mockTransactionValidator.validateForSender(any(), any(), any()))
.thenReturn(ValidationResult.valid());
when(mockWorldState.copy()).thenReturn(mockWorldState);
when(mockWorldStateArchive.getMutable(any(), any(), anyBoolean()))
when(mockWorldStateArchive.getMutable(any(), anyBoolean()))
.thenReturn(Optional.of(mockWorldState));
blockBroadcaster = new BlockBroadcaster(mockEthContext);

View File

@@ -159,6 +159,7 @@ public class BlockReplay {
}
final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(header);
final MainnetTransactionProcessor transactionProcessor = protocolSpec.getTransactionProcessor();
return action.perform(body, header, blockchain, transactionProcessor, protocolSpec);
}

View File

@@ -63,12 +63,6 @@ public class Tracer {
this.mutableWorldState = mutableWorldState;
}
@Override
public MutableWorldState copy() {
throw new UnsupportedOperationException(
"This method is not supported and will not exist in the future version of MutableWorldState interface");
}
@Override
public void persist(final BlockHeader blockHeader) {
mutableWorldState.persist(blockHeader);

View File

@@ -866,10 +866,7 @@ public class BlockchainQueries {
.getBlockHeader(blockHash)
.flatMap(
blockHeader -> {
try (var ws =
worldStateArchive
.getMutable(blockHeader.getStateRoot(), blockHeader.getBlockHash(), false)
.orElse(null)) {
try (var ws = worldStateArchive.getMutable(blockHeader, false).orElse(null)) {
if (ws != null) {
return mapper.apply(ws);
}

View File

@@ -151,8 +151,7 @@ class EthGetProofTest {
final JsonRpcErrorResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.WORLD_STATE_UNAVAILABLE);
when(archive.getMutable(any(Hash.class), any(Hash.class), anyBoolean()))
.thenReturn(Optional.empty());
when(archive.getMutable(any(BlockHeader.class), anyBoolean())).thenReturn(Optional.empty());
final JsonRpcRequestContext request =
requestWithParams(

View File

@@ -318,16 +318,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
final Hash parentStateRoot = parentHeader.getStateRoot();
return protocolContext
.getWorldStateArchive()
.getMutable(parentStateRoot, parentHeader.getHash(), false)
.map(
ws -> {
if (ws.isPersistable()) {
return ws;
} else {
// non-persistable worldstates should return a copy which is persistable:
return ws.copy();
}
})
.getMutable(parentHeader, false)
.orElseThrow(
() -> {
LOG.info("Unable to create block because world state is not available");

View File

@@ -41,6 +41,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -53,7 +54,7 @@ public class PrunerIntegrationTest {
private final BlockDataGenerator gen = new BlockDataGenerator();
private final NoOpMetricsSystem metricsSystem = new NoOpMetricsSystem();
private final Map<Bytes, byte[]> hashValueStore = new HashMap<>();
private final Map<Bytes, Optional<byte[]>> hashValueStore = new HashMap<>();
private final InMemoryKeyValueStorage stateStorage = new TestInMemoryStorage(hashValueStore);
private final WorldStateStorage worldStateStorage = new WorldStateKeyValueStorage(stateStorage);
private final WorldStateArchive worldStateArchive =
@@ -164,7 +165,7 @@ public class PrunerIntegrationTest {
// Check that storage contains only the values we expect
assertThat(hashValueStore.size()).isEqualTo(expectedNodes.size());
assertThat(hashValueStore.values())
assertThat(hashValueStore.values().stream().map(Optional::get))
.containsExactlyInAnyOrderElementsOf(
expectedNodes.stream().map(Bytes::toArrayUnsafe).collect(Collectors.toSet()));
}
@@ -252,7 +253,7 @@ public class PrunerIntegrationTest {
// Proxy class so that we have access to the constructor that takes our own map
private static class TestInMemoryStorage extends InMemoryKeyValueStorage {
public TestInMemoryStorage(final Map<Bytes, byte[]> hashValueStore) {
public TestInMemoryStorage(final Map<Bytes, Optional<byte[]>> hashValueStore) {
super(hashValueStore);
}
}

View File

@@ -122,19 +122,8 @@ public class MainnetBlockValidator implements BlockValidator {
handleAndLogImportFailure(block, retval, shouldRecordBadBlock);
return retval;
}
try (final var worldState =
context
.getWorldStateArchive()
.getMutable(parentHeader.getStateRoot(), parentHeader.getBlockHash(), shouldPersist)
.map(
ws -> {
if (!ws.isPersistable()) {
return ws.copy();
}
return ws;
})
.orElse(null)) {
context.getWorldStateArchive().getMutable(parentHeader, shouldPersist).orElse(null)) {
if (worldState == null) {
var retval =

View File

@@ -19,6 +19,7 @@ package org.hyperledger.besu.ethereum.bonsai;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldView;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPException;
@@ -53,7 +54,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount {
private final Map<UInt256, UInt256> updatedStorage = new HashMap<>();
BonsaiAccount(
public BonsaiAccount(
final BonsaiWorldView context,
final Address address,
final Hash addressHash,
@@ -73,7 +74,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount {
this.mutable = mutable;
}
BonsaiAccount(
public BonsaiAccount(
final BonsaiWorldView context,
final Address address,
final StateTrieAccountValue stateTrieAccount,
@@ -89,11 +90,12 @@ public class BonsaiAccount implements MutableAccount, EvmAccount {
mutable);
}
BonsaiAccount(final BonsaiAccount toCopy) {
public BonsaiAccount(final BonsaiAccount toCopy) {
this(toCopy, toCopy.context, false);
}
BonsaiAccount(final BonsaiAccount toCopy, final BonsaiWorldView context, final boolean mutable) {
public BonsaiAccount(
final BonsaiAccount toCopy, final BonsaiWorldView context, final boolean mutable) {
this.context = context;
this.address = toCopy.address;
this.addressHash = toCopy.addressHash;
@@ -107,7 +109,8 @@ public class BonsaiAccount implements MutableAccount, EvmAccount {
this.mutable = mutable;
}
BonsaiAccount(final BonsaiWorldView context, final UpdateTrackingAccount<BonsaiAccount> tracked) {
public BonsaiAccount(
final BonsaiWorldView context, final UpdateTrackingAccount<BonsaiAccount> tracked) {
this.context = context;
this.address = tracked.getAddress();
this.addressHash = tracked.getAddressHash();
@@ -121,7 +124,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount {
this.mutable = true;
}
static BonsaiAccount fromRLP(
public static BonsaiAccount fromRLP(
final BonsaiWorldView context,
final Address address,
final Bytes encoded,
@@ -219,7 +222,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount {
throw new RuntimeException("Bonsai Tries does not currently support enumerating storage");
}
Bytes serializeAccount() {
public Bytes serializeAccount() {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.startList();
@@ -294,7 +297,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount {
* @param context a description to be added to the thrown exceptions
* @throws IllegalStateException if the stored values differ
*/
static void assertCloseEnoughForDiffing(
public static void assertCloseEnoughForDiffing(
final BonsaiAccount source, final StateTrieAccountValue account, final String context) {
if (source == null) {
throw new IllegalStateException(context + ": source is null but target isn't");

View File

@@ -1,182 +0,0 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
public class BonsaiInMemoryWorldState extends BonsaiPersistedWorldState
implements BonsaiStorageSubscriber {
private boolean isPersisted = false;
private final Long worldstateSubcriberId;
public BonsaiInMemoryWorldState(
final BonsaiWorldStateArchive archive,
final BonsaiWorldStateKeyValueStorage worldStateStorage) {
super(archive, worldStateStorage);
worldstateSubcriberId = worldStateStorage.subscribe(this);
}
@Override
public Hash rootHash() {
if (isPersisted) {
return worldStateRootHash;
}
return rootHash(updater.copy());
}
public Hash rootHash(final BonsaiWorldStateUpdater localUpdater) {
final Hash calculatedRootHash = calculateRootHash(localUpdater);
return Hash.wrap(calculatedRootHash);
}
protected Hash calculateRootHash(final BonsaiWorldStateUpdater worldStateUpdater) {
// second update account storage state. This must be done before updating the accounts so
// that we can get the storage state hash
worldStateUpdater.getStorageToUpdate().entrySet().parallelStream()
.forEach(
addressMapEntry -> {
updateAccountStorage(worldStateUpdater, addressMapEntry);
});
// next walk the account trie
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStateTrieNode(worldStateStorage, location, hash),
worldStateRootHash,
Function.identity(),
Function.identity());
// for manicured tries and composting, collect branches here (not implemented)
// now add the accounts
for (final Map.Entry<Address, BonsaiValue<BonsaiAccount>> accountUpdate :
worldStateUpdater.getAccountsToUpdate().entrySet()) {
final Bytes accountKey = accountUpdate.getKey();
final BonsaiValue<BonsaiAccount> bonsaiValue = accountUpdate.getValue();
final BonsaiAccount updatedAccount = bonsaiValue.getUpdated();
try {
if (updatedAccount == null) {
final Hash addressHash = Hash.hash(accountKey);
accountTrie.remove(addressHash);
} else {
final Hash addressHash = updatedAccount.getAddressHash();
final Bytes accountValue = updatedAccount.serializeAccount();
accountTrie.put(addressHash, accountValue);
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(), Optional.of(Address.wrap(accountKey)), e.getHash(), e.getLocation());
}
}
// TODO write to a cache and then generate a layer update from that and the
// DB tx updates. Right now it is just DB updates.
return Hash.wrap(accountTrie.getRootHash());
}
private void updateAccountStorage(
final BonsaiWorldStateUpdater worldStateUpdater,
final Map.Entry<Address, BonsaiWorldStateUpdater.StorageConsumingMap<BonsaiValue<UInt256>>>
storageAccountUpdate) {
final Address updatedAddress = storageAccountUpdate.getKey();
final Hash updatedAddressHash = Hash.hash(updatedAddress);
if (worldStateUpdater.getAccountsToUpdate().containsKey(updatedAddress)) {
final BonsaiValue<BonsaiAccount> accountValue =
worldStateUpdater.getAccountsToUpdate().get(updatedAddress);
final BonsaiAccount accountOriginal = accountValue.getPrior();
final Hash storageRoot =
(accountOriginal == null) ? Hash.EMPTY_TRIE_HASH : accountOriginal.getStorageRoot();
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, key) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStorageTrieNode(
worldStateStorage, updatedAddressHash, location, key),
storageRoot,
Function.identity(),
Function.identity());
// for manicured tries and composting, collect branches here (not implemented)
for (final Map.Entry<Hash, BonsaiValue<UInt256>> storageUpdate :
storageAccountUpdate.getValue().entrySet()) {
final Hash keyHash = storageUpdate.getKey();
final UInt256 updatedStorage = storageUpdate.getValue().getUpdated();
try {
if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) {
storageTrie.remove(keyHash);
} else {
storageTrie.put(keyHash, BonsaiWorldView.encodeTrieValue(updatedStorage));
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(),
Optional.of(Address.wrap(updatedAddress)),
e.getHash(),
e.getLocation());
}
}
final BonsaiAccount accountUpdated = accountValue.getUpdated();
if (accountUpdated != null) {
final Hash newStorageRoot = Hash.wrap(storageTrie.getRootHash());
accountUpdated.setStorageRoot(newStorageRoot);
}
}
}
@Override
public void persist(final BlockHeader blockHeader) {
final BonsaiWorldStateUpdater localUpdater = updater.copy();
final Hash newWorldStateRootHash = rootHash(localUpdater);
archive
.getTrieLogManager()
.saveTrieLog(archive, localUpdater, newWorldStateRootHash, blockHeader, this);
worldStateRootHash = newWorldStateRootHash;
worldStateBlockHash = blockHeader.getBlockHash();
isPersisted = true;
}
@Override
public void close() throws Exception {
// if storage is snapshot-based we need to close:
worldStateStorage.unSubscribe(worldstateSubcriberId);
worldStateStorage.close();
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.bonsai;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BonsaiInMemoryWorldStateKeyValueStorage extends BonsaiWorldStateKeyValueStorage
implements WorldStateStorage {
private static final Logger LOG =
LoggerFactory.getLogger(BonsaiInMemoryWorldStateKeyValueStorage.class);
public BonsaiInMemoryWorldStateKeyValueStorage(
final KeyValueStorage accountStorage,
final KeyValueStorage codeStorage,
final KeyValueStorage storageStorage,
final KeyValueStorage trieBranchStorage,
final KeyValueStorage trieLogStorage) {
super(accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage);
}
@Override
public InMemoryUpdater updater() {
return new InMemoryUpdater(
accountStorage.startTransaction(),
codeStorage.startTransaction(),
storageStorage.startTransaction(),
trieBranchStorage.startTransaction(),
trieLogStorage.startTransaction());
}
public static class InMemoryUpdater extends BonsaiWorldStateKeyValueStorage.Updater
implements WorldStateStorage.Updater {
public InMemoryUpdater(
final KeyValueStorageTransaction accountStorageTransaction,
final KeyValueStorageTransaction codeStorageTransaction,
final KeyValueStorageTransaction storageStorageTransaction,
final KeyValueStorageTransaction trieBranchStorageTransaction,
final KeyValueStorageTransaction trieLogStorageTransaction) {
super(
accountStorageTransaction,
codeStorageTransaction,
storageStorageTransaction,
trieBranchStorageTransaction,
trieLogStorageTransaction);
}
@Override
public void commit() {
LOG.trace("Cannot commit using an in memory key value storage");
}
}
}

View File

@@ -1,328 +0,0 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.SnapshotMutableWorldState;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.worldstate.WorldState;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.plugin.services.exception.StorageException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import com.google.errorprone.annotations.MustBeClosed;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
/** A World State backed first by trie log layer and then by another world state. */
public class BonsaiLayeredWorldState
implements MutableWorldState,
BonsaiWorldView,
WorldState,
BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber {
private Optional<BonsaiWorldView> nextWorldView = Optional.empty();
private Optional<Long> newtWorldViewSubscribeId = Optional.empty();
protected final long height;
protected final TrieLogLayer trieLog;
private final Hash worldStateRootHash;
private final Blockchain blockchain;
private final BonsaiWorldStateArchive archive;
BonsaiLayeredWorldState(
final Blockchain blockchain,
final BonsaiWorldStateArchive archive,
final Optional<BonsaiWorldView> nextWorldView,
final long height,
final Hash worldStateRootHash,
final TrieLogLayer trieLog) {
this.blockchain = blockchain;
this.archive = archive;
this.setNextWorldView(nextWorldView);
this.height = height;
this.worldStateRootHash = worldStateRootHash;
this.trieLog = trieLog;
}
public Optional<BonsaiWorldView> getNextWorldView() {
if (nextWorldView.isEmpty()) {
final Optional<Hash> blockHashByNumber = blockchain.getBlockHashByNumber(height + 1);
nextWorldView =
blockHashByNumber
.map(hash -> archive.getMutable(null, hash, false).map(BonsaiWorldView.class::cast))
.orElseGet(() -> Optional.of(archive.getMutable()).map(BonsaiWorldView.class::cast));
}
return nextWorldView;
}
public void setNextWorldView(final Optional<BonsaiWorldView> nextWorldView) {
maybeUnSubscribe(); // unsubscribe the old view
this.nextWorldView = nextWorldView;
maybeSubscribe(); // subscribe the next view
}
private void maybeSubscribe() {
nextWorldView
.filter(BonsaiPersistedWorldState.class::isInstance)
.map(BonsaiPersistedWorldState.class::cast)
.ifPresent(
worldState -> {
newtWorldViewSubscribeId = Optional.of(worldState.worldStateStorage.subscribe(this));
});
}
private void maybeUnSubscribe() {
nextWorldView
.filter(BonsaiPersistedWorldState.class::isInstance)
.map(BonsaiPersistedWorldState.class::cast)
.ifPresent(
worldState -> {
newtWorldViewSubscribeId.ifPresent(worldState.worldStateStorage::unSubscribe);
});
}
@Override
public void close() throws Exception {
maybeUnSubscribe();
}
public TrieLogLayer getTrieLog() {
return trieLog;
}
public long getHeight() {
return height;
}
@Override
public Optional<Bytes> getCode(final Address address, final Hash codeHash) {
BonsaiLayeredWorldState currentLayer = this;
while (currentLayer != null) {
final Optional<Bytes> maybeCode = currentLayer.trieLog.getCode(address);
final Optional<Bytes> maybePriorCode = currentLayer.trieLog.getPriorCode(address);
if (currentLayer == this && maybeCode.isPresent()) {
return maybeCode;
} else if (maybePriorCode.isPresent()) {
return maybePriorCode;
} else if (maybeCode.isPresent()) {
return Optional.empty();
}
if (currentLayer.getNextWorldView().isEmpty()) {
currentLayer = null;
} else if (currentLayer.getNextWorldView().get() instanceof BonsaiLayeredWorldState) {
currentLayer = (BonsaiLayeredWorldState) currentLayer.getNextWorldView().get();
} else {
return currentLayer.getNextWorldView().get().getCode(address, codeHash);
}
}
return Optional.empty();
}
@Override
public Optional<Bytes> getStateTrieNode(final Bytes location) {
// this must be iterative and lambda light because the stack may blow up
// mainly because we don't have tail calls.
BonsaiLayeredWorldState currentLayer = this;
while (currentLayer != null) {
if (currentLayer.getNextWorldView().isEmpty()) {
currentLayer = null;
} else if (currentLayer.getNextWorldView().get() instanceof BonsaiLayeredWorldState) {
currentLayer = (BonsaiLayeredWorldState) currentLayer.getNextWorldView().get();
} else {
return currentLayer.getNextWorldView().get().getStateTrieNode(location);
}
}
return Optional.empty();
}
@Override
public UInt256 getStorageValue(final Address address, final UInt256 key) {
return getStorageValueBySlotHash(address, Hash.hash(key)).orElse(UInt256.ZERO);
}
@Override
public Optional<UInt256> getStorageValueBySlotHash(final Address address, final Hash slotHash) {
// this must be iterative and lambda light because the stack may blow up
// mainly because we don't have tail calls.
BonsaiLayeredWorldState currentLayer = this;
while (currentLayer != null) {
final Optional<UInt256> maybeValue =
currentLayer.trieLog.getStorageBySlotHash(address, slotHash);
final Optional<UInt256> maybePriorValue =
currentLayer.trieLog.getPriorStorageBySlotHash(address, slotHash);
if (currentLayer == this && maybeValue.isPresent()) {
return maybeValue;
} else if (maybePriorValue.isPresent()) {
return maybePriorValue;
} else if (maybeValue.isPresent()) {
return Optional.empty();
}
if (currentLayer.getNextWorldView().isEmpty()) {
currentLayer = null;
} else if (currentLayer.getNextWorldView().get() instanceof BonsaiLayeredWorldState) {
currentLayer = (BonsaiLayeredWorldState) currentLayer.getNextWorldView().get();
} else {
return currentLayer.getNextWorldView().get().getStorageValueBySlotHash(address, slotHash);
}
}
return Optional.empty();
}
@Override
public UInt256 getPriorStorageValue(final Address address, final UInt256 key) {
// This is the base layer for a block, all values are original.
return getStorageValue(address, key);
}
@Override
public Map<Bytes32, Bytes> getAllAccountStorage(final Address address, final Hash rootHash) {
// this must be iterative and lambda light because the stack may blow up
// mainly because we don't have tail calls.
final Map<Bytes32, Bytes> results = new HashMap<>();
BonsaiLayeredWorldState currentLayer = this;
while (currentLayer != null) {
if (currentLayer.trieLog.hasStorageChanges(address)) {
currentLayer
.trieLog
.streamStorageChanges(address)
.forEach(
entry -> {
if (!results.containsKey(entry.getKey())) {
final UInt256 value = entry.getValue().getUpdated();
// yes, store the nulls. If it was deleted it should stay deleted
results.put(entry.getKey(), value);
}
});
}
if (currentLayer.getNextWorldView().isEmpty()) {
currentLayer = null;
} else if (currentLayer.getNextWorldView().get() instanceof BonsaiLayeredWorldState) {
currentLayer = (BonsaiLayeredWorldState) currentLayer.getNextWorldView().get();
} else {
final Account account = currentLayer.getNextWorldView().get().get(address);
if (account != null) {
account
.storageEntriesFrom(Hash.ZERO, Integer.MAX_VALUE)
.forEach(
(k, v) -> {
if (!results.containsKey(k)) {
results.put(k, v.getValue());
}
});
}
currentLayer = null;
}
}
return results;
}
@Override
public Account get(final Address address) {
// this must be iterative and lambda light because the stack may blow up
// mainly because we don't have tail calls.
BonsaiLayeredWorldState currentLayer = this;
while (currentLayer != null) {
final Optional<StateTrieAccountValue> maybeStateTrieAccount =
currentLayer.trieLog.getAccount(address);
final Optional<StateTrieAccountValue> maybePriorStateTrieAccount =
currentLayer.trieLog.getPriorAccount(address);
if (currentLayer == this && maybeStateTrieAccount.isPresent()) {
return new BonsaiAccount(
BonsaiLayeredWorldState.this, address, maybeStateTrieAccount.get(), false);
} else if (maybePriorStateTrieAccount.isPresent()) {
return new BonsaiAccount(
BonsaiLayeredWorldState.this, address, maybePriorStateTrieAccount.get(), false);
} else if (maybeStateTrieAccount.isPresent()) {
return null;
}
if (currentLayer.getNextWorldView().isEmpty()) {
currentLayer = null;
} else if (currentLayer.getNextWorldView().get() instanceof BonsaiLayeredWorldState) {
currentLayer = (BonsaiLayeredWorldState) currentLayer.getNextWorldView().get();
} else {
return currentLayer.getNextWorldView().get().get(address);
}
}
return null;
}
@Override
public Hash rootHash() {
return worldStateRootHash;
}
@Override
public Hash frontierRootHash() {
// maybe throw?
return rootHash();
}
public Hash blockHash() {
return trieLog.getBlockHash();
}
@Override
public Stream<StreamableAccount> streamAccounts(final Bytes32 startKeyHash, final int limit) {
throw new UnsupportedOperationException("Bonsai does not support pruning and debug RPCs");
}
@Override
@MustBeClosed
public MutableWorldState copy() {
// return an in-memory worldstate that is based on a persisted snapshot for this blockhash.
try (SnapshotMutableWorldState snapshot =
archive
.getMutableSnapshot(this.blockHash())
.map(SnapshotMutableWorldState.class::cast)
.orElseThrow(
() ->
new StorageException(
"Unable to copy Layered Worldstate for " + blockHash().toHexString()))) {
return new BonsaiInMemoryWorldState(archive, snapshot.getWorldStateStorage());
} catch (MerkleTrieException ex) {
throw ex; // need to throw to trigger the heal
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@Override
public boolean isPersistable() {
return false;
}
@Override
public void persist(final BlockHeader blockHeader) {
// no-op, layered worldstates do not persist, not even as a trielog.
}
@Override
public WorldUpdater updater() {
return new BonsaiWorldStateUpdater(this);
}
}

View File

@@ -1,478 +0,0 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import static org.hyperledger.besu.ethereum.bonsai.BonsaiAccount.fromRLP;
import static org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY;
import static org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.plugin.services.exception.StorageException;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorldView {
private static final Logger LOG = LoggerFactory.getLogger(BonsaiPersistedWorldState.class);
protected final BonsaiWorldStateKeyValueStorage worldStateStorage;
protected final BonsaiWorldStateArchive archive;
protected final BonsaiWorldStateUpdater updater;
protected Hash worldStateRootHash;
protected Hash worldStateBlockHash;
public BonsaiPersistedWorldState(
final BonsaiWorldStateArchive archive,
final BonsaiWorldStateKeyValueStorage worldStateStorage) {
this.archive = archive;
this.worldStateStorage = worldStateStorage;
worldStateRootHash =
Hash.wrap(
Bytes32.wrap(worldStateStorage.getWorldStateRootHash().orElse(Hash.EMPTY_TRIE_HASH)));
worldStateBlockHash =
Hash.wrap(Bytes32.wrap(worldStateStorage.getWorldStateBlockHash().orElse(Hash.ZERO)));
updater =
new BonsaiWorldStateUpdater(
this,
(addr, value) ->
archive
.getCachedMerkleTrieLoader()
.preLoadAccount(getWorldStateStorage(), worldStateRootHash, addr),
(addr, value) ->
archive
.getCachedMerkleTrieLoader()
.preLoadStorageSlot(getWorldStateStorage(), addr, value));
}
public BonsaiWorldStateArchive getArchive() {
return archive;
}
@Override
public MutableWorldState copy() {
BonsaiInMemoryWorldStateKeyValueStorage bonsaiInMemoryWorldStateKeyValueStorage =
new BonsaiInMemoryWorldStateKeyValueStorage(
worldStateStorage.accountStorage,
worldStateStorage.codeStorage,
worldStateStorage.storageStorage,
worldStateStorage.trieBranchStorage,
worldStateStorage.trieLogStorage);
return new BonsaiInMemoryWorldState(archive, bonsaiInMemoryWorldStateKeyValueStorage);
}
@Override
public Optional<Bytes> getCode(@Nonnull final Address address, final Hash codeHash) {
return worldStateStorage.getCode(codeHash, Hash.hash(address));
}
public void setArchiveStateUnSafe(final BlockHeader blockHeader) {
worldStateBlockHash = Hash.fromPlugin(blockHeader.getBlockHash());
worldStateRootHash = Hash.fromPlugin(blockHeader.getStateRoot());
}
public BonsaiWorldStateKeyValueStorage getWorldStateStorage() {
return worldStateStorage;
}
protected Hash calculateRootHash(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
clearStorage(stateUpdater, worldStateUpdater);
// This must be done before updating the accounts so
// that we can get the storage state hash
updateAccountStorageState(stateUpdater, worldStateUpdater);
// Third update the code. This has the side effect of ensuring a code hash is calculated.
updateCode(stateUpdater, worldStateUpdater);
// next walk the account trie
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStateTrieNode(worldStateStorage, location, hash),
worldStateRootHash,
Function.identity(),
Function.identity());
// for manicured tries and composting, collect branches here (not implemented)
addTheAccounts(stateUpdater, worldStateUpdater, accountTrie);
// TODO write to a cache and then generate a layer update from that and the
// DB tx updates. Right now it is just DB updates.
accountTrie.commit(
(location, hash, value) ->
writeTrieNode(stateUpdater.getTrieBranchStorageTransaction(), location, value));
final Bytes32 rootHash = accountTrie.getRootHash();
return Hash.wrap(rootHash);
}
private void addTheAccounts(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater,
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie) {
for (final Map.Entry<Address, BonsaiValue<BonsaiAccount>> accountUpdate :
worldStateUpdater.getAccountsToUpdate().entrySet()) {
final Bytes accountKey = accountUpdate.getKey();
final BonsaiValue<BonsaiAccount> bonsaiValue = accountUpdate.getValue();
final BonsaiAccount updatedAccount = bonsaiValue.getUpdated();
try {
if (updatedAccount == null) {
final Hash addressHash = Hash.hash(accountKey);
accountTrie.remove(addressHash);
stateUpdater.removeAccountInfoState(addressHash);
} else {
final Hash addressHash = updatedAccount.getAddressHash();
final Bytes accountValue = updatedAccount.serializeAccount();
stateUpdater.putAccountInfoState(Hash.hash(accountKey), accountValue);
accountTrie.put(addressHash, accountValue);
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(), Optional.of(Address.wrap(accountKey)), e.getHash(), e.getLocation());
}
}
}
private void updateCode(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
for (final Map.Entry<Address, BonsaiValue<Bytes>> codeUpdate :
worldStateUpdater.getCodeToUpdate().entrySet()) {
final Bytes updatedCode = codeUpdate.getValue().getUpdated();
final Hash accountHash = Hash.hash(codeUpdate.getKey());
if (updatedCode == null || updatedCode.size() == 0) {
stateUpdater.removeCode(accountHash);
} else {
stateUpdater.putCode(accountHash, null, updatedCode);
}
}
}
private void updateAccountStorageState(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
for (final Map.Entry<Address, BonsaiWorldStateUpdater.StorageConsumingMap<BonsaiValue<UInt256>>>
storageAccountUpdate : worldStateUpdater.getStorageToUpdate().entrySet()) {
final Address updatedAddress = storageAccountUpdate.getKey();
final Hash updatedAddressHash = Hash.hash(updatedAddress);
if (worldStateUpdater.getAccountsToUpdate().containsKey(updatedAddress)) {
final BonsaiValue<BonsaiAccount> accountValue =
worldStateUpdater.getAccountsToUpdate().get(updatedAddress);
final BonsaiAccount accountOriginal = accountValue.getPrior();
final Hash storageRoot =
(accountOriginal == null) ? Hash.EMPTY_TRIE_HASH : accountOriginal.getStorageRoot();
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, key) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStorageTrieNode(
worldStateStorage, updatedAddressHash, location, key),
storageRoot,
Function.identity(),
Function.identity());
// for manicured tries and composting, collect branches here (not implemented)
for (final Map.Entry<Hash, BonsaiValue<UInt256>> storageUpdate :
storageAccountUpdate.getValue().entrySet()) {
final Hash keyHash = storageUpdate.getKey();
final UInt256 updatedStorage = storageUpdate.getValue().getUpdated();
try {
if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) {
stateUpdater.removeStorageValueBySlotHash(updatedAddressHash, keyHash);
storageTrie.remove(keyHash);
} else {
stateUpdater.putStorageValueBySlotHash(updatedAddressHash, keyHash, updatedStorage);
storageTrie.put(keyHash, BonsaiWorldView.encodeTrieValue(updatedStorage));
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(),
Optional.of(Address.wrap(updatedAddress)),
e.getHash(),
e.getLocation());
}
}
final BonsaiAccount accountUpdated = accountValue.getUpdated();
if (accountUpdated != null) {
storageTrie.commit(
(location, key, value) ->
writeStorageTrieNode(stateUpdater, updatedAddressHash, location, key, value));
final Hash newStorageRoot = Hash.wrap(storageTrie.getRootHash());
accountUpdated.setStorageRoot(newStorageRoot);
}
}
// for manicured tries and composting, trim and compost here
}
}
private void clearStorage(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
for (final Address address : worldStateUpdater.getStorageToClear()) {
// because we are clearing persisted values we need the account root as persisted
final BonsaiAccount oldAccount =
worldStateStorage
.getAccount(Hash.hash(address))
.map(bytes -> fromRLP(BonsaiPersistedWorldState.this, address, bytes, true))
.orElse(null);
if (oldAccount == null) {
// This is when an account is both created and deleted within the scope of the same
// block. A not-uncommon DeFi bot pattern.
continue;
}
final Hash addressHash = Hash.hash(address);
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, key) -> getStorageTrieNode(addressHash, location, key),
oldAccount.getStorageRoot(),
Function.identity(),
Function.identity());
try {
Map<Bytes32, Bytes> entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
while (!entriesToDelete.isEmpty()) {
entriesToDelete
.keySet()
.forEach(
k -> stateUpdater.removeStorageValueBySlotHash(Hash.hash(address), Hash.wrap(k)));
entriesToDelete.keySet().forEach(storageTrie::remove);
if (entriesToDelete.size() == 256) {
entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
} else {
break;
}
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(), Optional.of(Address.wrap(address)), e.getHash(), e.getLocation());
}
}
}
@Override
public void persist(final BlockHeader blockHeader) {
final Optional<BlockHeader> maybeBlockHeader = Optional.ofNullable(blockHeader);
LOG.atDebug()
.setMessage("Persist world state for block {}")
.addArgument(maybeBlockHeader)
.log();
boolean success = false;
final BonsaiWorldStateUpdater localUpdater = updater.copy();
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater = worldStateStorage.updater();
Runnable saveTrieLog = () -> {};
try {
final Hash newWorldStateRootHash = calculateRootHash(stateUpdater, localUpdater);
// if we are persisted with a block header, and the prior state is the parent
// then persist the TrieLog for that transition.
// If specified but not a direct descendant simply store the new block hash.
if (blockHeader != null) {
if (!newWorldStateRootHash.equals(blockHeader.getStateRoot())) {
throw new RuntimeException(
"World State Root does not match expected value, header "
+ blockHeader.getStateRoot().toHexString()
+ " calculated "
+ newWorldStateRootHash.toHexString());
}
saveTrieLog =
() ->
archive
.getTrieLogManager()
.saveTrieLog(archive, localUpdater, newWorldStateRootHash, blockHeader, this);
stateUpdater
.getTrieBranchStorageTransaction()
.put(WORLD_BLOCK_HASH_KEY, blockHeader.getHash().toArrayUnsafe());
worldStateBlockHash = blockHeader.getHash();
} else {
stateUpdater.getTrieBranchStorageTransaction().remove(WORLD_BLOCK_HASH_KEY);
worldStateBlockHash = null;
}
stateUpdater
.getTrieBranchStorageTransaction()
.put(WORLD_ROOT_HASH_KEY, newWorldStateRootHash.toArrayUnsafe());
worldStateRootHash = newWorldStateRootHash;
success = true;
} finally {
if (success) {
stateUpdater.commit();
updater.reset();
saveTrieLog.run();
} else {
stateUpdater.rollback();
updater.reset();
}
}
}
@Override
public WorldUpdater updater() {
return updater;
}
@Override
public Hash rootHash() {
return Hash.wrap(worldStateRootHash);
}
static final KeyValueStorageTransaction noOpTx =
new KeyValueStorageTransaction() {
@Override
public void put(final byte[] key, final byte[] value) {
// no-op
}
@Override
public void remove(final byte[] key) {
// no-op
}
@Override
public void commit() throws StorageException {
// no-op
}
@Override
public void rollback() {
// no-op
}
};
@Override
public Hash frontierRootHash() {
return calculateRootHash(
new BonsaiWorldStateKeyValueStorage.Updater(noOpTx, noOpTx, noOpTx, noOpTx, noOpTx),
updater.copy());
}
public Hash blockHash() {
return worldStateBlockHash;
}
@Override
public Stream<StreamableAccount> streamAccounts(final Bytes32 startKeyHash, final int limit) {
throw new RuntimeException("Bonsai Tries do not provide account streaming.");
}
@Override
public Account get(final Address address) {
return worldStateStorage
.getAccount(Hash.hash(address))
.map(bytes -> fromRLP(updater, address, bytes, true))
.orElse(null);
}
protected Optional<Bytes> getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) {
return worldStateStorage.getAccountStateTrieNode(location, nodeHash);
}
private void writeTrieNode(
final KeyValueStorageTransaction tx, final Bytes location, final Bytes value) {
tx.put(location.toArrayUnsafe(), value.toArrayUnsafe());
}
protected Optional<Bytes> getStorageTrieNode(
final Hash accountHash, final Bytes location, final Bytes32 nodeHash) {
return worldStateStorage.getAccountStorageTrieNode(accountHash, location, nodeHash);
}
private void writeStorageTrieNode(
final WorldStateStorage.Updater stateUpdater,
final Hash accountHash,
final Bytes location,
final Bytes32 nodeHash,
final Bytes value) {
stateUpdater.putAccountStorageTrieNode(accountHash, location, nodeHash, value);
}
@Override
public Optional<Bytes> getStateTrieNode(final Bytes location) {
return worldStateStorage.getStateTrieNode(location);
}
@Override
public UInt256 getStorageValue(final Address address, final UInt256 storageKey) {
return getStorageValueBySlotHash(address, Hash.hash(storageKey)).orElse(UInt256.ZERO);
}
@Override
public Optional<UInt256> getStorageValueBySlotHash(final Address address, final Hash slotHash) {
return worldStateStorage
.getStorageValueBySlotHash(Hash.hash(address), slotHash)
.map(UInt256::fromBytes);
}
public Optional<UInt256> getStorageValueBySlotHash(
final Supplier<Optional<Hash>> storageRootSupplier,
final Address address,
final Hash slotHash) {
return worldStateStorage
.getStorageValueBySlotHash(storageRootSupplier, Hash.hash(address), slotHash)
.map(UInt256::fromBytes);
}
@Override
public UInt256 getPriorStorageValue(final Address address, final UInt256 storageKey) {
return getStorageValue(address, storageKey);
}
@Override
public Map<Bytes32, Bytes> getAllAccountStorage(final Address address, final Hash rootHash) {
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, key) -> getStorageTrieNode(Hash.hash(address), location, key),
rootHash,
Function.identity(),
Function.identity());
return storageTrie.entriesFrom(Bytes32.ZERO, Integer.MAX_VALUE);
}
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.SnapshotMutableWorldState;
import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage;
/**
* This class extends BonsaiPersistedWorldstate directly such that it commits/perists directly to
* the transaction state. A SnapshotMutableWorldState is used to accumulate changes to a
* non-persisting mutable world state rather than writing worldstate changes directly.
*/
public class BonsaiSnapshotWorldState extends BonsaiPersistedWorldState
implements SnapshotMutableWorldState {
private final SnappedKeyValueStorage accountSnap;
private final SnappedKeyValueStorage codeSnap;
private final SnappedKeyValueStorage storageSnap;
private final SnappedKeyValueStorage trieBranchSnap;
private final BonsaiWorldStateKeyValueStorage parentWorldStateStorage;
private final BonsaiSnapshotWorldStateKeyValueStorage snapshotWorldStateStorage;
private BonsaiSnapshotWorldState(
final BonsaiWorldStateArchive archive,
final BonsaiSnapshotWorldStateKeyValueStorage snapshotWorldStateStorage,
final BonsaiWorldStateKeyValueStorage parentWorldStateStorage) {
super(archive, snapshotWorldStateStorage);
this.snapshotWorldStateStorage = snapshotWorldStateStorage;
this.accountSnap = (SnappedKeyValueStorage) snapshotWorldStateStorage.accountStorage;
this.codeSnap = (SnappedKeyValueStorage) snapshotWorldStateStorage.codeStorage;
this.storageSnap = (SnappedKeyValueStorage) snapshotWorldStateStorage.storageStorage;
this.trieBranchSnap = (SnappedKeyValueStorage) snapshotWorldStateStorage.trieBranchStorage;
this.parentWorldStateStorage = parentWorldStateStorage;
}
public static BonsaiSnapshotWorldState create(
final BonsaiWorldStateArchive archive,
final BonsaiWorldStateKeyValueStorage parentWorldStateStorage) {
return new BonsaiSnapshotWorldState(
archive,
new BonsaiSnapshotWorldStateKeyValueStorage(
((SnappableKeyValueStorage) parentWorldStateStorage.accountStorage).takeSnapshot(),
((SnappableKeyValueStorage) parentWorldStateStorage.codeStorage).takeSnapshot(),
((SnappableKeyValueStorage) parentWorldStateStorage.storageStorage).takeSnapshot(),
((SnappableKeyValueStorage) parentWorldStateStorage.trieBranchStorage)
.takeSnapshot(),
parentWorldStateStorage.trieLogStorage),
parentWorldStateStorage)
.subscribeToParentStorage();
}
@Override
public Hash rootHash() {
if (updater.isDirty()) {
this.worldStateRootHash = calculateRootHash(worldStateStorage.updater(), updater);
}
return this.worldStateRootHash;
}
@Override
public MutableWorldState copy() {
// return a clone-based copy of worldstate storage
return new BonsaiSnapshotWorldState(
archive,
new BonsaiSnapshotWorldStateKeyValueStorage(
accountSnap.cloneFromSnapshot(),
codeSnap.cloneFromSnapshot(),
storageSnap.cloneFromSnapshot(),
trieBranchSnap.cloneFromSnapshot(),
worldStateStorage.trieLogStorage),
parentWorldStateStorage)
.subscribeToParentStorage();
}
@Override
public void close() throws Exception {
snapshotWorldStateStorage.close();
}
@Override
public BonsaiWorldStateKeyValueStorage getWorldStateStorage() {
return snapshotWorldStateStorage;
}
protected BonsaiSnapshotWorldState subscribeToParentStorage() {
snapshotWorldStateStorage.subscribeToParentStorage(parentWorldStateStorage);
return this;
}
}

View File

@@ -1,307 +0,0 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.plugin.services.exception.StorageException;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BonsaiSnapshotWorldStateKeyValueStorage extends BonsaiWorldStateKeyValueStorage
implements BonsaiStorageSubscriber {
private static final Logger LOG =
LoggerFactory.getLogger(BonsaiSnapshotWorldStateKeyValueStorage.class);
private final AtomicReference<BonsaiWorldStateKeyValueStorage> parentStorage =
new AtomicReference<>();
private final AtomicLong parentStorageSubscriberId = new AtomicLong(Long.MAX_VALUE);
private final AtomicBoolean shouldClose = new AtomicBoolean(false);
private final AtomicBoolean isClosed = new AtomicBoolean(false);
public BonsaiSnapshotWorldStateKeyValueStorage(
final SnappedKeyValueStorage accountStorage,
final SnappedKeyValueStorage codeStorage,
final SnappedKeyValueStorage storageStorage,
final SnappedKeyValueStorage trieBranchStorage,
final KeyValueStorage trieLogStorage) {
super(accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage);
}
@Override
public BonsaiUpdater updater() {
return new SnapshotUpdater(
(SnappedKeyValueStorage) accountStorage,
(SnappedKeyValueStorage) codeStorage,
(SnappedKeyValueStorage) storageStorage,
(SnappedKeyValueStorage) trieBranchStorage,
trieLogStorage);
}
@Override
public void clear() {
// snapshot storage does not implement clear
throw new StorageException("Snapshot storage does not implement clear");
}
@Override
public void clearFlatDatabase() {
// snapshot storage does not implement clear
throw new StorageException("Snapshot storage does not implement clear");
}
@Override
public void clearTrieLog() {
// snapshot storage does not implement clear
throw new StorageException("Snapshot storage does not implement clear");
}
@Override
public synchronized long subscribe(final BonsaiStorageSubscriber sub) {
if (isClosed.get()) {
throw new RuntimeException("Storage is marked to close or has already closed");
}
return super.subscribe(sub);
}
@Override
public synchronized void unSubscribe(final long id) {
super.unSubscribe(id);
try {
tryClose();
} catch (Exception e) {
LOG.atWarn()
.setMessage("exception while trying to close : {}")
.addArgument(e::getMessage)
.log();
}
}
void subscribeToParentStorage(final BonsaiWorldStateKeyValueStorage parentStorage) {
this.parentStorage.set(parentStorage);
parentStorageSubscriberId.set(parentStorage.subscribe(this));
}
@Override
public void onClearStorage() {
try {
// when the parent storage clears, close regardless of subscribers
doClose();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void onClearFlatDatabaseStorage() {
// when the parent storage clears, close regardless of subscribers
try {
doClose();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void onClearTrieLog() {
// when the parent storage clears, close regardless of subscribers
try {
doClose();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public synchronized void close() throws Exception {
// when the parent storage clears, close
shouldClose.set(true);
tryClose();
}
protected synchronized void tryClose() throws Exception {
if (shouldClose.get() && subscribers.getSubscriberCount() < 1) {
// attempting to close already closed snapshots will segfault
doClose();
}
}
private void doClose() throws Exception {
if (!isClosed.get()) {
// alert any subscribers we are closing:
subscribers.forEach(BonsaiStorageSubscriber::onCloseStorage);
// unsubscribe from parent storage if we have subscribed
Optional.ofNullable(parentStorage.get())
.filter(__ -> parentStorageSubscriberId.get() != Long.MAX_VALUE)
.ifPresent(parent -> parent.unSubscribe(parentStorageSubscriberId.get()));
// close all of the SnappedKeyValueStorages:
accountStorage.close();
codeStorage.close();
storageStorage.close();
trieBranchStorage.close();
// set storage closed
isClosed.set(true);
}
}
public static class SnapshotUpdater implements BonsaiWorldStateKeyValueStorage.BonsaiUpdater {
private final KeyValueStorageTransaction accountStorageTransaction;
private final KeyValueStorageTransaction codeStorageTransaction;
private final KeyValueStorageTransaction storageStorageTransaction;
private final KeyValueStorageTransaction trieBranchStorageTransaction;
private final KeyValueStorageTransaction trieLogStorageTransaction;
public SnapshotUpdater(
final SnappedKeyValueStorage accountStorage,
final SnappedKeyValueStorage codeStorage,
final SnappedKeyValueStorage storageStorage,
final SnappedKeyValueStorage trieBranchStorage,
final KeyValueStorage trieLogStorage) {
this.accountStorageTransaction = accountStorage.getSnapshotTransaction();
this.codeStorageTransaction = codeStorage.getSnapshotTransaction();
this.storageStorageTransaction = storageStorage.getSnapshotTransaction();
this.trieBranchStorageTransaction = trieBranchStorage.getSnapshotTransaction();
this.trieLogStorageTransaction = trieLogStorage.startTransaction();
}
@Override
public BonsaiUpdater removeCode(final Hash accountHash) {
codeStorageTransaction.remove(accountHash.toArrayUnsafe());
return this;
}
@Override
public WorldStateStorage.Updater putCode(
final Hash accountHash, final Bytes32 nodeHash, final Bytes code) {
if (code.size() == 0) {
// Don't save empty values
return this;
}
codeStorageTransaction.put(accountHash.toArrayUnsafe(), code.toArrayUnsafe());
return this;
}
@Override
public BonsaiUpdater removeAccountInfoState(final Hash accountHash) {
accountStorageTransaction.remove(accountHash.toArrayUnsafe());
return this;
}
@Override
public BonsaiUpdater putAccountInfoState(final Hash accountHash, final Bytes accountValue) {
if (accountValue.size() == 0) {
// Don't save empty values
return this;
}
accountStorageTransaction.put(accountHash.toArrayUnsafe(), accountValue.toArrayUnsafe());
return this;
}
@Override
public BonsaiUpdater putStorageValueBySlotHash(
final Hash accountHash, final Hash slotHash, final Bytes storage) {
storageStorageTransaction.put(
Bytes.concatenate(accountHash, slotHash).toArrayUnsafe(), storage.toArrayUnsafe());
return this;
}
@Override
public void removeStorageValueBySlotHash(final Hash accountHash, final Hash slotHash) {
storageStorageTransaction.remove(Bytes.concatenate(accountHash, slotHash).toArrayUnsafe());
}
@Override
public KeyValueStorageTransaction getTrieBranchStorageTransaction() {
return trieBranchStorageTransaction;
}
@Override
public KeyValueStorageTransaction getTrieLogStorageTransaction() {
return trieLogStorageTransaction;
}
@Override
public WorldStateStorage.Updater saveWorldState(
final Bytes blockHash, final Bytes32 nodeHash, final Bytes node) {
trieBranchStorageTransaction.put(Bytes.EMPTY.toArrayUnsafe(), node.toArrayUnsafe());
trieBranchStorageTransaction.put(WORLD_ROOT_HASH_KEY, nodeHash.toArrayUnsafe());
trieBranchStorageTransaction.put(WORLD_BLOCK_HASH_KEY, blockHash.toArrayUnsafe());
return this;
}
@Override
public WorldStateStorage.Updater putAccountStateTrieNode(
final Bytes location, final Bytes32 nodeHash, final Bytes node) {
if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) {
// Don't save empty nodes
return this;
}
trieBranchStorageTransaction.put(location.toArrayUnsafe(), node.toArrayUnsafe());
return this;
}
@Override
public WorldStateStorage.Updater removeAccountStateTrieNode(
final Bytes location, final Bytes32 nodeHash) {
trieBranchStorageTransaction.remove(location.toArrayUnsafe());
return this;
}
@Override
public WorldStateStorage.Updater putAccountStorageTrieNode(
final Hash accountHash, final Bytes location, final Bytes32 nodeHash, final Bytes node) {
if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) {
// Don't save empty nodes
return this;
}
trieBranchStorageTransaction.put(
Bytes.concatenate(accountHash, location).toArrayUnsafe(), node.toArrayUnsafe());
return this;
}
@Override
public void commit() {
accountStorageTransaction.commit();
codeStorageTransaction.commit();
storageStorageTransaction.commit();
trieBranchStorageTransaction.commit();
trieLogStorageTransaction.commit();
}
@Override
public void rollback() {
// no-op
}
}
}

View File

@@ -26,13 +26,13 @@ public class BonsaiValue<T> {
private T updated;
private boolean cleared;
BonsaiValue(final T prior, final T updated) {
public BonsaiValue(final T prior, final T updated) {
this.prior = prior;
this.updated = updated;
this.cleared = false;
}
BonsaiValue(final T prior, final T updated, final boolean cleared) {
public BonsaiValue(final T prior, final T updated, final boolean cleared) {
this.prior = prior;
this.updated = updated;
this.cleared = cleared;
@@ -57,13 +57,13 @@ public class BonsaiValue<T> {
return this;
}
void writeRlp(final RLPOutput output, final BiConsumer<RLPOutput, T> writer) {
public void writeRlp(final RLPOutput output, final BiConsumer<RLPOutput, T> writer) {
output.startList();
writeInnerRlp(output, writer);
output.endList();
}
void writeInnerRlp(final RLPOutput output, final BiConsumer<RLPOutput, T> writer) {
public void writeInnerRlp(final RLPOutput output, final BiConsumer<RLPOutput, T> writer) {
if (prior == null) {
output.writeNull();
} else {
@@ -76,7 +76,7 @@ public class BonsaiValue<T> {
}
}
boolean isUnchanged() {
public boolean isUnchanged() {
return Objects.equals(updated, prior);
}

View File

@@ -16,22 +16,25 @@
package org.hyperledger.besu.ethereum.bonsai;
import static org.hyperledger.besu.datatypes.Hash.fromPlugin;
import static org.hyperledger.besu.ethereum.bonsai.LayeredTrieLogManager.RETAINED_LAYERS;
import static org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager.RETAINED_LAYERS;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer;
import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.SnapshotMutableWorldState;
import org.hyperledger.besu.ethereum.proof.WorldStateProof;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie;
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration;
import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat;
import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
@@ -50,21 +53,19 @@ import org.apache.tuweni.units.bigints.UInt256;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BonsaiWorldStateArchive implements WorldStateArchive {
public class BonsaiWorldStateProvider implements WorldStateArchive {
private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldStateArchive.class);
private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldStateProvider.class);
private final Blockchain blockchain;
private final TrieLogManager trieLogManager;
private final BonsaiPersistedWorldState persistedState;
private final BonsaiWorldState persistedState;
private final BonsaiWorldStateKeyValueStorage worldStateStorage;
private final CachedMerkleTrieLoader cachedMerkleTrieLoader;
private final boolean useSnapshots;
public BonsaiWorldStateArchive(
public BonsaiWorldStateProvider(
final StorageProvider provider,
final Blockchain blockchain,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
@@ -73,149 +74,107 @@ public class BonsaiWorldStateArchive implements WorldStateArchive {
provider.createWorldStateStorage(DataStorageFormat.BONSAI),
blockchain,
Optional.empty(),
provider.isWorldStateSnappable(),
cachedMerkleTrieLoader);
}
public BonsaiWorldStateArchive(
public BonsaiWorldStateProvider(
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Blockchain blockchain,
final Optional<Long> maxLayersToLoad,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
// overload while snapshots are an experimental option:
this(
worldStateStorage,
blockchain,
maxLayersToLoad,
DataStorageConfiguration.DEFAULT_BONSAI_USE_SNAPSHOTS,
cachedMerkleTrieLoader);
}
public BonsaiWorldStateArchive(
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Blockchain blockchain,
final Optional<Long> maxLayersToLoad,
final boolean useSnapshots,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
this(
useSnapshots
? new SnapshotTrieLogManager(
blockchain, worldStateStorage, maxLayersToLoad.orElse(RETAINED_LAYERS))
: new LayeredTrieLogManager(
blockchain, worldStateStorage, maxLayersToLoad.orElse(RETAINED_LAYERS)),
worldStateStorage,
blockchain,
useSnapshots,
cachedMerkleTrieLoader);
// TODO: de-dup constructors
this.trieLogManager =
new CachedWorldStorageManager(
this, blockchain, worldStateStorage, maxLayersToLoad.orElse(RETAINED_LAYERS));
this.blockchain = blockchain;
this.worldStateStorage = worldStateStorage;
this.persistedState = new BonsaiWorldState(this, worldStateStorage);
this.cachedMerkleTrieLoader = cachedMerkleTrieLoader;
blockchain
.getBlockHeader(persistedState.worldStateBlockHash)
.ifPresent(
blockHeader -> {
this.trieLogManager.addCachedLayer(
blockHeader, persistedState.worldStateRootHash, persistedState);
});
}
@VisibleForTesting
BonsaiWorldStateArchive(
BonsaiWorldStateProvider(
final TrieLogManager trieLogManager,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Blockchain blockchain,
final boolean useSnapshots,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
this.trieLogManager = trieLogManager;
this.blockchain = blockchain;
this.worldStateStorage = worldStateStorage;
this.persistedState = new BonsaiPersistedWorldState(this, worldStateStorage);
// TODO: https://github.com/hyperledger/besu/issues/4641
// useSnapshots is disabled for now
this.useSnapshots = false;
this.persistedState = new BonsaiWorldState(this, worldStateStorage);
this.cachedMerkleTrieLoader = cachedMerkleTrieLoader;
blockchain.observeBlockAdded(this::blockAddedHandler);
}
private void blockAddedHandler(final BlockAddedEvent event) {
LOG.debug("New block add event {}", event);
if (event.isNewCanonicalHead()) {
final BlockHeader eventBlockHeader = event.getBlock().getHeader();
trieLogManager.updateCachedLayers(
eventBlockHeader.getParentHash(), eventBlockHeader.getHash());
}
blockchain
.getBlockHeader(persistedState.worldStateBlockHash)
.ifPresent(
blockHeader -> {
this.trieLogManager.addCachedLayer(
blockHeader, persistedState.worldStateRootHash, persistedState);
});
}
@Override
public Optional<WorldState> get(final Hash rootHash, final Hash blockHash) {
final Optional<MutableWorldState> layeredWorldState =
trieLogManager.getBonsaiCachedWorldState(blockHash);
if (layeredWorldState.isPresent()) {
return Optional.of(layeredWorldState.get());
} else if (rootHash.equals(persistedState.blockHash())) {
return Optional.of(persistedState);
} else {
return Optional.empty();
}
return trieLogManager
.getWorldState(blockHash)
.or(
() -> {
if (blockHash.equals(persistedState.blockHash())) {
return Optional.of(persistedState);
} else {
return Optional.empty();
}
})
.map(WorldState.class::cast);
}
@Override
public boolean isWorldStateAvailable(final Hash rootHash, final Hash blockHash) {
return trieLogManager.getBonsaiCachedWorldState(blockHash).isPresent()
return trieLogManager.containWorldStateStorage(blockHash)
|| persistedState.blockHash().equals(blockHash)
|| worldStateStorage.isWorldStateAvailable(rootHash, blockHash);
}
public Optional<MutableWorldState> getMutableSnapshot(final Hash blockHash) {
return rollMutableStateToBlockHash(
BonsaiSnapshotWorldState.create(this, worldStateStorage), blockHash)
.map(SnapshotMutableWorldState.class::cast);
}
@Override
public Optional<MutableWorldState> getMutable(
final Hash rootHash, final Hash blockHash, final boolean shouldPersistState) {
final BlockHeader blockHeader, final boolean shouldPersistState) {
if (shouldPersistState) {
return getMutable(rootHash, blockHash);
return getMutable(blockHeader.getStateRoot(), blockHeader.getHash());
} else {
final BlockHeader chainHeadBlockHeader = blockchain.getChainHeadHeader();
if (chainHeadBlockHeader.getNumber() - blockHeader.getNumber()
>= trieLogManager.getMaxLayersToLoad()) {
LOG.warn(
"Exceeded the limit of back layers that can be loaded ({})",
trieLogManager.getMaxLayersToLoad());
return Optional.empty();
}
return trieLogManager
.getBonsaiCachedWorldState(blockHash)
.or(
() ->
blockchain
.getBlockHeader(blockHash)
.filter(
header -> {
if (blockchain.getChainHeadHeader().getNumber() - header.getNumber()
>= trieLogManager.getMaxLayersToLoad()) {
LOG.warn(
"Exceeded the limit of back layers that can be loaded ({})",
trieLogManager.getMaxLayersToLoad());
return false;
}
return true;
})
.flatMap(header -> snapshotOrLayeredWorldState(blockHash, header)));
}
}
private Optional<MutableWorldState> snapshotOrLayeredWorldState(
final Hash blockHash, final BlockHeader blockHeader) {
if (useSnapshots) {
// use snapshots:
return getMutableSnapshot(blockHash);
} else {
// otherwise use layered worldstate:
final Optional<TrieLogLayer> trieLogLayer = trieLogManager.getTrieLogLayer(blockHash);
return trieLogLayer.map(
layer ->
new BonsaiLayeredWorldState(
blockchain,
this,
Optional.empty(),
blockHeader.getNumber(),
fromPlugin(blockHeader.getStateRoot()),
layer));
.getWorldState(blockHeader.getHash())
.or(() -> trieLogManager.getNearestWorldState(blockHeader))
.or(() -> trieLogManager.getHeadWorldState(blockchain::getBlockHeader))
.flatMap(
bonsaiWorldState ->
rollMutableStateToBlockHash(bonsaiWorldState, blockHeader.getHash()))
.map(MutableWorldState::freeze);
}
}
@Override
public Optional<MutableWorldState> getMutable(final Hash rootHash, final Hash blockHash) {
public synchronized Optional<MutableWorldState> getMutable(
final Hash rootHash, final Hash blockHash) {
return rollMutableStateToBlockHash(persistedState, blockHash);
}
Optional<MutableWorldState> rollMutableStateToBlockHash(
final BonsaiPersistedWorldState mutableState, final Hash blockHash) {
final BonsaiWorldState mutableState, final Hash blockHash) {
if (blockHash.equals(mutableState.blockHash())) {
return Optional.of(mutableState);
} else {
@@ -264,21 +223,26 @@ public class BonsaiWorldStateArchive implements WorldStateArchive {
}
// attempt the state rolling
final BonsaiWorldStateUpdater bonsaiUpdater = getUpdaterFromPersistedState(mutableState);
final BonsaiWorldStateUpdateAccumulator bonsaiUpdater =
(BonsaiWorldStateUpdateAccumulator) mutableState.updater();
try {
for (final TrieLogLayer rollBack : rollBacks) {
LOG.debug("Attempting Rollback of {}", rollBack.getBlockHash());
bonsaiUpdater.rollBack(rollBack);
}
for (int i = rollForwards.size() - 1; i >= 0; i--) {
final var forward = rollForwards.get(i);
LOG.debug("Attempting Rollforward of {}", rollForwards.get(i).getBlockHash());
bonsaiUpdater.rollForward(rollForwards.get(i));
bonsaiUpdater.rollForward(forward);
}
bonsaiUpdater.commit();
mutableState.persist(blockchain.getBlockHeader(blockHash).get());
LOG.debug("Archive rolling finished, now at {}", blockHash);
LOG.debug(
"Archive rolling finished, {} now at {}",
mutableState.getWorldStateStorage().getClass().getSimpleName(),
blockHash);
return Optional.of(mutableState);
} catch (final MerkleTrieException re) {
// need to throw to trigger the heal
@@ -286,25 +250,27 @@ public class BonsaiWorldStateArchive implements WorldStateArchive {
} catch (final Exception e) {
// if we fail we must clean up the updater
bonsaiUpdater.reset();
LOG.debug("State rolling failed for block hash " + blockHash, e);
LOG.debug(
"State rolling failed on "
+ mutableState.getWorldStateStorage().getClass().getSimpleName()
+ " for block hash "
+ blockHash,
e);
return Optional.empty();
}
} catch (final RuntimeException re) {
LOG.trace("Archive rolling failed for block hash " + blockHash, re);
LOG.info("Archive rolling failed for block hash " + blockHash, re);
if (re instanceof MerkleTrieException) {
// need to throw to trigger the heal
throw re;
}
return Optional.empty();
throw new MerkleTrieException(
"invalid", Optional.of(Address.ZERO), Hash.EMPTY, Bytes.EMPTY);
}
}
}
BonsaiWorldStateUpdater getUpdaterFromPersistedState(
final BonsaiPersistedWorldState mutableState) {
return (BonsaiWorldStateUpdater) mutableState.updater();
}
public CachedMerkleTrieLoader getCachedMerkleTrieLoader() {
return cachedMerkleTrieLoader;
}
@@ -387,4 +353,13 @@ public class BonsaiWorldStateArchive implements WorldStateArchive {
// FIXME we can do proofs for layered tries and the persisted trie
return Optional.empty();
}
@Override
public void close() {
try {
worldStateStorage.close();
} catch (Exception e) {
// no op
}
}
}

View File

@@ -1,115 +0,0 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.bonsai;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LayeredTrieLogManager extends AbstractTrieLogManager<BonsaiLayeredWorldState> {
private static final Logger LOG = LoggerFactory.getLogger(LayeredTrieLogManager.class);
LayeredTrieLogManager(
final Blockchain blockchain,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final long maxLayersToLoad,
final Map<Bytes32, CachedWorldState<BonsaiLayeredWorldState>> cachedWorldStatesByHash) {
super(blockchain, worldStateStorage, maxLayersToLoad, cachedWorldStatesByHash);
}
public LayeredTrieLogManager(
final Blockchain blockchain,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final long maxLayersToLoad) {
this(blockchain, worldStateStorage, maxLayersToLoad, new HashMap<>());
}
@Override
public synchronized void addCachedLayer(
final BlockHeader blockHeader,
final Hash worldStateRootHash,
final TrieLogLayer trieLog,
final BonsaiWorldStateArchive worldStateArchive,
final BonsaiPersistedWorldState forWorldState) {
final BonsaiLayeredWorldState bonsaiLayeredWorldState =
new BonsaiLayeredWorldState(
blockchain,
worldStateArchive,
Optional.of(forWorldState),
blockHeader.getNumber(),
worldStateRootHash,
trieLog);
LOG.atDebug()
.setMessage("adding layered world state for block {}, state root hash {}")
.addArgument(blockHeader::toLogString)
.addArgument(worldStateRootHash::toShortHexString)
.log();
cachedWorldStatesByHash.put(
blockHeader.getHash(), new LayeredWorldStateCache(bonsaiLayeredWorldState));
}
@Override
public synchronized void updateCachedLayers(final Hash blockParentHash, final Hash blockHash) {
cachedWorldStatesByHash.computeIfPresent(
blockParentHash,
(parentHash, bonsaiLayeredWorldState) -> {
if (cachedWorldStatesByHash.containsKey(blockHash)) {
bonsaiLayeredWorldState
.getMutableWorldState()
.setNextWorldView(
Optional.of(cachedWorldStatesByHash.get(blockHash).getMutableWorldState()));
}
return bonsaiLayeredWorldState;
});
}
public static class LayeredWorldStateCache implements CachedWorldState<BonsaiLayeredWorldState> {
final BonsaiLayeredWorldState layeredWorldState;
public LayeredWorldStateCache(final BonsaiLayeredWorldState layeredWorldState) {
this.layeredWorldState = layeredWorldState;
}
@Override
public void dispose() {
// no-op
}
@Override
public long getHeight() {
return layeredWorldState.getHeight();
}
@Override
public TrieLogLayer getTrieLog() {
return layeredWorldState.getTrieLog();
}
@Override
public BonsaiLayeredWorldState getMutableWorldState() {
return layeredWorldState;
}
}
}

View File

@@ -1,157 +0,0 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SnapshotTrieLogManager extends AbstractTrieLogManager<BonsaiSnapshotWorldState>
implements BonsaiStorageSubscriber {
private static final Logger LOG = LoggerFactory.getLogger(SnapshotTrieLogManager.class);
public SnapshotTrieLogManager(
final Blockchain blockchain,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final long maxLayersToLoad) {
this(blockchain, worldStateStorage, maxLayersToLoad, new ConcurrentHashMap<>());
}
SnapshotTrieLogManager(
final Blockchain blockchain,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final long maxLayersToLoad,
final Map<Bytes32, CachedWorldState<BonsaiSnapshotWorldState>> cachedWorldStatesByHash) {
super(blockchain, worldStateStorage, maxLayersToLoad, cachedWorldStatesByHash);
worldStateStorage.subscribe(this);
}
@Override
protected void addCachedLayer(
final BlockHeader blockHeader,
final Hash worldStateRootHash,
final TrieLogLayer trieLog,
final BonsaiWorldStateArchive worldStateArchive,
final BonsaiPersistedWorldState worldState) {
LOG.atDebug()
.setMessage("adding snapshot world state for block {}, state root hash {}")
.addArgument(blockHeader::toLogString)
.addArgument(worldStateRootHash::toShortHexString)
.log();
// TODO: add a generic param so we don't have to cast:
BonsaiSnapshotWorldState snapshotWorldState;
if (worldState instanceof BonsaiSnapshotWorldState) {
snapshotWorldState = (BonsaiSnapshotWorldState) worldState;
} else {
snapshotWorldState =
BonsaiSnapshotWorldState.create(worldStateArchive, rootWorldStateStorage);
}
cachedWorldStatesByHash.put(
blockHeader.getHash(),
new CachedSnapshotWorldState(snapshotWorldState, trieLog, blockHeader.getNumber()));
}
@Override
public void updateCachedLayers(final Hash blockParentHash, final Hash blockHash) {
// no-op.
}
@Override
public synchronized Optional<MutableWorldState> getBonsaiCachedWorldState(final Hash blockHash) {
if (cachedWorldStatesByHash.containsKey(blockHash)) {
return Optional.ofNullable(cachedWorldStatesByHash.get(blockHash))
.map(CachedWorldState::getMutableWorldState)
.map(MutableWorldState::copy);
}
return Optional.empty();
}
@Override
public synchronized void onClearStorage() {
dropArchive();
}
@Override
public synchronized void onClearFlatDatabaseStorage() {
dropArchive();
}
@Override
public void onClearTrieLog() {
dropArchive();
}
private void dropArchive() {
// drop all cached snapshot worldstates, they are unsafe when the db has been truncated
LOG.info("Key-value storage truncated, dropping cached worldstates");
cachedWorldStatesByHash.clear();
}
public static class CachedSnapshotWorldState
implements CachedWorldState<BonsaiSnapshotWorldState>, BonsaiStorageSubscriber {
final BonsaiSnapshotWorldState snapshot;
final Long snapshotSubscriberId;
final TrieLogLayer trieLog;
final long height;
final AtomicBoolean isClosed = new AtomicBoolean(false);
public CachedSnapshotWorldState(
final BonsaiSnapshotWorldState snapshot, final TrieLogLayer trieLog, final long height) {
this.snapshotSubscriberId = snapshot.getWorldStateStorage().subscribe(this);
this.snapshot = snapshot;
this.trieLog = trieLog;
this.height = height;
}
@Override
public void dispose() {
snapshot.worldStateStorage.unSubscribe(snapshotSubscriberId);
}
@Override
public long getHeight() {
return height;
}
@Override
public TrieLogLayer getTrieLog() {
return trieLog;
}
@Override
public synchronized BonsaiSnapshotWorldState getMutableWorldState() {
if (isClosed.get()) {
return null;
}
return snapshot;
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright Hyperledger Besu contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai.cache;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CachedBonsaiWorldView implements BonsaiStorageSubscriber {
private BonsaiWorldStateKeyValueStorage worldStateStorage;
private final BlockHeader blockHeader;
private long worldViewSubscriberId;
private static final Logger LOG = LoggerFactory.getLogger(CachedBonsaiWorldView.class);
public CachedBonsaiWorldView(
final BlockHeader blockHeader, final BonsaiWorldStateKeyValueStorage worldView) {
this.blockHeader = blockHeader;
this.worldStateStorage = worldView;
this.worldViewSubscriberId = worldStateStorage.subscribe(this);
}
public BonsaiWorldStateKeyValueStorage getWorldStateStorage() {
return worldStateStorage;
}
public long getBlockNumber() {
return blockHeader.getNumber();
}
public Hash getBlockHash() {
return blockHeader.getHash();
}
public synchronized void close() {
worldStateStorage.unSubscribe(this.worldViewSubscriberId);
try {
worldStateStorage.close();
} catch (final Exception e) {
LOG.warn("Failed to close worldstate storage for block " + blockHeader.toLogString(), e);
}
}
public synchronized void updateWorldStateStorage(
final BonsaiWorldStateKeyValueStorage newWorldStateStorage) {
long newSubscriberId = newWorldStateStorage.subscribe(this);
this.worldStateStorage.unSubscribe(this.worldViewSubscriberId);
BonsaiWorldStateKeyValueStorage oldWorldStateStorage = this.worldStateStorage;
this.worldStateStorage = newWorldStateStorage;
this.worldViewSubscriberId = newSubscriberId;
try {
oldWorldStateStorage.close();
} catch (final Exception e) {
LOG.warn(
"During update, failed to close prior worldstate storage for block "
+ blockHeader.toLogString(),
e);
}
}
}

View File

@@ -13,11 +13,12 @@
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
package org.hyperledger.besu.ethereum.bonsai.cache;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie;

View File

@@ -0,0 +1,183 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.bonsai.cache;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiSnapshotWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateLayerStorage;
import org.hyperledger.besu.ethereum.bonsai.trielog.AbstractTrieLogManager;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CachedWorldStorageManager extends AbstractTrieLogManager
implements BonsaiStorageSubscriber {
private static final Logger LOG = LoggerFactory.getLogger(CachedWorldStorageManager.class);
private final BonsaiWorldStateProvider archive;
CachedWorldStorageManager(
final BonsaiWorldStateProvider archive,
final Blockchain blockchain,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final long maxLayersToLoad,
final Map<Bytes32, CachedBonsaiWorldView> cachedWorldStatesByHash) {
super(blockchain, worldStateStorage, maxLayersToLoad, cachedWorldStatesByHash);
worldStateStorage.subscribe(this);
this.archive = archive;
}
public CachedWorldStorageManager(
final BonsaiWorldStateProvider archive,
final Blockchain blockchain,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final long maxLayersToLoad) {
this(archive, blockchain, worldStateStorage, maxLayersToLoad, new ConcurrentHashMap<>());
}
@Override
public synchronized void addCachedLayer(
final BlockHeader blockHeader,
final Hash worldStateRootHash,
final BonsaiWorldState forWorldState) {
final Optional<CachedBonsaiWorldView> cachedBonsaiWorldView =
Optional.ofNullable(this.cachedWorldStatesByHash.get(blockHeader.getBlockHash()));
if (cachedBonsaiWorldView.isPresent()) {
// only replace if it is a layered storage
if (forWorldState.isPersisted()
&& cachedBonsaiWorldView.get().getWorldStateStorage()
instanceof BonsaiWorldStateLayerStorage) {
LOG.atDebug()
.setMessage("updating layered world state for block {}, state root hash {}")
.addArgument(blockHeader::toLogString)
.addArgument(worldStateRootHash::toShortHexString)
.log();
cachedBonsaiWorldView
.get()
.updateWorldStateStorage(
new BonsaiSnapshotWorldStateKeyValueStorage(forWorldState.worldStateStorage));
}
} else {
LOG.atDebug()
.setMessage("adding layered world state for block {}, state root hash {}")
.addArgument(blockHeader::toLogString)
.addArgument(worldStateRootHash::toShortHexString)
.log();
if (forWorldState.isPersisted()) {
cachedWorldStatesByHash.put(
blockHeader.getHash(),
new CachedBonsaiWorldView(
blockHeader,
new BonsaiSnapshotWorldStateKeyValueStorage(forWorldState.worldStateStorage)));
} else {
// otherwise, add the layer to the cache
cachedWorldStatesByHash.put(
blockHeader.getHash(),
new CachedBonsaiWorldView(
blockHeader,
((BonsaiWorldStateLayerStorage) forWorldState.getWorldStateStorage()).clone()));
}
}
scrubCachedLayers(blockHeader.getNumber());
}
@Override
public Optional<BonsaiWorldState> getWorldState(final Hash blockHash) {
if (cachedWorldStatesByHash.containsKey(blockHash)) {
// return a new worldstate using worldstate storage and an isolated copy of the updater
return Optional.ofNullable(cachedWorldStatesByHash.get(blockHash))
.map(
cached ->
new BonsaiWorldState(
archive, new BonsaiWorldStateLayerStorage(cached.getWorldStateStorage())));
}
return Optional.empty();
}
@Override
public Optional<BonsaiWorldState> getNearestWorldState(final BlockHeader blockHeader) {
return Optional.ofNullable(
cachedWorldStatesByHash.get(blockHeader.getParentHash())) // search parent block
.map(CachedBonsaiWorldView::getWorldStateStorage)
.or(
() -> {
// or else search the nearest state in the cache
final List<CachedBonsaiWorldView> cachedBonsaiWorldViews =
new ArrayList<>(cachedWorldStatesByHash.values());
return cachedBonsaiWorldViews.stream()
.sorted(
Comparator.comparingLong(
view -> Math.abs(blockHeader.getNumber() - view.getBlockNumber())))
.map(CachedBonsaiWorldView::getWorldStateStorage)
.findFirst();
})
.map(
storage ->
new BonsaiWorldState( // wrap the state in a layered worldstate
archive, new BonsaiWorldStateLayerStorage(storage)));
}
@Override
public Optional<BonsaiWorldState> getHeadWorldState(
final Function<Hash, Optional<BlockHeader>> hashBlockHeaderFunction) {
return rootWorldStateStorage
.getWorldStateBlockHash()
.flatMap(hashBlockHeaderFunction)
.flatMap(
blockHeader -> {
// add the head to the cache
addCachedLayer(
blockHeader,
blockHeader.getStateRoot(),
new BonsaiWorldState(archive, rootWorldStateStorage));
return getWorldState(blockHeader.getHash());
});
}
@Override
public void onClearStorage() {
this.cachedWorldStatesByHash.clear();
}
@Override
public void onClearFlatDatabaseStorage() {
this.cachedWorldStatesByHash.clear();
}
@Override
public void onClearTrieLog() {
this.cachedWorldStatesByHash.clear();
}
@Override
public void onCloseStorage() {
this.cachedWorldStatesByHash.clear();
}
}

View File

@@ -0,0 +1,217 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai.storage;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.plugin.services.exception.StorageException;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage;
import java.util.Optional;
import java.util.function.Supplier;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
public class BonsaiSnapshotWorldStateKeyValueStorage extends BonsaiWorldStateKeyValueStorage
implements BonsaiStorageSubscriber {
protected final BonsaiWorldStateKeyValueStorage parentWorldStateStorage;
private final long subscribeParentId;
public BonsaiSnapshotWorldStateKeyValueStorage(
final BonsaiWorldStateKeyValueStorage parentWorldStateStorage,
final SnappedKeyValueStorage accountStorage,
final SnappedKeyValueStorage codeStorage,
final SnappedKeyValueStorage storageStorage,
final SnappedKeyValueStorage trieBranchStorage,
final KeyValueStorage trieLogStorage) {
super(accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage);
this.parentWorldStateStorage = parentWorldStateStorage;
this.subscribeParentId = parentWorldStateStorage.subscribe(this);
}
public BonsaiSnapshotWorldStateKeyValueStorage(
final BonsaiWorldStateKeyValueStorage worldStateStorage) {
this(
worldStateStorage,
((SnappableKeyValueStorage) worldStateStorage.accountStorage).takeSnapshot(),
((SnappableKeyValueStorage) worldStateStorage.codeStorage).takeSnapshot(),
((SnappableKeyValueStorage) worldStateStorage.storageStorage).takeSnapshot(),
((SnappableKeyValueStorage) worldStateStorage.trieBranchStorage).takeSnapshot(),
worldStateStorage.trieLogStorage);
}
@Override
public BonsaiUpdater updater() {
return new Updater(
((SnappedKeyValueStorage) accountStorage).getSnapshotTransaction(),
((SnappedKeyValueStorage) codeStorage).getSnapshotTransaction(),
((SnappedKeyValueStorage) storageStorage).getSnapshotTransaction(),
((SnappedKeyValueStorage) trieBranchStorage).getSnapshotTransaction(),
trieLogStorage.startTransaction());
}
@Override
public Optional<Bytes> getAccount(final Hash accountHash) {
return isClosed.get() ? Optional.empty() : super.getAccount(accountHash);
}
@Override
public Optional<Bytes> getCode(final Bytes32 codeHash, final Hash accountHash) {
return isClosed.get() ? Optional.empty() : super.getCode(codeHash, accountHash);
}
@Override
public Optional<Bytes> getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) {
return isClosed.get() ? Optional.empty() : super.getAccountStateTrieNode(location, nodeHash);
}
@Override
public Optional<Bytes> getAccountStorageTrieNode(
final Hash accountHash, final Bytes location, final Bytes32 nodeHash) {
return isClosed.get()
? Optional.empty()
: super.getAccountStorageTrieNode(accountHash, location, nodeHash);
}
@Override
public Optional<byte[]> getTrieLog(final Hash blockHash) {
return isClosed.get() ? Optional.empty() : super.getTrieLog(blockHash);
}
@Override
public Optional<Bytes> getStateTrieNode(final Bytes location) {
return isClosed.get() ? Optional.empty() : super.getStateTrieNode(location);
}
@Override
public Optional<Bytes> getWorldStateRootHash() {
return isClosed.get() ? Optional.empty() : super.getWorldStateRootHash();
}
@Override
public Optional<Hash> getWorldStateBlockHash() {
return isClosed.get() ? Optional.empty() : super.getWorldStateBlockHash();
}
@Override
public Optional<Bytes> getStorageValueBySlotHash(final Hash accountHash, final Hash slotHash) {
return isClosed.get()
? Optional.empty()
: super.getStorageValueBySlotHash(accountHash, slotHash);
}
@Override
public Optional<Bytes> getStorageValueBySlotHash(
final Supplier<Optional<Hash>> storageRootSupplier,
final Hash accountHash,
final Hash slotHash) {
return isClosed.get()
? Optional.empty()
: super.getStorageValueBySlotHash(storageRootSupplier, accountHash, slotHash);
}
@Override
public boolean isWorldStateAvailable(final Bytes32 rootHash, final Hash blockHash) {
return !isClosed.get() && super.isWorldStateAvailable(rootHash, blockHash);
}
@Override
public void clear() {
// snapshot storage does not implement clear
throw new StorageException("Snapshot storage does not implement clear");
}
@Override
public void clearFlatDatabase() {
// snapshot storage does not implement clear
throw new StorageException("Snapshot storage does not implement clear");
}
@Override
public void clearTrieLog() {
// snapshot storage does not implement clear
throw new StorageException("Snapshot storage does not implement clear");
}
@Override
public void onCloseStorage() {
try {
// when the parent storage clears, close regardless of subscribers
doClose();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void onClearStorage() {
try {
// when the parent storage clears, close regardless of subscribers
doClose();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void onClearFlatDatabaseStorage() {
// when the parent storage clears, close regardless of subscribers
try {
doClose();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void onClearTrieLog() {
// when the parent storage clears, close regardless of subscribers
try {
doClose();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected synchronized void doClose() throws Exception {
if (!isClosed.get()) {
// alert any subscribers we are closing:
subscribers.forEach(BonsaiStorageSubscriber::onCloseStorage);
// close all of the SnappedKeyValueStorages:
accountStorage.close();
codeStorage.close();
storageStorage.close();
trieBranchStorage.close();
// unsubscribe the parent worldstate
parentWorldStateStorage.unSubscribe(subscribeParentId);
// set storage closed
isClosed.set(true);
}
}
public BonsaiWorldStateKeyValueStorage getParentWorldStateStorage() {
return parentWorldStateStorage;
}
}

View File

@@ -12,7 +12,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.bonsai;
package org.hyperledger.besu.ethereum.bonsai.storage;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
@@ -28,6 +28,7 @@ import org.hyperledger.besu.util.Subscribers;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -35,8 +36,13 @@ import java.util.function.Supplier;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.rlp.RLP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldStateKeyValueStorage.class);
// 0x776f726c64526f6f74
public static final byte[] WORLD_ROOT_HASH_KEY = "worldRoot".getBytes(StandardCharsets.UTF_8);
// 0x776f726c64426c6f636b48617368
@@ -48,6 +54,11 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
protected final KeyValueStorage storageStorage;
protected final KeyValueStorage trieBranchStorage;
protected final KeyValueStorage trieLogStorage;
private final AtomicBoolean shouldClose = new AtomicBoolean(false);
protected final AtomicBoolean isClosed = new AtomicBoolean(false);
protected final Subscribers<BonsaiStorageSubscriber> subscribers = Subscribers.create();
public BonsaiWorldStateKeyValueStorage(final StorageProvider provider) {
@@ -144,8 +155,8 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
return trieBranchStorage.get(WORLD_ROOT_HASH_KEY).map(Bytes::wrap);
}
public Optional<Bytes> getWorldStateBlockHash() {
return trieBranchStorage.get(WORLD_BLOCK_HASH_KEY).map(Bytes::wrap);
public Optional<Hash> getWorldStateBlockHash() {
return trieBranchStorage.get(WORLD_BLOCK_HASH_KEY).map(Bytes32::wrap).map(Hash::wrap);
}
public Optional<Bytes> getStorageValueBySlotHash(final Hash accountHash, final Hash slotHash) {
@@ -249,19 +260,6 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
throw new RuntimeException("removeNodeAddedListener not available");
}
public synchronized long subscribe(final BonsaiStorageSubscriber sub) {
return subscribers.subscribe(sub);
}
public synchronized void unSubscribe(final long id) {
subscribers.unsubscribe(id);
}
@Override
public void close() throws Exception {
// No need to close or notify because BonsaiWorldStateKeyValueStorage is persistent
}
public interface BonsaiUpdater extends WorldStateStorage.Updater {
BonsaiUpdater removeCode(final Hash accountHash);
@@ -360,7 +358,7 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
}
@Override
public BonsaiUpdater putAccountStorageTrieNode(
public synchronized BonsaiUpdater putAccountStorageTrieNode(
final Hash accountHash, final Bytes location, final Bytes32 nodeHash, final Bytes node) {
if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) {
// Don't save empty nodes
@@ -414,7 +412,56 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
}
}
interface BonsaiStorageSubscriber {
@Override
public synchronized void close() throws Exception {
// when the storage clears, close
shouldClose.set(true);
tryClose();
}
public synchronized long subscribe(final BonsaiStorageSubscriber sub) {
if (isClosed.get()) {
throw new RuntimeException("Storage is marked to close or has already closed");
}
return subscribers.subscribe(sub);
}
public synchronized void unSubscribe(final long id) {
subscribers.unsubscribe(id);
try {
tryClose();
} catch (Exception e) {
LOG.atWarn()
.setMessage("exception while trying to close : {}")
.addArgument(e::getMessage)
.log();
}
}
protected synchronized void tryClose() throws Exception {
if (shouldClose.get() && subscribers.getSubscriberCount() < 1) {
doClose();
}
}
protected synchronized void doClose() throws Exception {
if (!isClosed.get()) {
// alert any subscribers we are closing:
subscribers.forEach(BonsaiStorageSubscriber::onCloseStorage);
// close all of the KeyValueStorages:
accountStorage.close();
codeStorage.close();
storageStorage.close();
trieBranchStorage.close();
trieLogStorage.close();
// set storage closed
isClosed.set(true);
}
}
public interface BonsaiStorageSubscriber {
default void onClearStorage() {}
default void onClearFlatDatabaseStorage() {}

View File

@@ -0,0 +1,56 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai.storage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage;
import org.hyperledger.besu.services.kvstore.LayeredKeyValueStorage;
public class BonsaiWorldStateLayerStorage extends BonsaiSnapshotWorldStateKeyValueStorage
implements BonsaiStorageSubscriber {
public BonsaiWorldStateLayerStorage(final BonsaiWorldStateKeyValueStorage parent) {
this(
new LayeredKeyValueStorage(parent.accountStorage),
new LayeredKeyValueStorage(parent.codeStorage),
new LayeredKeyValueStorage(parent.storageStorage),
new LayeredKeyValueStorage(parent.trieBranchStorage),
parent.trieLogStorage,
parent);
}
public BonsaiWorldStateLayerStorage(
final SnappedKeyValueStorage accountStorage,
final SnappedKeyValueStorage codeStorage,
final SnappedKeyValueStorage storageStorage,
final SnappedKeyValueStorage trieBranchStorage,
final KeyValueStorage trieLogStorage,
final BonsaiWorldStateKeyValueStorage parent) {
super(parent, accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage);
}
@Override
public BonsaiWorldStateLayerStorage clone() {
return new BonsaiWorldStateLayerStorage(
((LayeredKeyValueStorage) accountStorage).clone(),
((LayeredKeyValueStorage) codeStorage).clone(),
((LayeredKeyValueStorage) storageStorage).clone(),
((LayeredKeyValueStorage) trieBranchStorage).clone(),
trieLogStorage,
parentWorldStateStorage);
}
}

View File

@@ -13,40 +13,41 @@
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
package org.hyperledger.besu.ethereum.bonsai.trielog;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.BonsaiUpdater;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedBonsaiWorldView;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiUpdater;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.google.common.annotations.VisibleForTesting;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractTrieLogManager<T extends MutableWorldState>
implements TrieLogManager {
public abstract class AbstractTrieLogManager implements TrieLogManager {
private static final Logger LOG = LoggerFactory.getLogger(AbstractTrieLogManager.class);
public static final long RETAINED_LAYERS = 512; // at least 256 + typical rollbacks
protected final Blockchain blockchain;
protected final BonsaiWorldStateKeyValueStorage rootWorldStateStorage;
protected final Map<Bytes32, CachedWorldState<T>> cachedWorldStatesByHash;
protected final Map<Bytes32, CachedBonsaiWorldView> cachedWorldStatesByHash;
protected final long maxLayersToLoad;
AbstractTrieLogManager(
protected AbstractTrieLogManager(
final Blockchain blockchain,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final long maxLayersToLoad,
final Map<Bytes32, CachedWorldState<T>> cachedWorldStatesByHash) {
final Map<Bytes32, CachedBonsaiWorldView> cachedWorldStatesByHash) {
this.blockchain = blockchain;
this.rootWorldStateStorage = worldStateStorage;
this.cachedWorldStatesByHash = cachedWorldStatesByHash;
@@ -55,11 +56,10 @@ public abstract class AbstractTrieLogManager<T extends MutableWorldState>
@Override
public synchronized void saveTrieLog(
final BonsaiWorldStateArchive worldStateArchive,
final BonsaiWorldStateUpdater localUpdater,
final BonsaiWorldStateUpdateAccumulator localUpdater,
final Hash forWorldStateRootHash,
final BlockHeader forBlockHeader,
final BonsaiPersistedWorldState forWorldState) {
final BonsaiWorldState forWorldState) {
// do not overwrite a trielog layer that already exists in the database.
// if it's only in memory we need to save it
// for example, in case of reorg we don't replace a trielog layer
@@ -67,13 +67,7 @@ public abstract class AbstractTrieLogManager<T extends MutableWorldState>
final BonsaiUpdater stateUpdater = forWorldState.getWorldStateStorage().updater();
boolean success = false;
try {
final TrieLogLayer trieLog =
prepareTrieLog(
forBlockHeader,
forWorldStateRootHash,
localUpdater,
worldStateArchive,
forWorldState);
final TrieLogLayer trieLog = prepareTrieLog(forBlockHeader, localUpdater);
persistTrieLog(forBlockHeader, forWorldStateRootHash, trieLog, stateUpdater);
success = true;
} finally {
@@ -86,52 +80,28 @@ public abstract class AbstractTrieLogManager<T extends MutableWorldState>
}
}
protected abstract void addCachedLayer(
final BlockHeader blockHeader,
final Hash worldStateRootHash,
final TrieLogLayer trieLog,
final BonsaiWorldStateArchive worldStateArchive,
final BonsaiPersistedWorldState forWorldState);
@VisibleForTesting
TrieLogLayer prepareTrieLog(
final BlockHeader forBlockHeader,
final Hash forWorldStateRootHash,
final BonsaiWorldStateUpdater localUpdater,
final BonsaiWorldStateArchive worldStateArchive,
final BonsaiPersistedWorldState forWorldState) {
final BlockHeader blockHeader, final BonsaiWorldStateUpdateAccumulator localUpdater) {
LOG.atDebug()
.setMessage("Adding layered world state for {}")
.addArgument(forBlockHeader::toLogString)
.addArgument(blockHeader::toLogString)
.log();
final TrieLogLayer trieLog = localUpdater.generateTrieLog(forBlockHeader.getBlockHash());
final TrieLogLayer trieLog = localUpdater.generateTrieLog(blockHeader.getBlockHash());
trieLog.freeze();
addCachedLayer(
forBlockHeader, forWorldStateRootHash, trieLog, worldStateArchive, forWorldState);
scrubCachedLayers(forBlockHeader.getNumber());
return trieLog;
}
synchronized void scrubCachedLayers(final long newMaxHeight) {
public synchronized void scrubCachedLayers(final long newMaxHeight) {
if (cachedWorldStatesByHash.size() > RETAINED_LAYERS) {
final long waterline = newMaxHeight - RETAINED_LAYERS;
cachedWorldStatesByHash.values().stream()
.filter(layer -> layer.getHeight() < waterline)
.collect(Collectors.toList())
.stream()
.filter(layer -> layer.getBlockNumber() < waterline)
.toList()
.forEach(
layer -> {
cachedWorldStatesByHash.remove(layer.getTrieLog().getBlockHash());
layer.dispose();
Optional.ofNullable(layer.getMutableWorldState())
.ifPresent(
ws -> {
try {
ws.close();
} catch (Exception e) {
LOG.warn("Error closing bonsai worldstate layer", e);
}
});
cachedWorldStatesByHash.remove(layer.getBlockHash());
layer.close();
});
}
}
@@ -154,12 +124,8 @@ public abstract class AbstractTrieLogManager<T extends MutableWorldState>
}
@Override
public Optional<MutableWorldState> getBonsaiCachedWorldState(final Hash blockHash) {
if (cachedWorldStatesByHash.containsKey(blockHash)) {
return Optional.ofNullable(cachedWorldStatesByHash.get(blockHash))
.map(CachedWorldState::getMutableWorldState);
}
return Optional.empty();
public boolean containWorldStateStorage(final Hash blockHash) {
return cachedWorldStatesByHash.containsKey(blockHash);
}
@Override
@@ -169,10 +135,6 @@ public abstract class AbstractTrieLogManager<T extends MutableWorldState>
@Override
public Optional<TrieLogLayer> getTrieLogLayer(final Hash blockHash) {
if (cachedWorldStatesByHash.containsKey(blockHash)) {
return Optional.of(cachedWorldStatesByHash.get(blockHash).getTrieLog());
} else {
return rootWorldStateStorage.getTrieLog(blockHash).map(TrieLogLayer::fromBytes);
}
return rootWorldStateStorage.getTrieLog(blockHash).map(TrieLogLayer::fromBytes);
}
}

View File

@@ -14,12 +14,13 @@
*
*/
package org.hyperledger.besu.ethereum.bonsai;
package org.hyperledger.besu.ethereum.bonsai.trielog;
import static com.google.common.base.Preconditions.checkState;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiValue;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
@@ -53,7 +54,7 @@ public class TrieLogLayer {
private final Map<Address, Map<Hash, BonsaiValue<UInt256>>> storage;
private boolean frozen = false;
TrieLogLayer() {
public TrieLogLayer() {
// TODO when tuweni fixes zero length byte comparison consider TreeMap
this.accounts = new HashMap<>();
this.code = new HashMap<>();
@@ -74,7 +75,7 @@ public class TrieLogLayer {
this.blockHash = blockHash;
}
void addAccountChange(
public void addAccountChange(
final Address address,
final StateTrieAccountValue oldValue,
final StateTrieAccountValue newValue) {
@@ -82,7 +83,7 @@ public class TrieLogLayer {
accounts.put(address, new BonsaiValue<>(oldValue, newValue));
}
void addCodeChange(
public void addCodeChange(
final Address address, final Bytes oldValue, final Bytes newValue, final Hash blockHash) {
checkState(!frozen, "Layer is Frozen");
code.put(
@@ -91,7 +92,7 @@ public class TrieLogLayer {
oldValue == null ? Bytes.EMPTY : oldValue, newValue == null ? Bytes.EMPTY : newValue));
}
void addStorageChange(
public void addStorageChange(
final Address address, final Hash slotHash, final UInt256 oldValue, final UInt256 newValue) {
checkState(!frozen, "Layer is Frozen");
storage

View File

@@ -13,38 +13,37 @@
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
package org.hyperledger.besu.ethereum.bonsai.trielog;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import java.util.Optional;
import java.util.function.Function;
public interface TrieLogManager {
void saveTrieLog(
final BonsaiWorldStateArchive worldStateArchive,
final BonsaiWorldStateUpdater localUpdater,
final BonsaiWorldStateUpdateAccumulator localUpdater,
final Hash forWorldStateRootHash,
final BlockHeader forBlockHeader,
final BonsaiPersistedWorldState forWorldState);
final BonsaiWorldState forWorldState);
Optional<MutableWorldState> getBonsaiCachedWorldState(final Hash blockHash);
void addCachedLayer(
BlockHeader blockHeader, Hash worldStateRootHash, BonsaiWorldState forWorldState);
boolean containWorldStateStorage(final Hash blockHash);
Optional<BonsaiWorldState> getWorldState(final Hash blockHash);
Optional<BonsaiWorldState> getNearestWorldState(final BlockHeader blockHeader);
Optional<BonsaiWorldState> getHeadWorldState(
final Function<Hash, Optional<BlockHeader>> hashBlockHeaderFunction);
long getMaxLayersToLoad();
void updateCachedLayers(final Hash blockParentHash, final Hash blockHash);
Optional<TrieLogLayer> getTrieLogLayer(final Hash blockHash);
interface CachedWorldState<Z extends MutableWorldState> {
void dispose();
long getHeight();
TrieLogLayer getTrieLog();
Z getMutableWorldState();
}
}

View File

@@ -0,0 +1,570 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai.worldview;
import static org.hyperledger.besu.ethereum.bonsai.BonsaiAccount.fromRLP;
import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY;
import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiAccount;
import org.hyperledger.besu.ethereum.bonsai.BonsaiValue;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiSnapshotWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateLayerStorage;
import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator.StorageConsumingMap;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.trie.NodeLoader;
import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.plugin.services.exception.StorageException;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BonsaiWorldState
implements MutableWorldState, BonsaiWorldView, BonsaiStorageSubscriber {
private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldState.class);
public BonsaiWorldStateKeyValueStorage worldStateStorage;
private final BonsaiWorldStateProvider archive;
private final BonsaiWorldStateUpdateAccumulator accumulator;
public Hash worldStateRootHash;
public Hash worldStateBlockHash;
private boolean isFrozen;
public BonsaiWorldState(
final BonsaiWorldStateProvider archive,
final BonsaiWorldStateKeyValueStorage worldStateStorage) {
this.archive = archive;
this.worldStateStorage = worldStateStorage;
worldStateRootHash =
Hash.wrap(
Bytes32.wrap(worldStateStorage.getWorldStateRootHash().orElse(Hash.EMPTY_TRIE_HASH)));
worldStateBlockHash =
Hash.wrap(Bytes32.wrap(worldStateStorage.getWorldStateBlockHash().orElse(Hash.ZERO)));
accumulator =
new BonsaiWorldStateUpdateAccumulator(
this,
(addr, value) ->
archive
.getCachedMerkleTrieLoader()
.preLoadAccount(getWorldStateStorage(), worldStateRootHash, addr),
(addr, value) ->
archive
.getCachedMerkleTrieLoader()
.preLoadStorageSlot(getWorldStateStorage(), addr, value));
}
public BonsaiWorldState(
final BonsaiWorldStateProvider archive,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final BonsaiWorldStateUpdateAccumulator updater) {
this.archive = archive;
this.worldStateStorage = worldStateStorage;
this.worldStateRootHash =
Hash.wrap(
Bytes32.wrap(worldStateStorage.getWorldStateRootHash().orElse(Hash.EMPTY_TRIE_HASH)));
this.worldStateBlockHash =
Hash.wrap(Bytes32.wrap(worldStateStorage.getWorldStateBlockHash().orElse(Hash.ZERO)));
this.accumulator = updater;
}
public BonsaiWorldStateProvider getArchive() {
return archive;
}
@Override
public boolean isPersisted() {
return isPersisted(worldStateStorage);
}
private boolean isPersisted(final WorldStateStorage worldStateStorage) {
return !(worldStateStorage instanceof BonsaiSnapshotWorldStateKeyValueStorage);
}
@Override
public Optional<Bytes> getCode(@Nonnull final Address address, final Hash codeHash) {
return worldStateStorage.getCode(codeHash, Hash.hash(address));
}
public void setArchiveStateUnSafe(final BlockHeader blockHeader) {
worldStateBlockHash = Hash.fromPlugin(blockHeader.getBlockHash());
worldStateRootHash = Hash.fromPlugin(blockHeader.getStateRoot());
}
@Override
public BonsaiWorldStateKeyValueStorage getWorldStateStorage() {
return worldStateStorage;
}
private Hash calculateRootHash(
final Optional<BonsaiWorldStateKeyValueStorage.BonsaiUpdater> maybeStateUpdater,
final BonsaiWorldStateUpdateAccumulator worldStateUpdater) {
clearStorage(maybeStateUpdater, worldStateUpdater);
// This must be done before updating the accounts so
// that we can get the storage state hash
Stream<Map.Entry<Address, StorageConsumingMap<BonsaiValue<UInt256>>>> storageStream =
worldStateUpdater.getStorageToUpdate().entrySet().stream();
if (maybeStateUpdater.isEmpty()) {
storageStream =
storageStream
.parallel(); // if we are not updating the state updater we can use parallel stream
}
storageStream.forEach(
addressMapEntry ->
updateAccountStorageState(maybeStateUpdater, worldStateUpdater, addressMapEntry));
// Third update the code. This has the side effect of ensuring a code hash is calculated.
updateCode(maybeStateUpdater, worldStateUpdater);
// next walk the account trie
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie =
createTrie(
(location, hash) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStateTrieNode(worldStateStorage, location, hash),
worldStateRootHash);
// for manicured tries and composting, collect branches here (not implemented)
updateTheAccounts(maybeStateUpdater, worldStateUpdater, accountTrie);
// TODO write to a cache and then generate a layer update from that and the
// DB tx updates. Right now it is just DB updates.
maybeStateUpdater.ifPresent(
bonsaiUpdater -> {
accountTrie.commit(
(location, hash, value) ->
writeTrieNode(bonsaiUpdater.getTrieBranchStorageTransaction(), location, value));
});
final Bytes32 rootHash = accountTrie.getRootHash();
return Hash.wrap(rootHash);
}
private void updateTheAccounts(
final Optional<BonsaiWorldStateKeyValueStorage.BonsaiUpdater> maybeStateUpdater,
final BonsaiWorldStateUpdateAccumulator worldStateUpdater,
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie) {
for (final Map.Entry<Address, BonsaiValue<BonsaiAccount>> accountUpdate :
worldStateUpdater.getAccountsToUpdate().entrySet()) {
final Bytes accountKey = accountUpdate.getKey();
final BonsaiValue<BonsaiAccount> bonsaiValue = accountUpdate.getValue();
final BonsaiAccount updatedAccount = bonsaiValue.getUpdated();
try {
if (updatedAccount == null) {
final Hash addressHash = Hash.hash(accountKey);
accountTrie.remove(addressHash);
maybeStateUpdater.ifPresent(
bonsaiUpdater -> bonsaiUpdater.removeAccountInfoState(addressHash));
} else {
final Hash addressHash = updatedAccount.getAddressHash();
final Bytes accountValue = updatedAccount.serializeAccount();
maybeStateUpdater.ifPresent(
bonsaiUpdater ->
bonsaiUpdater.putAccountInfoState(Hash.hash(accountKey), accountValue));
accountTrie.put(addressHash, accountValue);
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(), Optional.of(Address.wrap(accountKey)), e.getHash(), e.getLocation());
}
}
}
private void updateCode(
final Optional<BonsaiWorldStateKeyValueStorage.BonsaiUpdater> maybeStateUpdater,
final BonsaiWorldStateUpdateAccumulator worldStateUpdater) {
maybeStateUpdater.ifPresent(
bonsaiUpdater -> {
for (final Map.Entry<Address, BonsaiValue<Bytes>> codeUpdate :
worldStateUpdater.getCodeToUpdate().entrySet()) {
final Bytes updatedCode = codeUpdate.getValue().getUpdated();
final Hash accountHash = Hash.hash(codeUpdate.getKey());
if (updatedCode == null || updatedCode.size() == 0) {
bonsaiUpdater.removeCode(accountHash);
} else {
bonsaiUpdater.putCode(accountHash, null, updatedCode);
}
}
});
}
private void updateAccountStorageState(
final Optional<BonsaiWorldStateKeyValueStorage.BonsaiUpdater> maybeStateUpdater,
final BonsaiWorldStateUpdateAccumulator worldStateUpdater,
final Map.Entry<Address, StorageConsumingMap<BonsaiValue<UInt256>>> storageAccountUpdate) {
final Address updatedAddress = storageAccountUpdate.getKey();
final Hash updatedAddressHash = Hash.hash(updatedAddress);
if (worldStateUpdater.getAccountsToUpdate().containsKey(updatedAddress)) {
final BonsaiValue<BonsaiAccount> accountValue =
worldStateUpdater.getAccountsToUpdate().get(updatedAddress);
final BonsaiAccount accountOriginal = accountValue.getPrior();
final Hash storageRoot =
(accountOriginal == null) ? Hash.EMPTY_TRIE_HASH : accountOriginal.getStorageRoot();
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
createTrie(
(location, key) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStorageTrieNode(
worldStateStorage, updatedAddressHash, location, key),
storageRoot);
// for manicured tries and composting, collect branches here (not implemented)
for (final Map.Entry<Hash, BonsaiValue<UInt256>> storageUpdate :
storageAccountUpdate.getValue().entrySet()) {
final Hash keyHash = storageUpdate.getKey();
final UInt256 updatedStorage = storageUpdate.getValue().getUpdated();
try {
if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) {
maybeStateUpdater.ifPresent(
bonsaiUpdater ->
bonsaiUpdater.removeStorageValueBySlotHash(updatedAddressHash, keyHash));
storageTrie.remove(keyHash);
} else {
maybeStateUpdater.ifPresent(
bonsaiUpdater ->
bonsaiUpdater.putStorageValueBySlotHash(
updatedAddressHash, keyHash, updatedStorage));
storageTrie.put(keyHash, BonsaiWorldView.encodeTrieValue(updatedStorage));
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(),
Optional.of(Address.wrap(updatedAddress)),
e.getHash(),
e.getLocation());
}
}
final BonsaiAccount accountUpdated = accountValue.getUpdated();
if (accountUpdated != null) {
maybeStateUpdater.ifPresent(
bonsaiUpdater -> {
storageTrie.commit(
(location, key, value) ->
writeStorageTrieNode(
bonsaiUpdater, updatedAddressHash, location, key, value));
});
final Hash newStorageRoot = Hash.wrap(storageTrie.getRootHash());
accountUpdated.setStorageRoot(newStorageRoot);
}
}
// for manicured tries and composting, trim and compost here
}
private void clearStorage(
final Optional<BonsaiWorldStateKeyValueStorage.BonsaiUpdater> maybeStateUpdater,
final BonsaiWorldStateUpdateAccumulator worldStateUpdater) {
maybeStateUpdater.ifPresent(
bonsaiUpdater -> {
for (final Address address : worldStateUpdater.getStorageToClear()) {
// because we are clearing persisted values we need the account root as persisted
final BonsaiAccount oldAccount =
worldStateStorage
.getAccount(Hash.hash(address))
.map(bytes -> fromRLP(BonsaiWorldState.this, address, bytes, true))
.orElse(null);
if (oldAccount == null) {
// This is when an account is both created and deleted within the scope of the same
// block. A not-uncommon DeFi bot pattern.
continue;
}
final Hash addressHash = Hash.hash(address);
final MerklePatriciaTrie<Bytes, Bytes> storageTrie =
createTrie(
(location, key) -> getStorageTrieNode(addressHash, location, key),
oldAccount.getStorageRoot());
try {
Map<Bytes32, Bytes> entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
while (!entriesToDelete.isEmpty()) {
entriesToDelete
.keySet()
.forEach(
k ->
bonsaiUpdater.removeStorageValueBySlotHash(
Hash.hash(address), Hash.wrap(k)));
entriesToDelete.keySet().forEach(storageTrie::remove);
if (entriesToDelete.size() == 256) {
entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
} else {
break;
}
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(), Optional.of(Address.wrap(address)), e.getHash(), e.getLocation());
}
}
});
}
@Override
public void persist(final BlockHeader blockHeader) {
final Optional<BlockHeader> maybeBlockHeader = Optional.ofNullable(blockHeader);
LOG.atDebug()
.setMessage("Persist world state for block {}")
.addArgument(maybeBlockHeader)
.log();
final BonsaiWorldStateUpdateAccumulator localCopy = accumulator.copy();
boolean success = false;
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater = worldStateStorage.updater();
Runnable saveTrieLog = () -> {};
try {
final Hash newWorldStateRootHash =
calculateRootHash(isFrozen ? Optional.empty() : Optional.of(stateUpdater), accumulator);
// if we are persisted with a block header, and the prior state is the parent
// then persist the TrieLog for that transition.
// If specified but not a direct descendant simply store the new block hash.
if (blockHeader != null) {
if (!newWorldStateRootHash.equals(blockHeader.getStateRoot())) {
throw new RuntimeException(
"World State Root does not match expected value, header "
+ blockHeader.getStateRoot().toHexString()
+ " calculated "
+ newWorldStateRootHash.toHexString());
}
saveTrieLog =
() -> {
final TrieLogManager trieLogManager = archive.getTrieLogManager();
trieLogManager.saveTrieLog(localCopy, newWorldStateRootHash, blockHeader, this);
// not save a frozen state in the cache
if (!isFrozen) {
trieLogManager.addCachedLayer(blockHeader, newWorldStateRootHash, this);
}
};
stateUpdater
.getTrieBranchStorageTransaction()
.put(WORLD_BLOCK_HASH_KEY, blockHeader.getHash().toArrayUnsafe());
worldStateBlockHash = blockHeader.getHash();
} else {
stateUpdater.getTrieBranchStorageTransaction().remove(WORLD_BLOCK_HASH_KEY);
worldStateBlockHash = null;
}
stateUpdater
.getTrieBranchStorageTransaction()
.put(WORLD_ROOT_HASH_KEY, newWorldStateRootHash.toArrayUnsafe());
worldStateRootHash = newWorldStateRootHash;
success = true;
} finally {
if (success) {
stateUpdater.commit();
accumulator.reset();
saveTrieLog.run();
} else {
stateUpdater.rollback();
accumulator.reset();
}
}
}
@Override
public WorldUpdater updater() {
return accumulator;
}
@Override
public Hash rootHash() {
if (isFrozen && accumulator.isAccumulatorStateChanged()) {
worldStateRootHash = calculateRootHash(Optional.empty(), accumulator.copy());
accumulator.resetAccumulatorStateChanged();
}
return Hash.wrap(worldStateRootHash);
}
static final KeyValueStorageTransaction noOpTx =
new KeyValueStorageTransaction() {
@Override
public void put(final byte[] key, final byte[] value) {
// no-op
}
@Override
public void remove(final byte[] key) {
// no-op
}
@Override
public void commit() throws StorageException {
// no-op
}
@Override
public void rollback() {
// no-op
}
};
@Override
public Hash frontierRootHash() {
return calculateRootHash(
Optional.of(
new BonsaiWorldStateKeyValueStorage.Updater(noOpTx, noOpTx, noOpTx, noOpTx, noOpTx)),
accumulator.copy());
}
public Hash blockHash() {
return worldStateBlockHash;
}
@Override
public Stream<StreamableAccount> streamAccounts(final Bytes32 startKeyHash, final int limit) {
throw new RuntimeException("Bonsai Tries do not provide account streaming.");
}
@Override
public Account get(final Address address) {
return worldStateStorage
.getAccount(Hash.hash(address))
.map(bytes -> fromRLP(accumulator, address, bytes, true))
.orElse(null);
}
protected Optional<Bytes> getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) {
return worldStateStorage.getAccountStateTrieNode(location, nodeHash);
}
private void writeTrieNode(
final KeyValueStorageTransaction tx, final Bytes location, final Bytes value) {
tx.put(location.toArrayUnsafe(), value.toArrayUnsafe());
}
protected Optional<Bytes> getStorageTrieNode(
final Hash accountHash, final Bytes location, final Bytes32 nodeHash) {
return worldStateStorage.getAccountStorageTrieNode(accountHash, location, nodeHash);
}
private void writeStorageTrieNode(
final WorldStateStorage.Updater stateUpdater,
final Hash accountHash,
final Bytes location,
final Bytes32 nodeHash,
final Bytes value) {
stateUpdater.putAccountStorageTrieNode(accountHash, location, nodeHash, value);
}
@Override
public UInt256 getStorageValue(final Address address, final UInt256 storageKey) {
return getStorageValueBySlotHash(address, Hash.hash(storageKey)).orElse(UInt256.ZERO);
}
@Override
public Optional<UInt256> getStorageValueBySlotHash(final Address address, final Hash slotHash) {
return worldStateStorage
.getStorageValueBySlotHash(Hash.hash(address), slotHash)
.map(UInt256::fromBytes);
}
public Optional<UInt256> getStorageValueBySlotHash(
final Supplier<Optional<Hash>> storageRootSupplier,
final Address address,
final Hash slotHash) {
return worldStateStorage
.getStorageValueBySlotHash(storageRootSupplier, Hash.hash(address), slotHash)
.map(UInt256::fromBytes);
}
@Override
public UInt256 getPriorStorageValue(final Address address, final UInt256 storageKey) {
return getStorageValue(address, storageKey);
}
@Override
public Map<Bytes32, Bytes> getAllAccountStorage(final Address address, final Hash rootHash) {
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
createTrie(
(location, key) -> getStorageTrieNode(Hash.hash(address), location, key), rootHash);
return storageTrie.entriesFrom(Bytes32.ZERO, Integer.MAX_VALUE);
}
@Override
public MutableWorldState freeze() {
this.isFrozen = true;
this.worldStateStorage = new BonsaiWorldStateLayerStorage(worldStateStorage);
return this;
}
private StoredMerklePatriciaTrie<Bytes, Bytes> createTrie(
final NodeLoader nodeLoader, final Bytes32 rootHash) {
return new StoredMerklePatriciaTrie<>(
nodeLoader, rootHash, Function.identity(), Function.identity());
}
@Override
public void close() {
try {
if (!isPersisted()) {
this.worldStateStorage.close();
if (isFrozen) {
closeFrozenStorage();
}
}
} catch (Exception e) {
// no op
}
}
private void closeFrozenStorage() {
try {
final BonsaiWorldStateLayerStorage worldStateLayerStorage =
(BonsaiWorldStateLayerStorage) worldStateStorage;
if (!isPersisted(worldStateLayerStorage.getParentWorldStateStorage())) {
worldStateLayerStorage.getParentWorldStateStorage().close();
}
} catch (Exception e) {
// no op
}
}
}

View File

@@ -14,11 +14,15 @@
*
*/
package org.hyperledger.besu.ethereum.bonsai;
package org.hyperledger.besu.ethereum.bonsai.worldview;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.bonsai.BonsaiAccount;
import org.hyperledger.besu.ethereum.bonsai.BonsaiValue;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue;
@@ -48,13 +52,14 @@ import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldView, BonsaiAccount>
implements BonsaiWorldView {
private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldStateUpdater.class);
private final AccountConsumingMap<BonsaiValue<BonsaiAccount>> accountsToUpdate;
public class BonsaiWorldStateUpdateAccumulator
extends AbstractWorldUpdater<BonsaiWorldView, BonsaiAccount> implements BonsaiWorldView {
private static final Logger LOG =
LoggerFactory.getLogger(BonsaiWorldStateUpdateAccumulator.class);
private final Consumer<BonsaiValue<BonsaiAccount>> accountPreloader;
private final Consumer<Hash> storagePreloader;
private final AccountConsumingMap<BonsaiValue<BonsaiAccount>> accountsToUpdate;
private final Map<Address, BonsaiValue<Bytes>> codeToUpdate = new ConcurrentHashMap<>();
private final Set<Address> storageToClear = Collections.synchronizedSet(new HashSet<>());
private final Set<Bytes> emptySlot = Collections.synchronizedSet(new HashSet<>());
@@ -65,11 +70,9 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
private final Map<Address, StorageConsumingMap<BonsaiValue<UInt256>>> storageToUpdate =
new ConcurrentHashMap<>();
BonsaiWorldStateUpdater(final BonsaiWorldView world) {
this(world, (__, ___) -> {}, (__, ___) -> {});
}
private boolean isAccumulatorStateChanged;
BonsaiWorldStateUpdater(
public BonsaiWorldStateUpdateAccumulator(
final BonsaiWorldView world,
final Consumer<BonsaiValue<BonsaiAccount>> accountPreloader,
final Consumer<Hash> storagePreloader) {
@@ -77,16 +80,18 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
this.accountsToUpdate = new AccountConsumingMap<>(new ConcurrentHashMap<>(), accountPreloader);
this.accountPreloader = accountPreloader;
this.storagePreloader = storagePreloader;
this.isAccumulatorStateChanged = false;
}
public BonsaiWorldStateUpdater copy() {
final BonsaiWorldStateUpdater copy =
new BonsaiWorldStateUpdater(wrappedWorldView(), accountPreloader, storagePreloader);
public BonsaiWorldStateUpdateAccumulator copy() {
final BonsaiWorldStateUpdateAccumulator copy =
new BonsaiWorldStateUpdateAccumulator(
wrappedWorldView(), accountPreloader, storagePreloader);
copy.cloneFromUpdater(this);
return copy;
}
void cloneFromUpdater(final BonsaiWorldStateUpdater source) {
void cloneFromUpdater(final BonsaiWorldStateUpdateAccumulator source) {
accountsToUpdate.putAll(source.getAccountsToUpdate());
codeToUpdate.putAll(source.codeToUpdate);
storageToClear.addAll(source.storageToClear);
@@ -94,6 +99,7 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
updatedAccounts.putAll(source.updatedAccounts);
deletedAccounts.addAll(source.deletedAccounts);
emptySlot.addAll(source.emptySlot);
this.isAccumulatorStateChanged = true;
}
@Override
@@ -115,12 +121,14 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
@Override
public EvmAccount createAccount(final Address address, final long nonce, final Wei balance) {
BonsaiValue<BonsaiAccount> bonsaiValue = accountsToUpdate.get(address);
if (bonsaiValue == null) {
bonsaiValue = new BonsaiValue<>(null, null);
accountsToUpdate.put(address, bonsaiValue);
} else if (bonsaiValue.getUpdated() != null) {
throw new IllegalStateException("Cannot create an account when one already exists");
}
final BonsaiAccount newAccount =
new BonsaiAccount(
this,
@@ -135,11 +143,11 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
return new WrappedEvmAccount(track(new UpdateTrackingAccount<>(newAccount)));
}
Map<Address, BonsaiValue<BonsaiAccount>> getAccountsToUpdate() {
public Map<Address, BonsaiValue<BonsaiAccount>> getAccountsToUpdate() {
return accountsToUpdate;
}
Map<Address, BonsaiValue<Bytes>> getCodeToUpdate() {
public Map<Address, BonsaiValue<Bytes>> getCodeToUpdate() {
return codeToUpdate;
}
@@ -147,7 +155,7 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
return storageToClear;
}
Map<Address, StorageConsumingMap<BonsaiValue<UInt256>>> getStorageToUpdate() {
public Map<Address, StorageConsumingMap<BonsaiValue<UInt256>>> getStorageToUpdate() {
return storageToUpdate;
}
@@ -162,7 +170,14 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
try {
final BonsaiValue<BonsaiAccount> bonsaiValue = accountsToUpdate.get(address);
if (bonsaiValue == null) {
final Account account = wrappedWorldView().get(address);
final Account account;
if (wrappedWorldView() instanceof BonsaiWorldStateUpdateAccumulator) {
account =
((BonsaiWorldStateUpdateAccumulator) wrappedWorldView())
.loadAccount(address, bonsaiAccountFunction);
} else {
account = wrappedWorldView().get(address);
}
if (account instanceof BonsaiAccount) {
final BonsaiAccount mutableAccount =
new BonsaiAccount((BonsaiAccount) account, this, true);
@@ -198,6 +213,7 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
@Override
public void commit() {
this.isAccumulatorStateChanged = true;
for (final Address deletedAddress : getDeletedAccounts()) {
final BonsaiValue<BonsaiAccount> accountValue =
accountsToUpdate.computeIfAbsent(
@@ -367,12 +383,6 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
}
}
@Override
public Optional<Bytes> getStateTrieNode(final Bytes location) {
// updater doesn't track trie nodes. Always a miss.
return Optional.empty();
}
@Override
public UInt256 getStorageValue(final Address address, final UInt256 storageKey) {
// TODO maybe log the read into the trie layer?
@@ -395,8 +405,8 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
} else {
try {
final Optional<UInt256> valueUInt =
(wrappedWorldView() instanceof BonsaiPersistedWorldState)
? ((BonsaiPersistedWorldState) wrappedWorldView())
(wrappedWorldView() instanceof BonsaiWorldState)
? ((BonsaiWorldState) wrappedWorldView())
.getStorageValueBySlotHash(
() ->
Optional.ofNullable(loadAccount(address, BonsaiValue::getPrior))
@@ -455,10 +465,24 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
@Override
public Map<Bytes32, Bytes> getAllAccountStorage(final Address address, final Hash rootHash) {
final Map<Bytes32, Bytes> results = wrappedWorldView().getAllAccountStorage(address, rootHash);
storageToUpdate.get(address).forEach((key, value) -> results.put(key, value.getUpdated()));
final StorageConsumingMap<BonsaiValue<UInt256>> bonsaiValueStorage =
storageToUpdate.get(address);
if (bonsaiValueStorage != null) {
bonsaiValueStorage.forEach((key, value) -> results.put(key, value.getUpdated()));
}
return results;
}
@Override
public boolean isPersisted() {
return true;
}
@Override
public BonsaiWorldStateKeyValueStorage getWorldStateStorage() {
return wrappedWorldView().getWorldStateStorage();
}
public TrieLogLayer generateTrieLog(final Hash blockHash) {
final TrieLogLayer layer = new TrieLogLayer();
importIntoTrieLog(layer, blockHash);
@@ -600,6 +624,8 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
}
if (replacementValue == null) {
if (accountValue.getPrior() == null) {
// TODO: should we remove from the parent accumulated change also? only if it is a
// private copy
accountsToUpdate.remove(address);
} else {
accountValue.setUpdated(null);
@@ -769,6 +795,22 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
return Objects.equals(sanitizedExpectedValue, sanitizedExistingSlotValue);
}
public boolean isAccumulatorStateChanged() {
return isAccumulatorStateChanged;
}
public void resetAccumulatorStateChanged() {
isAccumulatorStateChanged = false;
}
public Consumer<BonsaiValue<BonsaiAccount>> getAccountPreloader() {
return accountPreloader;
}
public Consumer<Hash> getStoragePreloader() {
return storagePreloader;
}
@Override
public void reset() {
storageToClear.clear();
@@ -776,18 +818,10 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
codeToUpdate.clear();
accountsToUpdate.clear();
emptySlot.clear();
resetAccumulatorStateChanged();
super.reset();
}
public boolean isDirty() {
return !(accountsToUpdate.isEmpty()
&& updatedAccounts.isEmpty()
&& deletedAccounts.isEmpty()
&& storageToUpdate.isEmpty()
&& storageToClear.isEmpty()
&& codeToUpdate.isEmpty());
}
public static class AccountConsumingMap<T> extends ForwardingMap<Address, T> {
private final ConcurrentHashMap<Address, T> accounts;

View File

@@ -14,11 +14,13 @@
*
*/
package org.hyperledger.besu.ethereum.bonsai;
package org.hyperledger.besu.ethereum.bonsai.worldview;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.evm.worldstate.WorldView;
import java.util.Map;
@@ -32,8 +34,6 @@ public interface BonsaiWorldView extends WorldView {
Optional<Bytes> getCode(Address address, final Hash codeHash);
Optional<Bytes> getStateTrieNode(Bytes location);
UInt256 getStorageValue(Address address, UInt256 key);
Optional<UInt256> getStorageValueBySlotHash(Address address, Hash slotHash);
@@ -41,18 +41,24 @@ public interface BonsaiWorldView extends WorldView {
UInt256 getPriorStorageValue(Address address, UInt256 key);
/**
* Retrieve all the storage values of a account.
* Retrieve all the storage values of an account.
*
* @param address the account to stream
* @param rootHash the root hash of the account storage trie
* @return A map that is a copy of the entries. The key is the hashed slot number, and the value
* is the Bytes representation of the storage value.
*/
Map<Bytes32, Bytes> getAllAccountStorage(Address address, Hash rootHash);
Map<Bytes32, Bytes> getAllAccountStorage(final Address address, final Hash rootHash);
static Bytes encodeTrieValue(final Bytes bytes) {
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeBytes(bytes.trimLeadingZeros());
return out.encoded();
}
boolean isPersisted();
BonsaiWorldStateKeyValueStorage getWorldStateStorage();
WorldUpdater updater();
}

View File

@@ -19,13 +19,6 @@ import org.hyperledger.besu.evm.worldstate.WorldState;
public interface MutableWorldState extends WorldState, MutableWorldView {
/**
* Creates an independent copy of this world state initially equivalent to this world state.
*
* @return a copy of this world state.
*/
MutableWorldState copy();
/**
* Persist accumulated changes to underlying storage.
*
@@ -35,13 +28,8 @@ public interface MutableWorldState extends WorldState, MutableWorldView {
*/
void persist(BlockHeader blockHeader);
/**
* Returns whether this mutable worldstate can be persisted to storage.
*
* @return boolean
*/
default boolean isPersistable() {
// default to true
return true;
default MutableWorldState freeze() {
// no op
throw new UnsupportedOperationException("cannot freeze");
}
}

View File

@@ -1,22 +0,0 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.core;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
public interface SnapshotMutableWorldState extends MutableWorldState, AutoCloseable {
BonsaiWorldStateKeyValueStorage getWorldStateStorage();
}

View File

@@ -19,8 +19,8 @@ import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingOutputs;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.bonsai.BonsaiPersistedWorldState;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateUpdater;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
@@ -140,8 +140,8 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
blockHeader.getHash().toHexString(),
transaction.getHash().toHexString());
LOG.info(errorMessage);
if (worldState instanceof BonsaiPersistedWorldState) {
((BonsaiWorldStateUpdater) worldStateUpdater).reset();
if (worldState instanceof BonsaiWorldState) {
((BonsaiWorldStateUpdateAccumulator) worldStateUpdater).reset();
}
return new BlockProcessingResult(Optional.empty(), errorMessage);
}
@@ -169,8 +169,8 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) {
// no need to log, rewardCoinbase logs the error.
if (worldState instanceof BonsaiPersistedWorldState) {
((BonsaiWorldStateUpdater) worldState.updater()).reset();
if (worldState instanceof BonsaiWorldState) {
((BonsaiWorldStateUpdateAccumulator) worldState.updater()).reset();
}
return new BlockProcessingResult(Optional.empty(), "ommer too old");
}
@@ -179,8 +179,8 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
worldState.persist(blockHeader);
} catch (MerkleTrieException e) {
LOG.trace("Merkle trie exception during Transaction processing ", e);
if (worldState instanceof BonsaiPersistedWorldState) {
((BonsaiWorldStateUpdater) worldState.updater()).reset();
if (worldState instanceof BonsaiWorldState) {
((BonsaiWorldStateUpdateAccumulator) worldState.updater()).reset();
}
throw e;
} catch (Exception e) {

View File

@@ -14,7 +14,7 @@
*/
package org.hyperledger.besu.ethereum.storage.keyvalue;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.chain.BlockchainStorage;
import org.hyperledger.besu.ethereum.goquorum.GoQuorumPrivateKeyValueStorage;
import org.hyperledger.besu.ethereum.goquorum.GoQuorumPrivateStorage;

View File

@@ -165,14 +165,7 @@ public class TransactionSimulator {
private MutableWorldState getWorldState(final BlockHeader header) {
return worldStateArchive
.getMutable(header.getStateRoot(), header.getHash(), false)
.map(
ws -> {
if (!ws.isPersistable()) {
return ws.copy();
}
return ws;
})
.getMutable(header, false)
.orElseThrow(
() ->
new IllegalArgumentException(
@@ -352,9 +345,7 @@ public class TransactionSimulator {
public Optional<Boolean> doesAddressExistAtHead(final Address address) {
final BlockHeader header = blockchain.getChainHeadHeader();
try (final MutableWorldState worldState =
worldStateArchive
.getMutable(header.getStateRoot(), header.getHash(), false)
.orElseThrow()) {
worldStateArchive.getMutable(header, false).orElseThrow()) {
return doesAddressExist(worldState, address, header);
} catch (final Exception ex) {
return Optional.empty();

View File

@@ -111,11 +111,6 @@ public class DefaultMutableWorldState implements MutableWorldState {
return rootHash();
}
@Override
public MutableWorldState copy() {
return new DefaultMutableWorldState(rootHash(), worldStateStorage, preimageStorage);
}
@Override
public Account get(final Address address) {
final Hash addressHash = Hash.hash(address);

View File

@@ -56,8 +56,8 @@ public class DefaultWorldStateArchive implements WorldStateArchive {
@Override
public Optional<MutableWorldState> getMutable(
final Hash rootHash, final Hash blockHash, final boolean isPersistingState) {
return getMutable(rootHash, blockHash);
final BlockHeader blockHeader, final boolean isPersistingState) {
return getMutable(blockHeader.getStateRoot(), blockHeader.getHash());
}
@Override
@@ -95,4 +95,9 @@ public class DefaultWorldStateArchive implements WorldStateArchive {
final List<UInt256> accountStorageKeys) {
return worldStateProof.getAccountProof(worldStateRoot, accountAddress, accountStorageKeys);
}
@Override
public void close() {
// no op
}
}

View File

@@ -22,20 +22,21 @@ import org.hyperledger.besu.ethereum.proof.WorldStateProof;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.evm.worldstate.WorldState;
import java.io.Closeable;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
public interface WorldStateArchive {
public interface WorldStateArchive extends Closeable {
Hash EMPTY_ROOT_HASH = Hash.wrap(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH);
Optional<WorldState> get(Hash rootHash, Hash blockHash);
boolean isWorldStateAvailable(Hash rootHash, Hash blockHash);
Optional<MutableWorldState> getMutable(Hash rootHash, Hash blockHash, boolean isPersistingState);
Optional<MutableWorldState> getMutable(BlockHeader blockHeader, boolean isPersistingState);
Optional<MutableWorldState> getMutable(Hash rootHash, Hash blockHash);

View File

@@ -14,8 +14,8 @@
*/
package org.hyperledger.besu.ethereum.core;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive;
import org.hyperledger.besu.ethereum.bonsai.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.chain.DefaultBlockchain;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
@@ -63,13 +63,13 @@ public class InMemoryKeyValueStorageProvider extends KeyValueStorageProvider {
new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()));
}
public static BonsaiWorldStateArchive createBonsaiInMemoryWorldStateArchive(
public static BonsaiWorldStateProvider createBonsaiInMemoryWorldStateArchive(
final Blockchain blockchain) {
final InMemoryKeyValueStorageProvider inMemoryKeyValueStorageProvider =
new InMemoryKeyValueStorageProvider();
final CachedMerkleTrieLoader cachedMerkleTrieLoader =
new CachedMerkleTrieLoader(new NoOpMetricsSystem());
return new BonsaiWorldStateArchive(
return new BonsaiWorldStateProvider(
inMemoryKeyValueStorageProvider, blockchain, cachedMerkleTrieLoader);
}

View File

@@ -16,7 +16,7 @@ package org.hyperledger.besu.ethereum.core;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie;

View File

@@ -23,10 +23,10 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.bonsai.BonsaiPersistedWorldState;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.chain.BadBlockManager;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
@@ -87,14 +87,14 @@ public class BlockImportExceptionHandlingTest {
private CachedMerkleTrieLoader cachedMerkleTrieLoader;
private final WorldStateArchive worldStateArchive =
// contains a BonsaiPersistedWorldState which we need to spy on.
// contains a BonsaiWorldState which we need to spy on.
// do we need to also test with a DefaultWorldStateArchive?
spy(new BonsaiWorldStateArchive(storageProvider, blockchain, cachedMerkleTrieLoader));
spy(new BonsaiWorldStateProvider(storageProvider, blockchain, cachedMerkleTrieLoader));
private final BonsaiPersistedWorldState persisted =
private final BonsaiWorldState persisted =
spy(
new BonsaiPersistedWorldState(
(BonsaiWorldStateArchive) worldStateArchive,
new BonsaiWorldState(
(BonsaiWorldStateProvider) worldStateArchive,
(BonsaiWorldStateKeyValueStorage) worldStateStorage));
private final BadBlockManager badBlockManager = new BadBlockManager();

View File

@@ -105,16 +105,16 @@ public class MainnetBlockValidatorTest {
@Test
public void shouldDetectAndCacheInvalidBlocksWhenParentWorldStateNotAvailable() {
when(blockchain.getBlockHeader(any(Hash.class)))
.thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader()));
final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader();
when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader));
when(blockHeaderValidator.validateHeader(
any(BlockHeader.class),
any(BlockHeader.class),
eq(protocolContext),
eq(HeaderValidationMode.DETACHED_ONLY)))
.thenReturn(true);
when(worldStateArchive.getMutable(any(Hash.class), any(Hash.class), anyBoolean()))
.thenReturn(Optional.empty());
when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean())).thenReturn(Optional.empty());
assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0);
mainnetBlockValidator.validateAndProcessBlock(
@@ -127,15 +127,15 @@ public class MainnetBlockValidatorTest {
@Test
public void shouldDetectAndCacheInvalidBlocksWhenProcessBlockFailed() {
when(blockchain.getBlockHeader(any(Hash.class)))
.thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader()));
final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader();
when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader));
when(blockHeaderValidator.validateHeader(
any(BlockHeader.class),
any(BlockHeader.class),
eq(protocolContext),
eq(HeaderValidationMode.DETACHED_ONLY)))
.thenReturn(true);
when(worldStateArchive.getMutable(any(Hash.class), any(Hash.class), anyBoolean()))
when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean()))
.thenReturn(Optional.of(mock(MutableWorldState.class)));
when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(badBlock)))
.thenReturn(new BlockProcessingResult(Optional.empty()));
@@ -150,15 +150,15 @@ public class MainnetBlockValidatorTest {
@Test
public void shouldDetectAndCacheInvalidBlocksWhenBodyInvalid() {
when(blockchain.getBlockHeader(any(Hash.class)))
.thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader()));
final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader();
when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader));
when(blockHeaderValidator.validateHeader(
any(BlockHeader.class),
any(BlockHeader.class),
eq(protocolContext),
eq(HeaderValidationMode.DETACHED_ONLY)))
.thenReturn(true);
when(worldStateArchive.getMutable(any(Hash.class), any(Hash.class), anyBoolean()))
when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean()))
.thenReturn(Optional.of(mock(MutableWorldState.class)));
when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(badBlock)))
.thenReturn(new BlockProcessingResult(Optional.empty()));
@@ -173,18 +173,18 @@ public class MainnetBlockValidatorTest {
@Test
public void shouldNotCacheWhenValidBlocks() {
MutableWorldState mockWorldState =
when(mock(MutableWorldState.class).isPersistable()).thenReturn(true).getMock();
when(blockchain.getBlockHeader(any(Hash.class)))
.thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader()));
MutableWorldState mockWorldState = mock(MutableWorldState.class);
final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader();
when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader));
when(blockHeaderValidator.validateHeader(
any(BlockHeader.class),
any(BlockHeader.class),
eq(protocolContext),
eq(HeaderValidationMode.DETACHED_ONLY)))
.thenReturn(true);
when(worldStateArchive.getMutable(any(Hash.class), any(Hash.class), anyBoolean()))
when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean()))
.thenReturn(Optional.of(mockWorldState));
when(worldStateArchive.getMutable(any(Hash.class), any(Hash.class)))
.thenReturn(Optional.of(mockWorldState));

View File

@@ -28,6 +28,8 @@ import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.AbstractBlockCreator;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.chain.GenesisState;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
@@ -72,7 +74,7 @@ import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
public abstract class AbstractIsolationTests {
protected BonsaiWorldStateArchive archive;
protected BonsaiWorldStateProvider archive;
protected BonsaiWorldStateKeyValueStorage bonsaiWorldStateStorage;
protected ProtocolContext protocolContext;
final Function<String, KeyPair> asKeyPair =
@@ -101,26 +103,19 @@ public abstract class AbstractIsolationTests {
@Rule public final TemporaryFolder tempData = new TemporaryFolder();
protected boolean shouldUseSnapshots() {
// override for layered worldstate
return true;
}
@Before
public void createStorage() {
bonsaiWorldStateStorage =
(BonsaiWorldStateKeyValueStorage)
createKeyValueStorageProvider().createWorldStateStorage(DataStorageFormat.BONSAI);
archive =
new BonsaiWorldStateArchive(
new BonsaiWorldStateProvider(
bonsaiWorldStateStorage,
blockchain,
Optional.of(16L),
shouldUseSnapshots(),
new CachedMerkleTrieLoader(new NoOpMetricsSystem()));
var ws = archive.getMutable();
genesisState.writeStateTo(ws);
ws.persist(blockchain.getChainHeadHeader());
protocolContext = new ProtocolContext(blockchain, archive, null);
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright Hyperledger Besu contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class BonsaiInMemoryIsolationTests extends AbstractIsolationTests {
@Override
protected boolean shouldUseSnapshots() {
// override for layered worldstate
return false;
}
@Test
public void testInMemoryWorldStateConsistentAfterMutatingPersistedState() {
Address testAddress = Address.fromHexString("0xdeadbeef");
var persisted = archive.getMutable();
var genesisBlock = genesisState.getBlock();
var layered =
archive
.getMutable(genesisBlock.getHeader().getStateRoot(), genesisBlock.getHash(), false)
.orElse(null);
var inMemoryBeforeMutation = layered.copy();
var firstBlock = forTransactions(List.of(burnTransaction(sender1, 0L, testAddress)));
var res = executeBlock(persisted, firstBlock);
assertThat(res.isSuccessful()).isTrue();
assertThat(archive.getTrieLogManager().getBonsaiCachedWorldState(firstBlock.getHash()))
.isNotEmpty();
assertThat(res.isSuccessful()).isTrue();
assertThat(persisted.get(testAddress)).isNotNull();
assertThat(persisted.get(testAddress).getBalance())
.isEqualTo(Wei.of(1_000_000_000_000_000_000L));
assertThat(persisted.rootHash()).isEqualTo(firstBlock.getHeader().getStateRoot());
var layered2 =
archive
.getMutable(genesisBlock.getHeader().getStateRoot(), genesisBlock.getHash(), false)
.orElse(null);
// assert layered before and after the block is what and where we expect it to be:
assertThat(layered2).isInstanceOf(BonsaiLayeredWorldState.class);
assertThat(layered2.rootHash()).isEqualTo(genesisBlock.getHeader().getStateRoot());
assertThat(layered2.get(testAddress)).isNull();
assertThat(layered).isInstanceOf(BonsaiLayeredWorldState.class);
assertThat(layered.rootHash()).isEqualTo(genesisBlock.getHeader().getStateRoot());
assertThat(layered.get(testAddress)).isNull();
var inMemoryAfterMutation = layered2.copy();
// assert we have not modified the head worldstate:
assertThat(persisted.rootHash()).isEqualTo(firstBlock.getHeader().getStateRoot());
// assert the inMemory copy of worldstate 0 from [before mutating persisted state to 1] is
// consistent
assertThat(inMemoryBeforeMutation).isInstanceOf(BonsaiInMemoryWorldState.class);
assertThat(inMemoryBeforeMutation.rootHash())
.isEqualTo(genesisBlock.getHeader().getStateRoot());
assertThat(inMemoryBeforeMutation.get(testAddress)).isNull();
// assert the inMemory copy of worldstate 0 from [after mutating persisted state to 1] is
// consistent
assertThat(inMemoryAfterMutation).isInstanceOf(BonsaiInMemoryWorldState.class);
assertThat(inMemoryAfterMutation.rootHash()).isEqualTo(genesisBlock.getHeader().getStateRoot());
assertThat(inMemoryAfterMutation.get(testAddress)).isNull();
try {
layered.close();
layered2.close();
inMemoryBeforeMutation.close();
inMemoryAfterMutation.close();
} catch (Exception ex) {
throw new RuntimeException("failed to close worldstates");
}
}
}

View File

@@ -21,10 +21,8 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import java.util.List;
import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,12 +34,11 @@ public class BonsaiSnapshotIsolationTests extends AbstractIsolationTests {
@Test
public void ensureTruncateDoesNotCauseSegfault() {
var preTruncatedWorldState = archive.getMutable(null, genesisState.getBlock().getHash(), false);
var preTruncatedWorldState = archive.getMutable(genesisState.getBlock().getHeader(), false);
assertThat(preTruncatedWorldState)
.isPresent(); // really just assert that we have not segfaulted after truncating
bonsaiWorldStateStorage.clear();
var postTruncatedWorldState =
archive.getMutable(null, genesisState.getBlock().getHash(), false);
var postTruncatedWorldState = archive.getMutable(genesisState.getBlock().getHeader(), false);
assertThat(postTruncatedWorldState).isEmpty();
// assert that trying to access pre-worldstate does not segfault after truncating
preTruncatedWorldState.get().get(Address.fromHexString(accounts.get(0).getAddress()));
@@ -52,22 +49,21 @@ public class BonsaiSnapshotIsolationTests extends AbstractIsolationTests {
public void testIsolatedFromHead_behindHead() {
Address testAddress = Address.fromHexString("0xdeadbeef");
// assert we can mutate head without mutating the isolated snapshot
var isolated = archive.getMutableSnapshot(genesisState.getBlock().getHash());
var isolated = archive.getMutable(genesisState.getBlock().getHeader(), false);
var firstBlock = forTransactions(List.of(burnTransaction(sender1, 0L, testAddress)));
var res = executeBlock(archive.getMutable(), firstBlock);
var isolated2 = archive.getMutableSnapshot(firstBlock.getHash());
var isolated2 = archive.getMutable(firstBlock.getHeader(), false);
var secondBlock = forTransactions(List.of(burnTransaction(sender1, 1L, testAddress)));
var res2 = executeBlock(archive.getMutable(), secondBlock);
assertThat(res.isSuccessful()).isTrue();
assertThat(res2.isSuccessful()).isTrue();
assertThat(archive.getTrieLogManager().getBonsaiCachedWorldState(firstBlock.getHash()))
.isNotEmpty();
assertThat(archive.getTrieLogManager().getBonsaiCachedWorldState(secondBlock.getHash()))
.isNotEmpty();
assertThat(archive.getTrieLogManager().containWorldStateStorage(firstBlock.getHash())).isTrue();
assertThat(archive.getTrieLogManager().containWorldStateStorage(secondBlock.getHash()))
.isTrue();
assertThat(archive.getMutable().get(testAddress)).isNotNull();
assertThat(archive.getMutable().get(testAddress).getBalance())
@@ -90,162 +86,6 @@ public class BonsaiSnapshotIsolationTests extends AbstractIsolationTests {
}
}
@Test
public void testIsolatedSnapshotMutation() {
Address testAddress = Address.fromHexString("0xdeadbeef");
// assert we can correctly execute a block on a mutable snapshot without mutating head
var isolated = archive.getMutableSnapshot(genesisState.getBlock().getHash());
var firstBlock = forTransactions(List.of(burnTransaction(sender1, 0L, testAddress)));
var res = executeBlock(isolated.get(), firstBlock);
assertThat(archive.getTrieLogManager().getBonsaiCachedWorldState(firstBlock.getHash()))
.isNotEmpty();
assertThat(res.isSuccessful()).isTrue();
assertThat(isolated.get().get(testAddress)).isNotNull();
assertThat(isolated.get().get(testAddress).getBalance())
.isEqualTo(Wei.of(1_000_000_000_000_000_000L));
assertThat(isolated.get().rootHash()).isEqualTo(firstBlock.getHeader().getStateRoot());
// persist the isolated worldstate as trielog only:
isolated.get().persist(firstBlock.getHeader());
// assert we have not modified the head worldstate:
assertThat(archive.getMutable().get(testAddress)).isNull();
// roll the persisted world state to the new trie log from the persisted snapshot
var ws = archive.getMutable(null, firstBlock.getHash());
assertThat(ws).isPresent();
assertThat(ws.get().get(testAddress)).isNotNull();
assertThat(ws.get().get(testAddress).getBalance())
.isEqualTo(Wei.of(1_000_000_000_000_000_000L));
assertThat(ws.get().rootHash()).isEqualTo(firstBlock.getHeader().getStateRoot());
try {
isolated.get().close();
} catch (Exception ex) {
throw new RuntimeException("failed to close isolated worldstates");
}
}
@Test
public void testSnapshotCloneIsolation() {
Address testAddress = Address.fromHexString("0xdeadbeef");
Address altTestAddress = Address.fromHexString("0xd1ffbeef");
// create a snapshot worldstate, and then clone it:
var isolated = archive.getMutableSnapshot(genesisState.getBlock().getHash()).get();
var isolatedClone = isolated.copy();
// execute a block with a single transaction on the first snapshot:
var firstBlock = forTransactions(List.of(burnTransaction(sender1, 0L, testAddress)));
var res = executeBlock(isolated, firstBlock);
assertThat(res.isSuccessful()).isTrue();
Runnable checkIsolatedState =
() -> {
assertThat(isolated.rootHash()).isEqualTo(firstBlock.getHeader().getStateRoot());
assertThat(isolated.get(testAddress)).isNotNull();
assertThat(isolated.get(altTestAddress)).isNull();
assertThat(isolated.get(testAddress).getBalance())
.isEqualTo(Wei.of(1_000_000_000_000_000_000L));
};
checkIsolatedState.run();
// assert clone is isolated and unmodified:
assertThat(isolatedClone.get(testAddress)).isNull();
assertThat(isolatedClone.rootHash())
.isEqualTo(genesisState.getBlock().getHeader().getStateRoot());
// assert clone isolated block execution
var cloneForkBlock =
forTransactions(
List.of(burnTransaction(sender1, 0L, altTestAddress)),
genesisState.getBlock().getHeader());
var altRes = executeBlock(isolatedClone, cloneForkBlock);
assertThat(altRes.isSuccessful()).isTrue();
assertThat(isolatedClone.rootHash()).isEqualTo(cloneForkBlock.getHeader().getStateRoot());
assertThat(isolatedClone.get(altTestAddress)).isNotNull();
assertThat(isolatedClone.get(testAddress)).isNull();
assertThat(isolatedClone.get(altTestAddress).getBalance())
.isEqualTo(Wei.of(1_000_000_000_000_000_000L));
assertThat(isolatedClone.rootHash()).isEqualTo(cloneForkBlock.getHeader().getStateRoot());
// re-check isolated state remains unchanged:
checkIsolatedState.run();
// assert that the actual persisted worldstate remains unchanged:
var persistedWorldState = archive.getMutable();
assertThat(persistedWorldState.rootHash())
.isEqualTo(genesisState.getBlock().getHeader().getStateRoot());
assertThat(persistedWorldState.get(testAddress)).isNull();
assertThat(persistedWorldState.get(altTestAddress)).isNull();
// assert that trieloglayers exist for both of the isolated states:
var firstBlockTrieLog = archive.getTrieLogManager().getTrieLogLayer(firstBlock.getHash());
assertThat(firstBlockTrieLog).isNotEmpty();
assertThat(firstBlockTrieLog.get().getAccount(testAddress)).isNotEmpty();
assertThat(firstBlockTrieLog.get().getAccount(altTestAddress)).isEmpty();
assertThat(archive.getTrieLogManager().getBonsaiCachedWorldState(firstBlock.getHash()))
.isNotEmpty();
var cloneForkTrieLog = archive.getTrieLogManager().getTrieLogLayer(cloneForkBlock.getHash());
assertThat(cloneForkTrieLog.get().getAccount(testAddress)).isEmpty();
assertThat(cloneForkTrieLog.get().getAccount(altTestAddress)).isNotEmpty();
try {
isolated.close();
isolatedClone.close();
} catch (Exception ex) {
throw new RuntimeException("failed to close isolated worldstates");
}
}
@Test
public void assertSnapshotDoesNotClose() {
Address testAddress = Address.fromHexString("0xdeadbeef");
// create a snapshot worldstate, and then clone it:
var isolated = archive.getMutableSnapshot(genesisState.getBlock().getHash()).get();
// execute a block with a single transaction on the first snapshot:
var firstBlock = forTransactions(List.of(burnTransaction(sender1, 0L, testAddress)));
var res = executeBlock(isolated, firstBlock);
assertThat(archive.getTrieLogManager().getBonsaiCachedWorldState(firstBlock.getHash()))
.isNotEmpty();
assertThat(res.isSuccessful()).isTrue();
Consumer<MutableWorldState> checkIsolatedState =
(ws) -> {
assertThat(ws.rootHash()).isEqualTo(firstBlock.getHeader().getStateRoot());
assertThat(ws.get(testAddress)).isNotNull();
assertThat(ws.get(testAddress).getBalance())
.isEqualTo(Wei.of(1_000_000_000_000_000_000L));
};
checkIsolatedState.accept(isolated);
var isolatedClone = isolated.copy();
checkIsolatedState.accept(isolatedClone);
try {
// close the first snapshot worldstate. The second worldstate should still be able to read
// through its snapshot
isolated.close();
} catch (Exception ex) {
// meh
}
// copy of closed isolated worldstate should still pass check
checkIsolatedState.accept(isolatedClone);
try {
isolatedClone.close();
} catch (Exception ex) {
throw new RuntimeException("failed to close isolated worldstates");
}
}
@Test
public void testSnapshotRollToTrieLogBlockHash() {
// assert we can roll a snapshot to a specific worldstate without mutating head
@@ -268,13 +108,13 @@ public class BonsaiSnapshotIsolationTests extends AbstractIsolationTests {
blockchain.rewindToBlock(2L);
var block1State = archive.getMutable(null, block2.getHash());
// BonsaiPersistedWorldState should be at block 2
// BonsaiWorldState should be at block 2
assertThat(block1State.get().get(testAddress)).isNotNull();
assertThat(block1State.get().get(testAddress).getBalance())
.isEqualTo(Wei.of(2_000_000_000_000_000_000L));
assertThat(block1State.get().rootHash()).isEqualTo(block2.getHeader().getStateRoot());
var isolatedRollForward = archive.getMutableSnapshot(block3.getHash());
var isolatedRollForward = archive.getMutable(block3.getHeader(), false);
// we should be at block 3, one block ahead of BonsaiPersistatedWorldState
assertThat(isolatedRollForward.get().get(testAddress)).isNotNull();
@@ -283,7 +123,7 @@ public class BonsaiSnapshotIsolationTests extends AbstractIsolationTests {
assertThat(isolatedRollForward.get().rootHash()).isEqualTo(block3.getHeader().getStateRoot());
// we should be at block 1, one block behind BonsaiPersistatedWorldState
var isolatedRollBack = archive.getMutableSnapshot(block1.getHash());
var isolatedRollBack = archive.getMutable(block1.getHeader(), false);
assertThat(isolatedRollBack.get().get(testAddress)).isNotNull();
assertThat(isolatedRollBack.get().get(testAddress).getBalance())
.isEqualTo(Wei.of(1_000_000_000_000_000_000L));
@@ -304,7 +144,7 @@ public class BonsaiSnapshotIsolationTests extends AbstractIsolationTests {
var head = archive.getMutable();
try (var shouldCloseSnapshot =
archive.getMutableSnapshot(genesisState.getBlock().getHash()).get()) {
archive.getMutable(genesisState.getBlock().getHeader(), false).get()) {
var tx1 = burnTransaction(sender1, 0L, testAddress);
Block oneTx = forTransactions(List.of(tx1));

View File

@@ -1,172 +0,0 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY;
import static org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.SnapshotTrieLogManager.CachedSnapshotWorldState;
import org.hyperledger.besu.ethereum.bonsai.TrieLogManager.CachedWorldState;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class BonsaiSnapshotWorldStateArchiveTest {
final BlockHeaderTestFixture blockBuilder = new BlockHeaderTestFixture();
@Mock Blockchain blockchain;
@Mock StorageProvider storageProvider;
@Mock SnappableKeyValueStorage keyValueStorage;
BonsaiWorldStateArchive bonsaiWorldStateArchive;
CachedMerkleTrieLoader cachedMerkleTrieLoader;
@Before
public void setUp() {
when(storageProvider.getStorageBySegmentIdentifier(any(KeyValueSegmentIdentifier.class)))
.thenReturn(keyValueStorage);
cachedMerkleTrieLoader = new CachedMerkleTrieLoader(new NoOpMetricsSystem());
}
@Test
public void testGetMutableReturnPersistedStateWhenNeeded() {
final BlockHeader chainHead = blockBuilder.number(0).buildHeader();
when(keyValueStorage.get(WORLD_ROOT_HASH_KEY))
.thenReturn(Optional.of(chainHead.getStateRoot().toArrayUnsafe()));
when(keyValueStorage.get(WORLD_BLOCK_HASH_KEY))
.thenReturn(Optional.of(chainHead.getHash().toArrayUnsafe()));
when(keyValueStorage.get(WORLD_ROOT_HASH_KEY))
.thenReturn(Optional.of(chainHead.getStateRoot().toArrayUnsafe()));
when(keyValueStorage.get(WORLD_BLOCK_HASH_KEY))
.thenReturn(Optional.of(chainHead.getHash().toArrayUnsafe()));
bonsaiWorldStateArchive =
new BonsaiWorldStateArchive(
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(1L),
true,
cachedMerkleTrieLoader);
assertThat(bonsaiWorldStateArchive.getMutable(null, chainHead.getHash(), true))
.containsInstanceOf(BonsaiPersistedWorldState.class);
}
@Test
public void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() {
bonsaiWorldStateArchive =
new BonsaiWorldStateArchive(
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(512L),
cachedMerkleTrieLoader);
final BlockHeader blockHeader = blockBuilder.number(0).buildHeader();
final BlockHeader chainHead = blockBuilder.number(512).buildHeader();
when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader));
when(blockchain.getChainHeadHeader()).thenReturn(chainHead);
assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash(), false)).isEmpty();
}
@SuppressWarnings({"unchecked"})
@Test
public void testGetMutableWithRollbackNotOverrideTrieLogLayer() {
final KeyValueStorageTransaction keyValueStorageTransaction =
mock(KeyValueStorageTransaction.class);
when(keyValueStorage.startTransaction()).thenReturn(keyValueStorageTransaction);
final BlockHeader genesis = blockBuilder.number(0).buildHeader();
final BlockHeader blockHeaderChainA =
blockBuilder.number(1).timestamp(1).parentHash(genesis.getHash()).buildHeader();
final BlockHeader blockHeaderChainB =
blockBuilder.number(1).timestamp(2).parentHash(genesis.getHash()).buildHeader();
final Map<Bytes32, CachedWorldState<BonsaiSnapshotWorldState>> worldStatesByHash =
new HashMap<>();
var mockCachedState =
new CachedSnapshotWorldState(
mock(BonsaiSnapshotWorldState.class, Answers.RETURNS_MOCKS),
mock(TrieLogLayer.class, Answers.RETURNS_MOCKS),
2);
worldStatesByHash.put(blockHeaderChainA.getHash(), mockCachedState);
worldStatesByHash.put(blockHeaderChainB.getHash(), mockCachedState);
var worldStateStorage = new BonsaiWorldStateKeyValueStorage(storageProvider);
bonsaiWorldStateArchive =
spy(
new BonsaiWorldStateArchive(
new SnapshotTrieLogManager(blockchain, worldStateStorage, 12L, worldStatesByHash),
worldStateStorage,
blockchain,
true,
cachedMerkleTrieLoader));
var worldState = (BonsaiPersistedWorldState) bonsaiWorldStateArchive.getMutable();
var updater = spy(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState));
when(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState)).thenReturn(updater);
// initial persisted state hash key
when(blockchain.getBlockHeader(eq(Hash.ZERO))).thenReturn(Optional.of(blockHeaderChainA));
// fake trie log layer
final BytesValueRLPOutput rlpLogBlockB = new BytesValueRLPOutput();
final TrieLogLayer trieLogLayerBlockB = new TrieLogLayer();
trieLogLayerBlockB.setBlockHash(blockHeaderChainB.getHash());
trieLogLayerBlockB.writeTo(rlpLogBlockB);
when(keyValueStorage.get(blockHeaderChainB.getHash().toArrayUnsafe()))
.thenReturn(Optional.of(rlpLogBlockB.encoded().toArrayUnsafe()));
when(blockchain.getBlockHeader(eq(blockHeaderChainB.getHash())))
.thenReturn(Optional.of(blockHeaderChainB));
when(blockchain.getBlockHeader(eq(genesis.getHash()))).thenReturn(Optional.of(genesis));
assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeaderChainB.getHash()))
.containsInstanceOf(BonsaiPersistedWorldState.class);
// verify is not persisting if already present
verify(keyValueStorageTransaction, never())
.put(eq(blockHeaderChainA.getHash().toArrayUnsafe()), any());
verify(keyValueStorageTransaction, never())
.put(eq(blockHeaderChainB.getHash().toArrayUnsafe()), any());
}
}

View File

@@ -16,20 +16,22 @@
package org.hyperledger.besu.ethereum.bonsai;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY;
import static org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY;
import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY;
import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.LayeredTrieLogManager.LayeredWorldStateCache;
import org.hyperledger.besu.ethereum.bonsai.TrieLogManager.CachedWorldState;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer;
import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
@@ -40,16 +42,14 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
@@ -63,7 +63,9 @@ public class BonsaiWorldStateArchiveTest {
@Mock SnappableKeyValueStorage keyValueStorage;
BonsaiWorldStateArchive bonsaiWorldStateArchive;
BonsaiWorldStateProvider bonsaiWorldStateArchive;
@Mock TrieLogManager trieLogManager;
@Before
public void setUp() {
@@ -84,56 +86,52 @@ public class BonsaiWorldStateArchiveTest {
when(keyValueStorage.get(WORLD_BLOCK_HASH_KEY))
.thenReturn(Optional.of(chainHead.getHash().toArrayUnsafe()));
bonsaiWorldStateArchive =
new BonsaiWorldStateArchive(
new BonsaiWorldStateProvider(
trieLogManager,
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(1L),
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem()));
assertThat(bonsaiWorldStateArchive.getMutable(null, chainHead.getHash(), true))
.containsInstanceOf(BonsaiPersistedWorldState.class);
assertThat(bonsaiWorldStateArchive.getMutable(chainHead, true))
.containsInstanceOf(BonsaiWorldState.class);
}
@Test
public void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() {
bonsaiWorldStateArchive =
new BonsaiWorldStateArchive(
new BonsaiWorldStateProvider(
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(512L),
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem()));
final BlockHeader blockHeader = blockBuilder.number(0).buildHeader();
final BlockHeader chainHead = blockBuilder.number(512).buildHeader();
when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader));
when(blockchain.getChainHeadHeader()).thenReturn(chainHead);
assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash(), false)).isEmpty();
assertThat(bonsaiWorldStateArchive.getMutable(blockHeader, false)).isEmpty();
verify(trieLogManager, Mockito.never()).getWorldState(any(Hash.class));
}
@Test
public void testGetMutableWhenLoadLessThanLimitLayersBack() {
bonsaiWorldStateArchive =
new BonsaiWorldStateArchive(
new BonsaiWorldStateProvider(
trieLogManager,
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(512L),
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem()));
final BlockHeader blockHeader = blockBuilder.number(0).buildHeader();
final BlockHeader chainHead = blockBuilder.number(511).buildHeader();
final BonsaiWorldState mockWorldState = mock(BonsaiWorldState.class);
when(mockWorldState.blockHash()).thenReturn(blockHeader.getHash());
when(mockWorldState.freeze()).thenReturn(mockWorldState);
final BytesValueRLPOutput rlpLog = new BytesValueRLPOutput();
final TrieLogLayer trieLogLayer = new TrieLogLayer();
trieLogLayer.setBlockHash(blockHeader.getHash());
trieLogLayer.writeTo(rlpLog);
when(keyValueStorage.get(blockHeader.getHash().toArrayUnsafe()))
.thenReturn(Optional.of(rlpLog.encoded().toArrayUnsafe()));
when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader));
when(trieLogManager.getMaxLayersToLoad()).thenReturn(Long.valueOf(512));
when(trieLogManager.getWorldState(blockHeader.getHash()))
.thenReturn(Optional.of(mockWorldState));
when(blockchain.getChainHeadHeader()).thenReturn(chainHead);
assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash(), false))
.containsInstanceOf(BonsaiLayeredWorldState.class);
assertThat(bonsaiWorldStateArchive.getMutable(blockHeader, false))
.containsInstanceOf(BonsaiWorldState.class);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@@ -141,27 +139,25 @@ public class BonsaiWorldStateArchiveTest {
public void testGetMutableWithStorageInconsistencyRollbackTheState() {
when(keyValueStorage.startTransaction()).thenReturn(mock(KeyValueStorageTransaction.class));
final Map layeredWorldStatesByHash = mock(HashMap.class);
when(trieLogManager.getTrieLogLayer(any())).thenReturn(Optional.of(mock(TrieLogLayer.class)));
var worldStateStorage = new BonsaiWorldStateKeyValueStorage(storageProvider);
bonsaiWorldStateArchive =
spy(
new BonsaiWorldStateArchive(
new LayeredTrieLogManager(
blockchain, worldStateStorage, 12L, layeredWorldStatesByHash),
new BonsaiWorldStateProvider(
trieLogManager,
worldStateStorage,
blockchain,
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem())));
final BlockHeader blockHeader = blockBuilder.number(0).buildHeader();
when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader));
assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash()))
.containsInstanceOf(BonsaiPersistedWorldState.class);
.containsInstanceOf(BonsaiWorldState.class);
// verify is trying to get the trie log layer to rollback
verify(layeredWorldStatesByHash).containsKey(Hash.ZERO);
verify(trieLogManager).getTrieLogLayer(Hash.ZERO);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@@ -169,34 +165,26 @@ public class BonsaiWorldStateArchiveTest {
public void testGetMutableWithStorageConsistencyNotRollbackTheState() {
when(keyValueStorage.startTransaction()).thenReturn(mock(KeyValueStorageTransaction.class));
final Map layeredWorldStatesByHash = mock(HashMap.class);
var worldStateStorage = new BonsaiWorldStateKeyValueStorage(storageProvider);
bonsaiWorldStateArchive =
spy(
new BonsaiWorldStateArchive(
new LayeredTrieLogManager(
blockchain, worldStateStorage, 12L, layeredWorldStatesByHash),
new BonsaiWorldStateProvider(
trieLogManager,
worldStateStorage,
blockchain,
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem())));
var worldState = (BonsaiPersistedWorldState) bonsaiWorldStateArchive.getMutable();
var updater = spy(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState));
when(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState)).thenReturn(updater);
final BlockHeader blockHeader = blockBuilder.number(0).buildHeader();
when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader));
when(blockchain.getBlockHeader(eq(Hash.ZERO))).thenReturn(Optional.of(blockHeader));
assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash()))
.containsInstanceOf(BonsaiPersistedWorldState.class);
.containsInstanceOf(BonsaiWorldState.class);
// verify is not trying to get the trie log layer to rollback when block is present
verify(updater, times(0)).rollBack(any());
verify(updater, times(0)).rollForward(any());
verify(trieLogManager, Mockito.never()).getTrieLogLayer(any());
}
@SuppressWarnings({"unchecked"})
@@ -209,30 +197,17 @@ public class BonsaiWorldStateArchiveTest {
final BlockHeader blockHeaderChainB =
blockBuilder.number(1).timestamp(2).parentHash(genesis.getHash()).buildHeader();
final Map<Bytes32, CachedWorldState<BonsaiLayeredWorldState>> layeredWorldStatesByHash =
mock(HashMap.class);
when(layeredWorldStatesByHash.containsKey(any(Bytes32.class))).thenReturn(true);
when(layeredWorldStatesByHash.get(eq(blockHeaderChainA.getHash())))
.thenReturn(
new LayeredWorldStateCache(mock(BonsaiLayeredWorldState.class, Answers.RETURNS_MOCKS)));
when(layeredWorldStatesByHash.get(eq(blockHeaderChainB.getHash())))
.thenReturn(
new LayeredWorldStateCache(mock(BonsaiLayeredWorldState.class, Answers.RETURNS_MOCKS)));
when(trieLogManager.getTrieLogLayer(any())).thenReturn(Optional.of(mock(TrieLogLayer.class)));
var worldStateStorage = new BonsaiWorldStateKeyValueStorage(storageProvider);
bonsaiWorldStateArchive =
spy(
new BonsaiWorldStateArchive(
new LayeredTrieLogManager(
blockchain, worldStateStorage, 12L, layeredWorldStatesByHash),
new BonsaiWorldStateProvider(
trieLogManager,
worldStateStorage,
blockchain,
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem())));
var worldState = (BonsaiPersistedWorldState) bonsaiWorldStateArchive.getMutable();
var updater = spy(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState));
when(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState)).thenReturn(updater);
// initial persisted state hash key
when(blockchain.getBlockHeader(eq(Hash.ZERO))).thenReturn(Optional.of(blockHeaderChainA));
@@ -241,19 +216,17 @@ public class BonsaiWorldStateArchiveTest {
when(blockchain.getBlockHeader(eq(genesis.getHash()))).thenReturn(Optional.of(genesis));
assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeaderChainB.getHash()))
.containsInstanceOf(BonsaiPersistedWorldState.class);
.containsInstanceOf(BonsaiWorldState.class);
// verify is trying to get the trie log layers to rollback and roll forward
verify(layeredWorldStatesByHash).containsKey(eq(blockHeaderChainA.getHash()));
verify(layeredWorldStatesByHash).get(eq(blockHeaderChainA.getHash()));
verify(layeredWorldStatesByHash).containsKey(eq(blockHeaderChainB.getHash()));
verify(layeredWorldStatesByHash).get(eq(blockHeaderChainB.getHash()));
verify(updater, times(1)).rollBack(any());
verify(updater, times(1)).rollForward(any());
verify(trieLogManager).getTrieLogLayer(eq(blockHeaderChainA.getHash()));
verify(trieLogManager).getTrieLogLayer(eq(blockHeaderChainB.getHash()));
}
@SuppressWarnings({"unchecked"})
@Test
// TODO: refactor to test original intent
@Ignore("needs refactor, getMutable(hash, hash) cannot trigger saveTrieLog")
public void testGetMutableWithRollbackNotOverrideTrieLogLayer() {
final KeyValueStorageTransaction keyValueStorageTransaction =
mock(KeyValueStorageTransaction.class);
@@ -264,28 +237,16 @@ public class BonsaiWorldStateArchiveTest {
final BlockHeader blockHeaderChainB =
blockBuilder.number(1).timestamp(2).parentHash(genesis.getHash()).buildHeader();
final Map<Bytes32, CachedWorldState<BonsaiLayeredWorldState>> layeredWorldStatesByHash =
mock(HashMap.class);
when(layeredWorldStatesByHash.containsKey(any(Bytes32.class))).thenReturn(true);
when(layeredWorldStatesByHash.get(eq(blockHeaderChainA.getHash())))
.thenReturn(
new LayeredWorldStateCache(mock(BonsaiLayeredWorldState.class, Answers.RETURNS_MOCKS)));
when(layeredWorldStatesByHash.get(eq(blockHeaderChainB.getHash())))
.thenReturn(
new LayeredWorldStateCache(mock(BonsaiLayeredWorldState.class, Answers.RETURNS_MOCKS)));
var worldStateStorage = new BonsaiWorldStateKeyValueStorage(storageProvider);
when(trieLogManager.getTrieLogLayer(any(Hash.class)))
.thenReturn(Optional.of(mock(TrieLogLayer.class)));
bonsaiWorldStateArchive =
spy(
new BonsaiWorldStateArchive(
new LayeredTrieLogManager(
blockchain, worldStateStorage, 12L, layeredWorldStatesByHash),
worldStateStorage,
new BonsaiWorldStateProvider(
trieLogManager,
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem())));
var worldState = (BonsaiPersistedWorldState) bonsaiWorldStateArchive.getMutable();
var updater = spy(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState));
when(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState)).thenReturn(updater);
// initial persisted state hash key
when(blockchain.getBlockHeader(eq(Hash.ZERO))).thenReturn(Optional.of(blockHeaderChainA));
@@ -302,7 +263,7 @@ public class BonsaiWorldStateArchiveTest {
when(blockchain.getBlockHeader(eq(genesis.getHash()))).thenReturn(Optional.of(genesis));
assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeaderChainB.getHash()))
.containsInstanceOf(BonsaiPersistedWorldState.class);
.containsInstanceOf(BonsaiWorldState.class);
// verify is not persisting if already present
verify(keyValueStorageTransaction, never())

View File

@@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.bonsai;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY;
import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;
import org.hyperledger.besu.ethereum.core.TrieGenerator;
import org.hyperledger.besu.ethereum.rlp.RLP;

View File

@@ -19,6 +19,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;
import org.hyperledger.besu.ethereum.core.TrieGenerator;
import org.hyperledger.besu.ethereum.rlp.RLP;

View File

@@ -1,131 +0,0 @@
/*
* Copyright Hyperledger Besu contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.SnapshotMutableWorldState;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class LayeredWorldStateTests {
@Mock BonsaiWorldStateArchive archive;
@Mock Blockchain blockchain;
@Test
public void layeredWorldStateUsesCorrectPersistedWorldStateOnCopy() {
// when copying a layered worldstate we return mutable copy,
// ensure it is for the correct/corresponding worldstate:
Hash state1Hash = Hash.hash(Bytes.of("first_state".getBytes(StandardCharsets.UTF_8)));
Hash block1Hash = Hash.hash(Bytes.of("first_block".getBytes(StandardCharsets.UTF_8)));
var mockStorage = mock(BonsaiWorldStateKeyValueStorage.class);
when(mockStorage.getWorldStateBlockHash()).thenReturn(Optional.of(block1Hash));
when(mockStorage.getWorldStateRootHash()).thenReturn(Optional.of(state1Hash));
SnapshotMutableWorldState mockState =
when(mock(SnapshotMutableWorldState.class).getWorldStateStorage())
.thenReturn(mockStorage)
.getMock();
TrieLogLayer mockLayer =
when(mock(TrieLogLayer.class).getBlockHash()).thenReturn(Hash.ZERO).getMock();
BonsaiLayeredWorldState mockLayerWs =
new BonsaiLayeredWorldState(
blockchain,
archive,
Optional.of(mock(BonsaiLayeredWorldState.class)),
1L,
state1Hash,
mockLayer);
// mimic persisted state being at a different state:
when(archive.getMutableSnapshot(mockLayer.getBlockHash())).thenReturn(Optional.of(mockState));
try (var copyOfLayer1 = mockLayerWs.copy()) {
assertThat(copyOfLayer1.rootHash()).isEqualTo(state1Hash);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@Test
public void saveTrieLogShouldUseCorrectPersistedWorldStateOnCopy() {
// when we save a snapshot based worldstate, ensure
// we used the passed in worldstate and roothash for calculating the trielog diff
Hash testStateRoot = Hash.fromHexStringLenient("0xdeadbeef");
BlockHeader testHeader = new BlockHeaderTestFixture().stateRoot(testStateRoot).buildHeader();
BonsaiWorldStateKeyValueStorage testStorage =
mock(BonsaiWorldStateKeyValueStorage.class, Answers.RETURNS_DEEP_STUBS);
BonsaiSnapshotWorldState testState = mock(BonsaiSnapshotWorldState.class);
when(testState.getWorldStateStorage()).thenReturn(testStorage);
when(testState.rootHash()).thenReturn(testStateRoot);
when(testState.blockHash()).thenReturn(testHeader.getBlockHash());
BonsaiWorldStateUpdater testUpdater = new BonsaiWorldStateUpdater(testState);
// mock kvstorage to mimic head being in a different state than testState
LayeredTrieLogManager manager =
spy(
new LayeredTrieLogManager(
blockchain, mock(BonsaiWorldStateKeyValueStorage.class), 10L, new HashMap<>()));
// assert we are using the target worldstate storage:
final AtomicBoolean calledPrepareTrieLog = new AtomicBoolean(false);
doAnswer(
prepareCallSpec -> {
Hash blockHash = prepareCallSpec.getArgument(0, BlockHeader.class).getHash();
Hash rootHash = prepareCallSpec.getArgument(1, Hash.class);
BonsaiPersistedWorldState ws =
prepareCallSpec.getArgument(4, BonsaiPersistedWorldState.class);
assertThat(ws.rootHash()).isEqualTo(rootHash);
assertThat(ws.blockHash()).isEqualTo(blockHash);
calledPrepareTrieLog.set(true);
return mock(TrieLogLayer.class);
})
.when(manager)
.prepareTrieLog(
any(BlockHeader.class),
any(Hash.class),
any(BonsaiWorldStateUpdater.class),
any(BonsaiWorldStateArchive.class),
any(BonsaiPersistedWorldState.class));
manager.saveTrieLog(archive, testUpdater, testStateRoot, testHeader, testState);
assertThat(calledPrepareTrieLog.get()).isTrue();
}
}

View File

@@ -22,6 +22,11 @@ import static org.mockito.Mockito.mock;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Difficulty;
@@ -50,14 +55,14 @@ import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class LogRollingTests {
private BonsaiWorldStateArchive archive;
private BonsaiWorldStateProvider archive;
private InMemoryKeyValueStorage accountStorage;
private InMemoryKeyValueStorage codeStorage;
private InMemoryKeyValueStorage storageStorage;
private InMemoryKeyValueStorage trieBranchStorage;
private InMemoryKeyValueStorage trieLogStorage;
private BonsaiWorldStateArchive secondArchive;
private BonsaiWorldStateProvider secondArchive;
private InMemoryKeyValueStorage secondAccountStorage;
private InMemoryKeyValueStorage secondCodeStorage;
private InMemoryKeyValueStorage secondStorageStorage;
@@ -116,7 +121,7 @@ public class LogRollingTests {
final InMemoryKeyValueStorageProvider provider = new InMemoryKeyValueStorageProvider();
final CachedMerkleTrieLoader cachedMerkleTrieLoader =
new CachedMerkleTrieLoader(new NoOpMetricsSystem());
archive = new BonsaiWorldStateArchive(provider, blockchain, cachedMerkleTrieLoader);
archive = new BonsaiWorldStateProvider(provider, blockchain, cachedMerkleTrieLoader);
accountStorage =
(InMemoryKeyValueStorage)
provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE);
@@ -138,7 +143,7 @@ public class LogRollingTests {
final CachedMerkleTrieLoader secondOptimizedMerkleTrieLoader =
new CachedMerkleTrieLoader(new NoOpMetricsSystem());
secondArchive =
new BonsaiWorldStateArchive(secondProvider, blockchain, secondOptimizedMerkleTrieLoader);
new BonsaiWorldStateProvider(secondProvider, blockchain, secondOptimizedMerkleTrieLoader);
secondAccountStorage =
(InMemoryKeyValueStorage)
secondProvider.getStorageBySegmentIdentifier(
@@ -163,8 +168,8 @@ public class LogRollingTests {
@Test
public void simpleRollForwardTest() {
final BonsaiPersistedWorldState worldState =
new BonsaiPersistedWorldState(
final BonsaiWorldState worldState =
new BonsaiWorldState(
archive,
new BonsaiWorldStateKeyValueStorage(
accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage));
@@ -177,8 +182,8 @@ public class LogRollingTests {
updater.commit();
worldState.persist(headerOne);
final BonsaiPersistedWorldState secondWorldState =
new BonsaiPersistedWorldState(
final BonsaiWorldState secondWorldState =
new BonsaiWorldState(
secondArchive,
new BonsaiWorldStateKeyValueStorage(
secondAccountStorage,
@@ -186,8 +191,8 @@ public class LogRollingTests {
secondStorageStorage,
secondTrieBranchStorage,
secondTrieLogStorage));
final BonsaiWorldStateUpdater secondUpdater =
(BonsaiWorldStateUpdater) secondWorldState.updater();
final BonsaiWorldStateUpdateAccumulator secondUpdater =
(BonsaiWorldStateUpdateAccumulator) secondWorldState.updater();
final Optional<byte[]> value = trieLogStorage.get(headerOne.getHash().toArrayUnsafe());
@@ -212,8 +217,8 @@ public class LogRollingTests {
@Test
public void rollForwardTwice() {
final BonsaiPersistedWorldState worldState =
new BonsaiPersistedWorldState(
final BonsaiWorldState worldState =
new BonsaiWorldState(
archive,
new BonsaiWorldStateKeyValueStorage(
accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage));
@@ -234,8 +239,8 @@ public class LogRollingTests {
worldState.persist(headerTwo);
final BonsaiPersistedWorldState secondWorldState =
new BonsaiPersistedWorldState(
final BonsaiWorldState secondWorldState =
new BonsaiWorldState(
secondArchive,
new BonsaiWorldStateKeyValueStorage(
secondAccountStorage,
@@ -243,8 +248,8 @@ public class LogRollingTests {
secondStorageStorage,
secondTrieBranchStorage,
secondTrieLogStorage));
final BonsaiWorldStateUpdater secondUpdater =
(BonsaiWorldStateUpdater) secondWorldState.updater();
final BonsaiWorldStateUpdateAccumulator secondUpdater =
(BonsaiWorldStateUpdateAccumulator) secondWorldState.updater();
final TrieLogLayer layerOne = getTrieLogLayer(trieLogStorage, headerOne.getHash());
secondUpdater.rollForward(layerOne);
@@ -270,8 +275,8 @@ public class LogRollingTests {
@Test
public void rollBackOnce() {
final BonsaiPersistedWorldState worldState =
new BonsaiPersistedWorldState(
final BonsaiWorldState worldState =
new BonsaiWorldState(
archive,
new BonsaiWorldStateKeyValueStorage(
accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage));
@@ -291,16 +296,16 @@ public class LogRollingTests {
updater2.commit();
worldState.persist(headerTwo);
final BonsaiWorldStateUpdater firstRollbackUpdater =
(BonsaiWorldStateUpdater) worldState.updater();
final BonsaiWorldStateUpdateAccumulator firstRollbackUpdater =
(BonsaiWorldStateUpdateAccumulator) worldState.updater();
final TrieLogLayer layerTwo = getTrieLogLayer(trieLogStorage, headerTwo.getHash());
firstRollbackUpdater.rollBack(layerTwo);
worldState.persist(headerOne);
final BonsaiPersistedWorldState secondWorldState =
new BonsaiPersistedWorldState(
final BonsaiWorldState secondWorldState =
new BonsaiWorldState(
secondArchive,
new BonsaiWorldStateKeyValueStorage(
secondAccountStorage,

View File

@@ -18,6 +18,11 @@ package org.hyperledger.besu.ethereum.bonsai;
import static com.google.common.base.Preconditions.checkArgument;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
@@ -41,8 +46,8 @@ public class RollingImport {
final InMemoryKeyValueStorageProvider provider = new InMemoryKeyValueStorageProvider();
final CachedMerkleTrieLoader cachedMerkleTrieLoader =
new CachedMerkleTrieLoader(new NoOpMetricsSystem());
final BonsaiWorldStateArchive archive =
new BonsaiWorldStateArchive(provider, null, cachedMerkleTrieLoader);
final BonsaiWorldStateProvider archive =
new BonsaiWorldStateProvider(provider, null, cachedMerkleTrieLoader);
final InMemoryKeyValueStorage accountStorage =
(InMemoryKeyValueStorage)
provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE);
@@ -59,8 +64,8 @@ public class RollingImport {
final InMemoryKeyValueStorage trieLogStorage =
(InMemoryKeyValueStorage)
provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE);
final BonsaiPersistedWorldState bonsaiState =
new BonsaiPersistedWorldState(
final BonsaiWorldState bonsaiState =
new BonsaiWorldState(
archive,
new BonsaiWorldStateKeyValueStorage(
accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage));
@@ -74,7 +79,8 @@ public class RollingImport {
}
final TrieLogLayer layer =
TrieLogLayer.readFrom(new BytesValueRLPInput(Bytes.wrap(bytes), false));
final BonsaiWorldStateUpdater updater = (BonsaiWorldStateUpdater) bonsaiState.updater();
final BonsaiWorldStateUpdateAccumulator updater =
(BonsaiWorldStateUpdateAccumulator) bonsaiState.updater();
updater.rollForward(layer);
updater.commit();
bonsaiState.persist(null);
@@ -102,7 +108,8 @@ public class RollingImport {
final byte[] bytes = reader.readBytes();
final TrieLogLayer layer =
TrieLogLayer.readFrom(new BytesValueRLPInput(Bytes.wrap(bytes), false));
final BonsaiWorldStateUpdater updater = (BonsaiWorldStateUpdater) bonsaiState.updater();
final BonsaiWorldStateUpdateAccumulator updater =
(BonsaiWorldStateUpdateAccumulator) bonsaiState.updater();
updater.rollBack(layer);
updater.commit();
bonsaiState.persist(null);

View File

@@ -38,7 +38,8 @@ class WithdrawalsProcessorTest {
void shouldProcessEmptyWithdrawalsWithoutChangingWorldState() {
final MutableWorldState worldState =
createWorldStateWithAccounts(List.of(entry("0x1", 1), entry("0x2", 2), entry("0x3", 3)));
final MutableWorldState originalState = worldState.copy();
final MutableWorldState originalWorldState =
createWorldStateWithAccounts(List.of(entry("0x1", 1), entry("0x2", 2), entry("0x3", 3)));
final WorldUpdater updater = worldState.updater();
final WithdrawalsProcessor withdrawalsProcessor = new WithdrawalsProcessor();
@@ -47,7 +48,7 @@ class WithdrawalsProcessorTest {
assertThat(worldState.get(Address.fromHexString("0x1")).getBalance()).isEqualTo(Wei.of(1));
assertThat(worldState.get(Address.fromHexString("0x2")).getBalance()).isEqualTo(Wei.of(2));
assertThat(worldState.get(Address.fromHexString("0x3")).getBalance()).isEqualTo(Wei.of(3));
assertThat(originalState).isEqualTo(worldState);
assertThat(originalWorldState).isEqualTo(worldState);
}
@Test

View File

@@ -31,6 +31,7 @@ import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.Transaction;
@@ -57,7 +58,6 @@ import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -74,9 +74,6 @@ public class TransactionSimulatorTest {
private static final Address DEFAULT_FROM =
Address.fromHexString("0x0000000000000000000000000000000000000000");
private static final Hash DEFAULT_BLOCK_HEADER_HASH =
Hash.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001");
private TransactionSimulator transactionSimulator;
@Mock private Blockchain blockchain;
@@ -87,9 +84,10 @@ public class TransactionSimulatorTest {
@Mock private MainnetTransactionProcessor transactionProcessor;
@Mock private MainnetTransactionValidator transactionValidator;
private final BlockHeaderTestFixture blockHeaderTestFixture = new BlockHeaderTestFixture();
@Before
public void setUp() {
when(this.worldState.isPersistable()).thenReturn(true);
this.transactionSimulator =
new TransactionSimulator(blockchain, worldStateArchive, protocolSchedule);
@@ -111,8 +109,11 @@ public class TransactionSimulatorTest {
public void shouldReturnSuccessfulResultWhenProcessingIsSuccessful() {
final CallParameter callParameter = legacyTransactionCallParameter();
mockBlockchainForBlockHeader(Hash.ZERO, 1L);
mockWorldStateForAccount(Hash.ZERO, callParameter.getFrom(), 1L);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -139,8 +140,11 @@ public class TransactionSimulatorTest {
public void shouldSetGasPriceToZeroWhenExceedingBalanceAllowed() {
final CallParameter callParameter = legacyTransactionCallParameter(Wei.ONE);
mockBlockchainForBlockHeader(Hash.ZERO, 1L);
mockWorldStateForAccount(Hash.ZERO, callParameter.getFrom(), 1L);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -170,8 +174,10 @@ public class TransactionSimulatorTest {
public void shouldSetFeePerGasToZeroWhenExceedingBalanceAllowed() {
final CallParameter callParameter = eip1559TransactionCallParameter(Wei.ONE, Wei.ONE);
mockBlockchainForBlockHeader(Hash.ZERO, 1L, Wei.ONE);
mockWorldStateForAccount(Hash.ZERO, callParameter.getFrom(), 1L);
final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE);
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -203,8 +209,11 @@ public class TransactionSimulatorTest {
public void shouldNotSetGasPriceToZeroWhenExceedingBalanceIsNotAllowed() {
final CallParameter callParameter = legacyTransactionCallParameter(Wei.ONE);
mockBlockchainForBlockHeader(Hash.ZERO, 1L);
mockWorldStateForAccount(Hash.ZERO, callParameter.getFrom(), 1L);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -234,8 +243,10 @@ public class TransactionSimulatorTest {
public void shouldNotSetFeePerGasToZeroWhenExceedingBalanceIsNotAllowed() {
final CallParameter callParameter = eip1559TransactionCallParameter(Wei.ONE, Wei.ONE);
mockBlockchainForBlockHeader(Hash.ZERO, 1L, Wei.ONE);
mockWorldStateForAccount(Hash.ZERO, callParameter.getFrom(), 1L);
final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE);
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -267,8 +278,11 @@ public class TransactionSimulatorTest {
public void shouldUseDefaultValuesWhenMissingOptionalFields() {
final CallParameter callParameter = legacyTransactionCallParameter();
mockBlockchainForBlockHeader(Hash.ZERO, 1L);
mockWorldStateForAccount(Hash.ZERO, Address.fromHexString("0x0"), 1L);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, Address.fromHexString("0x0"), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -293,8 +307,11 @@ public class TransactionSimulatorTest {
public void shouldUseZeroNonceWhenAccountDoesNotExist() {
final CallParameter callParameter = legacyTransactionCallParameter();
mockBlockchainForBlockHeader(Hash.ZERO, 1L);
mockWorldStateForAbsentAccount(Hash.ZERO);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAbsentAccount(blockHeader);
final Transaction expectedTransaction =
Transaction.builder()
@@ -319,8 +336,11 @@ public class TransactionSimulatorTest {
public void shouldReturnFailureResultWhenProcessingFails() {
final CallParameter callParameter = legacyTransactionCallParameter();
mockBlockchainForBlockHeader(Hash.ZERO, 1L);
mockWorldStateForAccount(Hash.ZERO, Address.fromHexString("0x0"), 1L);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, Address.fromHexString("0x0"), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -357,8 +377,11 @@ public class TransactionSimulatorTest {
public void shouldReturnSuccessfulResultWhenProcessingIsSuccessfulByHash() {
final CallParameter callParameter = legacyTransactionCallParameter();
mockBlockchainForBlockHeader(Hash.ZERO, 1L, DEFAULT_BLOCK_HEADER_HASH);
mockWorldStateForAccount(Hash.ZERO, callParameter.getFrom(), 1L);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -375,7 +398,7 @@ public class TransactionSimulatorTest {
mockProcessorStatusForTransaction(expectedTransaction, Status.SUCCESSFUL);
final Optional<TransactionSimulatorResult> result =
transactionSimulator.process(callParameter, DEFAULT_BLOCK_HEADER_HASH);
transactionSimulator.process(callParameter, blockHeader.getBlockHash());
assertThat(result.get().isSuccessful()).isTrue();
verifyTransactionWasProcessed(expectedTransaction);
@@ -385,8 +408,11 @@ public class TransactionSimulatorTest {
public void shouldUseDefaultValuesWhenMissingOptionalFieldsByHash() {
final CallParameter callParameter = legacyTransactionCallParameter();
mockBlockchainForBlockHeader(Hash.ZERO, 1L, DEFAULT_BLOCK_HEADER_HASH);
mockWorldStateForAccount(Hash.ZERO, Address.fromHexString("0x0"), 1L);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, Address.fromHexString("0x0"), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -402,7 +428,7 @@ public class TransactionSimulatorTest {
.build();
mockProcessorStatusForTransaction(expectedTransaction, Status.SUCCESSFUL);
transactionSimulator.process(callParameter, DEFAULT_BLOCK_HEADER_HASH);
transactionSimulator.process(callParameter, blockHeader.getBlockHash());
verifyTransactionWasProcessed(expectedTransaction);
}
@@ -411,8 +437,11 @@ public class TransactionSimulatorTest {
public void shouldUseZeroNonceWhenAccountDoesNotExistByHash() {
final CallParameter callParameter = legacyTransactionCallParameter();
mockBlockchainForBlockHeader(Hash.ZERO, 1L, DEFAULT_BLOCK_HEADER_HASH);
mockWorldStateForAbsentAccount(Hash.ZERO);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAbsentAccount(blockHeader);
final Transaction expectedTransaction =
Transaction.builder()
@@ -428,7 +457,7 @@ public class TransactionSimulatorTest {
.build();
mockProcessorStatusForTransaction(expectedTransaction, Status.SUCCESSFUL);
transactionSimulator.process(callParameter, DEFAULT_BLOCK_HEADER_HASH);
transactionSimulator.process(callParameter, blockHeader.getBlockHash());
verifyTransactionWasProcessed(expectedTransaction);
}
@@ -437,8 +466,11 @@ public class TransactionSimulatorTest {
public void shouldReturnFailureResultWhenProcessingFailsByHash() {
final CallParameter callParameter = legacyTransactionCallParameter();
mockBlockchainForBlockHeader(Hash.ZERO, 1L, DEFAULT_BLOCK_HEADER_HASH);
mockWorldStateForAccount(Hash.ZERO, Address.fromHexString("0x0"), 1L);
final BlockHeader blockHeader =
blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader();
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, Address.fromHexString("0x0"), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -455,7 +487,7 @@ public class TransactionSimulatorTest {
mockProcessorStatusForTransaction(expectedTransaction, Status.FAILED);
final Optional<TransactionSimulatorResult> result =
transactionSimulator.process(callParameter, DEFAULT_BLOCK_HEADER_HASH);
transactionSimulator.process(callParameter, blockHeader.getBlockHash());
assertThat(result.get().isSuccessful()).isFalse();
verifyTransactionWasProcessed(expectedTransaction);
@@ -465,8 +497,10 @@ public class TransactionSimulatorTest {
public void shouldReturnSuccessfulResultWhenEip1559TransactionProcessingIsSuccessful() {
final CallParameter callParameter = eip1559TransactionCallParameter();
mockBlockchainForBlockHeader(Hash.ZERO, 1L, Wei.ONE);
mockWorldStateForAccount(Hash.ZERO, callParameter.getFrom(), 1L);
final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE);
mockBlockchainForBlockHeader(blockHeader);
mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L);
final Transaction expectedTransaction =
Transaction.builder()
@@ -492,45 +526,38 @@ public class TransactionSimulatorTest {
}
private void mockWorldStateForAccount(
final Hash stateRoot, final Address address, final long nonce) {
final BlockHeader blockHeader, final Address address, final long nonce) {
final Account account = mock(Account.class);
when(account.getNonce()).thenReturn(nonce);
when(worldStateArchive.getMutable(eq(stateRoot), any(), anyBoolean()))
when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean()))
.thenReturn(Optional.of(worldState));
final WorldUpdater updater = mock(WorldUpdater.class);
when(updater.get(address)).thenReturn(account);
when(worldState.updater()).thenReturn(updater);
}
private void mockWorldStateForAbsentAccount(final Hash stateRoot) {
when(worldStateArchive.getMutable(eq(stateRoot), any(), anyBoolean()))
private void mockWorldStateForAbsentAccount(final BlockHeader blockHeader) {
when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean()))
.thenReturn(Optional.of(worldState));
final WorldUpdater updater = mock(WorldUpdater.class);
when(updater.get(any())).thenReturn(null);
when(worldState.updater()).thenReturn(updater);
}
private void mockBlockchainForBlockHeader(final Hash stateRoot, final long blockNumber) {
mockBlockchainForBlockHeader(stateRoot, blockNumber, Hash.ZERO);
}
private void mockBlockchainForBlockHeader(
final Hash stateRoot, final long blockNumber, final Hash headerHash) {
final BlockHeader blockHeader = mock(BlockHeader.class);
when(blockHeader.getStateRoot()).thenReturn(stateRoot);
when(blockHeader.getNumber()).thenReturn(blockNumber);
when(blockchain.getBlockHeader(blockNumber)).thenReturn(Optional.of(blockHeader));
when(blockchain.getBlockHeader(headerHash)).thenReturn(Optional.of(blockHeader));
}
private void mockBlockchainForBlockHeader(
private BlockHeader mockBlockHeader(
final Hash stateRoot, final long blockNumber, final Wei baseFee) {
final BlockHeader blockHeader = mock(BlockHeader.class, Answers.RETURNS_MOCKS);
when(blockHeader.getStateRoot()).thenReturn(stateRoot);
when(blockHeader.getNumber()).thenReturn(blockNumber);
when(blockHeader.getBaseFee()).thenReturn(Optional.of(baseFee));
when(blockHeader.getDifficulty()).thenReturn(Difficulty.ONE);
when(blockchain.getBlockHeader(blockNumber)).thenReturn(Optional.of(blockHeader));
return blockHeaderTestFixture
.stateRoot(stateRoot)
.number(blockNumber)
.baseFeePerGas(baseFee)
.difficulty(Difficulty.ONE)
.buildHeader();
}
private void mockBlockchainForBlockHeader(final BlockHeader blockHeader) {
when(blockchain.getBlockHeader(blockHeader.getNumber())).thenReturn(Optional.of(blockHeader));
when(blockchain.getBlockHeader(blockHeader.getBlockHash()))
.thenReturn(Optional.of(blockHeader));
}
private void mockProcessorStatusForTransaction(
@@ -553,6 +580,7 @@ public class TransactionSimulatorTest {
when(result.isSuccessful()).thenReturn(false);
break;
}
when(transactionProcessor.processTransaction(
any(),
any(),

View File

@@ -42,6 +42,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -58,7 +59,7 @@ public class MarkSweepPrunerTest {
private final BlockDataGenerator gen = new BlockDataGenerator();
private final NoOpMetricsSystem metricsSystem = new NoOpMetricsSystem();
private final Map<Bytes, byte[]> hashValueStore = spy(new HashMap<>());
private final Map<Bytes, Optional<byte[]>> hashValueStore = spy(new HashMap<>());
private final InMemoryKeyValueStorage stateStorage = new TestInMemoryStorage(hashValueStore);
private final WorldStateStorage worldStateStorage =
spy(new WorldStateKeyValueStorage(stateStorage));
@@ -113,7 +114,7 @@ public class MarkSweepPrunerTest {
// Check that storage contains only the values we expect
assertThat(hashValueStore.size()).isEqualTo(expectedNodes.size());
assertThat(hashValueStore.values())
assertThat(hashValueStore.values().stream().map(Optional::get))
.containsExactlyInAnyOrderElementsOf(
expectedNodes.stream().map(Bytes::toArrayUnsafe).collect(Collectors.toSet()));
}
@@ -275,7 +276,7 @@ public class MarkSweepPrunerTest {
// Proxy class so that we have access to the constructor that takes our own map
private static class TestInMemoryStorage extends InMemoryKeyValueStorage {
public TestInMemoryStorage(final Map<Bytes, byte[]> hashValueStore) {
public TestInMemoryStorage(final Map<Bytes, Optional<byte[]>> hashValueStore) {
super(hashValueStore);
}
}

View File

@@ -19,7 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import org.hyperledger.besu.consensus.merge.ForkchoiceEvent;
import org.hyperledger.besu.consensus.merge.UnverifiedForkchoiceListener;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
import org.hyperledger.besu.ethereum.eth.sync.checkpointsync.CheckpointDownloaderFactory;
@@ -43,6 +43,8 @@ import org.hyperledger.besu.plugin.services.BesuEvents.SyncStatusListener;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.util.log.FramedLogMessage;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Path;
import java.time.Clock;
import java.util.ArrayList;
@@ -206,6 +208,7 @@ public class DefaultSynchronizer implements Synchronizer, UnverifiedForkchoiceLi
manager.start();
}
});
CompletableFuture<Void> future;
if (fastSyncDownloader.isPresent()) {
future = fastSyncDownloader.get().start().thenCompose(this::handleSyncResult);
@@ -312,6 +315,16 @@ public class DefaultSynchronizer implements Synchronizer, UnverifiedForkchoiceLi
fastSyncDownloader.get().deleteFastSyncState();
}
LOG.atDebug()
.setMessage("heal stacktrace: \n{}")
.addArgument(
() -> {
var sw = new StringWriter();
new Exception().printStackTrace(new PrintWriter(sw, true));
return sw.toString();
})
.log();
final List<String> lines = new ArrayList<>();
lines.add("Besu has identified a problem with its worldstate database.");
lines.add("Your node will fetch the correct data from peers to repair the problem.");
@@ -322,8 +335,8 @@ public class DefaultSynchronizer implements Synchronizer, UnverifiedForkchoiceLi
this.syncState.markResyncNeeded();
maybeAccountToRepair.ifPresent(
address -> {
if (this.protocolContext.getWorldStateArchive() instanceof BonsaiWorldStateArchive) {
((BonsaiWorldStateArchive) this.protocolContext.getWorldStateArchive())
if (this.protocolContext.getWorldStateArchive() instanceof BonsaiWorldStateProvider) {
((BonsaiWorldStateProvider) this.protocolContext.getWorldStateArchive())
.prepareStateHealing(
org.hyperledger.besu.datatypes.Address.wrap(address), location);
}

View File

@@ -16,7 +16,7 @@ package org.hyperledger.besu.ethereum.eth.sync.fastsync;
import static org.hyperledger.besu.util.FutureUtils.exceptionallyCompose;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.eth.manager.exceptions.MaxRetriesReachedException;
import org.hyperledger.besu.ethereum.eth.sync.ChainDownloader;
import org.hyperledger.besu.ethereum.eth.sync.TrailingPeerRequirements;

View File

@@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.hyperledger.besu.ethereum.trie.CompactEncoding;

View File

@@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
import org.hyperledger.besu.ethereum.eth.sync.PivotBlockSelector;

View File

@@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.hyperledger.besu.ethereum.trie.CompactEncoding;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;

View File

@@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.eth.sync.snapsync.request;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncState;
import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState;
import org.hyperledger.besu.ethereum.rlp.RLP;

View File

@@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.eth.sync.snapsync.request;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncState;
import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState;
import org.hyperledger.besu.ethereum.trie.CompactEncoding;

View File

@@ -334,15 +334,7 @@ public class TransactionPool implements BlockAddedObserver {
try (final var worldState =
protocolContext
.getWorldStateArchive()
.getMutable(
chainHeadBlockHeader.getStateRoot(), chainHeadBlockHeader.getBlockHash(), false)
.map(
ws -> {
if (!ws.isPersistable()) {
return ws.copy();
}
return ws;
})
.getMutable(chainHeadBlockHeader, false)
.orElseThrow()) {
final Account senderAccount = worldState.get(transaction.getSender());
return new ValidationResultAndAccount(

View File

@@ -1110,14 +1110,15 @@ public final class EthProtocolManagerTest {
// Create a transaction pool. This has a side effect of registering a listener for the
// transactions message.
TransactionPoolFactory.createTransactionPool(
protocolSchedule,
protocolContext,
ethManager.ethContext(),
TestClock.system(ZoneId.systemDefault()),
metricsSystem,
new SyncState(blockchain, ethManager.ethContext().getEthPeers()),
new MiningParameters.Builder().minTransactionGasPrice(Wei.ZERO).build(),
TransactionPoolConfiguration.DEFAULT);
protocolSchedule,
protocolContext,
ethManager.ethContext(),
TestClock.system(ZoneId.systemDefault()),
metricsSystem,
new SyncState(blockchain, ethManager.ethContext().getEthPeers()),
new MiningParameters.Builder().minTransactionGasPrice(Wei.ZERO).build(),
TransactionPoolConfiguration.DEFAULT)
.setEnabled();
// Send just a transaction message.
final PeerConnection peer = setupPeer(ethManager, (cap, msg, connection) -> {});

View File

@@ -20,7 +20,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;

View File

@@ -25,7 +25,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.chain.BlockAddedEvent;
import org.hyperledger.besu.ethereum.chain.BlockAddedObserver;
import org.hyperledger.besu.ethereum.chain.Blockchain;

View File

@@ -31,6 +31,7 @@ dependencies {
api 'org.apache.commons:commons-lang3'
api 'org.apache.tuweni:tuweni-bytes'
api 'org.apache.tuweni:tuweni-units'
implementation 'com.google.guava:guava'
}
configurations { testArtifacts }
@@ -66,7 +67,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = 'sh5/mQlcilXjb/z51QbcvJTlqpI2uBe2EMli1WBV2tw='
knownHash = 'LhQ25UazqtTJuZJN1bj76imJ7QdkFRR6dUyxt7Ig5cs='
}
check.dependsOn('checkAPIChanges')

View File

@@ -24,11 +24,4 @@ public interface SnappedKeyValueStorage extends KeyValueStorage {
* @return the snapshot transaction
*/
KeyValueStorageTransaction getSnapshotTransaction();
/**
* Clone from snapshot.
*
* @return the snapped key value storage
*/
SnappedKeyValueStorage cloneFromSnapshot();
}

View File

@@ -26,19 +26,28 @@ import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDbSegmentIdenti
import java.io.IOException;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;
import org.rocksdb.OptimisticTransactionDB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** The RocksDb columnar key value snapshot. */
public class RocksDBColumnarKeyValueSnapshot implements SnappedKeyValueStorage {
private static final Logger LOG = LoggerFactory.getLogger(RocksDBColumnarKeyValueSnapshot.class);
/** The Db. */
final OptimisticTransactionDB db;
/** The Snap tx. */
final RocksDBSnapshotTransaction snapTx;
private final AtomicBoolean closed = new AtomicBoolean(false);
/**
* Instantiates a new RocksDb columnar key value snapshot.
*
@@ -54,29 +63,27 @@ public class RocksDBColumnarKeyValueSnapshot implements SnappedKeyValueStorage {
this.snapTx = new RocksDBSnapshotTransaction(db, segment.get(), metrics);
}
private RocksDBColumnarKeyValueSnapshot(
final OptimisticTransactionDB db, final RocksDBSnapshotTransaction snapTx) {
this.db = db;
this.snapTx = snapTx;
}
@Override
public Optional<byte[]> get(final byte[] key) throws StorageException {
throwIfClosed();
return snapTx.get(key);
}
@Override
public Stream<Pair<byte[], byte[]>> stream() {
throwIfClosed();
return snapTx.stream();
}
@Override
public Stream<byte[]> streamKeys() {
throwIfClosed();
return snapTx.streamKeys();
}
@Override
public boolean tryDelete(final byte[] key) throws StorageException {
throwIfClosed();
snapTx.remove(key);
return true;
}
@@ -109,21 +116,26 @@ public class RocksDBColumnarKeyValueSnapshot implements SnappedKeyValueStorage {
@Override
public boolean containsKey(final byte[] key) throws StorageException {
throwIfClosed();
return snapTx.get(key).isPresent();
}
@Override
public void close() throws IOException {
snapTx.close();
if (closed.compareAndSet(false, true)) {
snapTx.close();
}
}
private void throwIfClosed() {
if (closed.get()) {
LOG.error("Attempting to use a closed RocksDBKeyValueStorage");
throw new IllegalStateException("Storage has been closed");
}
}
@Override
public KeyValueStorageTransaction getSnapshotTransaction() {
return snapTx;
}
@Override
public SnappedKeyValueStorage cloneFromSnapshot() {
return new RocksDBColumnarKeyValueSnapshot(db, snapTx.copy());
}
}

View File

@@ -40,7 +40,6 @@ import org.slf4j.LoggerFactory;
public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(RocksDBSnapshotTransaction.class);
private static final String NO_SPACE_LEFT_ON_DEVICE = "No space left on device";
private final RocksDBMetrics metrics;
private final OptimisticTransactionDB db;
private final ColumnFamilyHandle columnFamilyHandle;

View File

@@ -97,6 +97,7 @@ public class RocksDBKeyValueStorage implements KeyValueStorage {
@Override
public void clear() throws StorageException {
throwIfClosed();
try (final RocksIterator rocksIterator = db.newIterator()) {
rocksIterator.seekToFirst();
if (rocksIterator.isValid()) {
@@ -140,6 +141,7 @@ public class RocksDBKeyValueStorage implements KeyValueStorage {
@Override
public Stream<Pair<byte[], byte[]>> stream() {
throwIfClosed();
final RocksIterator rocksIterator = db.newIterator();
rocksIterator.seekToFirst();
return RocksDbIterator.create(rocksIterator).toStream();
@@ -147,6 +149,7 @@ public class RocksDBKeyValueStorage implements KeyValueStorage {
@Override
public Stream<byte[]> streamKeys() {
throwIfClosed();
final RocksIterator rocksIterator = db.newIterator();
rocksIterator.seekToFirst();
return RocksDbIterator.create(rocksIterator).toStreamKeys();
@@ -162,6 +165,7 @@ public class RocksDBKeyValueStorage implements KeyValueStorage {
@Override
public boolean tryDelete(final byte[] key) {
throwIfClosed();
try {
db.delete(tryDeleteOptions, key);
return true;

View File

@@ -42,8 +42,11 @@ import org.apache.tuweni.bytes.Bytes;
public class InMemoryKeyValueStorage
implements SnappedKeyValueStorage, SnappableKeyValueStorage, KeyValueStorage {
private final Map<Bytes, byte[]> hashValueStore;
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
/** protected access for the backing hash map. */
protected final Map<Bytes, Optional<byte[]>> hashValueStore;
/** protected access to the rw lock. */
protected final ReadWriteLock rwLock = new ReentrantReadWriteLock();
/** Instantiates a new In memory key value storage. */
public InMemoryKeyValueStorage() {
@@ -55,7 +58,7 @@ public class InMemoryKeyValueStorage
*
* @param hashValueStore the hash value store
*/
protected InMemoryKeyValueStorage(final Map<Bytes, byte[]> hashValueStore) {
protected InMemoryKeyValueStorage(final Map<Bytes, Optional<byte[]>> hashValueStore) {
this.hashValueStore = hashValueStore;
}
@@ -80,7 +83,7 @@ public class InMemoryKeyValueStorage
final Lock lock = rwLock.readLock();
lock.lock();
try {
return Optional.ofNullable(hashValueStore.get(Bytes.wrap(key)));
return hashValueStore.getOrDefault(Bytes.wrap(key), Optional.empty());
} finally {
lock.unlock();
}
@@ -108,7 +111,10 @@ public class InMemoryKeyValueStorage
lock.lock();
try {
return ImmutableSet.copyOf(hashValueStore.entrySet()).stream()
.map(bytesEntry -> Pair.of(bytesEntry.getKey().toArrayUnsafe(), bytesEntry.getValue()));
.filter(bytesEntry -> bytesEntry.getValue().isPresent())
.map(
bytesEntry ->
Pair.of(bytesEntry.getKey().toArrayUnsafe(), bytesEntry.getValue().get()));
} finally {
lock.unlock();
}
@@ -167,19 +173,17 @@ public class InMemoryKeyValueStorage
return startTransaction();
}
@Override
public SnappedKeyValueStorage cloneFromSnapshot() {
return takeSnapshot();
}
/** In memory transaction. */
public class InMemoryTransaction implements KeyValueStorageTransaction {
private class InMemoryTransaction implements KeyValueStorageTransaction {
private Map<Bytes, byte[]> updatedValues = new HashMap<>();
private Set<Bytes> removedKeys = new HashSet<>();
/** protected access to updatedValues map for the transaction. */
protected Map<Bytes, Optional<byte[]>> updatedValues = new HashMap<>();
/** protected access to deletedValues set for the transaction. */
protected Set<Bytes> removedKeys = new HashSet<>();
@Override
public void put(final byte[] key, final byte[] value) {
updatedValues.put(Bytes.wrap(key), value);
updatedValues.put(Bytes.wrap(key), Optional.of(value));
removedKeys.remove(Bytes.wrap(key));
}
@@ -219,8 +223,14 @@ public class InMemoryKeyValueStorage
final Lock lock = rwLock.readLock();
lock.lock();
try {
hashValueStore.forEach(
(k, v) -> ps.printf(" %s : %s%n", k.toHexString(), Bytes.wrap(v).toHexString()));
ImmutableSet.copyOf(hashValueStore.entrySet()).stream()
.filter(bytesEntry -> bytesEntry.getValue().isPresent())
.forEach(
entry ->
ps.printf(
" %s : %s%n",
entry.getKey().toHexString(),
Bytes.wrap(entry.getValue().get()).toHexString()));
} finally {
lock.unlock();
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.services.kvstore;
import org.hyperledger.besu.plugin.services.exception.StorageException;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.stream.Stream;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.tuweni.bytes.Bytes;
/** Key value storage which stores in memory all updates to a parent worldstate storage. */
public class LayeredKeyValueStorage extends InMemoryKeyValueStorage
implements SnappedKeyValueStorage {
private final KeyValueStorage parent;
/**
* Instantiates a new Layered key value storage.
*
* @param parent the parent key value storage for this layered storage.
*/
public LayeredKeyValueStorage(final KeyValueStorage parent) {
this(new ConcurrentHashMap<>(), parent);
}
/**
* Constructor which takes an explicit backing map for the layered key value storage.
*
* @param map the backing map
* @param parent the parent key value storage for this layered storage.
*/
public LayeredKeyValueStorage(
final Map<Bytes, Optional<byte[]>> map, final KeyValueStorage parent) {
super(map);
this.parent = parent;
}
@Override
public boolean containsKey(final byte[] key) throws StorageException {
return get(key).isPresent();
}
@Override
public Optional<byte[]> get(final byte[] key) throws StorageException {
final Lock lock = rwLock.readLock();
lock.lock();
try {
Bytes wrapKey = Bytes.wrap(key);
final Optional<byte[]> foundKey = hashValueStore.get(wrapKey);
if (foundKey == null) {
return parent.get(key);
} else {
return foundKey;
}
} finally {
lock.unlock();
}
}
@Override
public Stream<Pair<byte[], byte[]>> stream() {
final Lock lock = rwLock.readLock();
lock.lock();
try {
// immutable copy of our in memory store to use for streaming and filtering:
Map<Bytes, Optional<byte[]>> ourLayerState = ImmutableMap.copyOf(hashValueStore);
return Streams.concat(
ourLayerState.entrySet().stream()
.filter(entry -> entry.getValue().isPresent())
.map(
bytesEntry ->
Pair.of(bytesEntry.getKey().toArrayUnsafe(), bytesEntry.getValue().get()))
// since we are layered, concat a parent stream filtered by our map entries:
,
parent.stream().filter(e -> !ourLayerState.containsKey(Bytes.of(e.getLeft()))));
} finally {
lock.unlock();
}
}
@Override
public Stream<byte[]> streamKeys() {
final Lock lock = rwLock.readLock();
lock.lock();
try {
// immutable copy of our in memory store to use for streaming and filtering:
Map<Bytes, Optional<byte[]>> ourLayerState = ImmutableMap.copyOf(hashValueStore);
return Streams.concat(
ourLayerState.entrySet().stream()
.filter(entry -> entry.getValue().isPresent())
.map(bytesEntry -> bytesEntry.getKey().toArrayUnsafe())
// since we are layered, concat a parent stream filtered by our map entries:
,
parent.streamKeys().filter(e -> !ourLayerState.containsKey(Bytes.of(e))));
} finally {
lock.unlock();
}
}
@Override
public boolean tryDelete(final byte[] key) {
hashValueStore.put(Bytes.wrap(key), Optional.empty());
return true;
}
@Override
public KeyValueStorageTransaction startTransaction() {
return new KeyValueStorageTransactionTransitionValidatorDecorator(
new InMemoryTransaction() {
@Override
public void commit() throws StorageException {
final Lock lock = rwLock.writeLock();
lock.lock();
try {
hashValueStore.putAll(updatedValues);
removedKeys.forEach(key -> hashValueStore.put(key, Optional.empty()));
// put empty and not removed to not ask parent in case of deletion
updatedValues = null;
removedKeys = null;
} finally {
lock.unlock();
}
}
});
}
@Override
public SnappedKeyValueStorage clone() {
return new LayeredKeyValueStorage(hashValueStore, parent);
}
}