diff --git a/app/src/main/java/org/hyperledger/besu/cli/options/BalConfigurationOptions.java b/app/src/main/java/org/hyperledger/besu/cli/options/BalConfigurationOptions.java index 979ebde4c7e..b6cd1ef0605 100644 --- a/app/src/main/java/org/hyperledger/besu/cli/options/BalConfigurationOptions.java +++ b/app/src/main/java/org/hyperledger/besu/cli/options/BalConfigurationOptions.java @@ -26,12 +26,6 @@ public class BalConfigurationOptions { /** Default constructor. */ public BalConfigurationOptions() {} - @CommandLine.Option( - names = {"--Xbal-optimization-enabled"}, - hidden = true, - description = "Allows disabling BAL-based optimizations.") - boolean balOptimizationEnabled = true; - @CommandLine.Option( names = {"--Xbal-perfect-parallelization-enabled"}, hidden = true, @@ -79,7 +73,6 @@ public BalConfigurationOptions() {} */ public BalConfiguration toDomainObject() { return ImmutableBalConfiguration.builder() - .isBalOptimisationEnabled(balOptimizationEnabled) .isPerfectParallelizationEnabled(balPerfectParallelizationEnabled) .shouldLogBalsOnMismatch(balLogBalsOnMismatch) .isBalLenientOnStateRootMismatch(balLenientOnStateRootMismatch) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java index 25ab9cca84b..516379730a9 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java @@ -53,7 +53,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.blockhash.FrontierPreExecutionProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitterFactoryDefault; +import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.DefaultStateRootCommitterFactory; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; @@ -178,7 +178,7 @@ public String description() { true, Optional.empty(), Optional.empty(), - new StateRootCommitterFactoryDefault(), + new DefaultStateRootCommitterFactory(), BlockGasAccountingStrategy.FRONTIER, BlockGasUsedValidator.FRONTIER); private final ProtocolSpec statusTransactionTypeSpec = @@ -214,7 +214,7 @@ public String description() { true, Optional.empty(), Optional.empty(), - new StateRootCommitterFactoryDefault(), + new DefaultStateRootCommitterFactory(), BlockGasAccountingStrategy.FRONTIER, BlockGasUsedValidator.FRONTIER); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/TestingBuildBlockIntegrationTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/TestingBuildBlockIntegrationTest.java index 01c0b7c46ba..a53972c03fe 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/TestingBuildBlockIntegrationTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/TestingBuildBlockIntegrationTest.java @@ -391,7 +391,8 @@ private TestContext createTestContext(final boolean withBAL) { new BadBlockManager(), false, ImmutableBalConfiguration.builder() - .isBalOptimisationEnabled(withBAL) + .isBalStateRootTrusted(withBAL) + .isPerfectParallelizationEnabled(withBAL) .build(), new NoOpMetricsSystem()) .createProtocolSchedule()) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MutableWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MutableWorldState.java index 0da6c7e4c19..6961c6542b5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MutableWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MutableWorldState.java @@ -14,11 +14,7 @@ */ package org.hyperledger.besu.ethereum.core; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitter; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitterImplSync; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.WorldStateConfig; -import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.evm.worldstate.MutableWorldView; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.plugin.data.BlockHeader; @@ -37,14 +33,7 @@ public interface MutableWorldState extends WorldState, MutableWorldView { void persist(BlockHeader blockHeader, StateRootCommitter committer); default void persist(final BlockHeader blockHeader) { - persist(blockHeader, new StateRootCommitterImplSync()); - } - - default Hash calculateOrReadRootHash( - final WorldStateKeyValueStorage.Updater stateUpdater, - final BlockHeader blockHeader, - final WorldStateConfig cfg) { - throw new UnsupportedOperationException("calculateOrReadRootHash is not supported"); + persist(blockHeader, StateRootCommitter.SYNCHRONOUS); } default MutableWorldState freezeStorage() { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java index 2801936e11c..e9f93121f01 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java @@ -239,10 +239,10 @@ public BlockProcessingResult processBlock( blockTracer.traceStartBlock(worldState, blockHeader, miningBeneficiary); final StateRootCommitter stateRootCommitter = - blockProcessingMetrics.wrapStateRootCommitter( - protocolSpec - .getStateRootCommitterFactory() - .forBlock(protocolContext, blockHeader, blockAccessList)); + protocolSpec + .getStateRootCommitterFactory() + .forBlock(protocolContext, blockHeader, blockAccessList) + .timed(blockProcessingMetrics.stateRootCalculationTimer()); final Optional blockAccessListBuilder = protocolSpec diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BalConfiguration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BalConfiguration.java index a6d8800bd80..fb5cfc0f536 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BalConfiguration.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BalConfiguration.java @@ -24,12 +24,6 @@ public interface BalConfiguration { BalConfiguration DEFAULT = ImmutableBalConfiguration.builder().build(); - /** Returns whether BAL-based optimisations should be disabled entirely. */ - @Value.Default - default boolean isBalOptimisationEnabled() { - return true; - } - /** Returns whether the BAL-computed state root should be trusted without verification. */ @Value.Default default boolean isBalStateRootTrusted() { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessingMetrics.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessingMetrics.java index d99f764ff6c..65116446b37 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessingMetrics.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessingMetrics.java @@ -14,15 +14,9 @@ */ package org.hyperledger.besu.ethereum.mainnet; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList; import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.AccountChanges; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitter; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.WorldStateConfig; -import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.metrics.BesuMetricCategory; -import org.hyperledger.besu.plugin.data.BlockHeader; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; import org.hyperledger.besu.plugin.services.metrics.OperationTimer; @@ -76,8 +70,8 @@ public BlockProcessingMetrics(final MetricsSystem metricsSystem) { "Time taken by state root calculation"); } - public StateRootCommitter wrapStateRootCommitter(final StateRootCommitter wrapped) { - return new TimedStateRootCommitter(wrapped, stateRootCalculationTimer); + public OperationTimer stateRootCalculationTimer() { + return stateRootCalculationTimer; } public void recordBlockAccessListMetrics(final BlockAccessList bal) { @@ -114,36 +108,6 @@ private long countUpdatedStorageSlots(final BlockAccessList bal) { } private boolean hasAnyChange(final AccountChanges accountChanges) { - return !accountChanges.balanceChanges().isEmpty() - || !accountChanges.nonceChanges().isEmpty() - || !accountChanges.codeChanges().isEmpty() - || !accountChanges.storageChanges().isEmpty(); - } - - private static class TimedStateRootCommitter implements StateRootCommitter { - private final StateRootCommitter wrapped; - private final OperationTimer operationTimer; - - private TimedStateRootCommitter( - final StateRootCommitter wrapped, final OperationTimer operationTimer) { - this.wrapped = wrapped; - this.operationTimer = operationTimer; - } - - @Override - public Hash computeRootAndCommit( - final MutableWorldState worldState, - final WorldStateKeyValueStorage.Updater stateUpdater, - final BlockHeader blockHeader, - final WorldStateConfig cfg) { - try (var timing = operationTimer.startTimer()) { - return wrapped.computeRootAndCommit(worldState, stateUpdater, blockHeader, cfg); - } - } - - @Override - public void cancel() { - wrapped.cancel(); - } + return accountChanges.hasAnyChange(); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index 0545264c05e..0eadce9189d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -77,7 +77,7 @@ import org.hyperledger.besu.ethereum.mainnet.requests.MainnetRequestsValidator; import org.hyperledger.besu.ethereum.mainnet.requests.RequestContractAddresses; import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitterFactoryBal; +import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.BalStateRootCommitterFactory; import org.hyperledger.besu.ethereum.mainnet.transactionpool.OsakaTransactionPoolPreProcessor; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.evm.MainnetEVMs; @@ -1224,7 +1224,7 @@ static ProtocolSpecBuilder amsterdamDefinition( .build()) .blockAccessListFactory(new BlockAccessListFactory()) .blockAccessListValidatorBuilder(MainnetBlockAccessListValidator::create) - .stateRootCommitterFactory(new StateRootCommitterFactoryBal(balConfiguration)) + .stateRootCommitterFactory(new BalStateRootCommitterFactory(balConfiguration)) // EIP-8037: Disable validation-time TX_MAX_GAS_LIMIT cap (enforced at runtime on regular // gas) .gasLimitCalculatorBuilder( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java index ebfa2ec0bc5..908f63b0c06 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java @@ -30,8 +30,8 @@ import org.hyperledger.besu.ethereum.mainnet.requests.ProhibitedRequestValidator; import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidator; +import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.DefaultStateRootCommitterFactory; import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitterFactory; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitterFactoryDefault; import org.hyperledger.besu.ethereum.mainnet.transactionpool.TransactionPoolPreProcessor; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -98,7 +98,7 @@ public class ProtocolSpecBuilder { private TransactionPoolPreProcessor transactionPoolPreProcessor; private BlockAccessListFactory blockAccessListFactory; private StateRootCommitterFactory stateRootCommitterFactory = - new StateRootCommitterFactoryDefault(); + new DefaultStateRootCommitterFactory(); private BalConfiguration balConfiguration = BalConfiguration.DEFAULT; private BlockGasAccountingStrategy blockGasAccountingStrategy = BlockGasAccountingStrategy.FRONTIER; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/block/access/list/BlockAccessList.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/block/access/list/BlockAccessList.java index 4de3dab39d2..5adc80fb5fe 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/block/access/list/BlockAccessList.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/block/access/list/BlockAccessList.java @@ -117,6 +117,14 @@ public record AccountChanges( List balanceChanges, List nonceChanges, List codeChanges) { + + public boolean hasAnyChange() { + return !balanceChanges.isEmpty() + || !nonceChanges.isEmpty() + || !codeChanges.isEmpty() + || !storageChanges.isEmpty(); + } + @Override public String toString() { return "AccountChanges{" diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/BalStateRootCommitterFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/BalStateRootCommitterFactory.java new file mode 100644 index 00000000000..394064fb57f --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/BalStateRootCommitterFactory.java @@ -0,0 +1,222 @@ +/* + * Copyright contributors to 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.ethereum.mainnet.staterootcommitter; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.mainnet.BalConfiguration; +import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList; +import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.worldview.BalStateRootCalculator; +import org.hyperledger.besu.ethereum.trie.pathbased.common.provider.PathBasedWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedLayeredWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.PathBasedWorldState; +import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.accumulator.PathBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.plugin.data.BlockHeader; + +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public final class BalStateRootCommitterFactory implements StateRootCommitterFactory { + + private static final Logger LOG = LoggerFactory.getLogger(BalStateRootCommitterFactory.class); + + private final BalConfiguration balConfiguration; + + public BalStateRootCommitterFactory(final BalConfiguration balConfiguration) { + this.balConfiguration = balConfiguration; + } + + @Override + public StateRootCommitter forBlock( + final ProtocolContext protocolContext, + final BlockHeader blockHeader, + final Optional maybeBal) { + + if (maybeBal.isEmpty() + || protocolContext.getWorldStateArchive() instanceof ForestWorldStateArchive + || isTrieDisabled(protocolContext)) { + return StateRootCommitter.SYNCHRONOUS; + } + + final CompletableFuture balFuture = + BalStateRootCalculator.computeAsync(protocolContext, blockHeader, maybeBal.get()); + final Duration timeout = balConfiguration.getBalStateRootTimeout(); + + if (balConfiguration.isBalStateRootTrusted()) { + return new TrustedBalCommitter(balFuture, timeout); + } + return new VerifyingBalCommitter( + balFuture, timeout, balConfiguration.isBalLenientOnStateRootMismatch()); + } + + // The BAL-computed root is the authoritative source. The standard trie + // computation (supplier) is never invoked. + private static final class TrustedBalCommitter implements StateRootCommitter { + + private final CompletableFuture balFuture; + private final Duration timeout; + + TrustedBalCommitter( + final CompletableFuture balFuture, final Duration timeout) { + this.balFuture = balFuture; + this.timeout = timeout; + } + + @Override + public Hash computeRoot( + final Supplier stateRootSupplier, + final MutableWorldState worldState, + final WorldStateKeyValueStorage.Updater stateUpdater, + final BlockHeader blockHeader) { + + final BalRootComputation bal = + awaitBal(balFuture, timeout, /* lenient= */ false) + .orElseThrow(); // unreachable in strict mode + + if (!blockHeader.getStateRoot().equals(bal.root())) { + throw new IllegalStateException("BAL-computed root does not match block header state root"); + } + + importBalStateChanges( + (PathBasedWorldState) worldState, + (PathBasedWorldStateKeyValueStorage.Updater) stateUpdater, + bal); + return bal.root(); + } + + @Override + public void cancel() { + balFuture.cancel(true); + } + } + + // The standard trie computation is the source of truth. The BAL result + // is awaited only to cross-check. + private static final class VerifyingBalCommitter implements StateRootCommitter { + + private final CompletableFuture balFuture; + private final Duration timeout; + private final boolean lenient; + + VerifyingBalCommitter( + final CompletableFuture balFuture, + final Duration timeout, + final boolean lenient) { + this.balFuture = balFuture; + this.timeout = timeout; + this.lenient = lenient; + } + + @Override + public Hash computeRoot( + final Supplier stateRootSupplier, + final MutableWorldState worldState, + final WorldStateKeyValueStorage.Updater stateUpdater, + final BlockHeader blockHeader) { + + final Hash syncRoot = stateRootSupplier.get(); + final Optional maybeBal = awaitBal(balFuture, timeout, lenient); + + if (maybeBal.isEmpty()) { + LOG.warn("BAL root unavailable (lenient mode); proceeding with computed root {}", syncRoot); + return syncRoot; + } + + final Hash balRoot = maybeBal.get().root(); + if (!syncRoot.equals(balRoot)) { + final String msg = + String.format("BAL root mismatch: computed %s vs BAL %s", syncRoot, balRoot); + if (lenient) { + LOG.error(msg); + } else { + throw new IllegalStateException(msg); + } + } + return syncRoot; + } + + @Override + public void cancel() { + balFuture.cancel(true); + } + } + + private static void importBalStateChanges( + final PathBasedWorldState worldState, + final PathBasedWorldStateKeyValueStorage.Updater stateUpdater, + final BalRootComputation bal) { + + final PathBasedWorldStateUpdateAccumulator balAccumulator = bal.accumulator(); + final PathBasedWorldStateUpdateAccumulator blockAccumulator = worldState.updater(); + blockAccumulator.importStateChangesFromSource(balAccumulator); + + if (!worldState.isStorageFrozen()) { + final PathBasedLayeredWorldStateKeyValueStorage balStorage = + (PathBasedLayeredWorldStateKeyValueStorage) balAccumulator.getWorldStateStorage(); + balStorage.mergeTo(stateUpdater.getWorldStateTransaction()); + } + } + + /** + * Awaits the BAL computation result with the configured timeout. In lenient mode, failures are + * logged and an empty Optional is returned. In strict mode, failures throw. + */ + private static Optional awaitBal( + final CompletableFuture future, + final Duration timeout, + final boolean lenient) { + try { + if (timeout.isNegative()) { + return Optional.of(future.join()); + } + return Optional.of(future.get(timeout.toNanos(), TimeUnit.NANOSECONDS)); + } catch (final TimeoutException e) { + future.cancel(true); + return handleFailure(lenient, "Timed out waiting " + timeout + " for BAL state root", e); + } catch (final ExecutionException e) { + return handleFailure(lenient, "Failed to compute BAL state root", e.getCause()); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return handleFailure(lenient, "Interrupted while waiting for BAL state root", e); + } + } + + private static Optional handleFailure( + final boolean lenient, final String message, final Throwable cause) { + if (lenient) { + LOG.warn("{} (lenient mode); proceeding.", message, cause); + return Optional.empty(); + } + throw new IllegalStateException(message, cause); + } + + private static boolean isTrieDisabled(final ProtocolContext protocolContext) { + return protocolContext.getWorldStateArchive() instanceof PathBasedWorldStateProvider provider + && provider.getWorldStateSharedSpec().isTrieDisabled(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactoryDefault.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/DefaultStateRootCommitterFactory.java similarity index 86% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactoryDefault.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/DefaultStateRootCommitterFactory.java index 0cfe5b9bcb5..e89b1da8dd9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactoryDefault.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/DefaultStateRootCommitterFactory.java @@ -15,17 +15,17 @@ package org.hyperledger.besu.ethereum.mainnet.staterootcommitter; import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList; +import org.hyperledger.besu.plugin.data.BlockHeader; import java.util.Optional; -public final class StateRootCommitterFactoryDefault implements StateRootCommitterFactory { +public final class DefaultStateRootCommitterFactory implements StateRootCommitterFactory { @Override public StateRootCommitter forBlock( final ProtocolContext protocolContext, final BlockHeader blockHeader, final Optional maybeBal) { - return new StateRootCommitterImplSync(); + return StateRootCommitter.SYNCHRONOUS; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitter.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitter.java index b7b1c903bf9..5286b087e4d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitter.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitter.java @@ -16,16 +16,65 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.WorldStateConfig; import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.services.metrics.OperationTimer; +import java.util.function.Supplier; + +/** + * Strategy for computing the state root hash during block persistence. + * + *

The caller provides a {@code stateRootSupplier} that encapsulates the standard (synchronous) + * trie computation. Implementations may: + * + *

    + *
  • Simply invoke the supplier (sync mode) + *
  • Ignore it and return a pre-computed root (BAL trusted mode) + *
  • Invoke it, then cross-check against an independently computed root (BAL verification mode) + *
+ */ public interface StateRootCommitter { - Hash computeRootAndCommit( + + /** Computes the state root synchronously via the standard trie path. */ + StateRootCommitter SYNCHRONOUS = + (supplier, worldState, stateUpdater, blockHeader) -> supplier.get(); + + /** + * Compute (or retrieve) the state root and apply any additional side-effects. + * + * @param stateRootSupplier lazily computes the state root via the standard trie path + * @param worldState the world state being persisted (used by BAL to import state changes) + * @param stateUpdater the storage updater (used by BAL to merge trie nodes) + * @param blockHeader the block being persisted + * @return the authoritative state root hash + */ + Hash computeRoot( + Supplier stateRootSupplier, MutableWorldState worldState, WorldStateKeyValueStorage.Updater stateUpdater, - BlockHeader blockHeader, - WorldStateConfig worldStateConfig); + BlockHeader blockHeader); default void cancel() {} + + default StateRootCommitter timed(final OperationTimer timer) { + final StateRootCommitter delegate = this; + return new StateRootCommitter() { + @Override + public Hash computeRoot( + final Supplier stateRootSupplier, + final MutableWorldState worldState, + final WorldStateKeyValueStorage.Updater stateUpdater, + final BlockHeader blockHeader) { + try (var ignored = timer.startTimer()) { + return delegate.computeRoot(stateRootSupplier, worldState, stateUpdater, blockHeader); + } + } + + @Override + public void cancel() { + delegate.cancel(); + } + }; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactory.java index 40c6118d1ce..3393c82ca55 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactory.java @@ -15,8 +15,8 @@ package org.hyperledger.besu.ethereum.mainnet.staterootcommitter; import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList; +import org.hyperledger.besu.plugin.data.BlockHeader; import java.util.Optional; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactoryBal.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactoryBal.java deleted file mode 100644 index 568e08cc233..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterFactoryBal.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.ethereum.mainnet.staterootcommitter; - -import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.mainnet.BalConfiguration; -import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList; -import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive; -import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.worldview.BlockAccessListStateRootHashCalculator; - -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -public final class StateRootCommitterFactoryBal implements StateRootCommitterFactory { - - private final BalConfiguration balConfiguration; - - public StateRootCommitterFactoryBal(final BalConfiguration balConfiguration) { - this.balConfiguration = balConfiguration; - } - - @Override - public StateRootCommitter forBlock( - final ProtocolContext protocolContext, - final BlockHeader blockHeader, - final Optional maybeBal) { - if (!balConfiguration.isBalOptimisationEnabled()) { - return new StateRootCommitterImplSync(); - } - - if (maybeBal.isEmpty()) { - return new StateRootCommitterImplSync(); - } - - // This is temporary workaround to not launch state root pre-computation in Forest mode - if (protocolContext.getWorldStateArchive() instanceof ForestWorldStateArchive) { - return new StateRootCommitterImplSync(); - } - - final CompletableFuture balRootFuture = - BlockAccessListStateRootHashCalculator.computeAsync( - protocolContext, blockHeader, maybeBal.get()); - return new StateRootCommitterImplBal(balRootFuture, balConfiguration); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterImplBal.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterImplBal.java deleted file mode 100644 index da9b99fe604..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterImplBal.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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.ethereum.mainnet.staterootcommitter; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.mainnet.BalConfiguration; -import org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedLayeredWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.PathBasedWorldState; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.WorldStateConfig; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.accumulator.PathBasedWorldStateUpdateAccumulator; -import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; -import org.hyperledger.besu.plugin.data.BlockHeader; - -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@SuppressWarnings({"rawtypes", "unchecked"}) -final class StateRootCommitterImplBal implements StateRootCommitter { - - private static final Logger LOG = LoggerFactory.getLogger(StateRootCommitterImplBal.class); - - private final CompletableFuture balRootFuture; - private final BalConfiguration balConfiguration; - private final StateRootCommitterImplSync syncCommitter = new StateRootCommitterImplSync(); - - StateRootCommitterImplBal( - final CompletableFuture balRootFuture, - final BalConfiguration balConfiguration) { - this.balRootFuture = balRootFuture; - this.balConfiguration = balConfiguration; - } - - @Override - public Hash computeRootAndCommit( - final MutableWorldState worldState, - final WorldStateKeyValueStorage.Updater stateUpdater, - final BlockHeader blockHeader, - final WorldStateConfig cfg) { - - final Duration balRootTimeout = balConfiguration.getBalStateRootTimeout(); - - if (balConfiguration.isBalStateRootTrusted()) { - return computeWithTrustedBalRoot( - (PathBasedWorldState) worldState, - (PathBasedWorldStateKeyValueStorage.Updater) stateUpdater, - blockHeader, - balRootTimeout); - } - - return computeWithBalVerification(worldState, stateUpdater, blockHeader, cfg, balRootTimeout); - } - - @Override - public void cancel() { - balRootFuture.cancel(true); - } - - private Hash computeWithTrustedBalRoot( - final PathBasedWorldState worldState, - final PathBasedWorldStateKeyValueStorage.Updater stateUpdater, - final BlockHeader blockHeader, - final Duration balRootTimeout) { - - final BalRootComputation balComputation = waitForBalRootStrict(balRootTimeout); - final Hash balStateRoot = balComputation.root(); - - if (!blockHeader.getStateRoot().equals(balStateRoot)) { - throw new IllegalStateException("BAL-computed root does not match block header state root"); - } - importBalStateChanges(worldState, stateUpdater, balComputation); - - return balStateRoot; - } - - private Hash computeWithBalVerification( - final MutableWorldState worldState, - final WorldStateKeyValueStorage.Updater stateUpdater, - final BlockHeader blockHeader, - final WorldStateConfig cfg, - final Duration balRootTimeout) { - - final Hash computedRoot = - syncCommitter.computeRootAndCommit(worldState, stateUpdater, blockHeader, cfg); - - if (balConfiguration.isBalLenientOnStateRootMismatch()) { - return handleBalLenientMode(computedRoot, balRootTimeout); - } else { - return handleBalStrictMode(computedRoot, balRootTimeout); - } - } - - private Hash handleBalLenientMode(final Hash computedRoot, final Duration balRootTimeout) { - final Optional maybeBalRoot = waitForBalRootLenient(balRootTimeout); - - if (maybeBalRoot.isEmpty()) { - LOG.warn( - "BAL root unavailable (lenient mode); proceeding with computed state root {}", - computedRoot); - return computedRoot; - } - - final Hash balRoot = maybeBalRoot.get().root(); - if (!computedRoot.equals(balRoot)) { - LOG.error("BAL root mismatch: computed {} vs BAL {}", computedRoot, balRoot); - } - - return computedRoot; - } - - private Hash handleBalStrictMode(final Hash computedRoot, final Duration balRootTimeout) { - final Hash balRoot = waitForBalRootStrict(balRootTimeout).root(); - - if (!computedRoot.equals(balRoot)) { - final String errorMessage = - String.format("BAL root mismatch: computed %s vs BAL %s", computedRoot, balRoot); - throw new IllegalStateException(errorMessage); - } - - return computedRoot; - } - - private void importBalStateChanges( - final PathBasedWorldState worldState, - final PathBasedWorldStateKeyValueStorage.Updater stateUpdater, - final BalRootComputation balComputation) { - - final PathBasedWorldStateUpdateAccumulator balAccumulator = balComputation.accumulator(); - final PathBasedWorldStateUpdateAccumulator blockAccumulator = worldState.updater(); - - blockAccumulator.importStateChangesFromSource(balAccumulator); - - final PathBasedLayeredWorldStateKeyValueStorage balStateUpdater = - (PathBasedLayeredWorldStateKeyValueStorage) balAccumulator.getWorldStateStorage(); - if (!worldState.isStorageFrozen()) { - balStateUpdater.mergeTo(stateUpdater.getWorldStateTransaction()); - } - } - - private BalRootComputation waitForBalRootStrict(final Duration balRootTimeout) { - try { - return getWithConfiguredTimeout(balRootTimeout); - } catch (final TimeoutException e) { - balRootFuture.cancel(true); - throw new IllegalStateException( - String.format("Timed out waiting %s for BAL state root", balRootTimeout), e); - } catch (final ExecutionException e) { - throw new IllegalStateException("Failed to compute BAL state root", e.getCause()); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Interrupted while waiting for BAL state root", e); - } - } - - private Optional waitForBalRootLenient(final Duration balRootTimeout) { - try { - return Optional.of(getWithConfiguredTimeout(balRootTimeout)); - } catch (final TimeoutException e) { - balRootFuture.cancel(true); - LOG.warn( - "Timed out waiting {} for BAL state root (lenient mode); proceeding.", balRootTimeout); - return Optional.empty(); - } catch (final ExecutionException e) { - LOG.warn("Failed to compute BAL state root (lenient mode); proceeding.", e.getCause()); - return Optional.empty(); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - LOG.warn("Interrupted while waiting for BAL state root (lenient mode); proceeding."); - return Optional.empty(); - } - } - - private BalRootComputation getWithConfiguredTimeout(final Duration balRootTimeout) - throws InterruptedException, ExecutionException, TimeoutException { - if (balRootTimeout.isNegative()) { - return balRootFuture.join(); - } - return balRootFuture.get(balRootTimeout.toNanos(), TimeUnit.NANOSECONDS); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterImplSync.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterImplSync.java deleted file mode 100644 index 0454ca23046..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterImplSync.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.ethereum.mainnet.staterootcommitter; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.WorldStateConfig; -import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; -import org.hyperledger.besu.plugin.data.BlockHeader; - -public final class StateRootCommitterImplSync implements StateRootCommitter { - - @Override - public Hash computeRootAndCommit( - final MutableWorldState worldState, - final WorldStateKeyValueStorage.Updater stateUpdater, - final BlockHeader blockHeader, - final WorldStateConfig cfg) { - return worldState.calculateOrReadRootHash(stateUpdater, blockHeader, cfg); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldState.java index bf0ddf8463d..ef67117249b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldState.java @@ -25,7 +25,6 @@ import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.common.PmtStateTrieAccountValue; import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.WorldStateConfig; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; @@ -179,42 +178,28 @@ public final boolean equals(final Object other) { public void persist(final BlockHeader blockHeader, final StateRootCommitter committer) { final ForestWorldStateKeyValueStorage.Updater stateUpdater = worldStateKeyValueStorage.updater(); - committer.computeRootAndCommit( - this, stateUpdater, blockHeader, WorldStateConfig.createStatefulConfigWithTrie()); + committer.computeRoot(() -> applyAndComputeRoot(stateUpdater), this, stateUpdater, blockHeader); } - @Override - public Hash calculateOrReadRootHash( - final WorldStateKeyValueStorage.Updater stateUpdater, - final BlockHeader blockHeader, - final WorldStateConfig cfg) { - - final ForestWorldStateKeyValueStorage.Updater forestUpdater = - (ForestWorldStateKeyValueStorage.Updater) stateUpdater; - // Store updated code + private Hash applyAndComputeRoot(final ForestWorldStateKeyValueStorage.Updater forestUpdater) { for (final Bytes code : updatedAccountCode.values()) { forestUpdater.putCode(code); } - // Commit account storage tries for (final MerkleTrie updatedStorage : updatedStorageTries.values()) { updatedStorage.commit( (location, hash, value) -> forestUpdater.putAccountStorageTrieNode(hash, value)); } - // Commit account updates accountStateTrie.commit( (location, hash, value) -> forestUpdater.putAccountStateTrieNode(hash, value)); - // Persist preimages final WorldStatePreimageStorage.Updater preimageUpdater = preimageStorage.updater(); newStorageKeyPreimages.forEach(preimageUpdater::putStorageTrieKeyPreimage); newAccountKeyPreimages.forEach(preimageUpdater::putAccountTrieKeyPreimage); - // Clear pending changes that we just flushed updatedStorageTries.clear(); updatedAccountCode.clear(); newStorageKeyPreimages.clear(); - // Push changes to underlying storage preimageUpdater.commit(); forestUpdater.commit(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BalStateRootCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BalStateRootCalculator.java new file mode 100644 index 00000000000..ddca24f990a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BalStateRootCalculator.java @@ -0,0 +1,123 @@ +/* + * Copyright contributors to 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.ethereum.trie.pathbased.bonsai.worldview; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList; +import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.AccountChanges; +import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.SlotChanges; +import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.BalRootComputation; +import org.hyperledger.besu.ethereum.trie.pathbased.common.provider.WorldStateQueryParams; +import org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.PathBasedWorldState; +import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.accumulator.PathBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.plugin.data.BlockHeader; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import org.apache.tuweni.units.bigints.UInt256; + +@SuppressWarnings("rawtypes") +public class BalStateRootCalculator { + + private BalStateRootCalculator() {} + + public static CompletableFuture computeAsync( + final ProtocolContext protocolContext, + final BlockHeader blockHeader, + final BlockAccessList bal) { + return CompletableFuture.supplyAsync( + () -> { + try (BonsaiWorldState ws = openParentWorldState(protocolContext, blockHeader)) { + applyBalChanges(ws.getAccumulator(), bal); + return computeRoot(ws); + } + }); + } + + private static BonsaiWorldState openParentWorldState( + final ProtocolContext protocolContext, final BlockHeader blockHeader) { + final Hash parentHash = blockHeader.getParentHash(); + final BlockHeader parentHeader = + protocolContext + .getBlockchain() + .getBlockHeader(parentHash) + .orElseThrow( + () -> + new IllegalStateException( + String.format( + "Parent %s of block %s not found", + parentHash, blockHeader.getBlockHash()))); + final BonsaiWorldState ws = + (BonsaiWorldState) + protocolContext + .getWorldStateArchive() + .getWorldState( + WorldStateQueryParams.withBlockHeaderAndNoUpdateNodeHead(parentHeader)) + .orElseThrow(); + ws.disableCacheMerkleTrieLoader(); + return ws; + } + + private static void applyBalChanges( + final PathBasedWorldStateUpdateAccumulator accumulator, final BlockAccessList bal) { + for (final AccountChanges changes : bal.accountChanges()) { + if (!changes.hasAnyChange()) { + continue; + } + final Address address = changes.address(); + final MutableAccount account = accumulator.getOrCreate(address); + + lastOf(changes.balanceChanges()) + .ifPresent(c -> account.setBalance(Wei.wrap(c.postBalance()))); + lastOf(changes.nonceChanges()).ifPresent(c -> account.setNonce(c.newNonce())); + lastOf(changes.codeChanges()).ifPresent(c -> account.setCode(c.newCode())); + + for (final SlotChanges slot : changes.storageChanges()) { + lastOf(slot.changes()) + .ifPresent( + change -> + slot.slot() + .getSlotKey() + .ifPresent( + key -> { + final UInt256 value = change.newValue(); + account.setStorageValue(key, value == null ? UInt256.ZERO : value); + })); + } + } + accumulator.clearAccountsThatAreEmpty(); + accumulator.commit(); + } + + private static BalRootComputation computeRoot(final PathBasedWorldState worldState) { + final PathBasedWorldStateUpdateAccumulator accumulator = worldState.getAccumulator(); + final PathBasedWorldStateKeyValueStorage.Updater updater = + worldState.getWorldStateStorage().updater(); + final Hash root = worldState.calculateRootHash(Optional.of(updater), accumulator); + updater.commit(); + return new BalRootComputation(root, accumulator); + } + + private static Optional lastOf(final List list) { + return list.isEmpty() ? Optional.empty() : Optional.of(list.getLast()); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BlockAccessListStateRootHashCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BlockAccessListStateRootHashCalculator.java deleted file mode 100644 index d1aea6992d1..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BlockAccessListStateRootHashCalculator.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright contributors to 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.ethereum.trie.pathbased.bonsai.worldview; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList; -import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.AccountChanges; -import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.BalanceChange; -import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.CodeChange; -import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.NonceChange; -import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.SlotChanges; -import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.StorageChange; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.BalRootComputation; -import org.hyperledger.besu.ethereum.trie.pathbased.common.provider.WorldStateQueryParams; -import org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.PathBasedWorldState; -import org.hyperledger.besu.ethereum.trie.pathbased.common.worldview.accumulator.PathBasedWorldStateUpdateAccumulator; -import org.hyperledger.besu.evm.account.MutableAccount; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -import org.apache.tuweni.units.bigints.UInt256; - -@SuppressWarnings("rawtypes") -public class BlockAccessListStateRootHashCalculator { - - private BlockAccessListStateRootHashCalculator() {} - - private static BonsaiWorldState prepareWorldState( - final ProtocolContext protocolContext, final BlockHeader blockHeader) { - final Hash parentHash = blockHeader.getParentHash(); - final Optional maybeParentHeader = - protocolContext.getBlockchain().getBlockHeader(parentHash); - if (maybeParentHeader.isEmpty()) { - throw new IllegalStateException( - String.format("Parent %s of block %s not found", parentHash, blockHeader.getHash())); - } - final BonsaiWorldState ws = - (BonsaiWorldState) - protocolContext - .getWorldStateArchive() - .getWorldState( - WorldStateQueryParams.withBlockHeaderAndNoUpdateNodeHead( - maybeParentHeader.get())) - .orElseThrow(); - ws.disableCacheMerkleTrieLoader(); - return ws; - } - - private static BalRootComputation accumulateAccessListAndComputeRoot( - final PathBasedWorldState worldState, final BlockAccessList blockAccessList) { - - final PathBasedWorldStateUpdateAccumulator accumulator = worldState.getAccumulator(); - PathBasedWorldStateKeyValueStorage.Updater worldStateKeyValueStorageUpdater = - worldState.getWorldStateStorage().updater(); - - for (AccountChanges accountChanges : blockAccessList.accountChanges()) { - final Address address = accountChanges.address(); - - final List balanceChanges = accountChanges.balanceChanges(); - final List nonceChanges = accountChanges.nonceChanges(); - final List codeChanges = accountChanges.codeChanges(); - final List storageChanges = accountChanges.storageChanges(); - - final boolean anyChange = - !balanceChanges.isEmpty() - || !nonceChanges.isEmpty() - || !codeChanges.isEmpty() - || !storageChanges.isEmpty(); - - if (!anyChange) { - continue; - } - - final MutableAccount account = accumulator.getOrCreate(address); - - if (!balanceChanges.isEmpty()) { - final BalanceChange change = balanceChanges.get(balanceChanges.size() - 1); - account.setBalance(Wei.wrap(change.postBalance())); - } - - if (!nonceChanges.isEmpty()) { - final NonceChange change = nonceChanges.get(nonceChanges.size() - 1); - account.setNonce(change.newNonce()); - } - - if (!codeChanges.isEmpty()) { - final CodeChange change = codeChanges.get(codeChanges.size() - 1); - account.setCode(change.newCode()); - } - - for (SlotChanges slotChanges : storageChanges) { - final List changes = slotChanges.changes(); - if (!changes.isEmpty()) { - final StorageChange change = changes.get(changes.size() - 1); - final Optional maybeKey = slotChanges.slot().getSlotKey(); - if (maybeKey.isPresent()) { - final UInt256 key = maybeKey.get(); - final UInt256 value = change.newValue(); - account.setStorageValue(key, value == null ? UInt256.ZERO : value); - } - } - } - } - - accumulator.clearAccountsThatAreEmpty(); - accumulator.commit(); - final Hash root = - worldState.calculateRootHash(Optional.of(worldStateKeyValueStorageUpdater), accumulator); - worldStateKeyValueStorageUpdater.commit(); - return new BalRootComputation(root, accumulator); - } - - public static CompletableFuture computeAsync( - final ProtocolContext protocolContext, - final BlockHeader blockHeader, - final BlockAccessList bal) { - return CompletableFuture.supplyAsync( - () -> { - try (BonsaiWorldState ws = prepareWorldState(protocolContext, blockHeader)) { - return accumulateAccessListAndComputeRoot(ws, bal); - } - }); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiWorldState.java index 978f1e2dc40..c73a0c11468 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiWorldState.java @@ -120,34 +120,51 @@ public BonsaiWorldStateKeyValueStorage getWorldStateStorage() { public Hash calculateRootHash( final Optional maybeStateUpdater, final PathBasedWorldStateUpdateAccumulator worldStateUpdater) { - return internalCalculateRootHash( + return applyUpdatesAndComputeRoot( maybeStateUpdater.map(BonsaiWorldStateKeyValueStorage.Updater.class::cast), (BonsaiWorldStateUpdateAccumulator) worldStateUpdater); } - private Hash internalCalculateRootHash( + /** + * Applies all pending state changes and computes the new state root hash. + * + *

The update proceeds in four ordered phases: + * + *

    + *
  1. Clear storage for self-destructed accounts + *
  2. Update per-account storage tries (must precede account updates to produce storage roots) + *
  3. Update contract code + *
  4. Update account trie and compute the new root hash + *
+ */ + private Hash applyUpdatesAndComputeRoot( final Optional maybeStateUpdater, final BonsaiWorldStateUpdateAccumulator worldStateUpdater) { + // Phase 1 — clear storage for accounts that were self-destructed clearStorage(maybeStateUpdater, worldStateUpdater); - // This must be done before updating the accounts so - // that we can get the storage state hash + // Phase 2 — update every account's storage trie (must happen before account updates + // because the storage root is embedded in the account node) + final boolean canParallelize = maybeStateUpdater.isEmpty(); Stream>>> storageStream = worldStateUpdater.getStorageToUpdate().entrySet().stream(); - if (maybeStateUpdater.isEmpty()) { - storageStream = - storageStream - .parallel(); // if we are not updating the state updater we can use parallel stream + if (canParallelize) { + storageStream = storageStream.parallel(); } storageStream.forEach( - addressMapEntry -> - updateAccountStorageState(maybeStateUpdater, worldStateUpdater, addressMapEntry)); + entry -> updateAccountStorageState(maybeStateUpdater, worldStateUpdater, entry)); - // Third update the code. This has the side effect of ensuring a code hash is calculated. + // Phase 3 — persist contract code changes (also computes code hashes) updateCode(maybeStateUpdater, worldStateUpdater); - // next walk the account trie + // Phase 4 — update the account trie, commit, and return the new root hash + return commitAccountTrieAndComputeRoot(maybeStateUpdater, worldStateUpdater); + } + + private Hash commitAccountTrieAndComputeRoot( + final Optional maybeStateUpdater, + final BonsaiWorldStateUpdateAccumulator worldStateUpdater) { final MerkleTrie accountTrie = createTrie( (location, hash) -> @@ -155,15 +172,11 @@ private Hash internalCalculateRootHash( getWorldStateStorage(), location, hash), Bytes32.wrap(worldStateRootHash.getBytes())); - // for manicured tries and composting, collect branches here (not implemented) updateTheAccounts(maybeStateUpdater, worldStateUpdater, accountTrie); - // TODO write to a cache and then generate a layer update from that and the - // DB tx updates. Right now it is just DB updates. maybeStateUpdater.ifPresent( bonsaiUpdater -> accountTrie.commit(bonsaiUpdater::putAccountStateTrieNode)); - final Bytes32 rootHash = accountTrie.getRootHash(); - return Hash.wrap(rootHash); + return Hash.wrap(accountTrie.getRootHash()); } private void updateTheAccounts( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/worldview/PathBasedWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/worldview/PathBasedWorldState.java index cdaf0095303..7504f3f868c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/worldview/PathBasedWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/worldview/PathBasedWorldState.java @@ -164,53 +164,17 @@ public PathBasedWorldStateUpdateAccumulator getAccumulator() { return accumulator; } - protected Hash unsafeRootHashUpdate( - final BlockHeader blockHeader, - final PathBasedWorldStateKeyValueStorage.Updater stateUpdater) { - // calling calculateRootHash in order to update the state - calculateRootHash(isStorageFrozen ? Optional.empty() : Optional.of(stateUpdater), accumulator); - return blockHeader.getStateRoot(); - } - @Override public MutableWorldState disableTrie() { this.worldStateConfig.setTrieDisabled(true); return this; } - @Override - public Hash calculateOrReadRootHash( - final WorldStateKeyValueStorage.Updater stateUpdater, - final BlockHeader blockHeader, - final WorldStateConfig cfg) { - final PathBasedWorldStateKeyValueStorage.Updater pathBasedUpdater = - (PathBasedWorldStateKeyValueStorage.Updater) stateUpdater; - if (blockHeader == null || !cfg.isTrieDisabled()) { - // TODO - rename calculateRootHash() to be clearer that it updates state, it doesn't just - // calculate a hash - return calculateRootHash( - isStorageFrozen ? Optional.empty() : Optional.of(pathBasedUpdater), accumulator); - } else { - // if the trie is disabled, we cannot calculate the state root, so we directly use the root - // of the block. It's important to understand that in all networks, - // the state root must be validated independently and the block should not be trusted - // implicitly. This mode - // can be used in cases where Besu would just be a follower of another trusted client. - LOG.atDebug() - .setMessage("Unsafe state root verification for block header {}") - .addArgument(blockHeader) - .log(); - return unsafeRootHashUpdate(blockHeader, pathBasedUpdater); - } - } - @Override public void persist(final BlockHeader blockHeader, final StateRootCommitter committer) { - - final Optional maybeBlockHeader = Optional.ofNullable(blockHeader); LOG.atDebug() .setMessage("Persist world state for block {}") - .addArgument(maybeBlockHeader) + .addArgument(() -> Optional.ofNullable(blockHeader)) .log(); boolean success = false; @@ -222,11 +186,9 @@ public void persist(final BlockHeader blockHeader, final StateRootCommitter comm try { final Hash calculatedRootHash = - committer.computeRootAndCommit(this, stateUpdater, blockHeader, worldStateConfig); + committer.computeRoot( + buildStateRootSupplier(stateUpdater, blockHeader), this, stateUpdater, blockHeader); - // if we are persisted with a block header, and the prior state is the parent - // then persist the TrieLog for that transition. - // If specified but not a direct descendant simply store the new block hash. if (blockHeader != null) { verifyWorldStateRoot(calculatedRootHash, blockHeader); saveTrieLog = @@ -235,7 +197,6 @@ public void persist(final BlockHeader blockHeader, final StateRootCommitter comm }; cacheWorldState = () -> cachedWorldStorageManager.addCachedLayer(blockHeader, calculatedRootHash, this); - stateUpdater .getWorldStateTransaction() .put( @@ -274,7 +235,6 @@ public void persist(final BlockHeader blockHeader, final StateRootCommitter comm // optionally save the committed worldstate state in the cache cacheWorldState.run(); } - accumulator.reset(); } else { stateUpdater.rollback(); @@ -283,6 +243,31 @@ public void persist(final BlockHeader blockHeader, final StateRootCommitter comm } } + /** + * Builds a lazy supplier that, when invoked, applies all accumulated state changes to the trie + * and returns the resulting state root hash. This supplier is passed to the {@link + * StateRootCommitter}, which may invoke it (sync / BAL-verification) or skip it entirely + * (BAL-trusted mode). + */ + private java.util.function.Supplier buildStateRootSupplier( + final PathBasedWorldStateKeyValueStorage.Updater stateUpdater, + final BlockHeader blockHeader) { + final Optional updaterForTrie = + isStorageFrozen ? Optional.empty() : Optional.of(stateUpdater); + + return () -> { + if (blockHeader != null && worldStateConfig.isTrieDisabled()) { + LOG.atDebug() + .setMessage("Unsafe state root verification for block header {}") + .addArgument(blockHeader) + .log(); + calculateRootHash(updaterForTrie, accumulator); + return blockHeader.getStateRoot(); + } + return calculateRootHash(updaterForTrie, accumulator); + }; + } + protected void verifyWorldStateRoot(final Hash calculatedStateRoot, final BlockHeader header) { if (!worldStateConfig.isTrieDisabled() && !calculatedStateRoot.equals(header.getStateRoot())) { throw new StateRootMismatchException(header.getStateRoot(), calculatedStateRoot); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/worldview/accumulator/PathBasedWorldStateUpdateAccumulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/worldview/accumulator/PathBasedWorldStateUpdateAccumulator.java index 494a22fc170..6ff962e6f01 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/worldview/accumulator/PathBasedWorldStateUpdateAccumulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/worldview/accumulator/PathBasedWorldStateUpdateAccumulator.java @@ -108,117 +108,108 @@ public void cloneFromUpdater(final PathBasedWorldStateUpdateAccumulator * the modification from the source will be taken. This approach ensures that the source's state * changes are prioritized and overrides any conflicting changes in the current state. * - * @param source The source accumulator + * @param source The source accumulator whose changes take priority */ public void importStateChangesFromSource( final PathBasedWorldStateUpdateAccumulator source) { - source - .getAccountsToUpdate() - .forEach( - (address, pathBasedValue) -> { - ACCOUNT copyPrior = - pathBasedValue.getPrior() != null - ? copyAccount(pathBasedValue.getPrior(), this, false) - : null; - ACCOUNT copyUpdated = - pathBasedValue.getUpdated() != null - ? copyAccount(pathBasedValue.getUpdated(), this, true) - : null; - accountsToUpdate.put( - address, - new PathBasedValue<>(copyPrior, copyUpdated, pathBasedValue.isLastStepCleared())); - }); - source - .getCodeToUpdate() - .forEach( - (address, pathBasedValue) -> { - codeToUpdate.put( - address, - new PathBasedValue<>( - pathBasedValue.getPrior(), - pathBasedValue.getUpdated(), - pathBasedValue.isLastStepCleared())); - }); - source - .getStorageToUpdate() - .forEach( - (address, slots) -> { - StorageConsumingMap> storageConsumingMap = - storageToUpdate.computeIfAbsent( - address, - k -> - new StorageConsumingMap<>( - address, new ConcurrentHashMap<>(), storagePreloader)); - slots.forEach( - (storageSlotKey, uInt256PathBasedValue) -> { - storageConsumingMap.put( - storageSlotKey, - new PathBasedValue<>( - uInt256PathBasedValue.getPrior(), - uInt256PathBasedValue.getUpdated(), - uInt256PathBasedValue.isLastStepCleared())); - }); - }); + importFrom(source, ImportMode.UPSERT); storageToClear.addAll(source.storageToClear); - storageKeyHashLookup.putAll(source.storageKeyHashLookup); - - this.isAccumulatorStateChanged = true; } /** - * Imports unchanged state data from an external source into the current state. This method - * focuses on integrating state data from the specified source that has been read but not - * modified. - * - *

The method ensures that only new, unmodified data from the source is added to the current - * state. If a state data has already been read or modified in the current state, it will not be - * added again to avoid overwriting any existing modifications. + * Imports unchanged (prior-only) state data from an external source. Only data not already + * present in this accumulator is imported — existing entries are never overwritten. Both prior + * and updated values are set to the source's prior value (i.e. read-only snapshot). * - * @param source The source accumulator + * @param source The source accumulator to import prior state from */ public void importPriorStateFromSource( final PathBasedWorldStateUpdateAccumulator source) { + importFrom(source, ImportMode.INSERT); + } + + private enum ImportMode { + /** Insert new entries and update existing ones with values from the source. */ + UPSERT, + /** Insert new entries only, existing entries are left untouched. */ + INSERT + } + + private void importFrom( + final PathBasedWorldStateUpdateAccumulator source, final ImportMode mode) { + final boolean priorOnly = mode == ImportMode.INSERT; source .getAccountsToUpdate() .forEach( - (address, pathBasedValue) -> { - ACCOUNT copyPrior = - pathBasedValue.getPrior() != null - ? copyAccount(pathBasedValue.getPrior(), this, false) - : null; - ACCOUNT copyUpdated = - pathBasedValue.getPrior() != null - ? copyAccount(pathBasedValue.getPrior(), this, true) + (address, srcValue) -> { + final ACCOUNT copyPrior = + srcValue.getPrior() != null + ? copyAccount(srcValue.getPrior(), this, false) : null; - accountsToUpdate.putIfAbsent(address, new PathBasedValue<>(copyPrior, copyUpdated)); + final ACCOUNT copyUpdated = + priorOnly + ? (srcValue.getPrior() != null + ? copyAccount(srcValue.getPrior(), this, true) + : null) + : (srcValue.getUpdated() != null + ? copyAccount(srcValue.getUpdated(), this, true) + : null); + final PathBasedValue newValue = + priorOnly + ? new PathBasedValue<>(copyPrior, copyUpdated) + : new PathBasedValue<>(copyPrior, copyUpdated, srcValue.isLastStepCleared()); + if (priorOnly) { + accountsToUpdate.putIfAbsent(address, newValue); + } else { + accountsToUpdate.put(address, newValue); + } }); + source .getCodeToUpdate() .forEach( - (address, pathBasedValue) -> { - codeToUpdate.putIfAbsent( - address, - new PathBasedValue<>(pathBasedValue.getPrior(), pathBasedValue.getPrior())); + (address, srcValue) -> { + final Bytes prior = srcValue.getPrior(); + final Bytes updated = priorOnly ? prior : srcValue.getUpdated(); + final PathBasedValue newValue = + priorOnly + ? new PathBasedValue<>(prior, updated) + : new PathBasedValue<>(prior, updated, srcValue.isLastStepCleared()); + if (priorOnly) { + codeToUpdate.putIfAbsent(address, newValue); + } else { + codeToUpdate.put(address, newValue); + } }); + source .getStorageToUpdate() .forEach( (address, slots) -> { - StorageConsumingMap> storageConsumingMap = + final StorageConsumingMap> targetSlots = storageToUpdate.computeIfAbsent( address, k -> new StorageConsumingMap<>( address, new ConcurrentHashMap<>(), storagePreloader)); slots.forEach( - (storageSlotKey, uInt256PathBasedValue) -> { - storageConsumingMap.putIfAbsent( - storageSlotKey, - new PathBasedValue<>( - uInt256PathBasedValue.getPrior(), uInt256PathBasedValue.getPrior())); + (slotKey, srcSlot) -> { + final UInt256 slotPrior = srcSlot.getPrior(); + final UInt256 slotUpdated = priorOnly ? slotPrior : srcSlot.getUpdated(); + final PathBasedValue newSlotValue = + priorOnly + ? new PathBasedValue<>(slotPrior, slotUpdated) + : new PathBasedValue<>( + slotPrior, slotUpdated, srcSlot.isLastStepCleared()); + if (priorOnly) { + targetSlots.putIfAbsent(slotKey, newSlotValue); + } else { + targetSlots.put(slotKey, newSlotValue); + } }); }); + storageKeyHashLookup.putAll(source.storageKeyHashLookup); this.isAccumulatorStateChanged = true; } 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 a906f7651d7..4f2697ac7b1 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 @@ -46,7 +46,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.blockhash.FrontierPreExecutionProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitterFactoryDefault; +import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.DefaultStateRootCommitterFactory; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.BonsaiWorldStateProvider; import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.cache.CodeCache; @@ -132,7 +132,7 @@ public void setup() { when(protocolSpec.getFeeMarket()).thenReturn(feeMarket); when(blockAccessListValidator.validate(any(), any(), anyInt())).thenReturn(true); when(protocolSpec.getStateRootCommitterFactory()) - .thenReturn(new StateRootCommitterFactoryDefault()); + .thenReturn(new DefaultStateRootCommitterFactory()); mainnetBlockValidator = MainnetBlockValidatorBuilder.frontier( blockHeaderValidator, blockBodyValidator, blockProcessor, blockAccessListValidator); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/SyncBlockBodyTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/SyncBlockBodyTest.java index b987694a7a0..bc03acbe125 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/SyncBlockBodyTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/SyncBlockBodyTest.java @@ -31,7 +31,7 @@ import org.hyperledger.besu.ethereum.mainnet.PoWHasher; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitterFactoryDefault; +import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.DefaultStateRootCommitterFactory; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.evm.precompile.PrecompileContractRegistry; @@ -224,7 +224,7 @@ private static ProtocolSpec getProtocolSpec() { true, Optional.empty(), Optional.empty(), - new StateRootCommitterFactoryDefault(), + new DefaultStateRootCommitterFactory(), BlockGasAccountingStrategy.FRONTIER, BlockGasUsedValidator.FRONTIER); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java index ae0ac9b7d46..9832cdc1267 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java @@ -41,7 +41,7 @@ import org.hyperledger.besu.ethereum.mainnet.parallelization.MainnetParallelBlockProcessor; import org.hyperledger.besu.ethereum.mainnet.parallelization.ParallelTransactionPreprocessing; import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.BonsaiAccount; -import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.worldview.BlockAccessListStateRootHashCalculator; +import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.worldview.BalStateRootCalculator; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; @@ -1286,8 +1286,7 @@ private void assertBalComputesHeaderRoot(final Block block, final BlockProcessin result.getYield().orElseThrow().getBlockAccessList().orElseThrow(); final Hash computedRoot = - BlockAccessListStateRootHashCalculator.computeAsync( - protocolContext, block.getHeader(), blockAccessList) + BalStateRootCalculator.computeAsync(protocolContext, block.getHeader(), blockAccessList) .join() .root(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java index e58f866b702..443d8eebe22 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java @@ -39,7 +39,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.mainnet.blockhash.FrontierPreExecutionProcessor; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitterFactoryDefault; +import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.DefaultStateRootCommitterFactory; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestBlockchain; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestWorldState; @@ -75,7 +75,7 @@ void baseSetup() { .thenReturn(new FrontierPreExecutionProcessor()); lenient() .when(protocolSpec.getStateRootCommitterFactory()) - .thenReturn(new StateRootCommitterFactoryDefault()); + .thenReturn(new DefaultStateRootCommitterFactory()); blockProcessor = new TestBlockProcessor( transactionProcessor, diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java index 26e5af529bd..1a4c0540f66 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java @@ -30,7 +30,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.mainnet.blockhash.FrontierPreExecutionProcessor; -import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.StateRootCommitterFactoryDefault; +import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.DefaultStateRootCommitterFactory; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestBlockchain; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestWorldState; @@ -55,7 +55,7 @@ public void setup() { when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); when(protocolSpec.getPreExecutionProcessor()).thenReturn(new FrontierPreExecutionProcessor()); when(protocolSpec.getStateRootCommitterFactory()) - .thenReturn(new StateRootCommitterFactoryDefault()); + .thenReturn(new DefaultStateRootCommitterFactory()); } @Test diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterImplBalTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/BalStateRootCommitterFactoryTest.java similarity index 90% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterImplBalTest.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/BalStateRootCommitterFactoryTest.java index 6b38514ad99..385cbe14f8d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/StateRootCommitterImplBalTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/staterootcommitter/BalStateRootCommitterFactoryTest.java @@ -51,7 +51,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class StateRootCommitterImplBalTest { +class BalStateRootCommitterFactoryTest { private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(5); @@ -106,11 +106,10 @@ void trustedMode_balAndSyncCommitterProduceSameRoot() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -160,11 +159,10 @@ void trustedMode_balRootMismatchThrowsException() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -213,11 +211,10 @@ void verificationMode_strictMode_matchingRootsSucceed() throws Exception { ImmutableBalConfiguration.builder() .isBalStateRootTrusted(false) .isBalLenientOnStateRootMismatch(false) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -268,11 +265,10 @@ void verificationMode_strictMode_mismatchThrowsException() throws Exception { ImmutableBalConfiguration.builder() .isBalStateRootTrusted(false) .isBalLenientOnStateRootMismatch(false) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -319,11 +315,10 @@ void verificationMode_lenientMode_mismatchLogsButSucceeds() throws Exception { ImmutableBalConfiguration.builder() .isBalStateRootTrusted(false) .isBalLenientOnStateRootMismatch(true) // Lenient mode - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -368,11 +363,10 @@ void cancel_cancelsBalFutureGracefully() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -391,53 +385,15 @@ void factoryReturnsSync_whenBalNotPresent() { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.empty()); - assertThat(committer).isInstanceOf(StateRootCommitterImplSync.class); - } - - @Test - void factoryReturnsSync_whenBalOptimisationDisabled() { - - final Address address = Address.fromHexString("0x0000000000000000000000000000000000000039"); - final Wei newBalance = Wei.of(1_234_567L); - - final BlockAccessList bal = - new BlockAccessList( - List.of( - new AccountChanges( - address, - List.of(), - List.of(), - List.of(new BalanceChange(0, newBalance)), - List.of(), - List.of()))); - - final BlockHeader blockHeader = - new BlockHeaderTestFixture() - .parentHash(chainHeadHeader.getHash()) - .number(chainHeadHeader.getNumber() + 1L) - .buildHeader(); - - final BalConfiguration balConfig = - ImmutableBalConfiguration.builder() - .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(false) // Disabled - .balStateRootTimeout(DEFAULT_TIMEOUT) - .build(); - - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); - final StateRootCommitter committer = - factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); - - assertThat(committer).isInstanceOf(StateRootCommitterImplSync.class); + assertThat(committer).isSameAs(StateRootCommitter.SYNCHRONOUS); } @Test @@ -495,11 +451,10 @@ void multipleAccountChanges_producesCorrectRoot() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -539,11 +494,10 @@ void emptyBalAccessList_producesCorrectRoot() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -586,11 +540,10 @@ void trustedMode_notMergeBalStateChangesForFrozenSate() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -639,11 +592,10 @@ void trustedMode_mergeBalStateChanges_balanceAndNonce() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -715,11 +667,10 @@ void trustedMode_mergeBalStateChanges_withStorage() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -783,11 +734,10 @@ void trustedMode_mergeBalStateChanges_withCode() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -866,11 +816,10 @@ void trustedMode_mergeBalStateChanges_complexScenario() throws Exception { final BalConfiguration balConfig = ImmutableBalConfiguration.builder() .isBalStateRootTrusted(true) - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); @@ -927,11 +876,10 @@ void verificationMode_doesNotImportBalStateChanges() throws Exception { ImmutableBalConfiguration.builder() .isBalStateRootTrusted(false) // Verification mode .isBalLenientOnStateRootMismatch(true) // Lenient to allow mismatch - .isBalOptimisationEnabled(true) .balStateRootTimeout(DEFAULT_TIMEOUT) .build(); - final StateRootCommitterFactory factory = new StateRootCommitterFactoryBal(balConfig); + final StateRootCommitterFactory factory = new BalStateRootCommitterFactory(balConfig); final StateRootCommitter committer = factory.forBlock(protocolContext, blockHeader, Optional.of(bal)); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BlockAccessListStateRootHashCalculatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BalStateRootCalculatorTest.java similarity index 98% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BlockAccessListStateRootHashCalculatorTest.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BalStateRootCalculatorTest.java index 2dd6ff4a8f1..c8aa25d0042 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BlockAccessListStateRootHashCalculatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BalStateRootCalculatorTest.java @@ -50,7 +50,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class BlockAccessListStateRootHashCalculatorTest { +class BalStateRootCalculatorTest { private static final Duration FUTURE_TIMEOUT = Duration.ofSeconds(3); @@ -304,6 +304,6 @@ private CompletableFuture computeRootFromBalAsync(final Bloc .number(chainHeadHeader.getNumber() + 1L) .buildHeader(); - return BlockAccessListStateRootHashCalculator.computeAsync(protocolContext, blockHeader, bal); + return BalStateRootCalculator.computeAsync(protocolContext, blockHeader, bal); } }