Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor get mutable methods of the worldtstate provider #8113

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

matkt
Copy link
Contributor

@matkt matkt commented Jan 13, 2025

PR description

This PR refactors Bonsai/DiffBased and its provider to simplify the future addition of Verkle and the stateless part. The goal is to maintain a logical code structure, ensuring easy integration of the stateless code.

Fixed Issue(s)

Thanks for sending a pull request! Have you done the following?

  • Checked out our contribution guidelines?
  • Considered documentation and added the doc-change-required label to this PR if updates are required.
  • Considered the changelog and included an update if required.
  • For database changes (e.g. KeyValueSegmentIdentifier) considered compatibility and performed forwards and backwards compatibility tests

Locally, you can run these tests to catch failures early:

  • unit tests: ./gradlew build
  • acceptance tests: ./gradlew acceptanceTest
  • integration tests: ./gradlew integrationTest
  • reference tests: ./gradlew ethereum:referenceTests:referenceTests

matkt added 2 commits January 13, 2025 11:31
Signed-off-by: Karim Taam <[email protected]>
matkt added 2 commits January 13, 2025 13:48
Signed-off-by: Karim Taam <[email protected]>
Signed-off-by: Karim Taam <[email protected]>
@matkt
Copy link
Contributor Author

matkt commented Jan 13, 2025

test in progress, not merge for the moment

Copy link
Contributor

@garyschulte garyschulte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming is hard. I see your intent 👍 Only thing I am not keen on is over use of the builder pattern for small classes

*
* @return a new builder
*/
public static Builder newBuilder() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to me like we should either expose a builder pattern or have static constructors. This class seems to be doing both.

}

