diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 1d21e27b8d..71a18daeab 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -26,6 +26,8 @@ import org.hyperledger.besu.ethereum.blockcreation.GasLimitCalculator; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.chain.BlockchainStorage; +import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Hash; @@ -230,23 +232,24 @@ public abstract class BesuControllerBuilder { final ProtocolSchedule protocolSchedule = createProtocolSchedule(); final GenesisState genesisState = GenesisState.fromConfig(genesisConfig, protocolSchedule); final WorldStateStorage worldStateStorage = storageProvider.createWorldStateStorage(); - final WorldStateArchive worldStateArchive = createWorldStateArchive(worldStateStorage); + + final BlockchainStorage blockchainStorage = + storageProvider.createBlockchainStorage(protocolSchedule); + + final MutableBlockchain blockchain = + DefaultBlockchain.createMutable( + genesisState.getBlock(), blockchainStorage, metricsSystem, reorgLoggingThreshold); + + final WorldStateArchive worldStateArchive = + createWorldStateArchive(worldStateStorage, blockchain); final ProtocolContext protocolContext = ProtocolContext.init( - storageProvider, - worldStateArchive, - genesisState, - protocolSchedule, - metricsSystem, - this::createConsensusContext, - reorgLoggingThreshold); + blockchain, worldStateArchive, genesisState, this::createConsensusContext); validateContext(protocolContext); protocolSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor( protocolContext.getWorldStateArchive()); - final MutableBlockchain blockchain = protocolContext.getBlockchain(); - Optional maybePruner = Optional.empty(); if (isPruningEnabled) { if (!storageProvider.isWorldStateIterable()) { @@ -427,10 +430,11 @@ public abstract class BesuControllerBuilder { genesisConfig.getForks()); } - public WorldStateArchive createWorldStateArchive(final WorldStateStorage worldStateStorage) { + private WorldStateArchive createWorldStateArchive( + final WorldStateStorage worldStateStorage, final Blockchain blockchain) { switch (dataStorageConfiguration.getDataStorageFormat()) { case BONSAI: - return new BonsaiWorldStateArchive(storageProvider); + return new BonsaiWorldStateArchive(storageProvider, blockchain); case FOREST: default: final WorldStatePreimageStorage preimageStorage = diff --git a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java index 8cdd710cd9..b68f44937a 100644 --- a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java +++ b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java @@ -163,7 +163,7 @@ public class TraceTransactionIntegrationTest { final Transaction transaction = Transaction.readFrom( new BytesValueRLPInput(Bytes.fromHexString(CONTRACT_CREATION_TX), false)); - BlockHeader genesisBlockHeader = genesisBlock.getHeader(); + final BlockHeader genesisBlockHeader = genesisBlock.getHeader(); transactionProcessor.processTransaction( blockchain, worldStateArchive diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java index f6a1bc66e1..af3b829143 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java @@ -15,14 +15,9 @@ package org.hyperledger.besu.ethereum; import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.chain.BlockchainStorage; -import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.function.BiFunction; @@ -46,20 +41,10 @@ public class ProtocolContext { } public static ProtocolContext init( - final StorageProvider storageProvider, + final MutableBlockchain blockchain, final WorldStateArchive worldStateArchive, final GenesisState genesisState, - final ProtocolSchedule protocolSchedule, - final MetricsSystem metricsSystem, - final BiFunction consensusContextFactory, - final long reorgLoggingThreshold) { - final BlockchainStorage blockchainStorage = - storageProvider.createBlockchainStorage(protocolSchedule); - - final MutableBlockchain blockchain = - DefaultBlockchain.createMutable( - genesisState.getBlock(), blockchainStorage, metricsSystem, reorgLoggingThreshold); - + final BiFunction consensusContextFactory) { if (blockchain.getChainHeadBlockNumber() < 1) { genesisState.writeStateTo(worldStateArchive.getMutable()); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java index 99e71076a6..41131b503f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java @@ -41,7 +41,7 @@ import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; public class BonsaiAccount implements MutableAccount, EvmAccount { - private final BonsaiWorldState context; + private final BonsaiWorldView context; private final boolean mutable; private final Address address; @@ -56,7 +56,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { private final Map updatedStorage = new HashMap<>(); BonsaiAccount( - final BonsaiWorldState context, + final BonsaiWorldView context, final Address address, final Hash addressHash, final long nonce, @@ -78,7 +78,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { } BonsaiAccount( - final BonsaiWorldState context, + final BonsaiWorldView context, final Address address, final StateTrieAccountValue stateTrieAccount, final boolean mutable) { @@ -98,7 +98,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { this(toCopy, toCopy.context, false); } - BonsaiAccount(final BonsaiAccount toCopy, final BonsaiWorldState context, final boolean mutable) { + BonsaiAccount(final BonsaiAccount toCopy, final BonsaiWorldView context, final boolean mutable) { this.context = context; this.address = toCopy.address; this.addressHash = toCopy.addressHash; @@ -113,8 +113,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { this.mutable = mutable; } - BonsaiAccount( - final BonsaiWorldState context, final UpdateTrackingAccount tracked) { + BonsaiAccount(final BonsaiWorldView context, final UpdateTrackingAccount tracked) { this.context = context; this.address = tracked.getAddress(); this.addressHash = tracked.getAddressHash(); @@ -130,7 +129,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { } static BonsaiAccount fromRLP( - final BonsaiWorldState context, + final BonsaiWorldView context, final Address address, final Bytes encoded, final boolean mutable) @@ -202,7 +201,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { @Override public Bytes getCode() { if (code == null) { - code = context.getCode(address); + code = context.getCode(address).orElse(Bytes.EMPTY); } return code; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java index d0a489eee1..1bb3a02e41 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiLayeredWorldState.java @@ -16,12 +16,11 @@ package org.hyperledger.besu.ethereum.bonsai; -import static com.google.common.base.Preconditions.checkArgument; - import org.hyperledger.besu.ethereum.core.Account; import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.WorldState; +import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import java.util.HashMap; import java.util.Map; @@ -33,21 +32,73 @@ 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 BonsaiWorldState, WorldState { +public class BonsaiLayeredWorldState implements BonsaiWorldView, WorldState { + + private final BonsaiWorldView parent; + protected final long height; + protected final TrieLogLayer trieLog; - private final BonsaiWorldState parent; - private final TrieLogLayer trieLog; - private final Map cachedAccounts = new HashMap<>(); + private final Hash worldStateRootHash; - public BonsaiLayeredWorldState(final BonsaiWorldState parent, final TrieLogLayer trieLog) { - checkArgument(trieLog.isFrozen(), "TrieLogs must be frozen to be used as world state."); + BonsaiLayeredWorldState( + final BonsaiWorldView parent, + final long height, + final Hash worldStateRootHash, + final TrieLogLayer trieLog) { this.parent = parent; + this.height = height; + this.worldStateRootHash = worldStateRootHash; this.trieLog = trieLog; } + public BonsaiWorldView getParent() { + return parent; + } + + public TrieLogLayer getTrieLog() { + return trieLog; + } + + public long getHeight() { + return height; + } + + @Override + public Optional getCode(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 maybeCode = currentLayer.trieLog.getCode(address); + if (maybeCode.isPresent()) { + return maybeCode; + } + if (currentLayer.parent == null) { + currentLayer = null; + } else if (currentLayer.parent instanceof BonsaiLayeredWorldState) { + currentLayer = (BonsaiLayeredWorldState) currentLayer.parent; + } else { + return currentLayer.parent.getCode(address); + } + } + return Optional.empty(); + } + @Override - public Bytes getCode(final Address address) { - return trieLog.getCode(address).orElseGet(() -> parent.getCode(address)); + public Optional 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.parent == null) { + currentLayer = null; + } else if (currentLayer.parent instanceof BonsaiLayeredWorldState) { + currentLayer = (BonsaiLayeredWorldState) currentLayer.parent; + } else { + return currentLayer.parent.getStateTrieNode(location); + } + } + return Optional.empty(); } @Override @@ -57,9 +108,24 @@ public class BonsaiLayeredWorldState implements BonsaiWorldState, WorldState { @Override public Optional getStorageValueBySlotHash(final Address address, final Hash slotHash) { - return trieLog - .getStorageBySlotHash(address, slotHash) - .or(() -> parent.getStorageValueBySlotHash(address, 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 maybeValue = + currentLayer.trieLog.getStorageBySlotHash(address, slotHash); + if (maybeValue.isPresent()) { + return maybeValue; + } + if (currentLayer.parent == null) { + currentLayer = null; + } else if (currentLayer.parent instanceof BonsaiLayeredWorldState) { + currentLayer = (BonsaiLayeredWorldState) currentLayer.parent; + } else { + return currentLayer.parent.getStorageValueBySlotHash(address, slotHash); + } + } + return Optional.empty(); } @Override @@ -70,30 +136,75 @@ public class BonsaiLayeredWorldState implements BonsaiWorldState, WorldState { @Override public Map getAllAccountStorage(final Address address, final Hash rootHash) { - final Map results = parent.getAllAccountStorage(address, rootHash); - trieLog - .streamStorageChanges(address) - .forEach(entry -> results.put(entry.getKey(), entry.getValue().getUpdated().toBytes())); + // this must be iterative and lambda light because the stack may blow up + // mainly because we don't have tail calls. + final Map 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 == null ? null : value.toBytes()); + } + }); + } + if (currentLayer.parent == null) { + currentLayer = null; + } else if (currentLayer.parent instanceof BonsaiLayeredWorldState) { + currentLayer = (BonsaiLayeredWorldState) currentLayer.parent; + } else { + final Account account = currentLayer.parent.get(address); + if (account != null) { + account + .storageEntriesFrom(Hash.ZERO, Integer.MAX_VALUE) + .forEach( + (k, v) -> { + if (!results.containsKey(k)) { + results.put(k, v.getValue().toBytes()); + } + }); + } + currentLayer = null; + } + } return results; } @Override public Account get(final Address address) { - return cachedAccounts.computeIfAbsent( - address, - addr -> - trieLog - .getAccount(addr) - .map( - stateTrieAccountValue -> - (Account) - new BonsaiAccount( - BonsaiLayeredWorldState.this, addr, stateTrieAccountValue, false)) - .orElseGet(() -> parent.get(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 maybeStateTrieAccount = + currentLayer.trieLog.getAccount(address); + if (maybeStateTrieAccount.isPresent()) { + return new BonsaiAccount( + BonsaiLayeredWorldState.this, address, maybeStateTrieAccount.get(), false); + } + if (currentLayer.parent == null) { + currentLayer = null; + } else if (currentLayer.parent instanceof BonsaiLayeredWorldState) { + currentLayer = (BonsaiLayeredWorldState) currentLayer.parent; + } else { + return currentLayer.parent.get(address); + } + } + return null; } @Override public Hash rootHash() { + return worldStateRootHash; + } + + public Hash blockHash() { return trieLog.getBlockHash(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java index 7933bcef87..63bc65ce2b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java @@ -37,13 +37,22 @@ import java.util.function.Function; import java.util.stream.Stream; import javax.annotation.Nonnull; +import com.google.common.annotations.VisibleForTesting; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; -public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorldState { +public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorldView { - private static final byte[] WORLD_ROOT_KEY = "worldRoot".getBytes(StandardCharsets.UTF_8); + private static final Logger LOG = LogManager.getLogger(); + + private static final byte[] WORLD_ROOT_HASH_KEY = + "worldRootHash".getBytes(StandardCharsets.UTF_8); + + @VisibleForTesting + static final byte[] WORLD_BLOCK_HASH_KEY = "worldBlockHash".getBytes(StandardCharsets.UTF_8); private final KeyValueStorage accountStorage; private final KeyValueStorage codeStorage; @@ -51,10 +60,11 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld private final KeyValueStorage trieBranchStorage; private final KeyValueStorage trieLogStorage; - private Bytes32 worldStateRootHash; - private final BonsaiWorldStateArchive archive; - private BonsaiWorldStateUpdater updater; + private final BonsaiWorldStateUpdater updater; + + private Hash worldStateRootHash; + private Hash worldStateBlockHash; public BonsaiPersistedWorldState( final BonsaiWorldStateArchive archive, @@ -70,8 +80,17 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld this.trieBranchStorage = trieBranchStorage; this.trieLogStorage = trieLogStorage; worldStateRootHash = - Bytes32.wrap( - trieBranchStorage.get(WORLD_ROOT_KEY).map(Bytes::wrap).orElse(Hash.EMPTY_TRIE_HASH)); + Hash.wrap( + Bytes32.wrap( + trieBranchStorage + .get(WORLD_ROOT_HASH_KEY) + .map(Bytes::wrap) + .orElse(Hash.EMPTY_TRIE_HASH))); + worldStateBlockHash = + Hash.wrap( + Bytes32.wrap( + trieBranchStorage.get(WORLD_BLOCK_HASH_KEY).map(Bytes::wrap).orElse(Hash.ZERO))); + updater = new BonsaiWorldStateUpdater(this); } public BonsaiWorldStateArchive getArchive() { @@ -85,14 +104,15 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld } @Override - public Bytes getCode(@Nonnull final Address address) { - return codeStorage.get(address.toArrayUnsafe()).map(Bytes::wrap).orElse(Bytes.EMPTY); + public Optional getCode(@Nonnull final Address address) { + return codeStorage.get(address.toArrayUnsafe()).map(Bytes::wrap); } @Override public void persist(final BlockHeader blockHeader) { - final Hash blockHash = blockHeader == null ? null : blockHeader.getHash(); boolean success = false; + final Hash originalBlockHash = worldStateBlockHash; + final Hash originalRootHash = worldStateRootHash; final KeyValueStorageTransaction accountTx = accountStorage.startTransaction(); final KeyValueStorageTransaction codeTx = codeStorage.startTransaction(); final KeyValueStorageTransaction storageTx = storageStorage.startTransaction(); @@ -165,7 +185,7 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld } else { final Bytes32 updatedStorageBytes = updatedStorage.toBytes(); storageTx.put(writeAddress, updatedStorageBytes.toArrayUnsafe()); - storageTrie.put(keyHash, rlpEncode(updatedStorageBytes)); + storageTrie.put(keyHash, BonsaiWorldView.encodeTrieValue(updatedStorageBytes)); } } @@ -216,21 +236,41 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld } } + // 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(trieBranchTx, location, value)); - worldStateRootHash = accountTrie.getRootHash(); - trieBranchTx.put(WORLD_ROOT_KEY, worldStateRootHash.toArrayUnsafe()); + worldStateRootHash = Hash.wrap(accountTrie.getRootHash()); + trieBranchTx.put(WORLD_ROOT_HASH_KEY, worldStateRootHash.toArrayUnsafe()); // for manicured tries and composting, trim and compost branches here - if (blockHash != null) { - final TrieLogLayer trieLog = updater.generateTrieLog(blockHash); - trieLog.freeze(); - // TODO add to archive here, but only once we get persisted follow distance implemented - // archive.addLayeredWorldState(new BonsaiLayeredWorldState(this, trieLog)); - - final BytesValueRLPOutput rlpLog = new BytesValueRLPOutput(); - trieLog.writeTo(rlpLog); - trieLogTx.put(blockHash.toArrayUnsafe(), rlpLog.encoded().toArrayUnsafe()); + // 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 (!worldStateRootHash.equals(blockHeader.getStateRoot())) { + throw new RuntimeException( + "World State Root does not match expected value, header " + + blockHeader.getStateRoot().toHexString() + + " calcualted " + + worldStateRootHash.toHexString()); + } + worldStateBlockHash = blockHeader.getHash(); + trieBranchTx.put(WORLD_BLOCK_HASH_KEY, worldStateBlockHash.toArrayUnsafe()); + if (originalBlockHash.equals(blockHeader.getParentHash())) { + LOG.debug("Writing Trie Log for {}", worldStateBlockHash); + final TrieLogLayer trieLog = updater.generateTrieLog(worldStateBlockHash); + trieLog.freeze(); + archive.addLayeredWorldState( + new BonsaiLayeredWorldState( + this, blockHeader.getNumber(), worldStateRootHash, trieLog)); + final BytesValueRLPOutput rlpLog = new BytesValueRLPOutput(); + trieLog.writeTo(rlpLog); + trieLogTx.put(worldStateBlockHash.toArrayUnsafe(), rlpLog.encoded().toArrayUnsafe()); + } + } else { + trieBranchTx.remove(WORLD_BLOCK_HASH_KEY); + worldStateBlockHash = null; } success = true; @@ -248,21 +288,17 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld storageTx.rollback(); trieBranchTx.rollback(); trieLogTx.rollback(); + worldStateBlockHash = originalBlockHash; + worldStateRootHash = originalRootHash; } } - } - - private static Bytes rlpEncode(final Bytes bytes) { - final BytesValueRLPOutput out = new BytesValueRLPOutput(); - out.writeBytes(bytes.trimLeadingZeros()); - return out.encoded(); + if (blockHeader != null) { + archive.scrubLayeredCache(blockHeader.getNumber()); + } } @Override public WorldUpdater updater() { - if (updater == null) { - updater = new BonsaiWorldStateUpdater(this); - } return updater; } @@ -271,6 +307,10 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld return Hash.wrap(worldStateRootHash); } + public Hash blockHash() { + return worldStateBlockHash; + } + @Override public Stream streamAccounts(final Bytes32 startKeyHash, final int limit) { throw new RuntimeException("Bonsai Tries do not provide account streaming."); @@ -288,10 +328,15 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) { return Optional.of(MerklePatriciaTrie.EMPTY_TRIE_NODE); } else { - return trieBranchStorage.get(location.toArrayUnsafe()).map(Bytes::wrap); + return getStateTrieNode(location); } } + @Override + public Optional getStateTrieNode(final Bytes location) { + return trieBranchStorage.get(location.toArrayUnsafe()).map(Bytes::wrap); + } + private void writeTrieNode( final KeyValueStorageTransaction tx, final Bytes location, final Bytes value) { tx.put(location.toArrayUnsafe(), value.toArrayUnsafe()); @@ -302,9 +347,7 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) { return Optional.of(MerklePatriciaTrie.EMPTY_TRIE_NODE); } else { - return trieBranchStorage - .get(Bytes.concatenate(address, location).toArrayUnsafe()) - .map(Bytes::wrap); + return getStateTrieNode(Bytes.concatenate(address, location)); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java index 00cbe0f0e5..3b641180f2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java @@ -16,7 +16,9 @@ package org.hyperledger.besu.ethereum.bonsai; +import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Address; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.WorldState; @@ -24,22 +26,36 @@ import org.hyperledger.besu.ethereum.proof.WorldStateProof; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; public class BonsaiWorldStateArchive implements WorldStateArchive { + private static final Logger LOG = LogManager.getLogger(); + + static final long RETAINED_LAYERS = 512; // at least 256 + typical rollbacks + + private final Blockchain blockchain; + private final BonsaiPersistedWorldState persistedState; private final Map layeredWorldStates; + private final KeyValueStorage trieLogStorage; - public BonsaiWorldStateArchive(final StorageProvider provider) { + public BonsaiWorldStateArchive(final StorageProvider provider, final Blockchain blockchain) { + this.blockchain = blockchain; + trieLogStorage = + provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE); persistedState = new BonsaiPersistedWorldState( this, @@ -48,37 +64,98 @@ public class BonsaiWorldStateArchive implements WorldStateArchive { provider.getStorageBySegmentIdentifier( KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE), provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE), - provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE)); + trieLogStorage); layeredWorldStates = new HashMap<>(); } @Override public Optional get(final Hash rootHash, final Hash blockHash) { if (layeredWorldStates.containsKey(rootHash)) { - return Optional.of(layeredWorldStates.get(rootHash)); - } else if (rootHash.equals(persistedState.rootHash())) { + return Optional.of(layeredWorldStates.get(blockHash)); + } else if (rootHash.equals(persistedState.blockHash())) { return Optional.of(persistedState); } else { return Optional.empty(); } } - public void addLayeredWorldState(final BonsaiLayeredWorldState worldState) { - layeredWorldStates.put(worldState.rootHash(), worldState); + void addLayeredWorldState(final BonsaiLayeredWorldState worldState) { + layeredWorldStates.put(worldState.blockHash(), worldState); + } + + private Optional getTrieLogLayer(final Hash blockHash) { + if (layeredWorldStates.containsKey(blockHash)) { + return Optional.of(layeredWorldStates.get(blockHash).getTrieLog()); + } else { + return trieLogStorage.get(blockHash.toArrayUnsafe()).map(TrieLogLayer::fromBytes); + } } @Override public boolean isWorldStateAvailable(final Hash rootHash, final Hash blockHash) { - return layeredWorldStates.containsKey(rootHash) - || persistedState.rootHash().equals(rootHash) /* || check disk storage */; + return layeredWorldStates.containsKey(blockHash) + || persistedState.blockHash().equals(blockHash) + || trieLogStorage.containsKey(blockHash.toArrayUnsafe()); } @Override public Optional getMutable(final Hash rootHash, final Hash blockHash) { - if (rootHash.equals(persistedState.rootHash())) { + if (blockHash.equals(persistedState.blockHash())) { return Optional.of(persistedState); } else { - return Optional.empty(); + try { + BlockHeader persistedHeader = blockchain.getBlockHeader(persistedState.blockHash()).get(); + BlockHeader targetHeader = blockchain.getBlockHeader(blockHash).get(); + + final List rollBacks = new ArrayList<>(); + final List rollForwards = new ArrayList<>(); + + // roll back from persisted to even with target + while (persistedHeader.getNumber() > targetHeader.getNumber()) { + LOG.debug("Rollback {}", persistedHeader.getHash()); + rollBacks.add(getTrieLogLayer(persistedHeader.getHash()).get()); + persistedHeader = blockchain.getBlockHeader(persistedHeader.getParentHash()).get(); + } + // roll forward to target + while (persistedHeader.getNumber() < targetHeader.getNumber()) { + LOG.debug("Rollforward {}", targetHeader.getHash()); + rollForwards.add(getTrieLogLayer(targetHeader.getHash()).get()); + targetHeader = blockchain.getBlockHeader(targetHeader.getParentHash()).get(); + } + + // roll back in tandem until we hit a shared state + while (!persistedHeader.getHash().equals(targetHeader.getHash())) { + LOG.debug("Paired Rollback {}", persistedHeader.getHash()); + LOG.debug("Paired Rollforward {}", targetHeader.getHash()); + rollForwards.add(getTrieLogLayer(targetHeader.getHash()).get()); + targetHeader = blockchain.getBlockHeader(targetHeader.getParentHash()).get(); + + rollBacks.add(getTrieLogLayer(persistedHeader.getHash()).get()); + persistedHeader = blockchain.getBlockHeader(persistedHeader.getParentHash()).get(); + } + + // attempt the state rolling + final BonsaiWorldStateUpdater bonsaiUpdater = + (BonsaiWorldStateUpdater) persistedState.updater(); + try { + for (final TrieLogLayer rollBack : rollBacks) { + bonsaiUpdater.rollBack(rollBack); + } + for (int i = rollForwards.size() - 1; i >= 0; i--) { + bonsaiUpdater.rollForward(rollForwards.get(i)); + } + bonsaiUpdater.commit(); + persistedState.persist(blockchain.getBlockHeader(blockHash).get()); + return Optional.of(persistedState); + } catch (final Exception e) { + // if we fail we must clean up the updater + bonsaiUpdater.reset(); + throw new RuntimeException(e); + } + } catch (final RuntimeException re) { + re.printStackTrace(System.out); + return Optional.empty(); + } } } @@ -97,6 +174,12 @@ public class BonsaiWorldStateArchive implements WorldStateArchive { final Hash worldStateRoot, final Address accountAddress, final List accountStorageKeys) { + // FIXME we can do proofs for layered tries and the persisted trie return Optional.empty(); } + + void scrubLayeredCache(final long newMaxHeight) { + final long waterline = newMaxHeight - RETAINED_LAYERS; + layeredWorldStates.entrySet().removeIf(entry -> entry.getValue().getHeight() < waterline); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java index b417169a8c..d393c7252d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java @@ -43,9 +43,8 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; -public class BonsaiWorldStateUpdater - extends AbstractWorldUpdater - implements BonsaiWorldState { +public class BonsaiWorldStateUpdater extends AbstractWorldUpdater + implements BonsaiWorldView { private final Map> accountsToUpdate = new HashMap<>(); private final Map> codeToUpdate = new HashMap<>(); @@ -56,7 +55,7 @@ public class BonsaiWorldStateUpdater // alternative was to keep a giant pre-image cache of the entire trie. private final Map>> storageToUpdate = new HashMap<>(); - BonsaiWorldStateUpdater(final BonsaiPersistedWorldState world) { + BonsaiWorldStateUpdater(final BonsaiWorldView world) { super(world); } @@ -156,10 +155,11 @@ public class BonsaiWorldStateUpdater if (codeValue != null) { codeValue.setUpdated(null); } else { - final Bytes deletedCode = wrappedWorldView().getCode(deletedAddress); - if (deletedCode != null) { - codeToUpdate.put(deletedAddress, new BonsaiValue<>(deletedCode, null)); - } + wrappedWorldView() + .getCode(deletedAddress) + .ifPresent( + deletedCode -> + codeToUpdate.put(deletedAddress, new BonsaiValue<>(deletedCode, null))); } final BonsaiValue accountValue = accountsToUpdate.computeIfAbsent( @@ -227,7 +227,8 @@ public class BonsaiWorldStateUpdater if (tracked.codeWasUpdated()) { final BonsaiValue pendingCode = codeToUpdate.computeIfAbsent( - updatedAddress, addr -> new BonsaiValue<>(wrappedWorldView().getCode(addr), null)); + updatedAddress, + addr -> new BonsaiValue<>(wrappedWorldView().getCode(addr).orElse(null), null)); pendingCode.setUpdated(updatedAccount.getCode()); } @@ -263,15 +264,21 @@ public class BonsaiWorldStateUpdater } @Override - public Bytes getCode(final Address address) { + public Optional getCode(final Address address) { final BonsaiValue localCode = codeToUpdate.get(address); if (localCode == null) { return wrappedWorldView().getCode(address); } else { - return localCode.getUpdated(); + return Optional.of(localCode.getUpdated()); } } + @Override + public Optional 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? @@ -325,6 +332,11 @@ public class BonsaiWorldStateUpdater public TrieLogLayer generateTrieLog(final Hash blockHash) { final TrieLogLayer layer = new TrieLogLayer(); + importIntoTrieLog(layer, blockHash); + return layer; + } + + private void importIntoTrieLog(final TrieLogLayer layer, final Hash blockHash) { layer.setBlockHash(blockHash); for (final Map.Entry> updatedAccount : accountsToUpdate.entrySet()) { @@ -371,8 +383,6 @@ public class BonsaiWorldStateUpdater slotUpdate.getValue().getUpdated()); } } - - return layer; } public void rollForward(final TrieLogLayer layer) { @@ -452,12 +462,17 @@ public class BonsaiWorldStateUpdater } } else { if (expectedValue == null) { - throw new IllegalStateException( - String.format( - "Expected to create account, but the account exists. Address=%s", address)); + if (accountValue.getUpdated() != null) { + throw new IllegalStateException( + String.format( + "Expected to create account, but the account exists. Address=%s", address)); + } + } else { + BonsaiAccount.assertCloseEnoughForDiffing( + accountValue.getUpdated(), + expectedValue, + "Address=" + address + " Prior Value in Rolling Change"); } - BonsaiAccount.assertCloseEnoughForDiffing( - accountValue.getUpdated(), expectedValue, "Prior Value in Rolling Change"); if (replacementValue == null) { if (accountValue.getOriginal() == null) { accountsToUpdate.remove(address); @@ -465,12 +480,8 @@ public class BonsaiWorldStateUpdater accountValue.setUpdated(null); } } else { - final BonsaiAccount existingAccount = accountValue.getUpdated(); - existingAccount.setNonce(replacementValue.getNonce()); - existingAccount.setBalance(replacementValue.getBalance()); - existingAccount.setStorageRoot(replacementValue.getStorageRoot()); - // depend on correctly structured layers to set code hash - existingAccount.setVersion(replacementValue.getVersion()); + accountValue.setUpdated( + new BonsaiAccount(wrappedWorldView(), address, replacementValue, true)); } } } @@ -497,7 +508,7 @@ public class BonsaiWorldStateUpdater } BonsaiValue codeValue = codeToUpdate.get(address); if (codeValue == null) { - final Bytes storedCode = wrappedWorldView().getCode(address); + final Bytes storedCode = wrappedWorldView().getCode(address).orElse(Bytes.EMPTY); if (!storedCode.isEmpty()) { codeValue = new BonsaiValue<>(storedCode, storedCode); codeToUpdate.put(address, codeValue); @@ -505,7 +516,7 @@ public class BonsaiWorldStateUpdater } if (codeValue == null) { - if (expectedCode == null && replacementCode != null) { + if ((expectedCode == null || expectedCode.size() == 0) && replacementCode != null) { codeToUpdate.put(address, new BonsaiValue<>(null, replacementCode)); } else { throw new IllegalStateException( @@ -513,22 +524,23 @@ public class BonsaiWorldStateUpdater "Expected to update code, but the code does not exist. Address=%s", address)); } } else { - if (expectedCode == null) { + final Bytes existingCode = codeValue.getUpdated(); + if ((expectedCode == null || expectedCode.isEmpty()) + && existingCode != null + && !existingCode.isEmpty()) { throw new IllegalStateException( String.format("Expected to create code, but the code exists. Address=%s", address)); } - if (!codeValue.getUpdated().equals(expectedCode)) { + if (!Objects.equals(expectedCode, existingCode)) { throw new IllegalStateException( String.format( "Old value of code does not match expected value. Address=%s ExpectedHash=%s ActualHash=%s", - address, Hash.hash(expectedCode), Hash.hash(codeValue.getUpdated()))); + address, + expectedCode == null ? "null" : Hash.hash(expectedCode), + Hash.hash(codeValue.getUpdated()))); } - if (replacementCode == null) { - if (codeValue.getOriginal() == null) { - codeToUpdate.remove(address); - } else { - codeValue.setUpdated(null); - } + if (replacementCode == null && codeValue.getOriginal() == null) { + codeToUpdate.remove(address); } else { codeValue.setUpdated(replacementCode); } @@ -555,6 +567,10 @@ public class BonsaiWorldStateUpdater // non-change, a cached read. return; } + if (replacementValue == null && expectedValue != null && expectedValue.isZero()) { + // corner case on deletes, non-change + return; + } final Map> storageMap = storageToUpdate.get(address); BonsaiValue slotValue = storageMap == null ? null : storageMap.get(slotHash); if (slotValue == null) { @@ -566,7 +582,7 @@ public class BonsaiWorldStateUpdater } } if (slotValue == null) { - if (expectedValue == null && replacementValue != null) { + if ((expectedValue == null || expectedValue.isZero()) && replacementValue != null) { maybeCreateStorageMap(storageMap, address) .put(slotHash, new BonsaiValue<>(null, replacementValue)); } else { @@ -576,32 +592,30 @@ public class BonsaiWorldStateUpdater address, slotHash)); } } else { - if (expectedValue == null) { + final UInt256 existingSlotValue = slotValue.getUpdated(); + if ((expectedValue == null || expectedValue.isZero()) + && existingSlotValue != null + && !existingSlotValue.isZero()) { throw new IllegalStateException( String.format( - "Expected to create slot, but the slot exists. Account=%s SlotHash=%s", - address, slotHash)); + "Expected to create slot, but the slot exists. Account=%s SlotHash=%s expectedValue=%s existingValue=%s", + address, slotHash, expectedValue, existingSlotValue)); } - final UInt256 existingSlotValue = slotValue.getUpdated(); - if (!existingSlotValue.equals(expectedValue)) { + if (!Objects.equals(expectedValue, existingSlotValue)) { throw new IllegalStateException( String.format( "Old value of slot does not match expected value. Account=%s SlotHash=%s Expected=%s Actual=%s", address, slotHash, - expectedValue.toShortHexString(), - existingSlotValue.toShortHexString())); + expectedValue == null ? "null" : expectedValue.toShortHexString(), + existingSlotValue == null ? "null" : existingSlotValue.toShortHexString())); } - if (replacementValue == null) { - if (slotValue.getOriginal() == null) { - final Map> thisStorageUpdate = - maybeCreateStorageMap(storageMap, address); - thisStorageUpdate.remove(slotHash); - if (thisStorageUpdate.isEmpty()) { - storageToUpdate.remove(address); - } - } else { - slotValue.setUpdated(null); + if (replacementValue == null && slotValue.getOriginal() == null) { + final Map> thisStorageUpdate = + maybeCreateStorageMap(storageMap, address); + thisStorageUpdate.remove(slotHash); + if (thisStorageUpdate.isEmpty()) { + storageToUpdate.remove(address); } } else { slotValue.setUpdated(replacementValue); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldView.java similarity index 77% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldState.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldView.java index 85904b58db..458f5c7d32 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldView.java @@ -19,6 +19,7 @@ package org.hyperledger.besu.ethereum.bonsai; import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.WorldView; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import java.util.Map; import java.util.Optional; @@ -27,9 +28,11 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; -public interface BonsaiWorldState extends WorldView { +public interface BonsaiWorldView extends WorldView { - Bytes getCode(Address address); + Optional getCode(Address address); + + Optional getStateTrieNode(Bytes location); UInt256 getStorageValue(Address address, UInt256 key); @@ -38,7 +41,7 @@ public interface BonsaiWorldState extends WorldView { UInt256 getOriginalStorageValue(Address address, UInt256 key); /** - * Stream all the storage values of a account. + * Retrieve all the storage values of a account. * * @param address the account to stream * @param rootHash the root hash of the account storage trie @@ -46,4 +49,10 @@ public interface BonsaiWorldState extends WorldView { * is the Bytes representation of the storage value. */ Map getAllAccountStorage(Address address, Hash rootHash); + + static Bytes encodeTrieValue(final Bytes bytes) { + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.writeBytes(bytes.trimLeadingZeros()); + return out.encoded(); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/TrieLogLayer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/TrieLogLayer.java index 410c46905b..5849c62464 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/TrieLogLayer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/TrieLogLayer.java @@ -20,11 +20,14 @@ import static com.google.common.base.Preconditions.checkState; import org.hyperledger.besu.ethereum.core.Address; import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.rlp.RLPOutput; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeMap; @@ -45,11 +48,18 @@ import org.apache.tuweni.units.bigints.UInt256; public class TrieLogLayer { private Hash blockHash; - private final Map> accounts = new TreeMap<>(); - private final Map> code = new TreeMap<>(); - private final Map>> storage = new TreeMap<>(); + private final Map> accounts; + private final Map> code; + private final Map>> storage; private boolean frozen = false; + TrieLogLayer() { + // TODO when tuweni fixes zero length byte comparison consider TreeMap + accounts = new HashMap<>(); + code = new HashMap<>(); + storage = new HashMap<>(); + } + /** Locks the layer so no new changes can be added; */ void freeze() { frozen = true; // The code never bothered me anyway @@ -64,7 +74,7 @@ public class TrieLogLayer { this.blockHash = blockHash; } - public void addAccountChange( + void addAccountChange( final Address address, final StateTrieAccountValue oldValue, final StateTrieAccountValue newValue) { @@ -88,6 +98,10 @@ public class TrieLogLayer { .put(slotHash, new BonsaiValue<>(oldValue, newValue)); } + static TrieLogLayer fromBytes(final byte[] bytes) { + return readFrom(new BytesValueRLPInput(Bytes.wrap(bytes), false)); + } + static TrieLogLayer readFrom(final RLPInput input) { final TrieLogLayer newLayer = new TrieLogLayer(); @@ -134,6 +148,9 @@ public class TrieLogLayer { input.leaveList(); newLayer.storage.put(address, storageChanges); } + + // TODO add trie nodes + // lenient leave list for forward compatible additions. input.leaveListLenient(); } @@ -186,6 +203,9 @@ public class TrieLogLayer { } output.endList(); } + + // TODO write trie nodes + output.endList(); // this change } output.endList(); // container @@ -203,6 +223,10 @@ public class TrieLogLayer { return storage.entrySet().stream(); } + boolean hasStorageChanges(final Address address) { + return storage.containsKey(address); + } + Stream>> streamStorageChanges(final Address address) { return storage.getOrDefault(address, Map.of()).entrySet().stream(); } @@ -216,10 +240,6 @@ public class TrieLogLayer { } } - boolean isFrozen() { - return frozen; - } - public Optional getCode(final Address address) { return Optional.ofNullable(code.get(address)).map(BonsaiValue::getUpdated); } @@ -233,4 +253,52 @@ public class TrieLogLayer { public Optional getAccount(final Address address) { return Optional.ofNullable(accounts.get(address)).map(BonsaiValue::getUpdated); } + + public String dump() { + final StringBuilder sb = new StringBuilder(); + sb.append("TrieLogLayer{" + "blockHash=").append(blockHash).append(frozen).append('}'); + sb.append("accounts\n"); + for (final Map.Entry> account : + accounts.entrySet()) { + sb.append(" : ").append(account.getKey()).append("\n"); + if (Objects.equals(account.getValue().getOriginal(), account.getValue().getUpdated())) { + sb.append(" = ").append(account.getValue().getUpdated()).append("\n"); + } else { + sb.append(" - ").append(account.getValue().getOriginal()).append("\n"); + sb.append(" + ").append(account.getValue().getUpdated()).append("\n"); + } + } + sb.append("code").append("\n"); + for (final Map.Entry> code : code.entrySet()) { + sb.append(" : ").append(code.getKey()).append("\n"); + if (Objects.equals(code.getValue().getOriginal(), code.getValue().getUpdated())) { + sb.append(" = ").append(code.getValue().getOriginal()).append("\n"); + } else { + sb.append(" - ").append(code.getValue().getOriginal()).append("\n"); + sb.append(" + ").append(code.getValue().getUpdated()).append("\n"); + } + } + sb.append("Storage").append("\n"); + for (final Map.Entry>> storage : storage.entrySet()) { + sb.append(" : ").append(storage.getKey()).append("\n"); + for (final Map.Entry> slot : storage.getValue().entrySet()) { + final UInt256 originalValue = slot.getValue().getOriginal(); + final UInt256 updatedValue = slot.getValue().getUpdated(); + sb.append(" : ").append(slot.getKey()).append("\n"); + if (Objects.equals(originalValue, updatedValue)) { + sb.append(" = ") + .append((originalValue == null) ? "null" : originalValue.toShortHexString()) + .append("\n"); + } else { + sb.append(" - ") + .append((originalValue == null) ? "null" : originalValue.toShortHexString()) + .append("\n"); + sb.append(" + ") + .append((updatedValue == null) ? "null" : updatedValue.toShortHexString()) + .append("\n"); + } + } + } + return sb.toString(); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java index cf8a38e174..43b00762c1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java @@ -108,7 +108,7 @@ public final class GenesisState { private static void writeAccountsTo( final MutableWorldState target, final List genesisAccounts, - final BlockHeader rootHash) { + final BlockHeader rootHeader) { final WorldUpdater updater = target.updater(); genesisAccounts.forEach( genesisAccount -> { @@ -120,7 +120,7 @@ public final class GenesisState { genesisAccount.storage.forEach(account::setStorageValue); }); updater.commit(); - target.persist(rootHash); + target.persist(rootHeader); } private static Hash calculateGenesisStateHash(final List genesisAccounts) { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java index bc2c19fd14..fbe47e255e 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.util.Optional; @@ -65,7 +66,7 @@ public class LogRollingTests { Hash.ZERO, Hash.EMPTY_LIST_HASH, Address.ZERO, - Hash.EMPTY_TRIE_HASH, + Hash.fromHexString("0x0ecfa454ddfe6b740f4af7b7f4c61b5c6bac2854efd2b07b27b1f53dba9bb46c"), Hash.EMPTY_TRIE_HASH, Hash.EMPTY_LIST_HASH, LogsBloomFilter.builder().build(), @@ -84,7 +85,7 @@ public class LogRollingTests { headerOne.getHash(), Hash.EMPTY_LIST_HASH, Address.ZERO, - Hash.EMPTY_TRIE_HASH, + Hash.fromHexString("0x5b675f79cd11ba67266161d79a8d5be3ac330dfbb76300a4f15d76b610b18193"), Hash.EMPTY_TRIE_HASH, Hash.EMPTY_LIST_HASH, LogsBloomFilter.builder().build(), @@ -102,7 +103,7 @@ public class LogRollingTests { @Before public void createStorage() { final InMemoryStorageProvider provider = new InMemoryStorageProvider(); - archive = new BonsaiWorldStateArchive(provider); + archive = new BonsaiWorldStateArchive(provider, null); accountStorage = (InMemoryKeyValueStorage) provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); @@ -121,7 +122,7 @@ public class LogRollingTests { provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE); final InMemoryStorageProvider secondProvider = new InMemoryStorageProvider(); - secondArchive = new BonsaiWorldStateArchive(secondProvider); + secondArchive = new BonsaiWorldStateArchive(secondProvider, null); secondAccountStorage = (InMemoryKeyValueStorage) provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); @@ -182,6 +183,9 @@ public class LogRollingTests { assertKeyValueStorageEqual(accountStorage, secondAccountStorage); assertKeyValueStorageEqual(codeStorage, secondCodeStorage); assertKeyValueStorageEqual(storageStorage, secondStorageStorage); + final KeyValueStorageTransaction tx = trieBranchStorage.startTransaction(); + tx.remove(BonsaiPersistedWorldState.WORLD_BLOCK_HASH_KEY); + tx.commit(); assertKeyValueStorageEqual(trieBranchStorage, secondTrieBranchStorage); // trie logs won't be the same, we shouldn't generate logs on rolls. assertKeyValueSubset(trieLogStorage, secondTrieLogStorage); @@ -239,6 +243,9 @@ public class LogRollingTests { assertKeyValueStorageEqual(accountStorage, secondAccountStorage); assertKeyValueStorageEqual(codeStorage, secondCodeStorage); assertKeyValueStorageEqual(storageStorage, secondStorageStorage); + final KeyValueStorageTransaction tx = trieBranchStorage.startTransaction(); + tx.remove(BonsaiPersistedWorldState.WORLD_BLOCK_HASH_KEY); + tx.commit(); assertKeyValueStorageEqual(trieBranchStorage, secondTrieBranchStorage); // trie logs won't be the same, we shouldn't generate logs on rolls. assertKeyValueSubset(trieLogStorage, secondTrieLogStorage); @@ -277,7 +284,7 @@ public class LogRollingTests { final TrieLogLayer layerTwo = getTrieLogLayer(trieLogStorage, headerTwo.getHash()); firstRollbackUpdater.rollBack(layerTwo); - worldState.persist(headerTwo); + worldState.persist(headerOne); final BonsaiPersistedWorldState secondWorldState = new BonsaiPersistedWorldState( @@ -300,6 +307,9 @@ public class LogRollingTests { assertKeyValueStorageEqual(accountStorage, secondAccountStorage); assertKeyValueStorageEqual(codeStorage, secondCodeStorage); assertKeyValueStorageEqual(storageStorage, secondStorageStorage); + final KeyValueStorageTransaction tx = trieBranchStorage.startTransaction(); + tx.remove(BonsaiPersistedWorldState.WORLD_BLOCK_HASH_KEY); + tx.commit(); assertKeyValueStorageEqual(trieBranchStorage, secondTrieBranchStorage); // trie logs won't be the same, we don't delete the roll back log assertKeyValueSubset(trieLogStorage, secondTrieLogStorage); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java index c5032a7c51..0d5af18b2e 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java @@ -38,7 +38,7 @@ public class RollingImport { new RollingFileReader((i, c) -> Path.of(String.format(arg[0] + "-%04d.rdat", i)), false); final InMemoryStorageProvider provider = new InMemoryStorageProvider(); - final BonsaiWorldStateArchive archive = new BonsaiWorldStateArchive(provider); + final BonsaiWorldStateArchive archive = new BonsaiWorldStateArchive(provider, null); final InMemoryKeyValueStorage accountStorage = (InMemoryKeyValueStorage) provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java index d1179e2ff1..601100df7f 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java @@ -121,7 +121,6 @@ public class PrivacyBlockProcessorTest { any()); } - @SuppressWarnings({"rawtypes", "unchecked"}) @Test public void mustPerformRehydration() { final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); @@ -189,7 +188,6 @@ public class PrivacyBlockProcessorTest { return mockPrivateState; } - @SuppressWarnings("rawtypes") private ProtocolSpec mockProtocolSpec() { final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); final MainnetTransactionProcessor mockPublicTransactionProcessor = diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManager.java index 76f984f917..021d1988f6 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManager.java @@ -60,9 +60,10 @@ class FullSyncTargetManager extends SyncTargetManager { return Optional.of(syncTarget); } else { LOG.warn( - "Disconnecting {} because world state is not available at common ancestor at block {}", + "Disconnecting {} because world state is not available at common ancestor at block {} ({})", syncTarget.peer(), - commonAncestor.getNumber()); + commonAncestor.getNumber(), + commonAncestor.getHash()); syncTarget.peer().disconnect(DisconnectReason.USELESS_PEER); return Optional.empty(); }