Improve Conflict Detection in Parallelization by Considering Slots to Reduce False Positives (#7923)

Signed-off-by: Karim Taam <karim.t2am@gmail.com>
This commit is contained in:
Karim Taam
2025-02-17 15:43:09 +01:00
committed by GitHub
parent efcefad475
commit 41a064730e
20 changed files with 507 additions and 158 deletions

View File

@@ -21,6 +21,7 @@
- Add TLS/mTLS options and configure the GraphQL HTTP service[#7910](https://github.com/hyperledger/besu/pull/7910)
- Allow plugins to propose transactions during block creation [#8268](https://github.com/hyperledger/besu/pull/8268)
- Update `eth_getLogs` to return a `Block not found` error when the requested block is not found. [#8290](https://github.com/hyperledger/besu/pull/8290)
- Improve Conflict Detection in Parallelization by Considering Slots to Reduce False Positives. [#7923](https://github.com/hyperledger/besu/pull/7923)
### Bug fixes
- Upgrade Netty to version 4.1.118 to fix CVE-2025-24970 [#8275](https://github.com/hyperledger/besu/pull/8275)
- Add missing RPC method `debug_accountRange` to `RpcMethod.java` and implemented its handler. [#8153](https://github.com/hyperledger/besu/issues/8153)

View File

@@ -248,7 +248,7 @@ public class MainnetBlockValidator implements BlockValidator {
protected BlockProcessingResult processBlock(
final ProtocolContext context, final MutableWorldState worldState, final Block block) {
return blockProcessor.processBlock(context.getBlockchain(), worldState, block);
return blockProcessor.processBlock(context, context.getBlockchain(), worldState, block);
}
@Override

View File

@@ -22,6 +22,7 @@ import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingOutputs;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
@@ -95,6 +96,7 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
@Override
public BlockProcessingResult processBlock(
final ProtocolContext protocolContext,
final Blockchain blockchain,
final MutableWorldState worldState,
final BlockHeader blockHeader,
@@ -103,6 +105,7 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
final Optional<List<Withdrawal>> maybeWithdrawals,
final PrivateMetadataUpdater privateMetadataUpdater) {
return processBlock(
protocolContext,
blockchain,
worldState,
blockHeader,
@@ -114,6 +117,7 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
}
protected BlockProcessingResult processBlock(
final ProtocolContext protocolContext,
final Blockchain blockchain,
final MutableWorldState worldState,
final BlockHeader blockHeader,
@@ -151,7 +155,7 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
final Optional<PreprocessingContext> preProcessingContext =
preprocessingBlockFunction.run(
worldState,
protocolContext,
privateMetadataUpdater,
blockHeader,
transactions,
@@ -342,7 +346,7 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
public interface PreprocessingFunction {
Optional<PreprocessingContext> run(
final MutableWorldState worldState,
final ProtocolContext protocolContext,
final PrivateMetadataUpdater privateMetadataUpdater,
final BlockHeader blockHeader,
final List<Transaction> transactions,
@@ -354,7 +358,7 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
@Override
public Optional<PreprocessingContext> run(
final MutableWorldState worldState,
final ProtocolContext protocolContext,
final PrivateMetadataUpdater privateMetadataUpdater,
final BlockHeader blockHeader,
final List<Transaction> transactions,

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.mainnet;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
@@ -68,14 +69,19 @@ public interface BlockProcessor {
/**
* Processes the block.
*
* @param protocolContext the current context of the protocol
* @param blockchain the blockchain to append the block to
* @param worldState the world state to apply changes to
* @param block the block to process
* @return the block processing result
*/
default BlockProcessingResult processBlock(
final Blockchain blockchain, final MutableWorldState worldState, final Block block) {
final ProtocolContext protocolContext,
final Blockchain blockchain,
final MutableWorldState worldState,
final Block block) {
return processBlock(
protocolContext,
blockchain,
worldState,
block.getHeader(),
@@ -88,6 +94,7 @@ public interface BlockProcessor {
/**
* Processes the block.
*
* @param protocolContext the current context of the protocol
* @param blockchain the blockchain to append the block to
* @param worldState the world state to apply changes to
* @param blockHeader the block header for the block
@@ -96,18 +103,27 @@ public interface BlockProcessor {
* @return the block processing result
*/
default BlockProcessingResult processBlock(
final ProtocolContext protocolContext,
final Blockchain blockchain,
final MutableWorldState worldState,
final BlockHeader blockHeader,
final List<Transaction> transactions,
final List<BlockHeader> ommers) {
return processBlock(
blockchain, worldState, blockHeader, transactions, ommers, Optional.empty(), null);
protocolContext,
blockchain,
worldState,
blockHeader,
transactions,
ommers,
Optional.empty(),
null);
}
/**
* Processes the block.
*
* @param protocolContext the current context of the protocol
* @param blockchain the blockchain to append the block to
* @param worldState the world state to apply changes to
* @param blockHeader the block header for the block
@@ -118,6 +134,7 @@ public interface BlockProcessor {
* @return the block processing result
*/
BlockProcessingResult processBlock(
ProtocolContext protocolContext,
Blockchain blockchain,
MutableWorldState worldState,
BlockHeader blockHeader,
@@ -129,6 +146,7 @@ public interface BlockProcessor {
/**
* Processes the block when running Besu in GoQuorum-compatible mode
*
* @param protocolContext the current context of the protocol
* @param blockchain the blockchain to append the block to
* @param worldState the world state to apply public transactions to
* @param privateWorldState the private world state to apply private transaction to
@@ -136,6 +154,7 @@ public interface BlockProcessor {
* @return the block processing result
*/
default BlockProcessingResult processBlock(
final ProtocolContext protocolContext,
final Blockchain blockchain,
final MutableWorldState worldState,
final MutableWorldState privateWorldState,

View File

@@ -26,6 +26,7 @@ import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.MainnetBlockValidator;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
@@ -1101,6 +1102,7 @@ public abstract class MainnetProtocolSpecs {
@Override
public BlockProcessingResult processBlock(
final ProtocolContext protocolContext,
final Blockchain blockchain,
final MutableWorldState worldState,
final BlockHeader blockHeader,
@@ -1110,6 +1112,7 @@ public abstract class MainnetProtocolSpecs {
final PrivateMetadataUpdater privateMetadataUpdater) {
updateWorldStateForDao(worldState);
return wrapped.processBlock(
protocolContext,
blockchain,
worldState,
blockHeader,

View File

@@ -21,6 +21,7 @@ import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.enclave.EnclaveClientException;
import org.hyperledger.besu.enclave.types.ReceiveResponse;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
@@ -83,6 +84,7 @@ public class PrivacyBlockProcessor implements BlockProcessor {
@Override
public BlockProcessingResult processBlock(
final ProtocolContext protocolContext,
final Blockchain blockchain,
final MutableWorldState worldState,
final BlockHeader blockHeader,
@@ -102,6 +104,7 @@ public class PrivacyBlockProcessor implements BlockProcessor {
final BlockProcessingResult result =
blockProcessor.processBlock(
protocolContext,
blockchain,
worldState,
blockHeader,

View File

@@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.mainnet.parallelization;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
@@ -31,7 +32,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState;
import org.hyperledger.besu.ethereum.trie.diffbased.common.provider.DiffBasedWorldStateProvider;
import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.metrics.BesuMetricCategory;
@@ -137,6 +138,7 @@ public class MainnetParallelBlockProcessor extends MainnetBlockProcessor {
@Override
public BlockProcessingResult processBlock(
final ProtocolContext protocolContext,
final Blockchain blockchain,
final MutableWorldState worldState,
final BlockHeader blockHeader,
@@ -146,6 +148,7 @@ public class MainnetParallelBlockProcessor extends MainnetBlockProcessor {
final PrivateMetadataUpdater privateMetadataUpdater) {
final BlockProcessingResult blockProcessingResult =
super.processBlock(
protocolContext,
blockchain,
worldState,
blockHeader,
@@ -154,6 +157,7 @@ public class MainnetParallelBlockProcessor extends MainnetBlockProcessor {
maybeWithdrawals,
privateMetadataUpdater,
new ParallelTransactionPreprocessing());
if (blockProcessingResult.isFailed()) {
// Fallback to non-parallel processing if there is a block processing exception .
LOG.info(
@@ -161,6 +165,7 @@ public class MainnetParallelBlockProcessor extends MainnetBlockProcessor {
blockHeader.getNumber(),
blockHeader.getBlockHash());
return super.processBlock(
protocolContext,
blockchain,
worldState,
blockHeader,
@@ -209,20 +214,20 @@ public class MainnetParallelBlockProcessor extends MainnetBlockProcessor {
@Override
public Optional<PreprocessingContext> run(
final MutableWorldState worldState,
final ProtocolContext protocolContext,
final PrivateMetadataUpdater privateMetadataUpdater,
final BlockHeader blockHeader,
final List<Transaction> transactions,
final Address miningBeneficiary,
final BlockHashLookup blockHashLookup,
final Wei blobGasPrice) {
if ((worldState instanceof DiffBasedWorldState)) {
if ((protocolContext.getWorldStateArchive() instanceof DiffBasedWorldStateProvider)) {
ParallelizedConcurrentTransactionProcessor parallelizedConcurrentTransactionProcessor =
new ParallelizedConcurrentTransactionProcessor(transactionProcessor);
// When enabled, runAsyncBlock performs non-conflicting parallel execution of transactions
// in the background using an optimistic approach.
// runAsyncBlock, if activated, facilitates the non-blocking parallel execution of
// transactions in the background through an optimistic strategy.
parallelizedConcurrentTransactionProcessor.runAsyncBlock(
worldState,
protocolContext,
blockHeader,
transactions,
miningBeneficiary,

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.mainnet.parallelization;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.Transaction;
@@ -23,8 +24,8 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoopBonsaiCachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.trie.diffbased.common.provider.WorldStateQueryParams;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator;
import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
@@ -61,6 +62,8 @@ public class ParallelizedConcurrentTransactionProcessor {
private final Map<Integer, ParallelizedTransactionContext>
parallelizedTransactionContextByLocation = new ConcurrentHashMap<>();
private CompletableFuture<Void>[] completableFuturesForBackgroundTransactions;
/**
* Constructs a PreloadConcurrentTransactionProcessor with a specified transaction processor. This
* processor is responsible for the individual processing of transactions.
@@ -88,8 +91,7 @@ public class ParallelizedConcurrentTransactionProcessor {
* state, ensuring that the original world state passed as a parameter remains unmodified during
* this process.
*
* @param worldState Mutable world state intended for applying transaction results. This world
* state is not modified directly; instead, copies are made for transaction execution.
* @param protocolContext the current context of the protocol
* @param blockHeader Header of the current block containing the transactions.
* @param transactions List of transactions to be processed.
* @param miningBeneficiary Address of the beneficiary to receive mining rewards.
@@ -98,13 +100,15 @@ public class ParallelizedConcurrentTransactionProcessor {
* @param privateMetadataUpdater Updater for private transaction metadata.
*/
public void runAsyncBlock(
final MutableWorldState worldState,
final ProtocolContext protocolContext,
final BlockHeader blockHeader,
final List<Transaction> transactions,
final Address miningBeneficiary,
final BlockHashLookup blockHashLookup,
final Wei blobGasPrice,
final PrivateMetadataUpdater privateMetadataUpdater) {
completableFuturesForBackgroundTransactions = new CompletableFuture[transactions.size()];
for (int i = 0; i < transactions.size(); i++) {
final Transaction transaction = transactions.get(i);
final int transactionLocation = i;
@@ -114,7 +118,7 @@ public class ParallelizedConcurrentTransactionProcessor {
CompletableFuture.runAsync(
() ->
runTransaction(
worldState,
protocolContext,
blockHeader,
transactionLocation,
transaction,
@@ -128,7 +132,7 @@ public class ParallelizedConcurrentTransactionProcessor {
@VisibleForTesting
public void runTransaction(
final MutableWorldState worldState,
final ProtocolContext protocolContext,
final BlockHeader blockHeader,
final int transactionLocation,
final Transaction transaction,
@@ -136,65 +140,77 @@ public class ParallelizedConcurrentTransactionProcessor {
final BlockHashLookup blockHashLookup,
final Wei blobGasPrice,
final PrivateMetadataUpdater privateMetadataUpdater) {
try (final DiffBasedWorldState roundWorldState =
new BonsaiWorldState(
(BonsaiWorldState) worldState, new NoopBonsaiCachedMerkleTrieLoader())) {
roundWorldState.freezeStorage(); // make the clone frozen
final ParallelizedTransactionContext.Builder contextBuilder =
new ParallelizedTransactionContext.Builder();
final DiffBasedWorldStateUpdateAccumulator<?> roundWorldStateUpdater =
(DiffBasedWorldStateUpdateAccumulator<?>) roundWorldState.updater();
final TransactionProcessingResult result =
transactionProcessor.processTransaction(
roundWorldStateUpdater,
blockHeader,
transaction,
miningBeneficiary,
new OperationTracer() {
@Override
public void traceBeforeRewardTransaction(
final WorldView worldView,
final org.hyperledger.besu.datatypes.Transaction tx,
final Wei miningReward) {
/*
* This part checks if the mining beneficiary's account was accessed before increasing its balance for rewards.
* Indeed, if the transaction has interacted with the address to read or modify it,
* it means that the value is necessary for the proper execution of the transaction and will therefore be considered in collision detection.
* If this is not the case, we can ignore this address during conflict detection.
*/
if (transactionCollisionDetector
.getAddressesTouchedByTransaction(
transaction, Optional.of(roundWorldStateUpdater))
.contains(miningBeneficiary)) {
contextBuilder.isMiningBeneficiaryTouchedPreRewardByTransaction(true);
}
contextBuilder.miningBeneficiaryReward(miningReward);
}
},
blockHashLookup,
true,
TransactionValidationParams.processingBlock(),
privateMetadataUpdater,
blobGasPrice);
final BlockHeader chainHeadHeader = protocolContext.getBlockchain().getChainHeadHeader();
if (chainHeadHeader.getHash().equals(blockHeader.getParentHash())) {
try (BonsaiWorldState ws =
(BonsaiWorldState)
protocolContext
.getWorldStateArchive()
.getWorldState(
WorldStateQueryParams.withBlockHeaderAndNoUpdateNodeHead(chainHeadHeader))
.orElse(null)) {
if (ws != null) {
ws.disableCacheMerkleTrieLoader();
final ParallelizedTransactionContext.Builder contextBuilder =
new ParallelizedTransactionContext.Builder();
final DiffBasedWorldStateUpdateAccumulator<?> roundWorldStateUpdater =
(DiffBasedWorldStateUpdateAccumulator<?>) ws.updater();
final TransactionProcessingResult result =
transactionProcessor.processTransaction(
roundWorldStateUpdater,
blockHeader,
transaction.detachedCopy(),
miningBeneficiary,
new OperationTracer() {
@Override
public void traceBeforeRewardTransaction(
final WorldView worldView,
final org.hyperledger.besu.datatypes.Transaction tx,
final Wei miningReward) {
/*
* This part checks if the mining beneficiary's account was accessed before increasing its balance for rewards.
* Indeed, if the transaction has interacted with the address to read or modify it,
* it means that the value is necessary for the proper execution of the transaction and will therefore be considered in collision detection.
* If this is not the case, we can ignore this address during conflict detection.
*/
if (transactionCollisionDetector
.getAddressesTouchedByTransaction(
transaction, Optional.of(roundWorldStateUpdater))
.contains(miningBeneficiary)) {
contextBuilder.isMiningBeneficiaryTouchedPreRewardByTransaction(true);
}
contextBuilder.miningBeneficiaryReward(miningReward);
}
},
blockHashLookup,
true,
TransactionValidationParams.processingBlock(),
privateMetadataUpdater,
blobGasPrice);
// commit the accumulator in order to apply all the modifications
roundWorldState.getAccumulator().commit();
// commit the accumulator in order to apply all the modifications
ws.getAccumulator().commit();
contextBuilder
.transactionAccumulator(roundWorldState.getAccumulator())
.transactionProcessingResult(result);
contextBuilder
.transactionAccumulator(ws.getAccumulator())
.transactionProcessingResult(result);
final ParallelizedTransactionContext parallelizedTransactionContext = contextBuilder.build();
if (!parallelizedTransactionContext.isMiningBeneficiaryTouchedPreRewardByTransaction()) {
/*
* If the address of the mining beneficiary has been touched only for adding rewards,
* we remove it from the accumulator to avoid a false positive collision.
* The balance will be increased during the sequential processing.
*/
roundWorldStateUpdater.getAccountsToUpdate().remove(miningBeneficiary);
final ParallelizedTransactionContext parallelizedTransactionContext =
contextBuilder.build();
if (!parallelizedTransactionContext.isMiningBeneficiaryTouchedPreRewardByTransaction()) {
/*
* If the address of the mining beneficiary has been touched only for adding rewards,
* we remove it from the accumulator to avoid a false positive collision.
* The balance will be increased during the sequential processing.
*/
roundWorldStateUpdater.getAccountsToUpdate().remove(miningBeneficiary);
}
parallelizedTransactionContextByLocation.put(
transactionLocation, parallelizedTransactionContext);
}
} catch (Exception ex) {
// no op as failing to get worldstate
}
parallelizedTransactionContextByLocation.put(
transactionLocation, parallelizedTransactionContext);
}
}
@@ -254,6 +270,7 @@ public class ParallelizedConcurrentTransactionProcessor {
if (confirmedParallelizedTransactionCounter.isPresent()) {
confirmedParallelizedTransactionCounter.get().inc();
transactionProcessingResult.setIsProcessedInParallel(Optional.of(Boolean.TRUE));
transactionProcessingResult.accumulator = transactionAccumulator;
}
return Optional.of(transactionProcessingResult);
} else {
@@ -264,6 +281,13 @@ public class ParallelizedConcurrentTransactionProcessor {
// re-execute the transaction.
return Optional.empty();
}
} else {
// stop background processing for this transaction as useless
final CompletableFuture<Void> completableFuturesForBackgroundTransaction =
completableFuturesForBackgroundTransactions[transactionLocation];
if (completableFuturesForBackgroundTransaction != null) {
completableFuturesForBackgroundTransaction.cancel(true);
}
}
return Optional.empty();
}

View File

@@ -15,49 +15,75 @@
package org.hyperledger.besu.ethereum.mainnet.parallelization;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.StorageSlotKey;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedAccount;
import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload.StorageConsumingMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import org.apache.tuweni.units.bigints.UInt256;
public class TransactionCollisionDetector {
/**
* Determines if a transaction has a collision based on the addresses it touches. A collision
* occurs if the transaction touches the mining beneficiary address or if there are common
* addresses touched by both the transaction and other transactions within the same block.
* Checks if there is a conflict between the current block's state and the given transaction.
*
* @param transaction The transaction to check for collisions.
* @param miningBeneficiary The address of the mining beneficiary.
* @param parallelizedTransactionContext The context containing the accumulator for the
* <p>This method detects conflicts between the transaction and the block's state by checking if
* the transaction modifies the same addresses and storage slots that are already modified by the
* block. A conflict occurs in two cases: 1. If the transaction touches an address that is also
* modified in the block, and the account details (excluding storage) are identical. In this case,
* it checks if there is an overlap in the storage slots affected by both the transaction and the
* block. 2. If the account details differ between the transaction and the block (excluding
* storage), it immediately detects a conflict.
*
* <p>The method returns `true` if any such conflict is found, otherwise `false`.
*
* @param transaction The transaction to check for conflicts with the block's state.
* @param parallelizedTransactionContext The context for the parallelized execution of the
* transaction.
* @param blockAccumulator The accumulator for the block.
* @return true if there is a collision; false otherwise.
* @param blockAccumulator The accumulator containing the state updates of the current block.
* @return true if there is a conflict between the transaction and the block's state, otherwise
* false.
*/
public boolean hasCollision(
final Transaction transaction,
final Address miningBeneficiary,
final ParallelizedTransactionContext parallelizedTransactionContext,
final DiffBasedWorldStateUpdateAccumulator<?> blockAccumulator) {
final DiffBasedWorldStateUpdateAccumulator<? extends DiffBasedAccount> blockAccumulator) {
final Set<Address> addressesTouchedByTransaction =
getAddressesTouchedByTransaction(
transaction, Optional.of(parallelizedTransactionContext.transactionAccumulator()));
if (addressesTouchedByTransaction.contains(miningBeneficiary)) {
return true;
}
final Set<Address> addressesTouchedByBlock =
getAddressesTouchedByBlock(Optional.of(blockAccumulator));
final Iterator<Address> it = addressesTouchedByTransaction.iterator();
boolean commonAddressFound = false;
while (it.hasNext() && !commonAddressFound) {
if (addressesTouchedByBlock.contains(it.next())) {
commonAddressFound = true;
for (final Address next : addressesTouchedByTransaction) {
final Optional<AccountUpdateContext> maybeAddressTouchedByBlock =
getAddressTouchedByBlock(next, Optional.of(blockAccumulator));
if (maybeAddressTouchedByBlock.isPresent()) {
if (maybeAddressTouchedByBlock.get().areAccountDetailsEqualExcludingStorage()) {
final Set<StorageSlotKey> slotsTouchedByBlockAndByAddress =
getSlotsTouchedByBlockAndByAddress(Optional.of(blockAccumulator), next);
final Set<StorageSlotKey> slotsTouchedByTransactionAndByAddress =
getSlotsTouchedByTransactionAndByAddress(
Optional.of(parallelizedTransactionContext.transactionAccumulator()), next);
for (final StorageSlotKey touchedByTransactionAndByAddress :
slotsTouchedByTransactionAndByAddress) {
if (slotsTouchedByBlockAndByAddress.contains(touchedByTransactionAndByAddress)) {
return true;
}
}
} else {
return true;
}
}
}
return commonAddressFound;
return false;
}
/**
@@ -81,34 +107,179 @@ public class TransactionCollisionDetector {
diffBasedWorldStateUpdateAccumulator -> {
diffBasedWorldStateUpdateAccumulator
.getAccountsToUpdate()
.forEach((address, diffBasedValue) -> addresses.add(address));
.forEach(
(address, diffBasedValue) -> {
addresses.add(address);
});
addresses.addAll(diffBasedWorldStateUpdateAccumulator.getDeletedAccountAddresses());
});
return addresses;
}
/**
* Retrieves the set of addresses that were touched by all transactions within a block. This
* method filters out addresses that were only read and not modified.
* Retrieves the set of storage slot keys that have been touched by the given transaction for the
* specified address, based on the provided world state update accumulator.
*
* @param accumulator An optional accumulator containing state changes made by the block.
* @return A set of addresses that were modified by the block's transactions.
* <p>This method checks if the accumulator contains storage updates for the specified address. If
* such updates are found, it adds the touched storage slot keys to the returned set. The method
* does not distinguish between changes or unchanged slots; it simply collects all the storage
* slot keys that have been touched by the transaction for the given address.
*
* @param accumulator An {@link Optional} containing the world state update accumulator, which
* holds the updates for storage slots.
* @param address The address for which the touched storage slots are being retrieved.
* @return A set of storage slot keys that have been touched by the transaction for the given
* address. If no updates are found, or the address has no associated updates, an empty set is
* returned.
*/
private Set<Address> getAddressesTouchedByBlock(
final Optional<DiffBasedWorldStateUpdateAccumulator<?>> accumulator) {
HashSet<Address> addresses = new HashSet<>();
private Set<StorageSlotKey> getSlotsTouchedByTransactionAndByAddress(
final Optional<DiffBasedWorldStateUpdateAccumulator<?>> accumulator, final Address address) {
HashSet<StorageSlotKey> slots = new HashSet<>();
accumulator.ifPresent(
diffBasedWorldStateUpdateAccumulator -> {
diffBasedWorldStateUpdateAccumulator
.getAccountsToUpdate()
.forEach(
(address, diffBasedValue) -> {
if (!diffBasedValue.isUnchanged()) {
addresses.add(address);
}
});
addresses.addAll(diffBasedWorldStateUpdateAccumulator.getDeletedAccountAddresses());
final StorageConsumingMap<StorageSlotKey, DiffBasedValue<UInt256>> map =
diffBasedWorldStateUpdateAccumulator.getStorageToUpdate().get(address);
if (map != null) {
map.forEach(
(storageSlotKey, slot) -> {
slots.add(storageSlotKey);
});
}
});
return addresses;
return slots;
}
/**
* Retrieves the update context for the given address from the block's world state update
* accumulator.
*
* <p>This method checks if the provided accumulator contains updates for the given address. If an
* update is found, it compares the prior and updated states of the account to determine if the
* key account details (excluding storage) are considered equal. It then returns an {@link
* AccountUpdateContext} containing the address and the result of that comparison.
*
* <p>If no update is found for the address or the accumulator is absent, the method returns an
* empty {@link Optional}.
*
* @param addressToFind The address for which the update context is being queried.
* @param maybeBlockAccumulator An {@link Optional} containing the block's world state update
* accumulator, which holds the updates for the accounts in the block.
* @return An {@link Optional} containing the {@link AccountUpdateContext} if the address is found
* in the block's updates, otherwise an empty {@link Optional}.
*/
private Optional<AccountUpdateContext> getAddressTouchedByBlock(
final Address addressToFind,
final Optional<DiffBasedWorldStateUpdateAccumulator<? extends DiffBasedAccount>>
maybeBlockAccumulator) {
if (maybeBlockAccumulator.isPresent()) {
final DiffBasedWorldStateUpdateAccumulator<? extends DiffBasedAccount> blockAccumulator =
maybeBlockAccumulator.get();
final DiffBasedValue<? extends DiffBasedAccount> diffBasedValue =
blockAccumulator.getAccountsToUpdate().get(addressToFind);
if (diffBasedValue != null) {
return Optional.of(
new AccountUpdateContext(
addressToFind,
areAccountDetailsEqualExcludingStorage(
diffBasedValue.getPrior(), diffBasedValue.getUpdated())));
}
}
return Optional.empty();
}
/**
* Retrieves the set of storage slot keys that have been updated in the block accumulator for the
* specified address.
*
* <p>This method checks if the accumulator contains a storage map for the provided address. If
* the address has associated storage updates, it iterates over the storage slots and add it to
* the list only if the corresponding storage value has been modified (i.e., is not unchanged).
*
* @param accumulator An Optional containing the world state block update accumulator, which holds
* the storage updates.
* @param address The address for which the storage slots are being queried.
* @return A set of storage slot keys that have been updated for the given address. If no updates
* are found, or the address has no associated updates, an empty set is returned.
*/
private Set<StorageSlotKey> getSlotsTouchedByBlockAndByAddress(
final Optional<DiffBasedWorldStateUpdateAccumulator<?>> accumulator, final Address address) {
HashSet<StorageSlotKey> slots = new HashSet<>();
accumulator.ifPresent(
diffBasedWorldStateUpdateAccumulator -> {
final StorageConsumingMap<StorageSlotKey, DiffBasedValue<UInt256>> map =
diffBasedWorldStateUpdateAccumulator.getStorageToUpdate().get(address);
if (map != null) {
map.forEach(
(storageSlotKey, slot) -> {
if (!slot.isUnchanged()) {
slots.add(storageSlotKey);
}
});
}
});
return slots;
}
/**
* Compares the state of two accounts to check if their key properties are identical, excluding
* any differences in their storage.
*
* <p>This method compares the following account properties: - Nonce - Balance - Code Hash
*
* <p>It returns true if these properties are equal for both accounts, and false otherwise. Note
* that this comparison does not include the account's storage.
*
* @param prior The first account to compare (could be null).
* @param next The second account to compare (could be null).
* @return true if the account state properties are equal excluding storage, false otherwise.
*/
private boolean areAccountDetailsEqualExcludingStorage(
final DiffBasedAccount prior, final DiffBasedAccount next) {
return (prior == null && next == null)
|| (prior != null
&& next != null
&& prior.getNonce() == next.getNonce()
&& prior.getBalance().equals(next.getBalance())
&& prior.getCodeHash().equals(next.getCodeHash()));
}
/**
* Represents the context of an account update, including the account's address and whether the
* key details of the account (excluding storage) are considered equal.
*
* <p>This record holds two main pieces of information: - `address`: The address of the account
* being updated. - `areAccountDetailsEqualExcludingStorage`: A boolean value indicating whether
* the account details, excluding the storage (nonce, balance, and code hash), are considered
* equal when compared to a previous state.
*
* <p>This record is used to track changes to account states and determine if key properties are
* unchanged, which helps in detecting whether further action is needed for the account update.
*/
private record AccountUpdateContext(
Address address, boolean areAccountDetailsEqualExcludingStorage) {
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AccountUpdateContext that = (AccountUpdateContext) o;
return address.equals(that.address);
}
@Override
public int hashCode() {
return Objects.hashCode(address);
}
@Override
public String toString() {
return "AccountUpdateContext{"
+ "address="
+ address
+ ", areAccountDetailsEqualExcludingStorage="
+ areAccountDetailsEqualExcludingStorage
+ '}';
}
}
}

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.processing;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator;
import org.hyperledger.besu.evm.log.Log;
import java.util.List;
@@ -54,6 +55,8 @@ public class TransactionProcessingResult
private final ValidationResult<TransactionInvalidReason> validationResult;
private final Optional<Bytes> revertReason;
public DiffBasedWorldStateUpdateAccumulator<?> accumulator;
public static TransactionProcessingResult invalid(
final ValidationResult<TransactionInvalidReason> validationResult) {
return new TransactionProcessingResult(

View File

@@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.trie.NodeLoader;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoopBonsaiCachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateLayerStorage;
import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue;
@@ -60,7 +61,7 @@ import org.apache.tuweni.units.bigints.UInt256;
public class BonsaiWorldState extends DiffBasedWorldState {
protected final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader;
protected BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader;
public BonsaiWorldState(
final BonsaiWorldStateProvider archive,
@@ -76,18 +77,6 @@ public class BonsaiWorldState extends DiffBasedWorldState {
worldStateConfig);
}
public BonsaiWorldState(
final BonsaiWorldState worldState,
final BonsaiCachedMerkleTrieLoader cachedMerkleTrieLoader) {
this(
new BonsaiWorldStateLayerStorage(worldState.getWorldStateStorage()),
cachedMerkleTrieLoader,
worldState.cachedWorldStorageManager,
worldState.trieLogManager,
worldState.accumulator.getEvmConfiguration(),
WorldStateConfig.newBuilder(worldState.worldStateConfig).build());
}
public BonsaiWorldState(
final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage,
final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader,
@@ -450,6 +439,10 @@ public class BonsaiWorldState extends DiffBasedWorldState {
return this;
}
public void disableCacheMerkleTrieLoader() {
this.bonsaiCachedMerkleTrieLoader = new NoopBonsaiCachedMerkleTrieLoader();
}
private MerkleTrie<Bytes, Bytes> createTrie(final NodeLoader nodeLoader, final Bytes32 rootHash) {
if (worldStateConfig.isTrieDisabled()) {
return new NoOpMerkleTrie<>();

View File

@@ -124,7 +124,9 @@ public abstract class DiffBasedWorldStateUpdateAccumulator<ACCOUNT extends DiffB
diffBasedValue.getUpdated() != null
? copyAccount(diffBasedValue.getUpdated(), this, true)
: null;
accountsToUpdate.put(address, new DiffBasedValue<>(copyPrior, copyUpdated));
accountsToUpdate.put(
address,
new DiffBasedValue<>(copyPrior, copyUpdated, diffBasedValue.isLastStepCleared()));
});
source
.getCodeToUpdate()
@@ -132,7 +134,10 @@ public abstract class DiffBasedWorldStateUpdateAccumulator<ACCOUNT extends DiffB
(address, diffBasedValue) -> {
codeToUpdate.put(
address,
new DiffBasedValue<>(diffBasedValue.getPrior(), diffBasedValue.getUpdated()));
new DiffBasedValue<>(
diffBasedValue.getPrior(),
diffBasedValue.getUpdated(),
diffBasedValue.isLastStepCleared()));
});
source
.getStorageToUpdate()
@@ -149,10 +154,13 @@ public abstract class DiffBasedWorldStateUpdateAccumulator<ACCOUNT extends DiffB
storageConsumingMap.put(
storageSlotKey,
new DiffBasedValue<>(
uInt256DiffBasedValue.getPrior(), uInt256DiffBasedValue.getUpdated()));
uInt256DiffBasedValue.getPrior(),
uInt256DiffBasedValue.getUpdated(),
uInt256DiffBasedValue.isLastStepCleared()));
});
});
storageToClear.addAll(source.storageToClear);
storageKeyHashLookup.putAll(source.storageKeyHashLookup);
this.isAccumulatorStateChanged = true;
}
@@ -211,6 +219,7 @@ public abstract class DiffBasedWorldStateUpdateAccumulator<ACCOUNT extends DiffB
uInt256DiffBasedValue.getPrior(), uInt256DiffBasedValue.getPrior()));
});
});
storageKeyHashLookup.putAll(source.storageKeyHashLookup);
this.isAccumulatorStateChanged = true;
}

View File

@@ -101,12 +101,13 @@ public class MainnetBlockValidatorTest {
when(blockBodyValidator.validateBody(any(), any(), any(), any(), any(), any()))
.thenReturn(true);
when(blockBodyValidator.validateBodyLight(any(), any(), any(), any())).thenReturn(true);
when(blockProcessor.processBlock(any(), any(), any())).thenReturn(successfulProcessingResult);
when(blockProcessor.processBlock(eq(protocolContext), any(), any(), any()))
.thenReturn(successfulProcessingResult);
when(blockProcessor.processBlock(any(), any(), any(), any()))
.thenReturn(successfulProcessingResult);
when(blockProcessor.processBlock(any(), any(), any(), any(), any()))
.thenReturn(successfulProcessingResult);
when(blockProcessor.processBlock(any(), any(), any(), any(), any(), any(), any()))
when(blockProcessor.processBlock(any(), any(), any(), any(), any(), any(), any(), any()))
.thenReturn(successfulProcessingResult);
assertNoBadBlocks();
@@ -211,7 +212,8 @@ public class MainnetBlockValidatorTest {
@Test
public void validateAndProcessBlock_whenProcessBlockFails() {
when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block)))
when(blockProcessor.processBlock(
eq(protocolContext), eq(blockchain), any(MutableWorldState.class), eq(block)))
.thenReturn(BlockProcessingResult.FAILED);
BlockProcessingResult result =
@@ -249,7 +251,7 @@ public class MainnetBlockValidatorTest {
final String caseName, final Exception storageException) {
doThrow(storageException)
.when(blockProcessor)
.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block));
.processBlock(eq(protocolContext), eq(blockchain), any(MutableWorldState.class), eq(block));
BlockProcessingResult result =
mainnetBlockValidator.validateAndProcessBlock(
@@ -288,7 +290,8 @@ public class MainnetBlockValidatorTest {
final String caseName, final Exception cause) {
final BlockProcessingResult exceptionalResult =
new BlockProcessingResult(Optional.empty(), cause);
when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block)))
when(blockProcessor.processBlock(
eq(protocolContext), eq(blockchain), any(MutableWorldState.class), eq(block)))
.thenReturn(exceptionalResult);
BlockProcessingResult result =
@@ -304,7 +307,8 @@ public class MainnetBlockValidatorTest {
@Test
public void validateAndProcessBlock_withShouldRecordBadBlockFalse() {
when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block)))
when(blockProcessor.processBlock(
eq(protocolContext), eq(blockchain), any(MutableWorldState.class), eq(block)))
.thenReturn(BlockProcessingResult.FAILED);
BlockProcessingResult result =
@@ -322,7 +326,8 @@ public class MainnetBlockValidatorTest {
@Test
public void validateAndProcessBlock_withShouldRecordBadBlockTrue() {
when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block)))
when(blockProcessor.processBlock(
eq(protocolContext), eq(blockchain), any(MutableWorldState.class), eq(block)))
.thenReturn(BlockProcessingResult.FAILED);
BlockProcessingResult result =
@@ -340,7 +345,8 @@ public class MainnetBlockValidatorTest {
@Test
public void validateAndProcessBlock_withShouldRecordBadBlockNotSet() {
when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block)))
when(blockProcessor.processBlock(
eq(protocolContext), eq(blockchain), any(MutableWorldState.class), eq(block)))
.thenReturn(BlockProcessingResult.FAILED);
BlockProcessingResult result =

View File

@@ -27,6 +27,7 @@ import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.DefaultBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
@@ -79,6 +80,7 @@ class AbstractBlockProcessorIntegrationTest {
private static final KeyPair ACCOUNT_GENESIS_2_KEYPAIR =
generateKeyPair("fc5141e75bf622179f8eedada7fab3e2e6b3e3da8eb9df4f46d84df22df7430e");
private ProtocolContext protocolContext;
private WorldStateArchive worldStateArchive;
private DefaultBlockchain blockchain;
private Address coinbase;
@@ -94,6 +96,7 @@ class AbstractBlockProcessorIntegrationTest {
final BlockHeader blockHeader = new BlockHeaderTestFixture().number(0L).buildHeader();
coinbase = blockHeader.getCoinbase();
worldStateArchive = contextTestFixture.getStateArchive();
protocolContext = contextTestFixture.getProtocolContext();
blockchain = (DefaultBlockchain) contextTestFixture.getBlockchain();
}
@@ -227,7 +230,7 @@ class AbstractBlockProcessorIntegrationTest {
transactionTransfer2);
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
BonsaiAccount updatedSenderAccount1 =
(BonsaiAccount) worldState.get(transactionTransfer1.getSender());
@@ -269,7 +272,7 @@ class AbstractBlockProcessorIntegrationTest {
transferTransaction3);
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
BonsaiAccount updatedSenderAccount =
(BonsaiAccount) worldState.get(transferTransaction1.getSender());
@@ -322,7 +325,7 @@ class AbstractBlockProcessorIntegrationTest {
transferTransaction2);
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
BonsaiAccount updatedSenderAccount1 =
(BonsaiAccount) worldState.get(transferTransaction1.getSender());
@@ -380,7 +383,7 @@ class AbstractBlockProcessorIntegrationTest {
transferTransaction2);
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
BonsaiAccount updatedSenderAccount1 =
(BonsaiAccount) worldState.get(transferTransaction1.getSender());
@@ -429,7 +432,7 @@ class AbstractBlockProcessorIntegrationTest {
MutableWorldState worldState = worldStateArchive.getWorldState();
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
assertTrue(blockProcessingResult.isSuccessful());
@@ -465,7 +468,7 @@ class AbstractBlockProcessorIntegrationTest {
MutableWorldState worldState = worldStateArchive.getWorldState();
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
assertTrue(blockProcessingResult.isSuccessful());
@@ -508,7 +511,7 @@ class AbstractBlockProcessorIntegrationTest {
MutableWorldState worldState = worldStateArchive.getWorldState();
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
assertTrue(blockProcessingResult.isSuccessful());
@@ -554,7 +557,7 @@ class AbstractBlockProcessorIntegrationTest {
MutableWorldState worldState = worldStateArchive.getWorldState();
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
assertTrue(blockProcessingResult.isSuccessful());
// Verify the state
@@ -599,7 +602,7 @@ class AbstractBlockProcessorIntegrationTest {
MutableWorldState worldState = worldStateArchive.getWorldState();
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
assertTrue(blockProcessingResult.isSuccessful());
@@ -645,7 +648,7 @@ class AbstractBlockProcessorIntegrationTest {
MutableWorldState worldState = worldStateArchive.getWorldState();
BlockProcessingResult blockProcessingResult =
blockProcessor.processBlock(blockchain, worldState, blockWithTransactions);
blockProcessor.processBlock(protocolContext, blockchain, worldState, blockWithTransactions);
assertTrue(blockProcessingResult.isSuccessful());

View File

@@ -27,6 +27,7 @@ import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.GWei;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
@@ -49,6 +50,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
abstract class AbstractBlockProcessorTest {
@Mock private ProtocolContext protocolContext;
@Mock private MainnetTransactionProcessor transactionProcessor;
@Mock private AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory;
@Mock private ProtocolSchedule protocolSchedule;
@@ -84,7 +86,14 @@ abstract class AbstractBlockProcessorTest {
void withProcessorAndEmptyWithdrawals_WithdrawalsAreNotProcessed() {
when(protocolSpec.getWithdrawalsProcessor()).thenReturn(Optional.empty());
blockProcessor.processBlock(
blockchain, worldState, emptyBlockHeader, emptyList(), emptyList(), Optional.empty(), null);
protocolContext,
blockchain,
worldState,
emptyBlockHeader,
emptyList(),
emptyList(),
Optional.empty(),
null);
verify(withdrawalsProcessor, never()).processWithdrawals(any(), any());
}
@@ -92,7 +101,14 @@ abstract class AbstractBlockProcessorTest {
void withNoProcessorAndEmptyWithdrawals_WithdrawalsAreNotProcessed() {
when(protocolSpec.getWithdrawalsProcessor()).thenReturn(Optional.empty());
blockProcessor.processBlock(
blockchain, worldState, emptyBlockHeader, emptyList(), emptyList(), Optional.empty(), null);
protocolContext,
blockchain,
worldState,
emptyBlockHeader,
emptyList(),
emptyList(),
Optional.empty(),
null);
verify(withdrawalsProcessor, never()).processWithdrawals(any(), any());
}
@@ -102,6 +118,7 @@ abstract class AbstractBlockProcessorTest {
final List<Withdrawal> withdrawals =
List.of(new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE));
blockProcessor.processBlock(
protocolContext,
blockchain,
worldState,
emptyBlockHeader,
@@ -119,6 +136,7 @@ abstract class AbstractBlockProcessorTest {
final List<Withdrawal> withdrawals =
List.of(new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE));
blockProcessor.processBlock(
protocolContext,
blockchain,
worldState,
emptyBlockHeader,

View File

@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
@@ -45,6 +46,7 @@ public class MainnetBlockProcessorTest extends AbstractBlockProcessorTest {
mock(AbstractBlockProcessor.TransactionReceiptFactory.class);
private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class);
private final ProtocolSpec protocolSpec = mock(ProtocolSpec.class);
private final ProtocolContext protocolContext = mock(ProtocolContext.class);
@BeforeEach
public void setup() {
@@ -72,7 +74,8 @@ public class MainnetBlockProcessorTest extends AbstractBlockProcessorTest {
.transactionsRoot(Hash.EMPTY_LIST_HASH)
.ommersHash(Hash.EMPTY_LIST_HASH)
.buildHeader();
blockProcessor.processBlock(blockchain, worldState, emptyBlockHeader, emptyList(), emptyList());
blockProcessor.processBlock(
protocolContext, blockchain, worldState, emptyBlockHeader, emptyList(), emptyList());
// An empty block with 0 reward should not change the world state
assertThat(worldState.rootHash()).isEqualTo(initialHash);
@@ -101,7 +104,8 @@ public class MainnetBlockProcessorTest extends AbstractBlockProcessorTest {
"0xa6b5d50f7b3c39b969c2fe8fed091939c674fef49b4826309cb6994361e39b71"))
.ommersHash(Hash.EMPTY_LIST_HASH)
.buildHeader();
blockProcessor.processBlock(blockchain, worldState, emptyBlockHeader, emptyList(), emptyList());
blockProcessor.processBlock(
protocolContext, blockchain, worldState, emptyBlockHeader, emptyList(), emptyList());
// An empty block with 0 reward should change the world state prior to EIP158
assertThat(worldState.rootHash()).isNotEqualTo(initialHash);

View File

@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.enclave.Enclave;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.chain.TransactionLocation;
import org.hyperledger.besu.ethereum.core.Block;
@@ -66,6 +67,7 @@ class PrivacyBlockProcessorTest {
private AbstractBlockProcessor blockProcessor;
private WorldStateArchive privateWorldStateArchive;
private Enclave enclave;
private ProtocolContext protocolContext;
private ProtocolSchedule protocolSchedule;
private WorldStateArchive publicWorldStateArchive;
@@ -75,6 +77,7 @@ class PrivacyBlockProcessorTest {
privateStateStorage = new PrivateStateKeyValueStorage(new InMemoryKeyValueStorage());
privateWorldStateArchive = mock(WorldStateArchive.class);
enclave = mock(Enclave.class);
protocolContext = mock(ProtocolContext.class);
protocolSchedule = mock(ProtocolSchedule.class);
this.privacyBlockProcessor =
new PrivacyBlockProcessor(
@@ -101,16 +104,17 @@ class PrivacyBlockProcessorTest {
final Block secondBlock =
blockDataGenerator.block(
BlockDataGenerator.BlockOptions.create().setParentHash(firstBlock.getHash()));
privacyBlockProcessor.processBlock(blockchain, mutableWorldState, firstBlock);
privacyBlockProcessor.processBlock(protocolContext, blockchain, mutableWorldState, firstBlock);
privateStateStorage
.updater()
.putPrivacyGroupHeadBlockMap(firstBlock.getHash(), expected)
.commit();
privacyBlockProcessor.processBlock(blockchain, mutableWorldState, secondBlock);
privacyBlockProcessor.processBlock(protocolContext, blockchain, mutableWorldState, secondBlock);
assertThat(privateStateStorage.getPrivacyGroupHeadBlockMap(secondBlock.getHash()))
.contains(expected);
verify(blockProcessor)
.processBlock(
eq(protocolContext),
eq(blockchain),
eq(mutableWorldState),
eq(firstBlock.getHeader()),
@@ -120,6 +124,7 @@ class PrivacyBlockProcessorTest {
any());
verify(blockProcessor)
.processBlock(
eq(protocolContext),
eq(blockchain),
eq(mutableWorldState),
eq(secondBlock.getHeader()),
@@ -172,9 +177,10 @@ class PrivacyBlockProcessorTest {
firstBlock.getHash(), VALID_BASE64_ENCLAVE_KEY, PrivateBlockMetadata.empty())
.commit();
privacyBlockProcessor.processBlock(blockchain, mutableWorldState, secondBlock);
privacyBlockProcessor.processBlock(protocolContext, blockchain, mutableWorldState, secondBlock);
verify(blockProcessor)
.processBlock(
eq(protocolContext),
eq(blockchain),
eq(mutableWorldState),
eq(secondBlock.getHeader()),

View File

@@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -26,6 +27,8 @@ import static org.mockito.Mockito.when;
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.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;
import org.hyperledger.besu.ethereum.core.Transaction;
@@ -42,6 +45,7 @@ import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorld
import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.NoOpTrieLogManager;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.tracing.OperationTracer;
@@ -62,7 +66,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
class ParallelizedConcurrentTransactionProcessorTest {
@Mock private MainnetTransactionProcessor transactionProcessor;
@Mock private BlockHeader chainHeadBlockHeader;
@Mock private BlockHeader blockHeader;
@Mock ProtocolContext protocolContext;
@Mock private Transaction transaction;
@Mock private PrivateMetadataUpdater privateMetadataUpdater;
@Mock private TransactionCollisionDetector transactionCollisionDetector;
@@ -89,6 +95,19 @@ class ParallelizedConcurrentTransactionProcessorTest {
new NoOpTrieLogManager(),
EvmConfiguration.DEFAULT,
createStatefulConfigWithTrie());
when(chainHeadBlockHeader.getHash()).thenReturn(Hash.ZERO);
when(chainHeadBlockHeader.getStateRoot()).thenReturn(Hash.EMPTY_TRIE_HASH);
when(blockHeader.getParentHash()).thenReturn(Hash.ZERO);
when(transaction.detachedCopy()).thenReturn(transaction);
final MutableBlockchain blockchain = mock(MutableBlockchain.class);
when(protocolContext.getBlockchain()).thenReturn(blockchain);
when(blockchain.getChainHeadHeader()).thenReturn(chainHeadBlockHeader);
final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class);
when(protocolContext.getWorldStateArchive()).thenReturn(worldStateArchive);
when(worldStateArchive.getWorldState(any())).thenReturn(Optional.of(worldState));
when(transactionCollisionDetector.hasCollision(any(), any(), any(), any())).thenReturn(false);
}
@@ -105,7 +124,7 @@ class ParallelizedConcurrentTransactionProcessorTest {
Collections.emptyList(), 0, 0, Bytes.EMPTY, ValidationResult.valid()));
processor.runTransaction(
worldState,
protocolContext,
blockHeader,
0,
transaction,
@@ -151,7 +170,7 @@ class ParallelizedConcurrentTransactionProcessorTest {
Optional.of(Bytes.EMPTY)));
processor.runTransaction(
worldState,
protocolContext,
blockHeader,
0,
transaction,
@@ -180,7 +199,7 @@ class ParallelizedConcurrentTransactionProcessorTest {
Collections.emptyList(), 0, 0, Bytes.EMPTY, ValidationResult.valid()));
processor.runTransaction(
worldState,
protocolContext,
blockHeader,
0,
transaction,

View File

@@ -19,17 +19,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.StorageSlotKey;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue;
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload.StorageConsumingMap;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import java.math.BigInteger;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -168,16 +172,22 @@ class TransactionCollisionDetectorTest {
}
@Test
void testCollisionWithModifiedStorageRoot() {
void testCollisionWithModifiedStorageRootAndSameSlot() {
final Address address = Address.fromHexString("0x1");
final BonsaiAccount priorAccountValue = createAccount(address);
final BonsaiAccount nextAccountValue = new BonsaiAccount(priorAccountValue, worldState, true);
nextAccountValue.setStorageRoot(Hash.EMPTY);
// Simulate that the address was already modified in the block
final StorageSlotKey updateStorageSlotKey = new StorageSlotKey(UInt256.ONE);
// Simulate that the address slot was already modified in the block
bonsaiUpdater
.getAccountsToUpdate()
.put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue));
bonsaiUpdater
.getStorageToUpdate()
.computeIfAbsent(
address,
__ -> new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), (___, ____) -> {}))
.put(updateStorageSlotKey, new DiffBasedValue<>(UInt256.ONE, UInt256.ZERO));
final Transaction transaction = createTransaction(address, address);
@@ -185,6 +195,12 @@ class TransactionCollisionDetectorTest {
trxUpdater
.getAccountsToUpdate()
.put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue));
trxUpdater
.getStorageToUpdate()
.computeIfAbsent(
address,
__ -> new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), (___, ____) -> {}))
.put(updateStorageSlotKey, new DiffBasedValue<>(UInt256.ONE, UInt256.ONE));
boolean hasCollision =
collisionDetector.hasCollision(
@@ -196,6 +212,48 @@ class TransactionCollisionDetectorTest {
assertTrue(hasCollision, "Expected a collision with the modified address");
}
@Test
void testCollisionWithModifiedStorageRootNotSameSlot() {
final Address address = Address.fromHexString("0x1");
final BonsaiAccount priorAccountValue = createAccount(address);
final BonsaiAccount nextAccountValue = new BonsaiAccount(priorAccountValue, worldState, true);
nextAccountValue.setStorageRoot(Hash.EMPTY);
// Simulate that the address slot was already modified in the block
bonsaiUpdater
.getAccountsToUpdate()
.put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue));
bonsaiUpdater
.getStorageToUpdate()
.computeIfAbsent(
address,
__ -> new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), (___, ____) -> {}))
.put(new StorageSlotKey(UInt256.ZERO), new DiffBasedValue<>(UInt256.ONE, UInt256.ZERO));
final Transaction transaction = createTransaction(address, address);
// Simulate that the address is read in the next transaction
trxUpdater
.getAccountsToUpdate()
.put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue));
trxUpdater
.getStorageToUpdate()
.computeIfAbsent(
address,
__ -> new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), (___, ____) -> {}))
.put(new StorageSlotKey(UInt256.ONE), new DiffBasedValue<>(UInt256.ONE, UInt256.ONE));
boolean hasCollision =
collisionDetector.hasCollision(
transaction,
Address.ZERO,
new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO),
bonsaiUpdater);
assertFalse(
hasCollision,
"Expected no collision when storage roots are modified but different slots are updated.");
}
@Test
void testCollisionWithMiningBeneficiaryAddress() {
final Address miningBeneficiary = Address.ZERO;
@@ -244,7 +302,7 @@ class TransactionCollisionDetectorTest {
final BonsaiAccount accountValue = createAccount(address);
// Simulate that the address was deleted in the block
bonsaiUpdater.getDeletedAccountAddresses().add(address);
bonsaiUpdater.getAccountsToUpdate().put(address, new DiffBasedValue<>(accountValue, null));
final Transaction transaction = createTransaction(address, address);

View File

@@ -343,7 +343,7 @@ public abstract class AbstractIsolationTests {
protocolSchedule
.getByBlockHeader(blockHeader(0))
.getBlockProcessor()
.processBlock(blockchain, ws, block);
.processBlock(protocolContext, blockchain, ws, block);
blockchain.appendBlock(block, res.getReceipts());
return res;
}