@Override
public Optional<MutableWorldState> getMutable(final Hash rootHash, final Hash blockHash) {
public MutableWorldState getWorldState() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be reasonable to rename this to getEmptyWorldState() ?

Copy link
Contributor Author

@matkt matkt Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a hard time choosing the name for this method because, for Forest, it's the empty state, whereas for Bonsai, it's the head without rollback. So I left it as getWorldState because I couldn't figure out how to align the two. I think once Forest is deprecated, we can rename it or maybe remove it.

Comment on lines +29 to +31
public static WorldStateStorageConfig.Builder newBuilder() {
return new WorldStateStorageConfig.Builder();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A class with a single boolean member var (for now) having a builder seems like overkill IMO. A single static constructor would probably be sufficient. Will Verkle or stateless extend this class with its own configs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I think it will be needed for stateless. I can remove it later if I don't use it in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the class

* @param worldStateKeyValueStorage the world state key-value storage to check.
* @return {@code true} if the provided storage is modifying the head, {@code false} otherwise.
*/
private boolean isHeadModifyingWorldState(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be renamed to isModifyingHeadWorldState() since it differentiates between modifications to worldstate or a snapshot of worldstate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes done

*
* @return the state root
*/
public Hash getStateRoot() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should return Optional using Optional.ofNullable(stateRoot) since it isn't a required field. Same for the other fields which are effectively Optional. returning null can be surprising

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right updated it

* @param queryParams the query parameters
* @return the stateful world state, if available
*/
private Optional<MutableWorldState> getStatefulWorldState(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having Stateful in these method names is redundant. The worldstate is state, and hence should be stateful. We are trying to signify whether or not it is backed by a trie or a flat db versus a witness/proof?

Maybe FullWorldState vs SparseWorldState or CompleteWorldState vs WitnessWorldState ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I will do a mix and use FullWorldState vs WitnessWorldState. Seems to be more clear, because SparseWorldState doesn't seems to be a good one

@@ -115,7 +116,7 @@ public BlockProcessingResult validateAndProcessBlock(
final Block block,
final HeaderValidationMode headerValidationMode,
final HeaderValidationMode ommerValidationMode,
final boolean shouldPersist,
final boolean shouldUpdateHead,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this new naming. It conveys how dangerous that param really is for diff based worldstates. Maybe even shouldUpdateGlobalState or similar that conveys it mutates the root worldstate upon which all diffs apply

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think head is better, but I'm not opposed to global state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like as is now

|| worldStateKeyValueStorage.isWorldStateAvailable(rootHash, blockHash);
}

/**
* Gets the mutable world state based on the provided query parameters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick, should be Gets a mutable world state. "The" to me would imply the head worldstate

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes clearly better like that

*
* @return the head world state
*/
MutableWorldState getWorldState();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So from what I understand, there's no way to get an immutable WorldState correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one called Optional get(final Hash rootHash, final Hash blockHash), but it is used very rarely (only by Forest) and in test classes or nearly deprecated subcommands. Therefore, I am considering removing it in the future as well, but I would prefer to do it in a separate PR.

@@ -81,7 +83,9 @@ public PrecompileContractResult computePrecompile(
privateStateRootResolver.resolveLastStateRoot(privacyGroupId, privateMetadataUpdater);

final MutableWorldState disposablePrivateState =
privateWorldStateArchive.getMutable(lastRootHash, null).get();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer not passing null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

* @param blockHash the block hash
* @return an instance of WorldStateQueryParams
*/
public static WorldStateQueryParams withStateRootAndBlockHashAndUpdateNodeHead(
Copy link
Contributor

@lu-pinto lu-pinto Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is future proof enough because the more parameters you add to the method the longer will be the name and less readable it will be. I was thinking and what about postponing calling build() using a lambda?
You then also have the benefit of postponing constructing any object until you need it:

  public interface WorldStateQueryParamsSupplier extends Supplier<WorldStateQueryParams> {
    WorldStateQueryParams.Builder createBuilder();

    default WorldStateQueryParams get() {
      return createBuilder().build();
    }
  }

Usage:

        worldStateArchive
            .getWorldState(() -> newBuilder()
                .stateRoot(lastRootHash)
                .blockHash(blockHash)
                .shouldWorldStateUpdateHead(true))

When you want to use it (e.g. DiffBasedWorldStateProvider):

WorldStateQueryParams queryParams = queryParamsSupplier.get();

Copy link
Contributor Author

@matkt matkt Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense I can try to do it and see how is the result

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it, and I don't find it clearer. I understand that it's not future-proof, but I think that in the majority of cases, we won't need to pass more fields, except in a few calls (e.g., for stateless scenarios). In those cases, we could call the builder instead of the method, but in many cases, this method will be sufficient (e.g., for private parts). So, overcomplicating things for a somewhat custom call makes me doubt. I would suggest leaving it as is for now and revisiting in the future to see if your approach makes more sense.

example of

     privacyParameters
                  .getPrivateWorldStateArchive()
                  .getWorldState(
                      () ->
                          newBuilder()
                              .withStateRoot(
                                  privacyController
                                      .getStateRootByBlockNumber(
                                          privacyGroupId, enclaveKey, blockNumber)
                                      .get())
                              .withBlockHash(parentHash)
                              .withShouldUpdateHead()
                              .build())
                  .get();

instead of

.getWorldState(
                    withStateRootAndBlockHashAndUpdateNodeHead(
                        privacyController
                            .getStateRootByBlockNumber(privacyGroupId, enclaveKey, blockNumber)
                            .get(),
                        parentHash))

what do you think ? @lu-pinto @garyschulte

Copy link
Contributor

@lu-pinto lu-pinto Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just suggested because I imagined this might grow and then your 50 character method name will turn into 80, 100 as you add fields :D
But I'm also fine with keeping it as is for now and revisit later if needed.

Signed-off-by: Karim Taam <[email protected]>
Copy link
Contributor

@lu-pinto lu-pinto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some suggestions for improvement

* otherwise
* @return the builder
*/
public Builder withShouldWorldStateUpdateHead(final boolean shouldWorldStateUpdateHead) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: create separate methods for withShouldWorldStateUpdateHead instead of passing a flag. It would make it more readable

@@ -61,9 +61,9 @@ public BonsaiWorldStateProvider(
provideCachedWorldStorageManager(
new BonsaiCachedWorldStorageManager(
this, worldStateKeyValueStorage, this::cloneBonsaiWorldStateConfig));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why do you pass this::cloneBonsaiWorldStateConfig instead of just copying on the spot? Do you intend to create a StorageManager and not use it? From how I see it, using a config doesn't seem to be optional so you will use for sure IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just checked, I think it was necessary in an old commit but not needed anymore, so I changed the code to remove that

@@ -18,7 +18,7 @@
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.WorldStateSharedConfig;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does WorldStateSharedConfig mean? I can find many different world state config classes and I don't know which is used for: WorldStateSharedConfig, WorldStateConfiguration and WorldStateStorageConfig

Copy link
Contributor Author

@matkt matkt Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked and WorldStateConfiguration is not used in main . I removed it.

WorldStateSharedConfig is the configuration shared across all world states. When we launch Besu, we provide a base configuration that specifies whether the tree is disabled or if it is stateful. All world states will share this configuration.

WorldStateStorageConfig is the configuration specific to the world state at the level of key-value storage. It determines, for instance, whether the storage in this world state will be frozen.

I’m not sure how to make this explanation clearer, but I’m open to any ideas you might have. I understand that this can be challenging to understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I can try to remove WorldStateStorageConfig if it's simplify the code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed and added more comment to explain the goal of this flag

Copy link
Contributor

@lu-pinto lu-pinto Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks removing the other configs really simplifies the code.

It's a nitpick but I'm still not sure about the WorldStateSharedConfig naming. This to me jumps out as a shared config at runtime, among different running entities (EL / CL - though I know it doesn't make sense to share world state with CL but couldn't remember of a better example 😓) rather than a shared config across multiple storage type definitions (Bonsai/Verkle). It implies that there is an exchange of configuration at runtime and each entity has a part that is common.
What about WorldStateCommonConfig? or just WorldStateConfig? In fact you would be extending WorldStateConfig and having extra configs that are specific to each storage type, no?

@@ -305,7 +302,7 @@ private void updateAccountStorageState(
writeStorageTrieNode(
bonsaiUpdater, updatedAddressHash, location, key, value)));
// only use storage root of the trie when trie is enabled
if (!worldStateConfig.isTrieDisabled()) {
if (!worldStateSpec.isTrieDisabled()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: personal preference I think the name was good before

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can use worldStateConfig again

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed and use again worldStateConfig

@@ -92,7 +94,7 @@ public synchronized void addCachedLayer(
Optional.ofNullable(this.cachedWorldStatesByHash.get(blockHeader.getBlockHash()));
if (cachedDiffBasedWorldView.isPresent()) {
// only replace if it is a layered storage
if (forWorldState.isPersisted()
if (forWorldState.isHeadModifyingWorldState()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: isMutable ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not isMutable. Technically, all the world states returned by getWorldState will be mutable at their own level. The difference lies in whether the mutation can affect the head of the node (i.e., the node's primary world state), or if it's just a copy that only impacts itself and can be deleted when it's no longer needed.

For instance, in cases like transaction simulation or block building, the mutations would only affect the copy and not the main world state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added more logs to explain this method

matkt added 2 commits January 14, 2025 16:58
Signed-off-by: Karim Taam <[email protected]>
Signed-off-by: Karim Taam <[email protected]>
@matkt matkt force-pushed the feature/refactor-get-mutable branch from 3d9d9a2 to 103ad88 Compare January 15, 2025 16:53
// retrieve the world state from the cache.
throw new IllegalArgumentException(
"Either blockHash or stateRoot must be provided to find the worldstate in the cache");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove these validation methods? I think that was good to validate before crashing on the spot where you're using them

Copy link
Contributor

@lu-pinto lu-pinto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just have the following queries open right now:
#8113 (comment)
#8113 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants