From c85841d308f9d4bc37b6d3a0f61737a3f4b18c35 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Mon, 15 May 2023 10:59:46 -0700 Subject: [PATCH] TrieLogFactory plugin support (#5440) * TrieLogs in plugin data * adds dagger-wired plugincontext and TrieLogService * add getTrieLogByRange, TrieLogRangeTuple composition --------- Signed-off-by: garyschulte --- .../org/hyperledger/besu/cli/BesuCommand.java | 10 +- .../besu/components/BesuCommandModule.java | 3 +- .../besu/components/BesuComponent.java | 12 +- .../components/BesuPluginContextModule.java | 29 ++- .../controller/BesuControllerBuilder.java | 3 +- .../besu/services/BesuPluginContextImpl.java | 3 +- .../besu/cli/CommandTestAbstract.java | 2 + .../besu/datatypes/AccountValue.java | 55 ++++++ .../besu/datatypes/StorageSlotKey.java | 111 +++++++++++ ethereum/core/build.gradle | 1 + .../besu/ethereum/bonsai/BonsaiAccount.java | 17 +- .../besu/ethereum/bonsai/BonsaiValue.java | 38 +--- .../bonsai/BonsaiWorldStateProvider.java | 21 ++- .../bonsai/cache/CachedMerkleTrieLoader.java | 4 +- .../cache/CachedWorldStorageManager.java | 91 ++++++++- ...nsaiSnapshotWorldStateKeyValueStorage.java | 2 +- .../BonsaiWorldStateKeyValueStorage.java | 6 +- .../trielog/AbstractTrieLogManager.java | 35 ++-- .../bonsai/trielog/TrieLogAddedEvent.java | 20 +- .../bonsai/trielog/TrieLogFactoryImpl.java | 120 ++++++------ .../ethereum/bonsai/trielog/TrieLogLayer.java | 78 +++++--- .../bonsai/trielog/TrieLogManager.java | 6 +- .../bonsai/worldview/BonsaiWorldState.java | 6 +- .../BonsaiWorldStateUpdateAccumulator.java | 50 ++--- .../bonsai/worldview/BonsaiWorldView.java | 1 + .../bonsai/worldview/StorageSlotKey.java | 62 ------ .../worldstate/StateTrieAccountValue.java | 16 +- .../core/InMemoryKeyValueStorageProvider.java | 3 +- .../BlockImportExceptionHandlingTest.java | 2 +- .../bonsai/AbstractIsolationTests.java | 3 +- .../bonsai/BonsaiWorldStateArchiveTest.java | 17 +- .../BonsaiWorldStateKeyValueStorageTest.java | 2 +- .../bonsai/CachedMerkleTrieLoaderTest.java | 2 +- .../besu/ethereum/bonsai/LogRollingTests.java | 8 +- .../besu/ethereum/bonsai/RollingImport.java | 2 +- .../bonsai/trielog/TrieLogFactoryTests.java | 52 +++--- .../bonsai/trielog/TrieLogLayerTests.java | 7 +- .../bonsai/trielog/TrieLogManagerTests.java | 7 +- plugin-api/build.gradle | 2 +- .../hyperledger/besu/plugin/BesuContext.java | 9 + .../besu/plugin/services/TrieLogService.java | 58 ++++++ .../plugin/services/trielogs/TrieLog.java | 176 ++++++++++++++++++ .../services/trielogs/TrieLogAccumulator.java | 50 +++++ .../services/trielogs/TrieLogEvent.java | 49 +++++ .../services/trielogs/TrieLogFactory.java | 46 +++++ .../services/trielogs/TrieLogProvider.java | 61 ++++++ 46 files changed, 1040 insertions(+), 318 deletions(-) rename ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactory.java => besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java (51%) create mode 100644 datatypes/src/main/java/org/hyperledger/besu/datatypes/AccountValue.java create mode 100644 datatypes/src/main/java/org/hyperledger/besu/datatypes/StorageSlotKey.java delete mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/StorageSlotKey.java create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TrieLogService.java create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLog.java create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogAccumulator.java create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogEvent.java create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogFactory.java create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogProvider.java diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 84ce3f918c..95b38786c1 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1329,11 +1329,10 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private BesuComponent besuComponent; private final Supplier metricsSystem = Suppliers.memoize( - () -> { - return besuComponent == null - ? MetricsSystemFactory.create(metricsConfiguration()) - : besuComponent.getObservableMetricsSystem(); - }); + () -> + besuComponent == null || besuComponent.getObservableMetricsSystem() == null + ? MetricsSystemFactory.create(metricsConfiguration()) + : besuComponent.getObservableMetricsSystem()); private Vertx vertx; private EnodeDnsConfiguration enodeDnsConfiguration; private KeyValueStorageProvider keyValueStorageProvider; @@ -1412,6 +1411,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { final PrivacyPluginServiceImpl privacyPluginService, final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider, final RpcEndpointServiceImpl rpcEndpointServiceImpl) { + this.besuComponent = besuComponent; this.logger = besuComponent.getBesuCommandLogger(); this.rlpBlockImporter = rlpBlockImporter; this.rlpBlockExporterFactory = rlpBlockExporterFactory; diff --git a/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java b/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java index cb9882238a..61770eae9f 100644 --- a/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java @@ -23,7 +23,6 @@ import org.hyperledger.besu.chainimport.RlpBlockImporter; import org.hyperledger.besu.cli.BesuCommand; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.services.BesuPluginContextImpl; import javax.inject.Named; import javax.inject.Singleton; @@ -50,7 +49,7 @@ public class BesuCommandModule { RlpBlockExporter::new, new RunnerBuilder(), new BesuController.Builder(), - new BesuPluginContextImpl(), + besuComponent.getBesuPluginContext(), System.getenv()); besuCommand.toCommandLine(); return besuCommand; diff --git a/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java b/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java index ab188399db..005008ef62 100644 --- a/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoaderModule; import org.hyperledger.besu.metrics.MetricsSystemModule; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.services.BesuPluginContextImpl; import javax.inject.Named; import javax.inject.Singleton; @@ -34,7 +35,8 @@ import org.slf4j.Logger; modules = { BesuCommandModule.class, MetricsSystemModule.class, - CachedMerkleTrieLoaderModule.class + CachedMerkleTrieLoaderModule.class, + BesuPluginContextModule.class }) public interface BesuComponent { @@ -66,4 +68,12 @@ public interface BesuComponent { */ @Named("besuCommandLogger") Logger getBesuCommandLogger(); + + /** + * Besu plugin context for doing plugin service discovery. + * + * @return BesuComponent + */ + @Named("besuPluginContext") + BesuPluginContextImpl getBesuPluginContext(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactory.java b/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java similarity index 51% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactory.java rename to besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java index f09e31b202..ddbb4c2a6e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactory.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java @@ -13,16 +13,29 @@ * SPDX-License-Identifier: Apache-2.0 * */ -package org.hyperledger.besu.ethereum.bonsai.trielog; +package org.hyperledger.besu.components; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; +import org.hyperledger.besu.services.BesuPluginContextImpl; -/** Interface for serializing and deserializing {@link TrieLogLayer} objects. */ -public interface TrieLogFactory { - T create(BonsaiWorldStateUpdateAccumulator accumulator, final Hash blockHash); +import javax.inject.Named; +import javax.inject.Singleton; - T deserialize(final byte[] bytes); +import dagger.Module; +import dagger.Provides; - byte[] serialize(final T layer); +/** A dagger module that know how to create the BesuPluginContextImpl singleton. */ +@Module +public class BesuPluginContextModule { + + /** + * Creates a BesuPluginContextImpl, used for plugin service discovery. + * + * @return the BesuPluginContext + */ + @Provides + @Named("besuPluginContext") + @Singleton + public BesuPluginContextImpl provideBesuPluginContext() { + return new BesuPluginContextImpl(); + } } 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 bde4b63b5b..052c827044 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -1022,7 +1022,8 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides blockchain, Optional.of(dataStorageConfiguration.getBonsaiMaxLayersToLoad()), cachedMerkleTrieLoader, - metricsSystem); + metricsSystem, + besuComponent.getBesuPluginContext()); case FOREST: default: diff --git a/besu/src/main/java/org/hyperledger/besu/services/BesuPluginContextImpl.java b/besu/src/main/java/org/hyperledger/besu/services/BesuPluginContextImpl.java index 56fa5530b9..04bf5447a4 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/BesuPluginContextImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/BesuPluginContextImpl.java @@ -83,6 +83,7 @@ public class BesuPluginContextImpl implements BesuContext, PluginVersionsProvide * @param serviceType the service type * @param service the service */ + @Override public void addService(final Class serviceType, final T service) { checkArgument(serviceType.isInterface(), "Services must be Java interfaces."); checkArgument( @@ -118,7 +119,7 @@ public class BesuPluginContextImpl implements BesuContext, PluginVersionsProvide for (final BesuPlugin plugin : serviceLoader) { try { plugin.register(this); - LOG.debug("Registered plugin of type {}.", plugin.getClass().getName()); + LOG.info("Registered plugin of type {}.", plugin.getClass().getName()); addPluginVersion(plugin); } catch (final Exception e) { LOG.error( diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index be536f8091..fcce5353ad 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -237,6 +237,8 @@ public abstract class CommandTestAbstract { when(mockControllerBuilder.lowerBoundPeers(anyInt())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.maxRemotelyInitiatedPeers(anyInt())) .thenReturn(mockControllerBuilder); + when(mockControllerBuilder.besuComponent(any(BesuComponent.class))) + .thenReturn(mockControllerBuilder); // doReturn used because of generic BesuController doReturn(mockController).when(mockControllerBuilder).build(); lenient().when(mockController.getProtocolManager()).thenReturn(mockEthProtocolManager); diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccountValue.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccountValue.java new file mode 100644 index 0000000000..b41d5b51e1 --- /dev/null +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccountValue.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.datatypes; + +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +/** The values of an account in the world state trie. */ +public interface AccountValue { + /** + * The nonce of the account. + * + * @return the nonce of the account. + */ + long getNonce(); + + /** + * The available balance of that account. + * + * @return the balance, in Wei, of the account. + */ + Wei getBalance(); + + /** + * The hash of the root of the storage trie associated with this account. + * + * @return the hash of the root node of the storage trie. + */ + Hash getStorageRoot(); + + /** + * The hash of the EVM bytecode associated with this account. + * + * @return the hash of the account code (which may be {@link Hash#EMPTY}). + */ + Hash getCodeHash(); + + /** + * Writes the account value to the provided {@link RLPOutput}. + * + * @param out the {@link RLPOutput} to write to. + */ + void writeTo(final RLPOutput out); +} diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/StorageSlotKey.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/StorageSlotKey.java new file mode 100644 index 0000000000..eb2424dfcf --- /dev/null +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/StorageSlotKey.java @@ -0,0 +1,111 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.datatypes; + +import java.util.Objects; +import java.util.Optional; + +import org.apache.tuweni.units.bigints.UInt256; +import org.jetbrains.annotations.NotNull; + +/** + * StorageSlotKey represents a key used for storage slots in Ethereum. It contains the hash of the + * slot key and an optional representation of the key itself. + * + *

The class provides methods for accessing the hash and key, as well as methods for equality, + * hashcode, and comparison. + * + *

StorageSlotKey is used to uniquely identify storage slots within the Ethereum state. + */ +public class StorageSlotKey implements Comparable { + + private final Hash slotHash; + private final Optional slotKey; + + /** + * Creates a StorageSlotKey. + * + * @param slotHash Hashed storage slot key. + * @param slotKey Optional UInt256 storage slot key. + */ + public StorageSlotKey(final Hash slotHash, final Optional slotKey) { + this.slotHash = slotHash; + this.slotKey = slotKey; + } + + /** + * Creates a StorageSlotKey, hashing the slotKey. + * + * @param slotKey the UInt256 storage slot key. + */ + public StorageSlotKey(final UInt256 slotKey) { + this(Hash.hash(slotKey), Optional.of(slotKey)); + } + + /** + * Gets the hash representation of the storage slot key. + * + * @return the hash of the storage slot key. + */ + public Hash getSlotHash() { + return slotHash; + } + + /** + * Gets the optional UInt256 representation of the storage slot key. + * + * @return an Optional containing the UInt256 storage slot key if present, otherwise an empty + * Optional. + */ + public Optional getSlotKey() { + return slotKey; + } + + /** + * Indicates whether some other object is "equal to" this one. Two StorageSlotKey objects are + * considered equal if their slot hash values are equal. + * + * @param o the reference object with which to compare. + * @return true if this object is the same as the obj argument; false otherwise. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StorageSlotKey that = (StorageSlotKey) o; + return Objects.equals(slotHash, that.slotHash); + } + + @Override + public int hashCode() { + return Objects.hash(slotHash.hashCode()); + } + + @Override + public String toString() { + return String.format( + "StorageSlotKey{slotHash=%s, slotKey=%s}", + slotHash, slotKey.map(UInt256::toString).orElse("null")); + } + + @Override + public int compareTo(@NotNull final StorageSlotKey other) { + return this.slotHash.compareTo(other.slotHash); + } +} diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index 53cb8ca6a7..690aad0410 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -50,6 +50,7 @@ dependencies { annotationProcessor 'com.google.dagger:dagger-compiler' implementation 'io.opentelemetry:opentelemetry-api' implementation 'io.vertx:vertx-core' + implementation 'net.java.dev.jna:jna' implementation 'org.apache.commons:commons-lang3' implementation 'org.apache.tuweni:tuweni-bytes' 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 978dcf5df3..a7692b48d2 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 @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.bonsai; +import org.hyperledger.besu.datatypes.AccountValue; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; @@ -24,7 +25,7 @@ import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.rlp.RLPInput; -import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; import org.hyperledger.besu.evm.ModificationNotAllowedException; import org.hyperledger.besu.evm.account.AccountStorageEntry; import org.hyperledger.besu.evm.account.EvmAccount; @@ -40,7 +41,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; -public class BonsaiAccount implements MutableAccount, EvmAccount { +public class BonsaiAccount implements MutableAccount, EvmAccount, AccountValue { private final BonsaiWorldView context; private final boolean mutable; @@ -77,7 +78,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { public BonsaiAccount( final BonsaiWorldView context, final Address address, - final StateTrieAccountValue stateTrieAccount, + final AccountValue stateTrieAccount, final boolean mutable) { this( context, @@ -224,6 +225,12 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { public Bytes serializeAccount() { final BytesValueRLPOutput out = new BytesValueRLPOutput(); + writeTo(out); + return out.encoded(); + } + + @Override + public void writeTo(final RLPOutput out) { out.startList(); out.writeLongScalar(nonce); @@ -232,7 +239,6 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { out.writeBytes(codeHash); out.endList(); - return out.encoded(); } @Override @@ -262,6 +268,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { } } + @Override public Hash getStorageRoot() { return storageRoot; } @@ -298,7 +305,7 @@ public class BonsaiAccount implements MutableAccount, EvmAccount { * @throws IllegalStateException if the stored values differ */ public static void assertCloseEnoughForDiffing( - final BonsaiAccount source, final StateTrieAccountValue account, final String context) { + final BonsaiAccount source, final AccountValue account, final String context) { if (source == null) { throw new IllegalStateException(context + ": source is null but target isn't"); } else { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiValue.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiValue.java index 41af1ad258..7d862c7fb9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiValue.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiValue.java @@ -16,15 +16,12 @@ package org.hyperledger.besu.ethereum.bonsai; -import org.hyperledger.besu.ethereum.rlp.RLPOutput; - -import java.util.Objects; -import java.util.function.BiConsumer; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; -public class BonsaiValue { +public class BonsaiValue implements TrieLog.LogTuple { private T prior; private T updated; private boolean cleared; @@ -41,10 +38,12 @@ public class BonsaiValue { this.cleared = cleared; } + @Override public T getPrior() { return prior; } + @Override public T getUpdated() { return updated; } @@ -60,38 +59,11 @@ public class BonsaiValue { return this; } - public void writeRlp(final RLPOutput output, final BiConsumer writer) { - output.startList(); - writeInnerRlp(output, writer); - output.endList(); - } - - public void writeInnerRlp(final RLPOutput output, final BiConsumer writer) { - if (prior == null) { - output.writeNull(); - } else { - writer.accept(output, prior); - } - if (updated == null) { - output.writeNull(); - } else { - writer.accept(output, updated); - } - if (!cleared) { - output.writeNull(); - } else { - output.writeInt(1); - } - } - - public boolean isUnchanged() { - return Objects.equals(updated, prior); - } - public void setCleared() { this.cleared = true; } + @Override public boolean isCleared() { return cleared; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java index 076597a189..60e629891c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java @@ -23,7 +23,6 @@ import org.hyperledger.besu.datatypes.Hash; 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; @@ -40,6 +39,8 @@ import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.plugin.BesuContext; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; import java.util.ArrayList; import java.util.HashSet; @@ -70,14 +71,16 @@ public class BonsaiWorldStateProvider implements WorldStateArchive { final StorageProvider provider, final Blockchain blockchain, final CachedMerkleTrieLoader cachedMerkleTrieLoader, - final ObservableMetricsSystem metricsSystem) { + final ObservableMetricsSystem metricsSystem, + final BesuContext pluginContext) { this( (BonsaiWorldStateKeyValueStorage) provider.createWorldStateStorage(DataStorageFormat.BONSAI), blockchain, Optional.empty(), cachedMerkleTrieLoader, - metricsSystem); + metricsSystem, + pluginContext); } public BonsaiWorldStateProvider( @@ -85,7 +88,8 @@ public class BonsaiWorldStateProvider implements WorldStateArchive { final Blockchain blockchain, final Optional maxLayersToLoad, final CachedMerkleTrieLoader cachedMerkleTrieLoader, - final ObservableMetricsSystem metricsSystem) { + final ObservableMetricsSystem metricsSystem, + final BesuContext pluginContext) { // TODO: de-dup constructors this.trieLogManager = @@ -94,7 +98,8 @@ public class BonsaiWorldStateProvider implements WorldStateArchive { blockchain, worldStateStorage, metricsSystem, - maxLayersToLoad.orElse(RETAINED_LAYERS)); + maxLayersToLoad.orElse(RETAINED_LAYERS), + pluginContext); this.blockchain = blockchain; this.worldStateStorage = worldStateStorage; this.persistedState = new BonsaiWorldState(this, worldStateStorage); @@ -189,8 +194,8 @@ public class BonsaiWorldStateProvider implements WorldStateArchive { final Optional maybePersistedHeader = blockchain.getBlockHeader(mutableState.blockHash()).map(BlockHeader.class::cast); - final List rollBacks = new ArrayList<>(); - final List rollForwards = new ArrayList<>(); + final List rollBacks = new ArrayList<>(); + final List rollForwards = new ArrayList<>(); if (maybePersistedHeader.isEmpty()) { trieLogManager.getTrieLogLayer(mutableState.blockHash()).ifPresent(rollBacks::add); } else { @@ -232,7 +237,7 @@ public class BonsaiWorldStateProvider implements WorldStateArchive { final BonsaiWorldStateUpdateAccumulator bonsaiUpdater = (BonsaiWorldStateUpdateAccumulator) mutableState.updater(); try { - for (final TrieLogLayer rollBack : rollBacks) { + for (final TrieLog rollBack : rollBacks) { LOG.debug("Attempting Rollback of {}", rollBack.getBlockHash()); bonsaiUpdater.rollBack(rollBack); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java index b0ae6b4ce3..9dd4955989 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java @@ -17,9 +17,9 @@ package org.hyperledger.besu.ethereum.bonsai.cache; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; -import org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey; import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; @@ -122,7 +122,7 @@ public class CachedMerkleTrieLoader implements BonsaiStorageSubscriber { Hash.hash(storageRoot), Function.identity(), Function.identity()); - storageTrie.get(slotKey.slotHash()); + storageTrie.get(slotKey.getSlotHash()); } catch (MerkleTrieException e) { // ignore exception for the cache } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java index 2ed09c3e1e..7dbf701724 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java @@ -21,10 +21,16 @@ import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStor 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.trielog.TrieLogFactoryImpl; 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.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.plugin.BesuContext; +import org.hyperledger.besu.plugin.services.TrieLogService; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogProvider; import java.util.ArrayList; import java.util.Comparator; @@ -33,7 +39,11 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes32; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,8 +60,9 @@ public class CachedWorldStorageManager extends AbstractTrieLogManager final BonsaiWorldStateKeyValueStorage worldStateStorage, final long maxLayersToLoad, final Map cachedWorldStatesByHash, + final BesuContext pluginContext, final ObservableMetricsSystem metricsSystem) { - super(blockchain, worldStateStorage, maxLayersToLoad, cachedWorldStatesByHash); + super(blockchain, worldStateStorage, maxLayersToLoad, cachedWorldStatesByHash, pluginContext); worldStateStorage.subscribe(this); this.archive = archive; this.metricsSystem = metricsSystem; @@ -62,13 +73,15 @@ public class CachedWorldStorageManager extends AbstractTrieLogManager final Blockchain blockchain, final BonsaiWorldStateKeyValueStorage worldStateStorage, final ObservableMetricsSystem metricsSystem, - final long maxLayersToLoad) { + final long maxLayersToLoad, + final BesuContext pluginContext) { this( archive, blockchain, worldStateStorage, maxLayersToLoad, new ConcurrentHashMap<>(), + pluginContext, metricsSystem); } @@ -198,4 +211,78 @@ public class CachedWorldStorageManager extends AbstractTrieLogManager public void onCloseStorage() { this.cachedWorldStatesByHash.clear(); } + + @VisibleForTesting + @Override + protected TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext) { + // if we have a TrieLogService from pluginContext, use it. + var trieLogServicez = + Optional.ofNullable(pluginContext) + .flatMap(context -> context.getService(TrieLogService.class)); + + if (trieLogServicez.isPresent()) { + var trieLogService = trieLogServicez.get(); + // push the TrieLogProvider into the TrieLogService + trieLogService.configureTrieLogProvider(getTrieLogProvider()); + + // configure plugin observers: + trieLogService.getObservers().forEach(trieLogObservers::subscribe); + + // return the TrieLogFactory implementation from the TrieLogService + return trieLogService.getTrieLogFactory(); + } else { + // Otherwise default to TrieLogFactoryImpl + return new TrieLogFactoryImpl(); + } + } + + @VisibleForTesting + TrieLogProvider getTrieLogProvider() { + return new TrieLogProvider() { + @Override + public > Optional getTrieLogLayer( + final Hash blockHash) { + return CachedWorldStorageManager.this.getTrieLogLayer(blockHash); + } + + @Override + public > Optional getTrieLogLayer( + final long blockNumber) { + return CachedWorldStorageManager.this + .blockchain + .getBlockHeader(blockNumber) + .map(BlockHeader::getHash) + .flatMap(CachedWorldStorageManager.this::getTrieLogLayer); + } + + @Override + public > List getTrieLogsByRange( + final long fromBlockNumber, final long toBlockNumber) { + return rangeAsStream(fromBlockNumber, toBlockNumber) + .map(blockchain::getBlockHeader) + .map( + headerOpt -> + headerOpt.flatMap( + header -> + CachedWorldStorageManager.this + .getTrieLogLayer(header.getBlockHash()) + .map( + layer -> + new TrieLogRangeTuple( + header.getBlockHash(), header.getNumber(), layer)))) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + + Stream rangeAsStream(final long fromBlockNumber, final long toBlockNumber) { + if (Math.abs(toBlockNumber - fromBlockNumber) > LOG_RANGE_LIMIT) { + throw new IllegalArgumentException("Requested Range too large"); + } + long left = Math.min(fromBlockNumber, toBlockNumber); + long right = Math.max(fromBlockNumber, toBlockNumber); + return LongStream.range(left, right).boxed(); + } + }; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java index 1aee3cb1fd..978d3712e4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java @@ -16,8 +16,8 @@ package org.hyperledger.besu.ethereum.bonsai.storage; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; -import org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java index 6664d59114..6955fdcdce 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java @@ -15,7 +15,7 @@ package org.hyperledger.besu.ethereum.bonsai.storage; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.trie.MerkleTrie; @@ -254,7 +254,7 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC getStorageValueCounter.inc(); Optional response = storageStorage - .get(Bytes.concatenate(accountHash, storageSlotKey.slotHash()).toArrayUnsafe()) + .get(Bytes.concatenate(accountHash, storageSlotKey.getSlotHash()).toArrayUnsafe()) .map(Bytes::wrap); if (response.isEmpty()) { final Optional storageRoot = storageRootSupplier.get(); @@ -267,7 +267,7 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC Function.identity(), Function.identity()), storageRoot.get()) - .get(storageSlotKey.slotHash()) + .get(storageSlotKey.getSlotHash()) .map(bytes -> Bytes32.leftPad(RLP.decodeValue(bytes))); if (response.isEmpty()) getStorageValueMissingMerkleTrieCounter.inc(); else getStorageValueMerkleTrieCounter.inc(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java index 3a501b9a1f..a20e7b200f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java @@ -19,11 +19,14 @@ import org.hyperledger.besu.datatypes.Hash; 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.trielog.TrieLogAddedEvent.TrieLogAddedObserver; 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.plugin.BesuContext; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent.TrieLogObserver; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; import org.hyperledger.besu.util.Subscribers; import java.util.Map; @@ -37,27 +40,31 @@ import org.slf4j.LoggerFactory; 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 + public static final long LOG_RANGE_LIMIT = 1000; // restrict trielog range queries to 1k logs protected final Blockchain blockchain; protected final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; protected final Map cachedWorldStatesByHash; protected final long maxLayersToLoad; - private final Subscribers trieLogAddedObservers = Subscribers.create(); + protected final Subscribers trieLogObservers = Subscribers.create(); - // TODO plumb factory from plugin service: - TrieLogFactory trieLogFactory = new TrieLogFactoryImpl(); + protected final TrieLogFactory trieLogFactory; protected AbstractTrieLogManager( final Blockchain blockchain, final BonsaiWorldStateKeyValueStorage worldStateStorage, final long maxLayersToLoad, - final Map cachedWorldStatesByHash) { + final Map cachedWorldStatesByHash, + final BesuContext pluginContext) { this.blockchain = blockchain; this.rootWorldStateStorage = worldStateStorage; this.cachedWorldStatesByHash = cachedWorldStatesByHash; this.maxLayersToLoad = maxLayersToLoad; + this.trieLogFactory = setupTrieLogFactory(pluginContext); } + protected abstract TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext); + @Override public synchronized void saveTrieLog( final BonsaiWorldStateUpdateAccumulator localUpdater, @@ -71,11 +78,11 @@ public abstract class AbstractTrieLogManager implements TrieLogManager { final BonsaiUpdater stateUpdater = forWorldState.getWorldStateStorage().updater(); boolean success = false; try { - final TrieLogLayer trieLog = prepareTrieLog(forBlockHeader, localUpdater); + final TrieLog trieLog = prepareTrieLog(forBlockHeader, localUpdater); persistTrieLog(forBlockHeader, forWorldStateRootHash, trieLog, stateUpdater); // notify trie log added observers, synchronously - trieLogAddedObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); + trieLogObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); success = true; } finally { @@ -89,13 +96,13 @@ public abstract class AbstractTrieLogManager implements TrieLogManager { } @VisibleForTesting - TrieLogLayer prepareTrieLog( + TrieLog prepareTrieLog( final BlockHeader blockHeader, final BonsaiWorldStateUpdateAccumulator localUpdater) { LOG.atDebug() .setMessage("Adding layered world state for {}") .addArgument(blockHeader::toLogString) .log(); - final TrieLogLayer trieLog = localUpdater.generateTrieLog(blockHeader.getBlockHash()); + final TrieLog trieLog = localUpdater.generateTrieLog(blockHeader); trieLog.freeze(); return trieLog; } @@ -117,7 +124,7 @@ public abstract class AbstractTrieLogManager implements TrieLogManager { private void persistTrieLog( final BlockHeader blockHeader, final Hash worldStateRootHash, - final TrieLogLayer trieLog, + final TrieLog trieLog, final BonsaiUpdater stateUpdater) { LOG.atDebug() .setMessage("Persisting trie log for block hash {} and world state root {}") @@ -141,17 +148,17 @@ public abstract class AbstractTrieLogManager implements TrieLogManager { } @Override - public Optional getTrieLogLayer(final Hash blockHash) { + public Optional getTrieLogLayer(final Hash blockHash) { return rootWorldStateStorage.getTrieLog(blockHash).map(trieLogFactory::deserialize); } @Override - public synchronized long subscribe(final TrieLogAddedObserver sub) { - return trieLogAddedObservers.subscribe(sub); + public synchronized long subscribe(final TrieLogObserver sub) { + return trieLogObservers.subscribe(sub); } @Override public synchronized void unsubscribe(final long id) { - trieLogAddedObservers.unsubscribe(id); + trieLogObservers.unsubscribe(id); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogAddedEvent.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogAddedEvent.java index 67fb16902d..c9df72f004 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogAddedEvent.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogAddedEvent.java @@ -15,21 +15,13 @@ */ package org.hyperledger.besu.ethereum.bonsai.trielog; -public class TrieLogAddedEvent { +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; - private final TrieLogLayer layer; +public record TrieLogAddedEvent(TrieLog layer) implements TrieLogEvent { - public TrieLogAddedEvent(final TrieLogLayer layer) { - this.layer = layer; - } - - public TrieLogLayer getLayer() { - return layer; - } - - @FunctionalInterface - interface TrieLogAddedObserver { - - void onTrieLogAdded(TrieLogAddedEvent event); + @Override + public Type getType() { + return Type.ADDED; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryImpl.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryImpl.java index 5245557551..ebac500061 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryImpl.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryImpl.java @@ -15,80 +15,61 @@ */ package org.hyperledger.besu.ethereum.bonsai.trielog; +import org.hyperledger.besu.datatypes.AccountValue; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.BonsaiAccount; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.bonsai.BonsaiValue; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; -import org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.rlp.RLPOutput; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogAccumulator; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import java.util.function.BiConsumer; import java.util.function.Function; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; -public class TrieLogFactoryImpl implements TrieLogFactory { +public class TrieLogFactoryImpl implements TrieLogFactory { @Override - public TrieLogLayer create( - final BonsaiWorldStateUpdateAccumulator accumulator, final Hash blockHash) { + public TrieLogLayer create(final TrieLogAccumulator accumulator, final BlockHeader blockHeader) { TrieLogLayer layer = new TrieLogLayer(); - layer.setBlockHash(blockHash); - for (final Map.Entry> updatedAccount : - accumulator.getAccountsToUpdate().entrySet()) { - final BonsaiValue bonsaiValue = updatedAccount.getValue(); - final BonsaiAccount oldValue = bonsaiValue.getPrior(); - final StateTrieAccountValue oldAccount = - oldValue == null - ? null - : new StateTrieAccountValue( - oldValue.getNonce(), - oldValue.getBalance(), - oldValue.getStorageRoot(), - oldValue.getCodeHash()); - final BonsaiAccount newValue = bonsaiValue.getUpdated(); - final StateTrieAccountValue newAccount = - newValue == null - ? null - : new StateTrieAccountValue( - newValue.getNonce(), - newValue.getBalance(), - newValue.getStorageRoot(), - newValue.getCodeHash()); - if (oldValue == null && newValue == null) { + layer.setBlockHash(blockHeader.getBlockHash()); + layer.setBlockNumber(blockHeader.getNumber()); + for (final var updatedAccount : accumulator.getAccountsToUpdate().entrySet()) { + final var bonsaiValue = updatedAccount.getValue(); + final var oldAccountValue = bonsaiValue.getPrior(); + final var newAccountValue = bonsaiValue.getUpdated(); + if (oldAccountValue == null && newAccountValue == null) { // by default do not persist empty reads of accounts to the trie log continue; } - layer.addAccountChange(updatedAccount.getKey(), oldAccount, newAccount); + layer.addAccountChange(updatedAccount.getKey(), oldAccountValue, newAccountValue); } - for (final Map.Entry> updatedCode : - accumulator.getCodeToUpdate().entrySet()) { + for (final var updatedCode : accumulator.getCodeToUpdate().entrySet()) { layer.addCodeChange( updatedCode.getKey(), updatedCode.getValue().getPrior(), updatedCode.getValue().getUpdated(), - blockHash); + blockHeader.getBlockHash()); } - for (final Map.Entry< - Address, - BonsaiWorldStateUpdateAccumulator.StorageConsumingMap< - StorageSlotKey, BonsaiValue>> - updatesStorage : accumulator.getStorageToUpdate().entrySet()) { + for (final var updatesStorage : accumulator.getStorageToUpdate().entrySet()) { final Address address = updatesStorage.getKey(); - for (final Map.Entry> slotUpdate : - updatesStorage.getValue().entrySet()) { + for (final var slotUpdate : updatesStorage.getValue().entrySet()) { var val = slotUpdate.getValue(); if (val.getPrior() == null && val.getUpdated() == null) { @@ -103,52 +84,53 @@ public class TrieLogFactoryImpl implements TrieLogFactory { } @Override - public byte[] serialize(final TrieLogLayer layer) { + public byte[] serialize(final TrieLog layer) { final BytesValueRLPOutput rlpLog = new BytesValueRLPOutput(); writeTo(layer, rlpLog); return rlpLog.encoded().toArrayUnsafe(); } - public static void writeTo(final TrieLogLayer layer, final RLPOutput output) { + public static void writeTo(final TrieLog layer, final RLPOutput output) { layer.freeze(); final Set

addresses = new TreeSet<>(); - addresses.addAll(layer.getAccounts().keySet()); - addresses.addAll(layer.getCode().keySet()); - addresses.addAll(layer.getStorage().keySet()); + addresses.addAll(layer.getAccountChanges().keySet()); + addresses.addAll(layer.getCodeChanges().keySet()); + addresses.addAll(layer.getStorageChanges().keySet()); output.startList(); // container - output.writeBytes(layer.blockHash); + output.writeBytes(layer.getBlockHash()); for (final Address address : addresses) { output.startList(); // this change output.writeBytes(address); - final BonsaiValue accountChange = layer.accounts.get(address); + final TrieLog.LogTuple accountChange = layer.getAccountChanges().get(address); if (accountChange == null || accountChange.isUnchanged()) { output.writeNull(); } else { - accountChange.writeRlp(output, (o, sta) -> sta.writeTo(o)); + writeRlp(accountChange, output, (o, sta) -> sta.writeTo(o)); } - final BonsaiValue codeChange = layer.code.get(address); + final TrieLog.LogTuple codeChange = layer.getCodeChanges().get(address); if (codeChange == null || codeChange.isUnchanged()) { output.writeNull(); } else { - codeChange.writeRlp(output, RLPOutput::writeBytes); + writeRlp(codeChange, output, RLPOutput::writeBytes); } - final Map> storageChanges = layer.storage.get(address); + final Map> storageChanges = + layer.getStorageChanges().get(address); if (storageChanges == null) { output.writeNull(); } else { output.startList(); - for (final Map.Entry> storageChangeEntry : + for (final Map.Entry> storageChangeEntry : storageChanges.entrySet()) { output.startList(); // do not write slotKey, it is not used in mainnet bonsai trielogs - output.writeBytes(storageChangeEntry.getKey().slotHash()); - storageChangeEntry.getValue().writeInnerRlp(output, RLPOutput::writeUInt256Scalar); + output.writeBytes(storageChangeEntry.getKey().getSlotHash()); + writeInnerRlp(storageChangeEntry.getValue(), output, RLPOutput::writeUInt256Scalar); output.endList(); } output.endList(); @@ -242,4 +224,34 @@ public class TrieLogFactoryImpl implements TrieLogFactory { .filter(i -> i == 1) .isPresent(); } + + public static void writeRlp( + final TrieLog.LogTuple value, + final RLPOutput output, + final BiConsumer writer) { + output.startList(); + writeInnerRlp(value, output, writer); + output.endList(); + } + + public static void writeInnerRlp( + final TrieLog.LogTuple value, + final RLPOutput output, + final BiConsumer writer) { + if (value.getPrior() == null) { + output.writeNull(); + } else { + writer.accept(output, value.getPrior()); + } + if (value.getUpdated() == null) { + output.writeNull(); + } else { + writer.accept(output, value.getUpdated()); + } + if (!value.isCleared()) { + output.writeNull(); + } else { + output.writeInt(1); + } + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayer.java index 68d381634a..7d61e50126 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayer.java @@ -18,18 +18,18 @@ package org.hyperledger.besu.ethereum.bonsai.trielog; import static com.google.common.base.Preconditions.checkState; +import org.hyperledger.besu.datatypes.AccountValue; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.bonsai.BonsaiValue; -import org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey; -import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.TreeMap; -import java.util.stream.Stream; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @@ -40,14 +40,16 @@ import org.apache.tuweni.units.bigints.UInt256; * This class encapsulates the changes that are done to transition one block to the next. This * includes serialization and deserialization tasks for storing this log to off-memory storage. * - *

In this particular formulation only the "Leaves" are tracked" Future layers may track patrica + *

In this particular formulation only the "Leaves" are tracked Future layers may track patricia * trie changes as well. */ -public class TrieLogLayer { +@SuppressWarnings("unchecked") +public class TrieLogLayer implements TrieLog { protected Hash blockHash; + protected Optional blockNumber = Optional.empty(); - Map> getAccounts() { + Map> getAccounts() { return accounts; } @@ -59,7 +61,7 @@ public class TrieLogLayer { return storage; } - protected final Map> accounts; + protected final Map> accounts; protected final Map> code; protected final Map>> storage; protected boolean frozen = false; @@ -72,10 +74,12 @@ public class TrieLogLayer { } /** Locks the layer so no new changes can be added; */ - void freeze() { - frozen = true; // The code never bothered me anyway + @Override + public void freeze() { + frozen = true; // The code never bothered me anyway 🥶 } + @Override public Hash getBlockHash() { return blockHash; } @@ -86,10 +90,19 @@ public class TrieLogLayer { return this; } + @Override + public Optional getBlockNumber() { + return blockNumber; + } + + public TrieLogLayer setBlockNumber(final long blockNumber) { + checkState(!frozen, "Layer is Frozen"); + this.blockNumber = Optional.of(blockNumber); + return this; + } + public TrieLogLayer addAccountChange( - final Address address, - final StateTrieAccountValue oldValue, - final StateTrieAccountValue newValue) { + final Address address, final AccountValue oldValue, final AccountValue newValue) { checkState(!frozen, "Layer is Frozen"); accounts.put(address, new BonsaiValue<>(oldValue, newValue)); return this; @@ -114,64 +127,71 @@ public class TrieLogLayer { return this; } - public Stream>> streamAccountChanges() { - return accounts.entrySet().stream(); + @Override + public Map> getAccountChanges() { + return accounts; } - public Stream>> streamCodeChanges() { - return code.entrySet().stream(); + @Override + public Map> getCodeChanges() { + return code; } - public Stream>>> - streamStorageChanges() { - return storage.entrySet().stream(); + @Override + public Map>> getStorageChanges() { + return storage; } public boolean hasStorageChanges(final Address address) { return storage.containsKey(address); } - public Stream>> streamStorageChanges( - final Address address) { - return storage.getOrDefault(address, Map.of()).entrySet().stream(); + @Override + public Map> getStorageChanges(final Address address) { + return storage.getOrDefault(address, Map.of()); } + @Override public Optional getPriorCode(final Address address) { return Optional.ofNullable(code.get(address)).map(BonsaiValue::getPrior); } + @Override public Optional getCode(final Address address) { return Optional.ofNullable(code.get(address)).map(BonsaiValue::getUpdated); } - Optional getPriorStorageByStorageSlotKey( + @Override + public Optional getPriorStorageByStorageSlotKey( final Address address, final StorageSlotKey storageSlotKey) { return Optional.ofNullable(storage.get(address)) .map(i -> i.get(storageSlotKey)) .map(BonsaiValue::getPrior); } - Optional getStorageByStorageSlotKey( + @Override + public Optional getStorageByStorageSlotKey( final Address address, final StorageSlotKey storageSlotKey) { return Optional.ofNullable(storage.get(address)) .map(i -> i.get(storageSlotKey)) .map(BonsaiValue::getUpdated); } - public Optional getPriorAccount(final Address address) { + @Override + public Optional getPriorAccount(final Address address) { return Optional.ofNullable(accounts.get(address)).map(BonsaiValue::getPrior); } - public Optional getAccount(final Address address) { + @Override + 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("TrieLog{" + "blockHash=").append(blockHash).append(frozen).append('}'); sb.append("accounts\n"); - for (final Map.Entry> account : - accounts.entrySet()) { + for (final Map.Entry> account : accounts.entrySet()) { sb.append(" : ").append(account.getKey()).append("\n"); if (Objects.equals(account.getValue().getPrior(), account.getValue().getUpdated())) { sb.append(" = ").append(account.getValue().getUpdated()).append("\n"); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java index dc0d1b18f3..5da83180a1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java @@ -19,6 +19,8 @@ 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.plugin.services.trielogs.TrieLog; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; import java.util.Optional; import java.util.function.Function; @@ -47,9 +49,9 @@ public interface TrieLogManager { void reset(); - Optional getTrieLogLayer(final Hash blockHash); + Optional getTrieLogLayer(final Hash blockHash); - long subscribe(final TrieLogAddedEvent.TrieLogAddedObserver sub); + long subscribe(final TrieLogEvent.TrieLogObserver sub); void unsubscribe(final long id); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java index ce942a9192..d3f14e8e72 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java @@ -22,6 +22,7 @@ import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyVa import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.bonsai.BonsaiAccount; import org.hyperledger.besu.ethereum.bonsai.BonsaiValue; import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; @@ -261,7 +262,7 @@ public class BonsaiWorldState // for manicured tries and composting, collect branches here (not implemented) for (final Map.Entry> storageUpdate : storageAccountUpdate.getValue().entrySet()) { - final Hash slotHash = storageUpdate.getKey().slotHash(); + final Hash slotHash = storageUpdate.getKey().getSlotHash(); final UInt256 updatedStorage = storageUpdate.getValue().getUpdated(); try { if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) { @@ -503,7 +504,8 @@ public class BonsaiWorldState @Override public UInt256 getStorageValue(final Address address, final UInt256 storageKey) { - return Optional.ofNullable(getStorageValue(address, storageKey)).orElse(UInt256.ZERO); + return getStorageValueByStorageSlotKey(address, new StorageSlotKey(storageKey)) + .orElse(UInt256.ZERO); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java index 8b2b845f6b..a70e3dfacb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java @@ -16,23 +16,26 @@ package org.hyperledger.besu.ethereum.bonsai.worldview; +import org.hyperledger.besu.datatypes.AccountValue; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; 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.TrieLogFactory; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.rlp.RLP; 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.account.EvmAccount; import org.hyperledger.besu.evm.worldstate.AbstractWorldUpdater; import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogAccumulator; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; import java.util.Collection; import java.util.Collections; @@ -55,7 +58,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BonsaiWorldStateUpdateAccumulator - extends AbstractWorldUpdater implements BonsaiWorldView { + extends AbstractWorldUpdater + implements BonsaiWorldView, TrieLogAccumulator { private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldStateUpdateAccumulator.class); private final Consumer> accountPreloader; @@ -72,7 +76,7 @@ public class BonsaiWorldStateUpdateAccumulator storageToUpdate = new ConcurrentHashMap<>(); // todo plumb me from plugin service: - TrieLogFactory trieLogFactory = new TrieLogFactoryImpl(); + TrieLogFactory trieLogFactory = new TrieLogFactoryImpl(); private boolean isAccumulatorStateChanged; @@ -146,10 +150,12 @@ public class BonsaiWorldStateUpdateAccumulator return new WrappedEvmAccount(track(new UpdateTrackingAccount<>(newAccount))); } + @Override public Map> getAccountsToUpdate() { return accountsToUpdate; } + @Override public Map> getCodeToUpdate() { return codeToUpdate; } @@ -158,6 +164,7 @@ public class BonsaiWorldStateUpdateAccumulator return storageToClear; } + @Override public Map>> getStorageToUpdate() { return storageToUpdate; @@ -477,7 +484,8 @@ public class BonsaiWorldStateUpdateAccumulator storageToUpdate.get(address); if (bonsaiValueStorage != null) { // hash the key to match the implied storage interface of hashed slotKey - bonsaiValueStorage.forEach((key, value) -> results.put(key.slotHash(), value.getUpdated())); + bonsaiValueStorage.forEach( + (key, value) -> results.put(key.getSlotHash(), value.getUpdated())); } return results; } @@ -492,25 +500,22 @@ public class BonsaiWorldStateUpdateAccumulator return wrappedWorldView().getWorldStateStorage(); } - public TrieLogLayer generateTrieLog(final Hash blockHash) { - return trieLogFactory.create(this, blockHash); + public TrieLog generateTrieLog(final BlockHeader blockHeader) { + return trieLogFactory.create(this, blockHeader); } - public void rollForward(final TrieLogLayer layer) { - layer - .streamAccountChanges() + public void rollForward(final TrieLog layer) { + layer.getAccountChanges().entrySet().stream() .forEach( entry -> rollAccountChange( entry.getKey(), entry.getValue().getPrior(), entry.getValue().getUpdated())); - layer - .streamCodeChanges() + layer.getCodeChanges().entrySet().stream() .forEach( entry -> rollCodeChange( entry.getKey(), entry.getValue().getPrior(), entry.getValue().getUpdated())); - layer - .streamStorageChanges() + layer.getStorageChanges().entrySet().stream() .forEach( entry -> entry @@ -524,21 +529,18 @@ public class BonsaiWorldStateUpdateAccumulator value.getUpdated()))); } - public void rollBack(final TrieLogLayer layer) { - layer - .streamAccountChanges() + public void rollBack(final TrieLog layer) { + layer.getAccountChanges().entrySet().stream() .forEach( entry -> rollAccountChange( entry.getKey(), entry.getValue().getUpdated(), entry.getValue().getPrior())); - layer - .streamCodeChanges() + layer.getCodeChanges().entrySet().stream() .forEach( entry -> rollCodeChange( entry.getKey(), entry.getValue().getUpdated(), entry.getValue().getPrior())); - layer - .streamStorageChanges() + layer.getStorageChanges().entrySet().stream() .forEach( entry -> entry @@ -554,8 +556,8 @@ public class BonsaiWorldStateUpdateAccumulator private void rollAccountChange( final Address address, - final StateTrieAccountValue expectedValue, - final StateTrieAccountValue replacementValue) { + final AccountValue expectedValue, + final AccountValue replacementValue) { if (Objects.equals(expectedValue, replacementValue)) { // non-change, a cached read. return; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldView.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldView.java index beab2fd4f1..7a307ec3f4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldView.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldView.java @@ -18,6 +18,7 @@ package org.hyperledger.besu.ethereum.bonsai.worldview; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.evm.worldstate.WorldUpdater; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/StorageSlotKey.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/StorageSlotKey.java deleted file mode 100644 index 6be17a3c58..0000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/StorageSlotKey.java +++ /dev/null @@ -1,62 +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.worldview; - -import org.hyperledger.besu.datatypes.Hash; - -import java.util.Objects; -import java.util.Optional; - -import org.apache.tuweni.units.bigints.UInt256; -import org.jetbrains.annotations.NotNull; - -public record StorageSlotKey(Hash slotHash, Optional slotKey) - implements Comparable { - - public StorageSlotKey(final UInt256 slotKey) { - this(Hash.hash(slotKey), Optional.of(slotKey)); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - StorageSlotKey that = (StorageSlotKey) o; - return Objects.equals(slotHash, that.slotHash); - } - - @Override - public int hashCode() { - return Objects.hash(slotHash.hashCode()); - } - - @Override - public String toString() { - return String.format( - "StorageSlotKey{slotHash=%s, slotKey=%s}", - slotHash, slotKey.map(UInt256::toString).orElse("null")); - } - - @Override - public int compareTo( - @NotNull final org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey other) { - return this.slotHash.compareTo(other.slotHash); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/StateTrieAccountValue.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/StateTrieAccountValue.java index a99675a800..40c420f751 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/StateTrieAccountValue.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/StateTrieAccountValue.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.worldstate; import static com.google.common.base.Preconditions.checkNotNull; +import org.hyperledger.besu.datatypes.AccountValue; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.rlp.RLPInput; @@ -26,12 +27,12 @@ import java.util.Objects; import org.apache.tuweni.bytes.Bytes32; /** Represents the raw values associated with an account in the world state trie. */ -public class StateTrieAccountValue { +public class StateTrieAccountValue implements AccountValue { - private final long nonce; - private final Wei balance; - private final Hash storageRoot; - private final Hash codeHash; + protected final long nonce; + protected final Wei balance; + protected final Hash storageRoot; + protected final Hash codeHash; public StateTrieAccountValue( final long nonce, final Wei balance, final Hash storageRoot, final Hash codeHash) { @@ -49,6 +50,7 @@ public class StateTrieAccountValue { * * @return the account nonce. */ + @Override public long getNonce() { return nonce; } @@ -58,6 +60,7 @@ public class StateTrieAccountValue { * * @return the balance, in Wei, of the account. */ + @Override public Wei getBalance() { return balance; } @@ -67,6 +70,7 @@ public class StateTrieAccountValue { * * @return the hash of the root node of the storage trie. */ + @Override public Hash getStorageRoot() { return storageRoot; } @@ -76,6 +80,7 @@ public class StateTrieAccountValue { * * @return the hash of the account code (which may be {@link Hash#EMPTY}). */ + @Override public Hash getCodeHash() { return codeHash; } @@ -96,6 +101,7 @@ public class StateTrieAccountValue { return Objects.hash(nonce, balance, storageRoot, codeHash); } + @Override public void writeTo(final RLPOutput out) { out.startList(); diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java index 84c96dc50c..58852f3b0b 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java @@ -73,7 +73,8 @@ public class InMemoryKeyValueStorageProvider extends KeyValueStorageProvider { inMemoryKeyValueStorageProvider, blockchain, cachedMerkleTrieLoader, - new NoOpMetricsSystem()); + new NoOpMetricsSystem(), + null); } public static MutableWorldState createInMemoryWorldState() { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java index 74f4bc8547..e1b4bc9b8e 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java @@ -89,7 +89,7 @@ public class BlockImportExceptionHandlingTest { // do we need to also test with a DefaultWorldStateArchive? spy( new BonsaiWorldStateProvider( - storageProvider, blockchain, cachedMerkleTrieLoader, new NoOpMetricsSystem())); + storageProvider, blockchain, cachedMerkleTrieLoader, new NoOpMetricsSystem(), null)); private final BonsaiWorldState persisted = spy( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java index af25a1afcd..9e6bef815d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java @@ -139,7 +139,8 @@ public abstract class AbstractIsolationTests { blockchain, Optional.of(16L), new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - new NoOpMetricsSystem()); + new NoOpMetricsSystem(), + null); var ws = archive.getMutable(); genesisState.writeStateTo(ws); protocolContext = new ProtocolContext(blockchain, archive, null); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java index 3cdd86c536..eff31c523a 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java @@ -20,6 +20,7 @@ import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyVa 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.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -105,7 +106,8 @@ public class BonsaiWorldStateArchiveTest { blockchain, Optional.of(512L), new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - new NoOpMetricsSystem()); + new NoOpMetricsSystem(), + null); final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); final BlockHeader chainHead = blockBuilder.number(512).buildHeader(); when(blockchain.getChainHeadHeader()).thenReturn(chainHead); @@ -141,7 +143,9 @@ public class BonsaiWorldStateArchiveTest { public void testGetMutableWithStorageInconsistencyRollbackTheState() { when(keyValueStorage.startTransaction()).thenReturn(mock(KeyValueStorageTransaction.class)); - when(trieLogManager.getTrieLogLayer(any())).thenReturn(Optional.of(mock(TrieLogLayer.class))); + doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) + .when(trieLogManager) + .getTrieLogLayer(any(Hash.class)); var worldStateStorage = new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); @@ -201,7 +205,9 @@ public class BonsaiWorldStateArchiveTest { final BlockHeader blockHeaderChainB = blockBuilder.number(1).timestamp(2).parentHash(genesis.getHash()).buildHeader(); - when(trieLogManager.getTrieLogLayer(any())).thenReturn(Optional.of(mock(TrieLogLayer.class))); + doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) + .when(trieLogManager) + .getTrieLogLayer(any(Hash.class)); var worldStateStorage = new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); @@ -242,8 +248,9 @@ public class BonsaiWorldStateArchiveTest { final BlockHeader blockHeaderChainB = blockBuilder.number(1).timestamp(2).parentHash(genesis.getHash()).buildHeader(); - when(trieLogManager.getTrieLogLayer(any(Hash.class))) - .thenReturn(Optional.of(mock(TrieLogLayer.class))); + doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) + .when(trieLogManager) + .getTrieLogLayer(any(Hash.class)); bonsaiWorldStateArchive = spy( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java index bceca66395..c0f758401e 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java @@ -24,8 +24,8 @@ import static org.mockito.Mockito.verify; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.core.TrieGenerator; import org.hyperledger.besu.ethereum.rlp.RLP; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java index 7c352cea15..00bd789288 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java @@ -19,9 +19,9 @@ import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.core.TrieGenerator; import org.hyperledger.besu.ethereum.rlp.RLP; 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 4de7040681..62e4332292 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 @@ -126,7 +126,7 @@ public class LogRollingTests { new CachedMerkleTrieLoader(new NoOpMetricsSystem()); archive = new BonsaiWorldStateProvider( - provider, blockchain, cachedMerkleTrieLoader, new NoOpMetricsSystem()); + provider, blockchain, cachedMerkleTrieLoader, new NoOpMetricsSystem(), null); accountStorage = (InMemoryKeyValueStorage) provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); @@ -149,7 +149,11 @@ public class LogRollingTests { new CachedMerkleTrieLoader(new NoOpMetricsSystem()); secondArchive = new BonsaiWorldStateProvider( - secondProvider, blockchain, secondOptimizedMerkleTrieLoader, new NoOpMetricsSystem()); + secondProvider, + blockchain, + secondOptimizedMerkleTrieLoader, + new NoOpMetricsSystem(), + null); secondAccountStorage = (InMemoryKeyValueStorage) secondProvider.getStorageBySegmentIdentifier( 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 26fc0ea7ae..62e9f82fd4 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 @@ -49,7 +49,7 @@ public class RollingImport { new CachedMerkleTrieLoader(new NoOpMetricsSystem()); final BonsaiWorldStateProvider archive = new BonsaiWorldStateProvider( - provider, null, cachedMerkleTrieLoader, new NoOpMetricsSystem()); + provider, null, cachedMerkleTrieLoader, new NoOpMetricsSystem(), null); final InMemoryKeyValueStorage accountStorage = (InMemoryKeyValueStorage) provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryTests.java index e9cf9d6949..7f0c6f6f6c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryTests.java @@ -19,13 +19,15 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; @@ -36,31 +38,37 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class TrieLogFactoryTests { - BlockchainSetupUtil setup = BlockchainSetupUtil.forTesting(DataStorageFormat.BONSAI); + final BlockchainSetupUtil setup = BlockchainSetupUtil.forTesting(DataStorageFormat.BONSAI); + + final Address accountFixture = Address.fromHexString("0xdeadbeef"); + + final BlockHeader headerFixture = + new BlockHeaderTestFixture() + .parentHash(setup.getGenesisState().getBlock().getHash()) + .coinbase(Address.ZERO) + .buildHeader(); + + final TrieLogLayer trieLogFixture = + new TrieLogLayer() + .setBlockHash(headerFixture.getBlockHash()) + .addAccountChange( + accountFixture, + null, + new StateTrieAccountValue(0, Wei.fromEth(1), Hash.EMPTY, Hash.EMPTY)) + .addCodeChange( + Address.ZERO, + null, + Bytes.fromHexString("0xfeeddeadbeef"), + headerFixture.getBlockHash()) + .addStorageChange(Address.ZERO, new StorageSlotKey(UInt256.ZERO), null, UInt256.ONE); @Test public void testSerializeDeserializeAreEqual() { - BlockHeader header = - new BlockHeaderTestFixture() - .parentHash(setup.getGenesisState().getBlock().getHash()) - .coinbase(Address.ZERO) - .buildHeader(); - - TrieLogLayer fixture = - new TrieLogLayer() - .setBlockHash(header.getBlockHash()) - .addAccountChange( - Address.fromHexString("0xdeadbeef"), - null, - new StateTrieAccountValue(0, Wei.fromEth(1), Hash.EMPTY, Hash.EMPTY)) - .addCodeChange( - Address.ZERO, null, Bytes.fromHexString("0xdeadbeef"), header.getBlockHash()) - .addStorageChange(Address.ZERO, new StorageSlotKey(UInt256.ZERO), null, UInt256.ONE); - TrieLogFactory factory = new TrieLogFactoryImpl(); - byte[] rlp = factory.serialize(fixture); + TrieLogFactory factory = new TrieLogFactoryImpl(); + byte[] rlp = factory.serialize(trieLogFixture); - TrieLogLayer layer = factory.deserialize(rlp); - assertThat(layer).isEqualTo(fixture); + TrieLog layer = factory.deserialize(rlp); + assertThat(layer).isEqualTo(trieLogFixture); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayerTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayerTests.java index d08f12612d..4dedd8e2b0 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayerTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayerTests.java @@ -15,10 +15,11 @@ */ package org.hyperledger.besu.ethereum.bonsai.trielog; +import org.hyperledger.besu.datatypes.AccountValue; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.bonsai.worldview.StorageSlotKey; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import java.util.Optional; @@ -66,11 +67,11 @@ public class TrieLogLayerTests { Assertions.assertThat(trieLogLayer).isEqualTo(otherTrieLogLayer); - Optional priorAccount = trieLogLayer.getPriorAccount(address); + Optional priorAccount = trieLogLayer.getPriorAccount(address); Assertions.assertThat(priorAccount).isPresent(); Assertions.assertThat(priorAccount.get()).isEqualTo(oldValue); - Optional updatedAccount = trieLogLayer.getAccount(address); + Optional updatedAccount = trieLogLayer.getAccount(address); Assertions.assertThat(updatedAccount).isPresent(); Assertions.assertThat(updatedAccount.get()).isEqualTo(newValue); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java index 8d0b1b14cc..9ab0cff2a2 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java @@ -59,7 +59,12 @@ public class TrieLogManagerTests { public void setup() { trieLogManager = new CachedWorldStorageManager( - archive, blockchain, bonsaiWorldStateKeyValueStorage, new NoOpMetricsSystem(), 512); + archive, + blockchain, + bonsaiWorldStateKeyValueStorage, + new NoOpMetricsSystem(), + 512, + null); } @Test diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 03b742443d..a465d64b0f 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -69,7 +69,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 = 'aqSCy4h1L4eGXKojEW09GiRoyl1YGN9hLf5B2wf0oxg=' + knownHash = '3/qsZ9+jA10YbctpPtQ5Rn7cvYHwKRWBv6jXa+7WQMY=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/BesuContext.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/BesuContext.java index 60c52d2d29..71b3612e51 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/BesuContext.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/BesuContext.java @@ -21,6 +21,15 @@ import java.util.Optional; /** Allows plugins to access Besu services. */ public interface BesuContext { + /** + * Add service. + * + * @param the type parameter + * @param serviceType the service type + * @param service the service + */ + void addService(final Class serviceType, final T service); + /** * Get the requested service, if it is available. There are a number of reasons that a service may * not be available: diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TrieLogService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TrieLogService.java new file mode 100644 index 0000000000..f3180c6db1 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TrieLogService.java @@ -0,0 +1,58 @@ +/* + * 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.plugin.services; + +import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogProvider; + +import java.util.List; + +/** + * A service interface for registering observers for trie log events. + * + *

Implementations must be thread-safe. + */ +public interface TrieLogService extends BesuService { + + /** + * Provides list of observers to configure for trie log events. + * + * @return the list of observers to configure + */ + List getObservers(); + + /** + * Provide a TrieLogFactory implementation to use for serializing and deserializing TrieLogs. + * + * @return the TrieLogFactory implementation + */ + TrieLogFactory getTrieLogFactory(); + + /** + * Configure a TrieLogProvider implementation to use for retrieving stored TrieLogs. + * + * @param provider the TrieLogProvider implementation + */ + void configureTrieLogProvider(TrieLogProvider provider); + + /** + * Retrieve the configured TrieLogProvider implementation. + * + * @return the TrieLogProvider implementation + */ + TrieLogProvider getTrieLogProvider(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLog.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLog.java new file mode 100644 index 0000000000..1a763ade96 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLog.java @@ -0,0 +1,176 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.plugin.services.trielogs; + +import org.hyperledger.besu.datatypes.AccountValue; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; + +/** + * An interface for interacting with TrieLog objects, which represent changes to accounts, code, and + * storage in a blockchain state trie. + */ +public interface TrieLog { + + /** + * Gets the block hash associated with this TrieLog. + * + * @return the block hash + */ + Hash getBlockHash(); + + /** + * Gets the block number associated with this TrieLog, if available. + * + * @return an Optional containing the block number if available, otherwise an empty Optional + */ + Optional getBlockNumber(); + + /** Freezes the TrieLog to prevent further modifications. */ + void freeze(); + + /** + * Gets a map of addresses to their account value changes. + * + * @param the type of LogTuple representing the account value changes + * @return a map of addresses to their account value changes + */ + > Map getAccountChanges(); + + /** + * Gets a map of addresses to their code changes. + * + * @param the type of LogTuple representing the code changes + * @return a map of addresses to their code changes + */ + > Map getCodeChanges(); + + /** + * Gets a map of addresses to their storage changes. + * + * @param the type of LogTuple representing the storage changes + * @return a map of addresses to their storage changes + */ + > Map> getStorageChanges(); + + /** + * Gets the storage changes for a specific address. + * + * @param address the address to get the storage changes for + * @param the type of LogTuple representing the storage changes + * @return a map of storage slot keys to their changes + */ + > Map getStorageChanges(final Address address); + + /** + * Gets the prior code for a specific address, if available. + * + * @param address the address to get the prior code for + * @return an Optional containing the prior code if available, otherwise an empty Optional + */ + Optional getPriorCode(final Address address); + + /** + * Gets the code for a specific address, if available. + * + * @param address the address to get the code for + * @return an Optional containing the code if available, otherwise an empty Optional + */ + Optional getCode(final Address address); + + /** + * Gets the prior storage value for a specific address and storage slot key, if available. + * + * @param address the address to get the prior storage value for + * @param storageSlotKey the storage slot key to get the prior storage value for + * @return an Optional containing the prior storage value if available, otherwise an empty + * Optional + */ + Optional getPriorStorageByStorageSlotKey( + final Address address, final StorageSlotKey storageSlotKey); + + /** + * Gets the storage value for a specific address and storage slot key, if available. + * + * @param address the address to get the storage value for + * @param storageSlotKey the storage slot key to get the storage value for + * @return an Optional containing the storage value if available, otherwise an empty Optional + */ + Optional getStorageByStorageSlotKey( + final Address address, final StorageSlotKey storageSlotKey); + + /** + * Gets the prior account value for a specific address, if available. + * + * @param address the address to get the prior account value for + * @return an Optional containing the prior account value if available, otherwise an empty + * Optional + */ + Optional getPriorAccount(final Address address); + /** + * Gets the account value for a specific address, if available. + * + * @param address the address to get the account value for + * @return an Optional containing the account value if available, otherwise an empty Optional + */ + Optional getAccount(final Address address); + + /** + * An interface representing a tuple of prior and updated values for a specific type T in a log. + * The interface also provides methods to check if the values are unchanged or cleared. + * + * @param the type of values stored in the log tuple + */ + public interface LogTuple { + + /** + * Gets the prior value of the tuple. + * + * @return the prior value of type T + */ + T getPrior(); + + /** + * Gets the updated value of the tuple. + * + * @return the updated value of type T + */ + T getUpdated(); + + /** + * Checks if the prior and updated values are equal. + * + * @return true if the prior and updated values are equal, false otherwise + */ + default boolean isUnchanged() { + return Objects.equals(getUpdated(), getPrior()); + } + + /** + * Checks if the updated value represents a cleared state. + * + * @return true if the updated value is cleared, false otherwise + */ + boolean isCleared(); + } +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogAccumulator.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogAccumulator.java new file mode 100644 index 0000000000..6984ca48a1 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogAccumulator.java @@ -0,0 +1,50 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.plugin.services.trielogs; + +import org.hyperledger.besu.datatypes.AccountValue; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.StorageSlotKey; + +import java.util.Map; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; + +/** Accumulator interface tor provding trie updates for creating TrieLogs. */ +public interface TrieLogAccumulator { + + /** + * Returns the state trie accounts which have been updated. + * + * @return the accounts to update + */ + Map> getAccountsToUpdate(); + + /** + * Returns code which has been updated. + * + * @return the code to update + */ + Map> getCodeToUpdate(); + + /** + * Returns storage which has been updated. + * + * @return the storage to update + */ + Map>> + getStorageToUpdate(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogEvent.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogEvent.java new file mode 100644 index 0000000000..dc5e60761d --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.plugin.services.trielogs; + +/** A TrieLog event. */ +public interface TrieLogEvent { + /** The type of the event. */ + enum Type { + /** TrieLog added event type */ + ADDED + } + + /** + * The type of the event. + * + * @return the type of the event + */ + TrieLogEvent.Type getType(); + + /** + * The TrieLog layer. + * + * @return the TrieLog layer + */ + TrieLog layer(); + + /** Observer interface for TrieLog events. */ + interface TrieLogObserver { + + /** + * Called when a TrieLog is added. + * + * @param event the TrieLog event + */ + void onTrieLogAdded(TrieLogEvent event); + } +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogFactory.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogFactory.java new file mode 100644 index 0000000000..143bb88095 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.plugin.services.trielogs; + +import org.hyperledger.besu.plugin.data.BlockHeader; + +/** Interface for serializing and deserializing {@link TrieLog} objects. */ +public interface TrieLogFactory { + + /** + * Creates a new TrieLog object. + * + * @param accumulator the accumulator + * @param blockHeader the block header + * @return a new TrieLog object + */ + TrieLog create(TrieLogAccumulator accumulator, BlockHeader blockHeader); + + /** + * Deserializes a TrieLog object. + * + * @param bytes the serialized TrieLog + * @return the deserialized TrieLog + */ + TrieLog deserialize(final byte[] bytes); + + /** + * Serializes a TrieLog object. + * + * @param layer the TrieLog + * @return the serialized TrieLog + */ + byte[] serialize(final TrieLog layer); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogProvider.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogProvider.java new file mode 100644 index 0000000000..5df4f73c45 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * 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.plugin.services.trielogs; + +import org.hyperledger.besu.datatypes.Hash; + +import java.util.List; +import java.util.Optional; + +/** Trielog provider interface for a given block hash. */ +public interface TrieLogProvider { + /** + * Returns the TrieLog layer for the given block hash. + * + * @param blockHash the block hash + * @return the TrieLog layer for the given block hash + * @param the type of the TrieLog + */ + > Optional getTrieLogLayer(final Hash blockHash); + + /** + * Returns the TrieLog layer for the given block number. + * + * @param blockNumber the block hash + * @return the TrieLog layer for the given block hash + * @param the type of the TrieLog + */ + > Optional getTrieLogLayer(final long blockNumber); + + /** + * Returns the TrieLog layers for the given block number range. + * + * @param fromBlockNumber the from block number + * @param toBlockNumber the to block number + * @return the TrieLog layers for the given block number range + * @param the type of the TrieLog + */ + > List getTrieLogsByRange( + long fromBlockNumber, long toBlockNumber); + + /** + * Block and TrieLog layer composition, used for returning a range of TrieLog layers. + * + * @param blockHash the block hash + * @param blockNumber the block number + * @param trieLog the associated TrieLog layer + */ + record TrieLogRangeTuple(Hash blockHash, long blockNumber, TrieLog trieLog) {} +}