mirror of https://github.com/hyperledger/besu
Optimistic parallelization of transactions to improve performance (#7296)
Optimistic transaction parallelization execution during block processing to improve the performances. This feature can enabled with a flag --Xbonsai-parallel-tx-processing-enabled=true Signed-off-by: Karim Taam <karim.t2am@gmail.com> Co-authored-by: Ameziane H <ameziane.hamlat@consensys.net> Co-authored-by: garyschulte <garyschulte@gmail.com>pull/7374/head
parent
7a905f8b0a
commit
30c96c7a1d
@ -0,0 +1,199 @@ |
||||
/* |
||||
* 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.parallelization; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
import org.hyperledger.besu.ethereum.mainnet.BlockProcessor; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockProcessor; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; |
||||
import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator; |
||||
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.evm.operation.BlockHashOperation; |
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||
import org.hyperledger.besu.metrics.BesuMetricCategory; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
import org.hyperledger.besu.plugin.services.metrics.Counter; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
public class MainnetParallelBlockProcessor extends MainnetBlockProcessor { |
||||
|
||||
private final Optional<MetricsSystem> metricsSystem; |
||||
private final Optional<Counter> confirmedParallelizedTransactionCounter; |
||||
private final Optional<Counter> conflictingButCachedTransactionCounter; |
||||
|
||||
public MainnetParallelBlockProcessor( |
||||
final MainnetTransactionProcessor transactionProcessor, |
||||
final TransactionReceiptFactory transactionReceiptFactory, |
||||
final Wei blockReward, |
||||
final MiningBeneficiaryCalculator miningBeneficiaryCalculator, |
||||
final boolean skipZeroBlockRewards, |
||||
final ProtocolSchedule protocolSchedule, |
||||
final MetricsSystem metricsSystem) { |
||||
super( |
||||
transactionProcessor, |
||||
transactionReceiptFactory, |
||||
blockReward, |
||||
miningBeneficiaryCalculator, |
||||
skipZeroBlockRewards, |
||||
protocolSchedule); |
||||
this.metricsSystem = Optional.of(metricsSystem); |
||||
this.confirmedParallelizedTransactionCounter = |
||||
Optional.of( |
||||
this.metricsSystem |
||||
.get() |
||||
.createCounter( |
||||
BesuMetricCategory.BLOCK_PROCESSING, |
||||
"parallelized_transactions_counter", |
||||
"Counter for the number of parallelized transactions during block processing")); |
||||
|
||||
this.conflictingButCachedTransactionCounter = |
||||
Optional.of( |
||||
this.metricsSystem |
||||
.get() |
||||
.createCounter( |
||||
BesuMetricCategory.BLOCK_PROCESSING, |
||||
"conflicted_transactions_counter", |
||||
"Counter for the number of conflicted transactions during block processing")); |
||||
} |
||||
|
||||
@Override |
||||
protected Optional<PreprocessingContext> runBlockPreProcessing( |
||||
final MutableWorldState worldState, |
||||
final PrivateMetadataUpdater privateMetadataUpdater, |
||||
final BlockHeader blockHeader, |
||||
final List<Transaction> transactions, |
||||
final Address miningBeneficiary, |
||||
final BlockHashOperation.BlockHashLookup blockHashLookup, |
||||
final Wei blobGasPrice) { |
||||
if ((worldState instanceof DiffBasedWorldState)) { |
||||
ParallelizedConcurrentTransactionProcessor parallelizedConcurrentTransactionProcessor = |
||||
new ParallelizedConcurrentTransactionProcessor(transactionProcessor); |
||||
// runAsyncBlock, if activated, facilitates the non-blocking parallel execution of
|
||||
// transactions in the background through an optimistic strategy.
|
||||
parallelizedConcurrentTransactionProcessor.runAsyncBlock( |
||||
worldState, |
||||
blockHeader, |
||||
transactions, |
||||
miningBeneficiary, |
||||
blockHashLookup, |
||||
blobGasPrice, |
||||
privateMetadataUpdater); |
||||
return Optional.of( |
||||
new ParallelizedPreProcessingContext(parallelizedConcurrentTransactionProcessor)); |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
@Override |
||||
protected TransactionProcessingResult getTransactionProcessingResult( |
||||
final Optional<PreprocessingContext> preProcessingContext, |
||||
final MutableWorldState worldState, |
||||
final WorldUpdater blockUpdater, |
||||
final PrivateMetadataUpdater privateMetadataUpdater, |
||||
final BlockHeader blockHeader, |
||||
final Wei blobGasPrice, |
||||
final Address miningBeneficiary, |
||||
final Transaction transaction, |
||||
final int location, |
||||
final BlockHashOperation.BlockHashLookup blockHashLookup) { |
||||
|
||||
TransactionProcessingResult transactionProcessingResult = null; |
||||
|
||||
if (preProcessingContext.isPresent()) { |
||||
final ParallelizedPreProcessingContext parallelizedPreProcessingContext = |
||||
(ParallelizedPreProcessingContext) preProcessingContext.get(); |
||||
transactionProcessingResult = |
||||
parallelizedPreProcessingContext |
||||
.getParallelizedConcurrentTransactionProcessor() |
||||
.applyParallelizedTransactionResult( |
||||
worldState, |
||||
miningBeneficiary, |
||||
transaction, |
||||
location, |
||||
confirmedParallelizedTransactionCounter, |
||||
conflictingButCachedTransactionCounter) |
||||
.orElse(null); |
||||
} |
||||
|
||||
if (transactionProcessingResult == null) { |
||||
return super.getTransactionProcessingResult( |
||||
preProcessingContext, |
||||
worldState, |
||||
blockUpdater, |
||||
privateMetadataUpdater, |
||||
blockHeader, |
||||
blobGasPrice, |
||||
miningBeneficiary, |
||||
transaction, |
||||
location, |
||||
blockHashLookup); |
||||
} else { |
||||
return transactionProcessingResult; |
||||
} |
||||
} |
||||
|
||||
static class ParallelizedPreProcessingContext implements PreprocessingContext { |
||||
final ParallelizedConcurrentTransactionProcessor parallelizedConcurrentTransactionProcessor; |
||||
|
||||
public ParallelizedPreProcessingContext( |
||||
final ParallelizedConcurrentTransactionProcessor |
||||
parallelizedConcurrentTransactionProcessor) { |
||||
this.parallelizedConcurrentTransactionProcessor = parallelizedConcurrentTransactionProcessor; |
||||
} |
||||
|
||||
public ParallelizedConcurrentTransactionProcessor |
||||
getParallelizedConcurrentTransactionProcessor() { |
||||
return parallelizedConcurrentTransactionProcessor; |
||||
} |
||||
} |
||||
|
||||
public static class ParallelBlockProcessorBuilder |
||||
implements ProtocolSpecBuilder.BlockProcessorBuilder { |
||||
|
||||
final MetricsSystem metricsSystem; |
||||
|
||||
public ParallelBlockProcessorBuilder(final MetricsSystem metricsSystem) { |
||||
this.metricsSystem = metricsSystem; |
||||
} |
||||
|
||||
@Override |
||||
public BlockProcessor apply( |
||||
final MainnetTransactionProcessor transactionProcessor, |
||||
final TransactionReceiptFactory transactionReceiptFactory, |
||||
final Wei blockReward, |
||||
final MiningBeneficiaryCalculator miningBeneficiaryCalculator, |
||||
final boolean skipZeroBlockRewards, |
||||
final ProtocolSchedule protocolSchedule) { |
||||
return new MainnetParallelBlockProcessor( |
||||
transactionProcessor, |
||||
transactionReceiptFactory, |
||||
blockReward, |
||||
miningBeneficiaryCalculator, |
||||
skipZeroBlockRewards, |
||||
protocolSchedule, |
||||
metricsSystem); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,268 @@ |
||||
/* |
||||
* 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.parallelization; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
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.worldview.DiffBasedWorldState; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; |
||||
import org.hyperledger.besu.evm.operation.BlockHashOperation; |
||||
import org.hyperledger.besu.evm.tracing.OperationTracer; |
||||
import org.hyperledger.besu.evm.worldstate.WorldView; |
||||
import org.hyperledger.besu.plugin.services.metrics.Counter; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
import java.util.concurrent.Executor; |
||||
import java.util.concurrent.Executors; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
|
||||
/** |
||||
* Optimizes transaction processing by executing transactions in parallel within a given block. |
||||
* Transactions are executed optimistically in a non-blocking manner. After execution, the class
|
||||
* checks for potential conflicts among transactions to ensure data integrity before applying the |
||||
* results to the world state. |
||||
*/ |
||||
@SuppressWarnings({"unchecked", "rawtypes"}) |
||||
public class ParallelizedConcurrentTransactionProcessor { |
||||
|
||||
private static final int NCPU = Runtime.getRuntime().availableProcessors(); |
||||
private static final Executor executor = Executors.newFixedThreadPool(NCPU); |
||||
|
||||
private final MainnetTransactionProcessor transactionProcessor; |
||||
|
||||
private final TransactionCollisionDetector transactionCollisionDetector; |
||||
|
||||
private final Map<Integer, ParallelizedTransactionContext> |
||||
parallelizedTransactionContextByLocation = new ConcurrentHashMap<>(); |
||||
|
||||
/** |
||||
* Constructs a PreloadConcurrentTransactionProcessor with a specified transaction processor. This |
||||
* processor is responsible for the individual processing of transactions. |
||||
* |
||||
* @param transactionProcessor The transaction processor for processing individual transactions. |
||||
*/ |
||||
public ParallelizedConcurrentTransactionProcessor( |
||||
final MainnetTransactionProcessor transactionProcessor) { |
||||
this.transactionProcessor = transactionProcessor; |
||||
this.transactionCollisionDetector = new TransactionCollisionDetector(); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
public ParallelizedConcurrentTransactionProcessor( |
||||
final MainnetTransactionProcessor transactionProcessor, |
||||
final TransactionCollisionDetector transactionCollisionDetector) { |
||||
this.transactionProcessor = transactionProcessor; |
||||
this.transactionCollisionDetector = transactionCollisionDetector; |
||||
} |
||||
|
||||
/** |
||||
* Initiates the parallel and optimistic execution of transactions within a block by creating a |
||||
* copy of the world state for each transaction. This method processes transactions in a |
||||
* non-blocking manner. Transactions are executed against their respective copies of the world |
||||
* 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 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. |
||||
* @param blockHashLookup Function for block hash lookup. |
||||
* @param blobGasPrice Gas price for blob transactions. |
||||
* @param privateMetadataUpdater Updater for private transaction metadata. |
||||
*/ |
||||
public void runAsyncBlock( |
||||
final MutableWorldState worldState, |
||||
final BlockHeader blockHeader, |
||||
final List<Transaction> transactions, |
||||
final Address miningBeneficiary, |
||||
final BlockHashOperation.BlockHashLookup blockHashLookup, |
||||
final Wei blobGasPrice, |
||||
final PrivateMetadataUpdater privateMetadataUpdater) { |
||||
for (int i = 0; i < transactions.size(); i++) { |
||||
final Transaction transaction = transactions.get(i); |
||||
final int transactionLocation = i; |
||||
/* |
||||
* All transactions are executed in the background by copying the world state of the block on which the transactions need to be executed, ensuring that each one has its own accumulator. |
||||
*/ |
||||
CompletableFuture.runAsync( |
||||
() -> |
||||
runTransaction( |
||||
worldState, |
||||
blockHeader, |
||||
transactionLocation, |
||||
transaction, |
||||
miningBeneficiary, |
||||
blockHashLookup, |
||||
blobGasPrice, |
||||
privateMetadataUpdater), |
||||
executor); |
||||
} |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
public void runTransaction( |
||||
final MutableWorldState worldState, |
||||
final BlockHeader blockHeader, |
||||
final int transactionLocation, |
||||
final Transaction transaction, |
||||
final Address miningBeneficiary, |
||||
final BlockHashOperation.BlockHashLookup blockHashLookup, |
||||
final Wei blobGasPrice, |
||||
final PrivateMetadataUpdater privateMetadataUpdater) { |
||||
try (final DiffBasedWorldState roundWorldState = |
||||
new BonsaiWorldState( |
||||
(BonsaiWorldState) worldState, new NoopBonsaiCachedMerkleTrieLoader())) { |
||||
roundWorldState.freeze(); // 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); |
||||
|
||||
// commit the accumulator in order to apply all the modifications
|
||||
roundWorldState.getAccumulator().commit(); |
||||
|
||||
contextBuilder |
||||
.transactionAccumulator(roundWorldState.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); |
||||
} |
||||
parallelizedTransactionContextByLocation.put( |
||||
transactionLocation, parallelizedTransactionContext); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Applies the results of parallelized transactions to the world state after checking for |
||||
* conflicts. |
||||
* |
||||
* <p>If a transaction was executed optimistically without any detected conflicts, its result is |
||||
* directly applied to the world state. If there is a conflict, this method does not apply the |
||||
* transaction's modifications directly to the world state. Instead, it caches the data read from |
||||
* the database during the transaction's execution. This cached data is then used to optimize the |
||||
* replay of the transaction by reducing the need for additional reads from the disk, thereby |
||||
* making the replay process faster. This approach ensures that the integrity of the world state |
||||
* is maintained while optimizing the performance of transaction processing. |
||||
* |
||||
* @param worldState Mutable world state intended for applying transaction results. |
||||
* @param miningBeneficiary Address of the beneficiary for mining rewards. |
||||
* @param transaction Transaction for which the result is to be applied. |
||||
* @param transactionLocation Index of the transaction within the block. |
||||
* @param confirmedParallelizedTransactionCounter Metric counter for confirmed parallelized |
||||
* transactions |
||||
* @param conflictingButCachedTransactionCounter Metric counter for conflicting but cached |
||||
* transactions |
||||
* @return Optional containing the transaction processing result if applied, or empty if the |
||||
* transaction needs to be replayed due to a conflict. |
||||
*/ |
||||
public Optional<TransactionProcessingResult> applyParallelizedTransactionResult( |
||||
final MutableWorldState worldState, |
||||
final Address miningBeneficiary, |
||||
final Transaction transaction, |
||||
final int transactionLocation, |
||||
final Optional<Counter> confirmedParallelizedTransactionCounter, |
||||
final Optional<Counter> conflictingButCachedTransactionCounter) { |
||||
final DiffBasedWorldState diffBasedWorldState = (DiffBasedWorldState) worldState; |
||||
final DiffBasedWorldStateUpdateAccumulator blockAccumulator = |
||||
(DiffBasedWorldStateUpdateAccumulator) diffBasedWorldState.updater(); |
||||
final ParallelizedTransactionContext parallelizedTransactionContext = |
||||
parallelizedTransactionContextByLocation.remove(transactionLocation); |
||||
/* |
||||
* If `parallelizedTransactionContext` is not null, it means that the transaction had time to complete in the background. |
||||
*/ |
||||
if (parallelizedTransactionContext != null) { |
||||
final DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator = |
||||
parallelizedTransactionContext.transactionAccumulator(); |
||||
final TransactionProcessingResult transactionProcessingResult = |
||||
parallelizedTransactionContext.transactionProcessingResult(); |
||||
final boolean hasCollision = |
||||
transactionCollisionDetector.hasCollision( |
||||
transaction, miningBeneficiary, parallelizedTransactionContext, blockAccumulator); |
||||
if (transactionProcessingResult.isSuccessful() && !hasCollision) { |
||||
blockAccumulator |
||||
.getOrCreate(miningBeneficiary) |
||||
.incrementBalance(parallelizedTransactionContext.miningBeneficiaryReward()); |
||||
|
||||
blockAccumulator.importStateChangesFromSource(transactionAccumulator); |
||||
|
||||
if (confirmedParallelizedTransactionCounter.isPresent()) |
||||
confirmedParallelizedTransactionCounter.get().inc(); |
||||
return Optional.of(transactionProcessingResult); |
||||
} else { |
||||
blockAccumulator.importPriorStateFromSource(transactionAccumulator); |
||||
if (conflictingButCachedTransactionCounter.isPresent()) |
||||
conflictingButCachedTransactionCounter.get().inc(); |
||||
// If there is a conflict, we return an empty result to signal the block processor to
|
||||
// re-execute the transaction.
|
||||
return Optional.empty(); |
||||
} |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
} |
@ -0,0 +1,133 @@ |
||||
/* |
||||
* 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.parallelization; |
||||
|
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
public final class ParallelizedTransactionContext { |
||||
private final DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator; |
||||
private final TransactionProcessingResult transactionProcessingResult; |
||||
private final boolean isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||
private final Wei miningBeneficiaryReward; |
||||
|
||||
public ParallelizedTransactionContext( |
||||
final DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator, |
||||
final TransactionProcessingResult transactionProcessingResult, |
||||
final boolean isMiningBeneficiaryTouchedPreRewardByTransaction, |
||||
final Wei miningBeneficiaryReward) { |
||||
this.transactionAccumulator = transactionAccumulator; |
||||
this.transactionProcessingResult = transactionProcessingResult; |
||||
this.isMiningBeneficiaryTouchedPreRewardByTransaction = |
||||
isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||
this.miningBeneficiaryReward = miningBeneficiaryReward; |
||||
} |
||||
|
||||
public DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator() { |
||||
return transactionAccumulator; |
||||
} |
||||
|
||||
public TransactionProcessingResult transactionProcessingResult() { |
||||
return transactionProcessingResult; |
||||
} |
||||
|
||||
public boolean isMiningBeneficiaryTouchedPreRewardByTransaction() { |
||||
return isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||
} |
||||
|
||||
public Wei miningBeneficiaryReward() { |
||||
return miningBeneficiaryReward; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(final Object obj) { |
||||
if (obj == this) return true; |
||||
if (obj == null || obj.getClass() != this.getClass()) return false; |
||||
var that = (ParallelizedTransactionContext) obj; |
||||
return Objects.equals(this.transactionAccumulator, that.transactionAccumulator) |
||||
&& Objects.equals(this.transactionProcessingResult, that.transactionProcessingResult) |
||||
&& this.isMiningBeneficiaryTouchedPreRewardByTransaction |
||||
== that.isMiningBeneficiaryTouchedPreRewardByTransaction |
||||
&& Objects.equals(this.miningBeneficiaryReward, that.miningBeneficiaryReward); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash( |
||||
transactionAccumulator, |
||||
transactionProcessingResult, |
||||
isMiningBeneficiaryTouchedPreRewardByTransaction, |
||||
miningBeneficiaryReward); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "ParallelizedTransactionContext[" |
||||
+ "transactionAccumulator=" |
||||
+ transactionAccumulator |
||||
+ ", " |
||||
+ "transactionProcessingResult=" |
||||
+ transactionProcessingResult |
||||
+ ", " |
||||
+ "isMiningBeneficiaryTouchedPreRewardByTransaction=" |
||||
+ isMiningBeneficiaryTouchedPreRewardByTransaction |
||||
+ ", " |
||||
+ "miningBeneficiaryReward=" |
||||
+ miningBeneficiaryReward |
||||
+ ']'; |
||||
} |
||||
|
||||
public static class Builder { |
||||
private DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator; |
||||
private TransactionProcessingResult transactionProcessingResult; |
||||
private boolean isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||
private Wei miningBeneficiaryReward = Wei.ZERO; |
||||
|
||||
public Builder transactionAccumulator( |
||||
final DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator) { |
||||
this.transactionAccumulator = transactionAccumulator; |
||||
return this; |
||||
} |
||||
|
||||
public Builder transactionProcessingResult( |
||||
final TransactionProcessingResult transactionProcessingResult) { |
||||
this.transactionProcessingResult = transactionProcessingResult; |
||||
return this; |
||||
} |
||||
|
||||
public Builder isMiningBeneficiaryTouchedPreRewardByTransaction( |
||||
final boolean isMiningBeneficiaryTouchedPreRewardByTransaction) { |
||||
this.isMiningBeneficiaryTouchedPreRewardByTransaction = |
||||
isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||
return this; |
||||
} |
||||
|
||||
public Builder miningBeneficiaryReward(final Wei miningBeneficiaryReward) { |
||||
this.miningBeneficiaryReward = miningBeneficiaryReward; |
||||
return this; |
||||
} |
||||
|
||||
public ParallelizedTransactionContext build() { |
||||
return new ParallelizedTransactionContext( |
||||
transactionAccumulator, |
||||
transactionProcessingResult, |
||||
isMiningBeneficiaryTouchedPreRewardByTransaction, |
||||
miningBeneficiaryReward); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,114 @@ |
||||
/* |
||||
* 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.parallelization; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.Iterator; |
||||
import java.util.Optional; |
||||
import java.util.Set; |
||||
|
||||
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. |
||||
* |
||||
* @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 |
||||
* transaction. |
||||
* @param blockAccumulator The accumulator for the block. |
||||
* @return true if there is a collision; false otherwise. |
||||
*/ |
||||
public boolean hasCollision( |
||||
final Transaction transaction, |
||||
final Address miningBeneficiary, |
||||
final ParallelizedTransactionContext parallelizedTransactionContext, |
||||
final DiffBasedWorldStateUpdateAccumulator<?> 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; |
||||
} |
||||
} |
||||
return commonAddressFound; |
||||
} |
||||
|
||||
/** |
||||
* Retrieves the set of addresses that were touched by a transaction. This includes the sender and |
||||
* recipient of the transaction, as well as any addresses that were read from or written to by the |
||||
* transaction's execution. |
||||
* |
||||
* @param transaction The transaction to analyze. |
||||
* @param accumulator An optional accumulator containing state changes made by the transaction. |
||||
* @return A set of addresses touched by the transaction. |
||||
*/ |
||||
public Set<Address> getAddressesTouchedByTransaction( |
||||
final Transaction transaction, |
||||
final Optional<DiffBasedWorldStateUpdateAccumulator<?>> accumulator) { |
||||
HashSet<Address> addresses = new HashSet<>(); |
||||
addresses.add(transaction.getSender()); |
||||
if (transaction.getTo().isPresent()) { |
||||
addresses.add(transaction.getTo().get()); |
||||
} |
||||
accumulator.ifPresent( |
||||
diffBasedWorldStateUpdateAccumulator -> { |
||||
diffBasedWorldStateUpdateAccumulator |
||||
.getAccountsToUpdate() |
||||
.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. |
||||
* |
||||
* @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. |
||||
*/ |
||||
private Set<Address> getAddressesTouchedByBlock( |
||||
final Optional<DiffBasedWorldStateUpdateAccumulator<?>> accumulator) { |
||||
HashSet<Address> addresses = new HashSet<>(); |
||||
accumulator.ifPresent( |
||||
diffBasedWorldStateUpdateAccumulator -> { |
||||
diffBasedWorldStateUpdateAccumulator |
||||
.getAccountsToUpdate() |
||||
.forEach( |
||||
(address, diffBasedValue) -> { |
||||
if (!diffBasedValue.isUnchanged()) { |
||||
addresses.add(address); |
||||
} |
||||
}); |
||||
addresses.addAll(diffBasedWorldStateUpdateAccumulator.getDeletedAccountAddresses()); |
||||
}); |
||||
return addresses; |
||||
} |
||||
} |
@ -0,0 +1,44 @@ |
||||
/* |
||||
* 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.trie.diffbased.bonsai.cache; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.datatypes.StorageSlotKey; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; |
||||
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; |
||||
|
||||
public class NoopBonsaiCachedMerkleTrieLoader extends BonsaiCachedMerkleTrieLoader { |
||||
|
||||
public NoopBonsaiCachedMerkleTrieLoader() { |
||||
super(new NoOpMetricsSystem()); |
||||
} |
||||
|
||||
@Override |
||||
public void preLoadAccount( |
||||
final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, |
||||
final Hash worldStateRootHash, |
||||
final Address account) { |
||||
// noop
|
||||
} |
||||
|
||||
@Override |
||||
public void preLoadStorageSlot( |
||||
final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, |
||||
final Address account, |
||||
final StorageSlotKey slotKey) { |
||||
// noop
|
||||
} |
||||
} |
@ -0,0 +1,213 @@ |
||||
/* |
||||
* 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.parallelization; |
||||
|
||||
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.times; |
||||
import static org.mockito.Mockito.verify; |
||||
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.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; |
||||
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; |
||||
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; |
||||
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; |
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoOpBonsaiCachedWorldStorageManager; |
||||
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.worldview.BonsaiWorldState; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.NoOpTrieLogManager; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; |
||||
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; |
||||
import org.hyperledger.besu.evm.internal.EvmConfiguration; |
||||
import org.hyperledger.besu.evm.operation.BlockHashOperation; |
||||
import org.hyperledger.besu.evm.tracing.OperationTracer; |
||||
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.Mockito; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
||||
@ExtendWith(MockitoExtension.class) |
||||
class ParallelizedConcurrentTransactionProcessorTest { |
||||
|
||||
@Mock private MainnetTransactionProcessor transactionProcessor; |
||||
@Mock private BlockHeader blockHeader; |
||||
@Mock private Transaction transaction; |
||||
@Mock private PrivateMetadataUpdater privateMetadataUpdater; |
||||
@Mock private TransactionCollisionDetector transactionCollisionDetector; |
||||
|
||||
private BonsaiWorldState worldState; |
||||
|
||||
private ParallelizedConcurrentTransactionProcessor processor; |
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
processor = |
||||
new ParallelizedConcurrentTransactionProcessor( |
||||
transactionProcessor, transactionCollisionDetector); |
||||
final BonsaiWorldStateKeyValueStorage bonsaiWorldStateKeyValueStorage = |
||||
new BonsaiWorldStateKeyValueStorage( |
||||
new InMemoryKeyValueStorageProvider(), |
||||
new NoOpMetricsSystem(), |
||||
DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); |
||||
worldState = |
||||
new BonsaiWorldState( |
||||
bonsaiWorldStateKeyValueStorage, |
||||
new NoopBonsaiCachedMerkleTrieLoader(), |
||||
new NoOpBonsaiCachedWorldStorageManager(bonsaiWorldStateKeyValueStorage), |
||||
new NoOpTrieLogManager(), |
||||
EvmConfiguration.DEFAULT, |
||||
new DiffBasedWorldStateConfig()); |
||||
when(transactionCollisionDetector.hasCollision(any(), any(), any(), any())).thenReturn(false); |
||||
} |
||||
|
||||
@Test |
||||
void testRunTransaction() { |
||||
Address miningBeneficiary = Address.fromHexString("0x1"); |
||||
Wei blobGasPrice = Wei.ZERO; |
||||
|
||||
Mockito.when( |
||||
transactionProcessor.processTransaction( |
||||
any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any(), any())) |
||||
.thenReturn( |
||||
TransactionProcessingResult.successful( |
||||
Collections.emptyList(), 0, 0, Bytes.EMPTY, ValidationResult.valid())); |
||||
|
||||
processor.runTransaction( |
||||
worldState, |
||||
blockHeader, |
||||
0, |
||||
transaction, |
||||
miningBeneficiary, |
||||
(blockNumber) -> Hash.EMPTY, |
||||
blobGasPrice, |
||||
privateMetadataUpdater); |
||||
|
||||
verify(transactionProcessor, times(1)) |
||||
.processTransaction( |
||||
any(DiffBasedWorldStateUpdateAccumulator.class), |
||||
eq(blockHeader), |
||||
eq(transaction), |
||||
eq(miningBeneficiary), |
||||
any(OperationTracer.class), |
||||
any(BlockHashOperation.BlockHashLookup.class), |
||||
eq(true), |
||||
eq(TransactionValidationParams.processingBlock()), |
||||
eq(privateMetadataUpdater), |
||||
eq(blobGasPrice)); |
||||
|
||||
assertTrue( |
||||
processor |
||||
.applyParallelizedTransactionResult( |
||||
worldState, miningBeneficiary, transaction, 0, Optional.empty(), Optional.empty()) |
||||
.isPresent(), |
||||
"Expected the transaction context to be stored"); |
||||
} |
||||
|
||||
@Test |
||||
void testRunTransactionWithFailure() { |
||||
Address miningBeneficiary = Address.fromHexString("0x1"); |
||||
Wei blobGasPrice = Wei.ZERO; |
||||
|
||||
when(transactionProcessor.processTransaction( |
||||
any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any(), any())) |
||||
.thenReturn( |
||||
TransactionProcessingResult.failed( |
||||
0, |
||||
0, |
||||
ValidationResult.invalid( |
||||
TransactionInvalidReason.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE), |
||||
Optional.of(Bytes.EMPTY))); |
||||
|
||||
processor.runTransaction( |
||||
worldState, |
||||
blockHeader, |
||||
0, |
||||
transaction, |
||||
miningBeneficiary, |
||||
(blockNumber) -> Hash.EMPTY, |
||||
blobGasPrice, |
||||
privateMetadataUpdater); |
||||
|
||||
Optional<TransactionProcessingResult> result = |
||||
processor.applyParallelizedTransactionResult( |
||||
worldState, miningBeneficiary, transaction, 0, Optional.empty(), Optional.empty()); |
||||
assertTrue(result.isEmpty(), "Expected the transaction result to indicate a failure"); |
||||
} |
||||
|
||||
@Test |
||||
void testRunTransactionWithConflict() { |
||||
|
||||
Address miningBeneficiary = Address.fromHexString("0x1"); |
||||
Wei blobGasPrice = Wei.ZERO; |
||||
|
||||
Mockito.when( |
||||
transactionProcessor.processTransaction( |
||||
any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any(), any())) |
||||
.thenReturn( |
||||
TransactionProcessingResult.successful( |
||||
Collections.emptyList(), 0, 0, Bytes.EMPTY, ValidationResult.valid())); |
||||
|
||||
processor.runTransaction( |
||||
worldState, |
||||
blockHeader, |
||||
0, |
||||
transaction, |
||||
miningBeneficiary, |
||||
(blockNumber) -> Hash.EMPTY, |
||||
blobGasPrice, |
||||
privateMetadataUpdater); |
||||
|
||||
verify(transactionProcessor, times(1)) |
||||
.processTransaction( |
||||
any(DiffBasedWorldStateUpdateAccumulator.class), |
||||
eq(blockHeader), |
||||
eq(transaction), |
||||
eq(miningBeneficiary), |
||||
any(OperationTracer.class), |
||||
any(BlockHashOperation.BlockHashLookup.class), |
||||
eq(true), |
||||
eq(TransactionValidationParams.processingBlock()), |
||||
eq(privateMetadataUpdater), |
||||
eq(blobGasPrice)); |
||||
|
||||
// simulate a conflict
|
||||
when(transactionCollisionDetector.hasCollision(any(), any(), any(), any())).thenReturn(true); |
||||
|
||||
Optional<TransactionProcessingResult> result = |
||||
processor.applyParallelizedTransactionResult( |
||||
worldState, miningBeneficiary, transaction, 0, Optional.empty(), Optional.empty()); |
||||
assertTrue(result.isEmpty(), "Expected no transaction result to be applied due to conflict"); |
||||
} |
||||
} |
@ -0,0 +1,290 @@ |
||||
/* |
||||
* 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.parallelization; |
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse; |
||||
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.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.evm.internal.EvmConfiguration; |
||||
|
||||
import java.math.BigInteger; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
||||
@ExtendWith(MockitoExtension.class) |
||||
class TransactionCollisionDetectorTest { |
||||
|
||||
private TransactionCollisionDetector collisionDetector; |
||||
@Mock BonsaiWorldState worldState; |
||||
BonsaiWorldStateUpdateAccumulator bonsaiUpdater; |
||||
BonsaiWorldStateUpdateAccumulator trxUpdater; |
||||
|
||||
@BeforeEach |
||||
public void setUp() { |
||||
collisionDetector = new TransactionCollisionDetector(); |
||||
bonsaiUpdater = |
||||
new BonsaiWorldStateUpdateAccumulator( |
||||
worldState, (__, ___) -> {}, (__, ___) -> {}, EvmConfiguration.DEFAULT); |
||||
trxUpdater = |
||||
new BonsaiWorldStateUpdateAccumulator( |
||||
worldState, (__, ___) -> {}, (__, ___) -> {}, EvmConfiguration.DEFAULT); |
||||
} |
||||
|
||||
private Transaction createTransaction(final Address sender, final Address to) { |
||||
return new Transaction.Builder() |
||||
.nonce(1) |
||||
.gasPrice(Wei.of(1)) |
||||
.gasLimit(21000) |
||||
.to(to) |
||||
.value(Wei.ZERO) |
||||
.payload(Bytes.EMPTY) |
||||
.chainId(BigInteger.ONE) |
||||
.sender(sender) |
||||
.build(); |
||||
} |
||||
|
||||
private BonsaiAccount createAccount(final Address address) { |
||||
return new BonsaiAccount( |
||||
worldState, |
||||
address, |
||||
Hash.hash(Address.ZERO), |
||||
0, |
||||
Wei.ONE, |
||||
Hash.EMPTY_TRIE_HASH, |
||||
Hash.EMPTY, |
||||
false); |
||||
} |
||||
|
||||
@Test |
||||
void testCollisionWithModifiedBalance() { |
||||
final Address address = Address.fromHexString("0x1"); |
||||
final BonsaiAccount priorAccountValue = createAccount(address); |
||||
final BonsaiAccount nextAccountValue = new BonsaiAccount(priorAccountValue, worldState, true); |
||||
nextAccountValue.setBalance(Wei.MAX_WEI); |
||||
|
||||
// Simulate that the address was already modified in the block
|
||||
bonsaiUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue)); |
||||
|
||||
final Transaction transaction = createTransaction(address, address); |
||||
|
||||
// Simulate that the address is read in the next transaction
|
||||
trxUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); |
||||
|
||||
boolean hasCollision = |
||||
collisionDetector.hasCollision( |
||||
transaction, |
||||
Address.ZERO, |
||||
new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), |
||||
bonsaiUpdater); |
||||
|
||||
assertTrue(hasCollision, "Expected a collision with the modified address"); |
||||
} |
||||
|
||||
@Test |
||||
void testCollisionWithModifiedNonce() { |
||||
final Address address = Address.fromHexString("0x1"); |
||||
final BonsaiAccount priorAccountValue = createAccount(address); |
||||
final BonsaiAccount nextAccountValue = new BonsaiAccount(priorAccountValue, worldState, true); |
||||
nextAccountValue.setNonce(1); |
||||
|
||||
// Simulate that the address was already modified in the block
|
||||
bonsaiUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue)); |
||||
|
||||
final Transaction transaction = createTransaction(address, address); |
||||
|
||||
// Simulate that the address is read in the next transaction
|
||||
trxUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); |
||||
|
||||
boolean hasCollision = |
||||
collisionDetector.hasCollision( |
||||
transaction, |
||||
Address.ZERO, |
||||
new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), |
||||
bonsaiUpdater); |
||||
|
||||
assertTrue(hasCollision, "Expected a collision with the modified address"); |
||||
} |
||||
|
||||
@Test |
||||
void testCollisionWithModifiedCode() { |
||||
final Address address = Address.fromHexString("0x1"); |
||||
final BonsaiAccount priorAccountValue = createAccount(address); |
||||
final BonsaiAccount nextAccountValue = new BonsaiAccount(priorAccountValue, worldState, true); |
||||
nextAccountValue.setCode(Bytes.repeat((byte) 0x01, 10)); |
||||
|
||||
// Simulate that the address was already modified in the block
|
||||
bonsaiUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue)); |
||||
|
||||
final Transaction transaction = createTransaction(address, address); |
||||
|
||||
// Simulate that the address is read in the next transaction
|
||||
trxUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); |
||||
|
||||
boolean hasCollision = |
||||
collisionDetector.hasCollision( |
||||
transaction, |
||||
Address.ZERO, |
||||
new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), |
||||
bonsaiUpdater); |
||||
|
||||
assertTrue(hasCollision, "Expected a collision with the modified address"); |
||||
} |
||||
|
||||
@Test |
||||
void testCollisionWithModifiedStorageRoot() { |
||||
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
|
||||
bonsaiUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue)); |
||||
|
||||
final Transaction transaction = createTransaction(address, address); |
||||
|
||||
// Simulate that the address is read in the next transaction
|
||||
trxUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); |
||||
|
||||
boolean hasCollision = |
||||
collisionDetector.hasCollision( |
||||
transaction, |
||||
Address.ZERO, |
||||
new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), |
||||
bonsaiUpdater); |
||||
|
||||
assertTrue(hasCollision, "Expected a collision with the modified address"); |
||||
} |
||||
|
||||
@Test |
||||
void testCollisionWithMiningBeneficiaryAddress() { |
||||
final Address miningBeneficiary = Address.ZERO; |
||||
final Address address = Address.fromHexString("0x1"); |
||||
|
||||
final Transaction transaction = createTransaction(miningBeneficiary, address); |
||||
|
||||
boolean hasCollision = |
||||
collisionDetector.hasCollision( |
||||
transaction, |
||||
miningBeneficiary, |
||||
new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), |
||||
bonsaiUpdater); |
||||
|
||||
assertTrue(hasCollision, "Expected collision with the mining beneficiary address as sender"); |
||||
} |
||||
|
||||
@Test |
||||
void testCollisionWithAnotherMiningBeneficiaryAddress() { |
||||
final Address miningBeneficiary = Address.ZERO; |
||||
final Address address = Address.fromHexString("0x1"); |
||||
final BonsaiAccount miningBeneficiaryValue = createAccount(address); |
||||
|
||||
final Transaction transaction = createTransaction(address, address); |
||||
|
||||
// Simulate that the mining beneficiary is read in the next transaction
|
||||
trxUpdater |
||||
.getAccountsToUpdate() |
||||
.put( |
||||
miningBeneficiary, |
||||
new DiffBasedValue<>(miningBeneficiaryValue, miningBeneficiaryValue)); |
||||
|
||||
boolean hasCollision = |
||||
collisionDetector.hasCollision( |
||||
transaction, |
||||
miningBeneficiary, |
||||
new ParallelizedTransactionContext(trxUpdater, null, true, Wei.ZERO), |
||||
bonsaiUpdater); |
||||
|
||||
assertTrue(hasCollision, "Expected collision with the read mining beneficiary address"); |
||||
} |
||||
|
||||
@Test |
||||
void testCollisionWithDeletedAddress() { |
||||
final Address address = Address.fromHexString("0x1"); |
||||
final BonsaiAccount accountValue = createAccount(address); |
||||
|
||||
// Simulate that the address was deleted in the block
|
||||
bonsaiUpdater.getDeletedAccountAddresses().add(address); |
||||
|
||||
final Transaction transaction = createTransaction(address, address); |
||||
|
||||
// Simulate that the deleted address is read in the next transaction
|
||||
trxUpdater.getAccountsToUpdate().put(address, new DiffBasedValue<>(accountValue, accountValue)); |
||||
|
||||
boolean hasCollision = |
||||
collisionDetector.hasCollision( |
||||
transaction, |
||||
Address.ZERO, |
||||
new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), |
||||
bonsaiUpdater); |
||||
|
||||
assertTrue(hasCollision, "Expected a collision with the deleted address"); |
||||
} |
||||
|
||||
@Test |
||||
void testCollisionWithNoModifiedAddress() { |
||||
final Address address = Address.fromHexString("0x1"); |
||||
final BonsaiAccount priorAccountValue = createAccount(address); |
||||
|
||||
// Simulate that the address was already read in the block
|
||||
bonsaiUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); |
||||
|
||||
final Transaction transaction = createTransaction(address, address); |
||||
|
||||
// Simulate that the address is read in the next transaction
|
||||
trxUpdater |
||||
.getAccountsToUpdate() |
||||
.put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); |
||||
|
||||
boolean hasCollision = |
||||
collisionDetector.hasCollision( |
||||
transaction, |
||||
Address.ZERO, |
||||
new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), |
||||
bonsaiUpdater); |
||||
|
||||
assertFalse(hasCollision, "Expected no collision with the read address"); |
||||
} |
||||
} |
Loading…
Reference in new issue