mirror of https://github.com/hyperledger/besu
parent
2ce37aea5e
commit
06cda00141
@ -0,0 +1,310 @@ |
||||
/* |
||||
* 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.trie.diffbased.common; |
||||
|
||||
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.proof.WorldStateProof; |
||||
import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; |
||||
import org.hyperledger.besu.ethereum.trie.MerkleTrieException; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.cache.DiffBasedCachedWorldStorageManager; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogPruner; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; |
||||
import org.hyperledger.besu.evm.worldstate.WorldState; |
||||
import org.hyperledger.besu.plugin.BesuContext; |
||||
import org.hyperledger.besu.plugin.services.trielogs.TrieLog; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.function.Function; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.units.bigints.UInt256; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class DiffBasedWorldStateProvider implements WorldStateArchive { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DiffBasedWorldStateProvider.class); |
||||
|
||||
protected final Blockchain blockchain; |
||||
|
||||
protected final TrieLogManager trieLogManager; |
||||
protected DiffBasedCachedWorldStorageManager cachedWorldStorageManager; |
||||
protected DiffBasedWorldState persistedState; |
||||
protected final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage; |
||||
|
||||
public DiffBasedWorldStateProvider( |
||||
final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, |
||||
final Blockchain blockchain, |
||||
final Optional<Long> maxLayersToLoad, |
||||
final BesuContext pluginContext, |
||||
final TrieLogPruner trieLogPruner) { |
||||
|
||||
this.worldStateKeyValueStorage = worldStateKeyValueStorage; |
||||
// TODO: de-dup constructors
|
||||
this.trieLogManager = |
||||
new TrieLogManager( |
||||
blockchain, |
||||
worldStateKeyValueStorage, |
||||
maxLayersToLoad.orElse(DiffBasedCachedWorldStorageManager.RETAINED_LAYERS), |
||||
pluginContext, |
||||
trieLogPruner); |
||||
this.blockchain = blockchain; |
||||
} |
||||
|
||||
public DiffBasedWorldStateProvider( |
||||
final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, |
||||
final Blockchain blockchain, |
||||
final TrieLogManager trieLogManager) { |
||||
|
||||
this.worldStateKeyValueStorage = worldStateKeyValueStorage; |
||||
// TODO: de-dup constructors
|
||||
this.trieLogManager = trieLogManager; |
||||
this.blockchain = blockchain; |
||||
} |
||||
|
||||
protected void provideCachedWorldStorageManager( |
||||
final DiffBasedCachedWorldStorageManager cachedWorldStorageManager) { |
||||
this.cachedWorldStorageManager = cachedWorldStorageManager; |
||||
} |
||||
|
||||
protected void loadPersistedState(final DiffBasedWorldState persistedState) { |
||||
this.persistedState = persistedState; |
||||
blockchain |
||||
.getBlockHeader(persistedState.getWorldStateBlockHash()) |
||||
.ifPresent( |
||||
blockHeader -> |
||||
this.cachedWorldStorageManager.addCachedLayer( |
||||
blockHeader, persistedState.getWorldStateRootHash(), persistedState)); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<WorldState> get(final Hash rootHash, final Hash blockHash) { |
||||
return cachedWorldStorageManager |
||||
.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 cachedWorldStorageManager.containWorldStateStorage(blockHash) |
||||
|| persistedState.blockHash().equals(blockHash) |
||||
|| worldStateKeyValueStorage.isWorldStateAvailable(rootHash, blockHash); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<MutableWorldState> getMutable( |
||||
final BlockHeader blockHeader, final boolean shouldPersistState) { |
||||
if (shouldPersistState) { |
||||
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 cachedWorldStorageManager |
||||
.getWorldState(blockHeader.getHash()) |
||||
.or(() -> cachedWorldStorageManager.getNearestWorldState(blockHeader)) |
||||
.or(() -> cachedWorldStorageManager.getHeadWorldState(blockchain::getBlockHeader)) |
||||
.flatMap(worldState -> rollMutableStateToBlockHash(worldState, blockHeader.getHash())) |
||||
.map(MutableWorldState::freeze); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public synchronized Optional<MutableWorldState> getMutable( |
||||
final Hash rootHash, final Hash blockHash) { |
||||
return rollMutableStateToBlockHash(persistedState, blockHash); |
||||
} |
||||
|
||||
Optional<MutableWorldState> rollMutableStateToBlockHash( |
||||
final DiffBasedWorldState mutableState, final Hash blockHash) { |
||||
if (blockHash.equals(mutableState.blockHash())) { |
||||
return Optional.of(mutableState); |
||||
} else { |
||||
try { |
||||
|
||||
final Optional<BlockHeader> maybePersistedHeader = |
||||
blockchain.getBlockHeader(mutableState.blockHash()).map(BlockHeader.class::cast); |
||||
|
||||
final List<TrieLog> rollBacks = new ArrayList<>(); |
||||
final List<TrieLog> rollForwards = new ArrayList<>(); |
||||
if (maybePersistedHeader.isEmpty()) { |
||||
trieLogManager.getTrieLogLayer(mutableState.blockHash()).ifPresent(rollBacks::add); |
||||
} else { |
||||
BlockHeader targetHeader = blockchain.getBlockHeader(blockHash).get(); |
||||
BlockHeader persistedHeader = maybePersistedHeader.get(); |
||||
// roll back from persisted to even with target
|
||||
Hash persistedBlockHash = persistedHeader.getBlockHash(); |
||||
while (persistedHeader.getNumber() > targetHeader.getNumber()) { |
||||
LOG.debug("Rollback {}", persistedBlockHash); |
||||
rollBacks.add(trieLogManager.getTrieLogLayer(persistedBlockHash).get()); |
||||
persistedHeader = blockchain.getBlockHeader(persistedHeader.getParentHash()).get(); |
||||
persistedBlockHash = persistedHeader.getBlockHash(); |
||||
} |
||||
// roll forward to target
|
||||
Hash targetBlockHash = targetHeader.getBlockHash(); |
||||
while (persistedHeader.getNumber() < targetHeader.getNumber()) { |
||||
LOG.debug("Rollforward {}", targetBlockHash); |
||||
rollForwards.add(trieLogManager.getTrieLogLayer(targetBlockHash).get()); |
||||
targetHeader = blockchain.getBlockHeader(targetHeader.getParentHash()).get(); |
||||
targetBlockHash = targetHeader.getBlockHash(); |
||||
} |
||||
|
||||
// roll back in tandem until we hit a shared state
|
||||
while (!persistedBlockHash.equals(targetBlockHash)) { |
||||
LOG.debug("Paired Rollback {}", persistedBlockHash); |
||||
LOG.debug("Paired Rollforward {}", targetBlockHash); |
||||
rollForwards.add(trieLogManager.getTrieLogLayer(targetBlockHash).get()); |
||||
targetHeader = blockchain.getBlockHeader(targetHeader.getParentHash()).get(); |
||||
|
||||
rollBacks.add(trieLogManager.getTrieLogLayer(persistedBlockHash).get()); |
||||
persistedHeader = blockchain.getBlockHeader(persistedHeader.getParentHash()).get(); |
||||
|
||||
targetBlockHash = targetHeader.getBlockHash(); |
||||
persistedBlockHash = persistedHeader.getBlockHash(); |
||||
} |
||||
} |
||||
|
||||
// attempt the state rolling
|
||||
final DiffBasedWorldStateUpdateAccumulator<?> diffBasedUpdater = |
||||
(DiffBasedWorldStateUpdateAccumulator<?>) mutableState.updater(); |
||||
try { |
||||
for (final TrieLog rollBack : rollBacks) { |
||||
LOG.debug("Attempting Rollback of {}", rollBack.getBlockHash()); |
||||
diffBasedUpdater.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()); |
||||
diffBasedUpdater.rollForward(forward); |
||||
} |
||||
diffBasedUpdater.commit(); |
||||
|
||||
mutableState.persist(blockchain.getBlockHeader(blockHash).get()); |
||||
|
||||
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
|
||||
throw re; |
||||
} catch (final Exception e) { |
||||
// if we fail we must clean up the updater
|
||||
diffBasedUpdater.reset(); |
||||
LOG.debug( |
||||
"State rolling failed on " |
||||
+ mutableState.getWorldStateStorage().getClass().getSimpleName() |
||||
+ " for block hash " |
||||
+ blockHash, |
||||
e); |
||||
|
||||
return Optional.empty(); |
||||
} |
||||
} catch (final RuntimeException re) { |
||||
LOG.info("Archive rolling failed for block hash " + blockHash, re); |
||||
if (re instanceof MerkleTrieException) { |
||||
// need to throw to trigger the heal
|
||||
throw re; |
||||
} |
||||
throw new MerkleTrieException( |
||||
"invalid", Optional.of(Address.ZERO), Hash.EMPTY, Bytes.EMPTY); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public MutableWorldState getMutable() { |
||||
return persistedState; |
||||
} |
||||
|
||||
public TrieLogManager getTrieLogManager() { |
||||
return trieLogManager; |
||||
} |
||||
|
||||
public DiffBasedCachedWorldStorageManager getCachedWorldStorageManager() { |
||||
return cachedWorldStorageManager; |
||||
} |
||||
|
||||
@Override |
||||
public void resetArchiveStateTo(final BlockHeader blockHeader) { |
||||
persistedState.resetWorldStateTo(blockHeader); |
||||
this.cachedWorldStorageManager.reset(); |
||||
this.cachedWorldStorageManager.addCachedLayer( |
||||
blockHeader, persistedState.getWorldStateRootHash(), persistedState); |
||||
} |
||||
|
||||
@Override |
||||
public <U> Optional<U> getAccountProof( |
||||
final BlockHeader blockHeader, |
||||
final Address accountAddress, |
||||
final List<UInt256> accountStorageKeys, |
||||
final Function<Optional<WorldStateProof>, ? extends Optional<U>> mapper) { |
||||
try (DiffBasedWorldState ws = |
||||
(DiffBasedWorldState) getMutable(blockHeader, false).orElse(null)) { |
||||
if (ws != null) { |
||||
final WorldStateProofProvider worldStateProofProvider = |
||||
new WorldStateProofProvider( |
||||
new WorldStateStorageCoordinator(ws.getWorldStateStorage())); |
||||
return mapper.apply( |
||||
worldStateProofProvider.getAccountProof( |
||||
ws.getWorldStateRootHash(), accountAddress, accountStorageKeys)); |
||||
} |
||||
} catch (Exception ex) { |
||||
LOG.error("failed proof query for " + blockHeader.getBlockHash().toShortHexString(), ex); |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Bytes> getNodeData(final Hash hash) { |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
try { |
||||
worldStateKeyValueStorage.close(); |
||||
} catch (Exception e) { |
||||
// no op
|
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue