mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-08 15:13:58 -05:00
Allow plugins to propose transactions during block creation (#8268)
* Allow plugins to propose transactions during block creation Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net> * Apply suggestions from code review Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net> * Document AbstractStatefulTransactionSelector Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net> * Unit tests for BlockSizeTransactionSelectorTest Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net> --------- Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
- Fast Sync
|
||||
### Additions and Improvements
|
||||
- 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)
|
||||
### 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)
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
*/
|
||||
package org.hyperledger.besu.services;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader;
|
||||
import org.hyperledger.besu.plugin.services.TransactionSelectionService;
|
||||
import org.hyperledger.besu.plugin.services.txselection.BlockTransactionSelectionService;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
|
||||
/** The Transaction Selection service implementation. */
|
||||
public class TransactionSelectionServiceImpl implements TransactionSelectionService {
|
||||
@@ -26,18 +29,27 @@ public class TransactionSelectionServiceImpl implements TransactionSelectionServ
|
||||
/** Default Constructor. */
|
||||
public TransactionSelectionServiceImpl() {}
|
||||
|
||||
private Optional<PluginTransactionSelectorFactory> factory = Optional.empty();
|
||||
private PluginTransactionSelectorFactory factory = PluginTransactionSelectorFactory.NO_OP_FACTORY;
|
||||
|
||||
@Override
|
||||
public PluginTransactionSelector createPluginTransactionSelector() {
|
||||
return factory
|
||||
.map(PluginTransactionSelectorFactory::create)
|
||||
.orElse(PluginTransactionSelector.ACCEPT_ALL);
|
||||
public PluginTransactionSelector createPluginTransactionSelector(
|
||||
final SelectorsStateManager selectorsStateManager) {
|
||||
return factory.create(selectorsStateManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectPendingTransactions(
|
||||
final BlockTransactionSelectionService selectionService,
|
||||
final ProcessableBlockHeader pendingBlockHeader) {
|
||||
factory.selectPendingTransactions(selectionService, pendingBlockHeader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerPluginTransactionSelectorFactory(
|
||||
final PluginTransactionSelectorFactory pluginTransactionSelectorFactory) {
|
||||
factory = Optional.ofNullable(pluginTransactionSelectorFactory);
|
||||
checkState(
|
||||
factory == PluginTransactionSelectorFactory.NO_OP_FACTORY,
|
||||
"PluginTransactionSelectorFactory was already registered");
|
||||
factory = pluginTransactionSelectorFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ import org.hyperledger.besu.evm.account.MutableAccount;
|
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
||||
import org.hyperledger.besu.plugin.services.exception.StorageException;
|
||||
import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException;
|
||||
import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -213,11 +213,15 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
|
||||
|
||||
throwIfStopped();
|
||||
|
||||
final PluginTransactionSelector pluginTransactionSelector =
|
||||
miningConfiguration.getTransactionSelectionService().createPluginTransactionSelector();
|
||||
|
||||
final BlockAwareOperationTracer operationTracer =
|
||||
pluginTransactionSelector.getOperationTracer();
|
||||
final var selectorsStateManager = new SelectorsStateManager();
|
||||
final var pluginTransactionSelector =
|
||||
miningConfiguration
|
||||
.getTransactionSelectionService()
|
||||
.createPluginTransactionSelector(selectorsStateManager);
|
||||
final var operationTracer = pluginTransactionSelector.getOperationTracer();
|
||||
pluginTransactionSelector
|
||||
.getOperationTracer()
|
||||
.traceStartBlock(processableBlockHeader, miningBeneficiary);
|
||||
|
||||
operationTracer.traceStartBlock(processableBlockHeader, miningBeneficiary);
|
||||
BlockProcessingContext blockProcessingContext =
|
||||
@@ -240,6 +244,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
|
||||
miningBeneficiary,
|
||||
newProtocolSpec,
|
||||
pluginTransactionSelector,
|
||||
selectorsStateManager,
|
||||
parentHeader);
|
||||
transactionResults.logSelectionStats();
|
||||
timings.register("txsSelection");
|
||||
@@ -362,6 +367,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
|
||||
final Address miningBeneficiary,
|
||||
final ProtocolSpec protocolSpec,
|
||||
final PluginTransactionSelector pluginTransactionSelector,
|
||||
final SelectorsStateManager selectorsStateManager,
|
||||
final BlockHeader parentHeader)
|
||||
throws RuntimeException {
|
||||
final MainnetTransactionProcessor transactionProcessor = protocolSpec.getTransactionProcessor();
|
||||
@@ -391,7 +397,8 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
|
||||
protocolSpec.getGasLimitCalculator(),
|
||||
protocolSpec.getBlockHashProcessor(),
|
||||
pluginTransactionSelector,
|
||||
ethScheduler);
|
||||
ethScheduler,
|
||||
selectorsStateManager);
|
||||
|
||||
if (transactions.isPresent()) {
|
||||
return selector.evaluateTransactions(transactions.get());
|
||||
|
||||
@@ -50,10 +50,14 @@ import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
|
||||
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
|
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
import org.hyperledger.besu.plugin.services.TransactionSelectionService;
|
||||
import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer;
|
||||
import org.hyperledger.besu.plugin.services.txselection.BlockTransactionSelectionService;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -88,7 +92,7 @@ import org.slf4j.LoggerFactory;
|
||||
* Once "used" this class must be discarded and another created. This class contains state which is
|
||||
* not cleared between executions of buildTransactionListForBlock().
|
||||
*/
|
||||
public class BlockTransactionSelector {
|
||||
public class BlockTransactionSelector implements BlockTransactionSelectionService {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BlockTransactionSelector.class);
|
||||
private final Supplier<Boolean> isCancelled;
|
||||
private final MainnetTransactionProcessor transactionProcessor;
|
||||
@@ -99,13 +103,17 @@ public class BlockTransactionSelector {
|
||||
private final TransactionSelectionResults transactionSelectionResults =
|
||||
new TransactionSelectionResults();
|
||||
private final List<AbstractTransactionSelector> transactionSelectors;
|
||||
private final SelectorsStateManager selectorsStateManager;
|
||||
private final TransactionSelectionService transactionSelectionService;
|
||||
private final PluginTransactionSelector pluginTransactionSelector;
|
||||
private final BlockAwareOperationTracer operationTracer;
|
||||
private final EthScheduler ethScheduler;
|
||||
private final AtomicBoolean isTimeout = new AtomicBoolean(false);
|
||||
private final long blockTxsSelectionMaxTime;
|
||||
private WorldUpdater blockWorldStateUpdater;
|
||||
private WorldUpdater txWorldStateUpdater;
|
||||
private volatile TransactionEvaluationContext currTxEvaluationContext;
|
||||
private final List<Runnable> selectedTxPendingActions = new ArrayList<>(1);
|
||||
|
||||
public BlockTransactionSelector(
|
||||
final MiningConfiguration miningConfiguration,
|
||||
@@ -123,7 +131,8 @@ public class BlockTransactionSelector {
|
||||
final GasLimitCalculator gasLimitCalculator,
|
||||
final BlockHashProcessor blockHashProcessor,
|
||||
final PluginTransactionSelector pluginTransactionSelector,
|
||||
final EthScheduler ethScheduler) {
|
||||
final EthScheduler ethScheduler,
|
||||
final SelectorsStateManager selectorsStateManager) {
|
||||
this.transactionProcessor = transactionProcessor;
|
||||
this.blockchain = blockchain;
|
||||
this.worldState = worldState;
|
||||
@@ -141,20 +150,24 @@ public class BlockTransactionSelector {
|
||||
blobGasPrice,
|
||||
miningBeneficiary,
|
||||
transactionPool);
|
||||
transactionSelectors = createTransactionSelectors(blockSelectionContext);
|
||||
this.selectorsStateManager = selectorsStateManager;
|
||||
this.transactionSelectionService = miningConfiguration.getTransactionSelectionService();
|
||||
this.transactionSelectors =
|
||||
createTransactionSelectors(blockSelectionContext, selectorsStateManager);
|
||||
this.pluginTransactionSelector = pluginTransactionSelector;
|
||||
this.operationTracer =
|
||||
new InterruptibleOperationTracer(pluginTransactionSelector.getOperationTracer());
|
||||
blockWorldStateUpdater = worldState.updater();
|
||||
txWorldStateUpdater = blockWorldStateUpdater.updater();
|
||||
blockTxsSelectionMaxTime = miningConfiguration.getBlockTxsSelectionMaxTime();
|
||||
}
|
||||
|
||||
private List<AbstractTransactionSelector> createTransactionSelectors(
|
||||
final BlockSelectionContext context) {
|
||||
final BlockSelectionContext context, final SelectorsStateManager selectorsStateManager) {
|
||||
return List.of(
|
||||
new SkipSenderTransactionSelector(context),
|
||||
new BlockSizeTransactionSelector(context),
|
||||
new BlobSizeTransactionSelector(context),
|
||||
new BlockSizeTransactionSelector(context, selectorsStateManager),
|
||||
new BlobSizeTransactionSelector(context, selectorsStateManager),
|
||||
new PriceTransactionSelector(context),
|
||||
new BlobPriceTransactionSelector(context),
|
||||
new MinPriorityFeePerGasTransactionSelector(context),
|
||||
@@ -171,10 +184,6 @@ public class BlockTransactionSelector {
|
||||
* evaluation.
|
||||
*/
|
||||
public TransactionSelectionResults buildTransactionListForBlock() {
|
||||
LOG.atDebug()
|
||||
.setMessage("Transaction pool stats {}")
|
||||
.addArgument(blockSelectionContext.transactionPool()::logStats)
|
||||
.log();
|
||||
timeLimitedSelection();
|
||||
LOG.atTrace()
|
||||
.setMessage("Transaction selection result {}")
|
||||
@@ -186,10 +195,19 @@ public class BlockTransactionSelector {
|
||||
private void timeLimitedSelection() {
|
||||
final var txSelectionTask =
|
||||
new FutureTask<Void>(
|
||||
() ->
|
||||
blockSelectionContext
|
||||
.transactionPool()
|
||||
.selectTransactions(this::evaluateTransaction),
|
||||
() -> {
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
|
||||
LOG.debug("Starting plugin transaction selection");
|
||||
transactionSelectionService.selectPendingTransactions(
|
||||
this, blockSelectionContext.pendingBlockHeader());
|
||||
|
||||
LOG.atDebug()
|
||||
.setMessage("Starting internal pool transaction selection, stats {}")
|
||||
.addArgument(blockSelectionContext.transactionPool()::logStats)
|
||||
.log();
|
||||
blockSelectionContext.transactionPool().selectTransactions(this::evaluateTransaction);
|
||||
},
|
||||
null);
|
||||
ethScheduler.scheduleBlockCreationTask(txSelectionTask);
|
||||
|
||||
@@ -208,11 +226,18 @@ public class BlockTransactionSelector {
|
||||
|
||||
cancelEvaluatingTxWithGraceTime(txSelectionTask);
|
||||
|
||||
LOG.warn(
|
||||
"Interrupting the selection of transactions for block inclusion as it exceeds the maximum configured duration of "
|
||||
+ blockTxsSelectionMaxTime
|
||||
+ "ms",
|
||||
e);
|
||||
final var logBuilder =
|
||||
LOG.atWarn()
|
||||
.setMessage(
|
||||
"Interrupting the selection of transactions for block inclusion as it exceeds"
|
||||
+ " the maximum configured duration of {}ms")
|
||||
.addArgument(blockTxsSelectionMaxTime);
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
logBuilder.setCause(e).log();
|
||||
} else {
|
||||
logBuilder.log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,11 +285,25 @@ public class BlockTransactionSelector {
|
||||
* evaluations.
|
||||
*/
|
||||
public TransactionSelectionResults evaluateTransactions(final List<Transaction> transactions) {
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
|
||||
transactions.forEach(
|
||||
transaction -> evaluateTransaction(new PendingTransaction.Local.Priority(transaction)));
|
||||
return transactionSelectionResults;
|
||||
}
|
||||
|
||||
private TransactionSelectionResult evaluateTransaction(
|
||||
final PendingTransaction pendingTransaction) {
|
||||
final var evaluationResult = evaluatePendingTransaction(pendingTransaction);
|
||||
|
||||
if (evaluationResult.selected()) {
|
||||
return commit() ? evaluationResult : BLOCK_SELECTION_TIMEOUT;
|
||||
} else {
|
||||
rollback();
|
||||
return evaluationResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed into the PendingTransactions, and is called on each transaction until sufficient
|
||||
* transactions are found which fill a block worth of gas. This function will continue to be
|
||||
@@ -275,10 +314,14 @@ public class BlockTransactionSelector {
|
||||
* @return The result of the transaction evaluation process.
|
||||
* @throws CancellationException if the transaction selection process is cancelled.
|
||||
*/
|
||||
private TransactionSelectionResult evaluateTransaction(
|
||||
final PendingTransaction pendingTransaction) {
|
||||
@Override
|
||||
public TransactionSelectionResult evaluatePendingTransaction(
|
||||
final org.hyperledger.besu.datatypes.PendingTransaction pendingTransaction) {
|
||||
|
||||
checkCancellation();
|
||||
|
||||
LOG.atTrace().setMessage("Starting evaluation of {}").addArgument(pendingTransaction).log();
|
||||
|
||||
final TransactionEvaluationContext evaluationContext =
|
||||
createTransactionEvaluationContext(pendingTransaction);
|
||||
currTxEvaluationContext = evaluationContext;
|
||||
@@ -288,27 +331,56 @@ public class BlockTransactionSelector {
|
||||
return handleTransactionNotSelected(evaluationContext, selectionResult);
|
||||
}
|
||||
|
||||
final WorldUpdater txWorldStateUpdater = blockWorldStateUpdater.updater();
|
||||
final TransactionProcessingResult processingResult =
|
||||
processTransaction(evaluationContext.getTransaction(), txWorldStateUpdater);
|
||||
processTransaction(evaluationContext.getTransaction());
|
||||
|
||||
txWorldStateUpdater.markTransactionBoundary();
|
||||
|
||||
var postProcessingSelectionResult = evaluatePostProcessing(evaluationContext, processingResult);
|
||||
|
||||
if (postProcessingSelectionResult.selected()) {
|
||||
return handleTransactionSelected(evaluationContext, processingResult, txWorldStateUpdater);
|
||||
return postProcessingSelectionResult.selected()
|
||||
? handleTransactionSelected(evaluationContext, processingResult)
|
||||
: handleTransactionNotSelected(evaluationContext, postProcessingSelectionResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commit() {
|
||||
// only add this tx to the selected set if it is not too late,
|
||||
// this need to be done synchronously to avoid that a concurrent timeout
|
||||
// could start packing a block while we are updating the state here
|
||||
synchronized (isTimeout) {
|
||||
if (!isTimeout.get()) {
|
||||
selectorsStateManager.commit();
|
||||
txWorldStateUpdater.commit();
|
||||
blockWorldStateUpdater.commit();
|
||||
for (final var pendingAction : selectedTxPendingActions) {
|
||||
pendingAction.run();
|
||||
}
|
||||
selectedTxPendingActions.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return handleTransactionNotSelected(
|
||||
evaluationContext, postProcessingSelectionResult, txWorldStateUpdater);
|
||||
selectedTxPendingActions.clear();
|
||||
blockWorldStateUpdater = worldState.updater();
|
||||
txWorldStateUpdater = blockWorldStateUpdater.updater();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
selectedTxPendingActions.clear();
|
||||
selectorsStateManager.rollback();
|
||||
txWorldStateUpdater = blockWorldStateUpdater.updater();
|
||||
}
|
||||
|
||||
private TransactionEvaluationContext createTransactionEvaluationContext(
|
||||
final PendingTransaction pendingTransaction) {
|
||||
final org.hyperledger.besu.datatypes.PendingTransaction pendingTransaction) {
|
||||
final Wei transactionGasPriceInBlock =
|
||||
blockSelectionContext
|
||||
.feeMarket()
|
||||
.getTransactionPriceCalculator()
|
||||
.price(
|
||||
pendingTransaction.getTransaction(),
|
||||
(Transaction) pendingTransaction.getTransaction(),
|
||||
blockSelectionContext.pendingBlockHeader().getBaseFee());
|
||||
|
||||
return new TransactionEvaluationContext(
|
||||
@@ -333,7 +405,7 @@ public class BlockTransactionSelector {
|
||||
|
||||
for (var selector : transactionSelectors) {
|
||||
TransactionSelectionResult result =
|
||||
selector.evaluateTransactionPreProcessing(evaluationContext, transactionSelectionResults);
|
||||
selector.evaluateTransactionPreProcessing(evaluationContext);
|
||||
if (!result.equals(SELECTED)) {
|
||||
return result;
|
||||
}
|
||||
@@ -357,8 +429,7 @@ public class BlockTransactionSelector {
|
||||
|
||||
for (var selector : transactionSelectors) {
|
||||
TransactionSelectionResult result =
|
||||
selector.evaluateTransactionPostProcessing(
|
||||
evaluationContext, transactionSelectionResults, processingResult);
|
||||
selector.evaluateTransactionPostProcessing(evaluationContext, processingResult);
|
||||
if (!result.equals(SELECTED)) {
|
||||
return result;
|
||||
}
|
||||
@@ -371,17 +442,15 @@ public class BlockTransactionSelector {
|
||||
* Processes a transaction
|
||||
*
|
||||
* @param transaction The transaction to be processed.
|
||||
* @param worldStateUpdater The world state updater.
|
||||
* @return The result of the transaction processing.
|
||||
*/
|
||||
private TransactionProcessingResult processTransaction(
|
||||
final Transaction transaction, final WorldUpdater worldStateUpdater) {
|
||||
private TransactionProcessingResult processTransaction(final Transaction transaction) {
|
||||
final BlockHashLookup blockHashLookup =
|
||||
blockSelectionContext
|
||||
.blockHashProcessor()
|
||||
.createBlockHashLookup(blockchain, blockSelectionContext.pendingBlockHeader());
|
||||
return transactionProcessor.processTransaction(
|
||||
worldStateUpdater,
|
||||
txWorldStateUpdater,
|
||||
blockSelectionContext.pendingBlockHeader(),
|
||||
transaction,
|
||||
blockSelectionContext.miningBeneficiary(),
|
||||
@@ -399,56 +468,48 @@ public class BlockTransactionSelector {
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @param processingResult The result of the transaction processing.
|
||||
* @param txWorldStateUpdater The world state updater.
|
||||
* @return The result of the transaction selection process.
|
||||
*/
|
||||
private TransactionSelectionResult handleTransactionSelected(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionProcessingResult processingResult,
|
||||
final WorldUpdater txWorldStateUpdater) {
|
||||
final TransactionProcessingResult processingResult) {
|
||||
final Transaction transaction = evaluationContext.getTransaction();
|
||||
|
||||
final long gasUsedByTransaction =
|
||||
transaction.getGasLimit() - processingResult.getGasRemaining();
|
||||
final long cumulativeGasUsed =
|
||||
transactionSelectionResults.getCumulativeGasUsed() + gasUsedByTransaction;
|
||||
final long blobGasUsed =
|
||||
blockSelectionContext.gasCalculator().blobGasCost(transaction.getBlobCount());
|
||||
|
||||
final boolean tooLate;
|
||||
// queue the creation of the receipt and the update of the final results
|
||||
// there actions will be performed on commit if the pending tx is definitely selected
|
||||
selectedTxPendingActions.add(
|
||||
() -> {
|
||||
final long cumulativeGasUsed =
|
||||
transactionSelectionResults.getCumulativeGasUsed() + gasUsedByTransaction;
|
||||
|
||||
// only add this tx to the selected set if it is not too late,
|
||||
// this need to be done synchronously to avoid that a concurrent timeout
|
||||
// could start packing a block while we are updating the state here
|
||||
synchronized (isTimeout) {
|
||||
tooLate = isTimeout.get();
|
||||
if (!tooLate) {
|
||||
txWorldStateUpdater.commit();
|
||||
blockWorldStateUpdater.commit();
|
||||
final TransactionReceipt receipt =
|
||||
transactionReceiptFactory.create(
|
||||
transaction.getType(), processingResult, worldState, cumulativeGasUsed);
|
||||
final TransactionReceipt receipt =
|
||||
transactionReceiptFactory.create(
|
||||
transaction.getType(), processingResult, worldState, cumulativeGasUsed);
|
||||
|
||||
transactionSelectionResults.updateSelected(
|
||||
transaction, receipt, gasUsedByTransaction, blobGasUsed);
|
||||
}
|
||||
}
|
||||
transactionSelectionResults.updateSelected(transaction, receipt, gasUsedByTransaction);
|
||||
|
||||
if (tooLate) {
|
||||
notifySelected(evaluationContext, processingResult);
|
||||
LOG.atTrace()
|
||||
.setMessage("Selected and commited {} for block creation")
|
||||
.addArgument(transaction::toTraceLog)
|
||||
.log();
|
||||
});
|
||||
|
||||
if (isTimeout.get()) {
|
||||
// even if this tx passed all the checks, it is too late to include it in this block,
|
||||
// so we need to treat it as not selected
|
||||
|
||||
// do not rely on the presence of this result, since by the time it is added, the code
|
||||
// reading it could have been already executed by another thread
|
||||
return handleTransactionNotSelected(
|
||||
evaluationContext, BLOCK_SELECTION_TIMEOUT, txWorldStateUpdater);
|
||||
return handleTransactionNotSelected(evaluationContext, BLOCK_SELECTION_TIMEOUT);
|
||||
}
|
||||
|
||||
notifySelected(evaluationContext, processingResult);
|
||||
|
||||
blockWorldStateUpdater = worldState.updater();
|
||||
LOG.atTrace()
|
||||
.setMessage("Selected {} for block creation, evaluated in {}")
|
||||
.setMessage(
|
||||
"Potentially selected {} for block creation, evaluated in {}, waiting for commit")
|
||||
.addArgument(transaction::toTraceLog)
|
||||
.addArgument(evaluationContext.getEvaluationTimer())
|
||||
.log();
|
||||
@@ -479,12 +540,16 @@ public class BlockTransactionSelector {
|
||||
|
||||
transactionSelectionResults.updateNotSelected(evaluationContext.getTransaction(), actualResult);
|
||||
notifyNotSelected(evaluationContext, actualResult);
|
||||
|
||||
LOG.atTrace()
|
||||
.setMessage(
|
||||
"Not selected {} for block creation with result {} (original result {}), evaluated in {}")
|
||||
.setMessage("Not selected {} for block creation with result {}{}, evaluated in {}")
|
||||
.addArgument(pendingTransaction::toTraceLog)
|
||||
.addArgument(actualResult)
|
||||
.addArgument(selectionResult)
|
||||
.addArgument(
|
||||
() ->
|
||||
selectionResult.equals(actualResult)
|
||||
? ""
|
||||
: " (original result " + selectionResult + ")")
|
||||
.addArgument(evaluationContext.getEvaluationTimer())
|
||||
.log();
|
||||
|
||||
@@ -545,14 +610,6 @@ public class BlockTransactionSelector {
|
||||
return false;
|
||||
}
|
||||
|
||||
private TransactionSelectionResult handleTransactionNotSelected(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResult selectionResult,
|
||||
final WorldUpdater txWorldStateUpdater) {
|
||||
txWorldStateUpdater.revert();
|
||||
return handleTransactionNotSelected(evaluationContext, selectionResult);
|
||||
}
|
||||
|
||||
private void notifySelected(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
|
||||
@@ -48,27 +48,20 @@ public class TransactionSelectionResults {
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private long cumulativeGasUsed = 0;
|
||||
private long cumulativeBlobGasUsed = 0;
|
||||
|
||||
void updateSelected(
|
||||
final Transaction transaction,
|
||||
final TransactionReceipt receipt,
|
||||
final long gasUsed,
|
||||
final long blobGasUsed) {
|
||||
final Transaction transaction, final TransactionReceipt receipt, final long gasUsed) {
|
||||
selectedTransactions.add(transaction);
|
||||
transactionsByType
|
||||
.computeIfAbsent(transaction.getType(), type -> new ArrayList<>())
|
||||
.add(transaction);
|
||||
receipts.add(receipt);
|
||||
cumulativeGasUsed += gasUsed;
|
||||
cumulativeBlobGasUsed += blobGasUsed;
|
||||
LOG.atTrace()
|
||||
.setMessage(
|
||||
"New selected transaction {}, total transactions {}, cumulative gas used {}, cumulative blob gas used {}")
|
||||
.setMessage("New selected transaction {}, total transactions {}, cumulative gas used {}")
|
||||
.addArgument(transaction::toTraceLog)
|
||||
.addArgument(selectedTransactions::size)
|
||||
.addArgument(cumulativeGasUsed)
|
||||
.addArgument(cumulativeBlobGasUsed)
|
||||
.log();
|
||||
}
|
||||
|
||||
@@ -93,10 +86,6 @@ public class TransactionSelectionResults {
|
||||
return cumulativeGasUsed;
|
||||
}
|
||||
|
||||
public long getCumulativeBlobGasUsed() {
|
||||
return cumulativeBlobGasUsed;
|
||||
}
|
||||
|
||||
public Map<Transaction, TransactionSelectionResult> getNotSelectedTransactions() {
|
||||
return Map.copyOf(notSelectedTransactions);
|
||||
}
|
||||
@@ -135,7 +124,6 @@ public class TransactionSelectionResults {
|
||||
}
|
||||
TransactionSelectionResults that = (TransactionSelectionResults) o;
|
||||
return cumulativeGasUsed == that.cumulativeGasUsed
|
||||
&& cumulativeBlobGasUsed == that.cumulativeBlobGasUsed
|
||||
&& selectedTransactions.equals(that.selectedTransactions)
|
||||
&& notSelectedTransactions.equals(that.notSelectedTransactions)
|
||||
&& receipts.equals(that.receipts);
|
||||
@@ -143,19 +131,12 @@ public class TransactionSelectionResults {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
selectedTransactions,
|
||||
notSelectedTransactions,
|
||||
receipts,
|
||||
cumulativeGasUsed,
|
||||
cumulativeBlobGasUsed);
|
||||
return Objects.hash(selectedTransactions, notSelectedTransactions, receipts, cumulativeGasUsed);
|
||||
}
|
||||
|
||||
public String toTraceLog() {
|
||||
return "cumulativeGasUsed="
|
||||
+ cumulativeGasUsed
|
||||
+ ", cumulativeBlobGasUsed="
|
||||
+ cumulativeBlobGasUsed
|
||||
+ ", selectedTransactions="
|
||||
+ selectedTransactions.stream()
|
||||
.map(Transaction::toTraceLog)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.blockcreation.txselection.selectors;
|
||||
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
|
||||
/**
|
||||
* This class represents an abstract transaction selector which provides methods to access and set
|
||||
* the selector working state.
|
||||
*
|
||||
* @param <S> The type of state specified by the implementing selector, not to be confused with the
|
||||
* world state.
|
||||
*/
|
||||
public abstract class AbstractStatefulTransactionSelector<S> extends AbstractTransactionSelector {
|
||||
private final SelectorsStateManager selectorsStateManager;
|
||||
|
||||
public AbstractStatefulTransactionSelector(
|
||||
final BlockSelectionContext context,
|
||||
final SelectorsStateManager selectorsStateManager,
|
||||
final S initialState,
|
||||
final SelectorsStateManager.StateDuplicator<S> stateDuplicator) {
|
||||
super(context);
|
||||
this.selectorsStateManager = selectorsStateManager;
|
||||
selectorsStateManager.createSelectorState(this, initialState, stateDuplicator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the working state specific to this selector.
|
||||
*
|
||||
* @return the working state of the selector
|
||||
*/
|
||||
protected S getWorkingState() {
|
||||
return selectorsStateManager.getSelectorWorkingState(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the working state specific to this selector.
|
||||
*
|
||||
* @param newState the new state of the selector
|
||||
*/
|
||||
protected void setWorkingState(final S newState) {
|
||||
selectorsStateManager.setSelectorWorkingState(this, newState);
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,15 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
import org.hyperledger.besu.plugin.services.txselection.TransactionSelector;
|
||||
|
||||
/**
|
||||
* This class represents an abstract transaction selector which provides methods to evaluate
|
||||
* transactions.
|
||||
*/
|
||||
public abstract class AbstractTransactionSelector {
|
||||
public abstract class AbstractTransactionSelector implements TransactionSelector {
|
||||
protected final BlockSelectionContext context;
|
||||
|
||||
public AbstractTransactionSelector(final BlockSelectionContext context) {
|
||||
@@ -35,25 +35,21 @@ public abstract class AbstractTransactionSelector {
|
||||
* Evaluates a transaction in the context of other transactions in the same block.
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @param blockTransactionResults The results of other transaction evaluations in the same block.
|
||||
* @return The result of the transaction evaluation
|
||||
*/
|
||||
public abstract TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults);
|
||||
final TransactionEvaluationContext evaluationContext);
|
||||
|
||||
/**
|
||||
* Evaluates a transaction considering other transactions in the same block and a transaction
|
||||
* processing result.
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @param blockTransactionResults The results of other transaction evaluations in the same block.
|
||||
* @param processingResult The result of transaction processing.
|
||||
* @return The result of the transaction evaluation
|
||||
*/
|
||||
public abstract TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult);
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,6 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
@@ -40,13 +39,11 @@ public class BlobPriceTransactionSelector extends AbstractTransactionSelector {
|
||||
* Evaluates a transaction considering its blob price.
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @param ignored The results of other transaction evaluations in the same block.
|
||||
* @return The result of the transaction selection.
|
||||
*/
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults ignored) {
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
if (transactionBlobPriceBelowMin(evaluationContext.getTransaction())) {
|
||||
return TransactionSelectionResult.BLOB_PRICE_BELOW_CURRENT_MIN;
|
||||
}
|
||||
@@ -56,7 +53,6 @@ public class BlobPriceTransactionSelector extends AbstractTransactionSelector {
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
// All necessary checks were done in the pre-processing method, so nothing to do here.
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
|
||||
@@ -16,9 +16,9 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -28,11 +28,12 @@ import org.slf4j.LoggerFactory;
|
||||
* evaluating transactions based on blobs size. It checks if a transaction supports blobs, and if
|
||||
* so, checks that there is enough remaining blob gas in the block to fit the blobs of the tx.
|
||||
*/
|
||||
public class BlobSizeTransactionSelector extends AbstractTransactionSelector {
|
||||
public class BlobSizeTransactionSelector extends AbstractStatefulTransactionSelector<Long> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BlobSizeTransactionSelector.class);
|
||||
|
||||
public BlobSizeTransactionSelector(final BlockSelectionContext context) {
|
||||
super(context);
|
||||
public BlobSizeTransactionSelector(
|
||||
final BlockSelectionContext context, final SelectorsStateManager selectorsStateManager) {
|
||||
super(context, selectorsStateManager, 0L, SelectorsStateManager.StateDuplicator::duplicateLong);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,21 +44,19 @@ public class BlobSizeTransactionSelector extends AbstractTransactionSelector {
|
||||
* number of blobs or not.
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @param transactionSelectionResults The results of other transaction evaluations in the same
|
||||
* block.
|
||||
* @return The result of the transaction selection.
|
||||
*/
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults transactionSelectionResults) {
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
|
||||
final var tx = evaluationContext.getTransaction();
|
||||
if (tx.getType().supportsBlob()) {
|
||||
|
||||
final var cumulativeBlobGasUsed = getWorkingState();
|
||||
|
||||
final var remainingBlobGas =
|
||||
context.gasLimitCalculator().currentBlobGasLimit()
|
||||
- transactionSelectionResults.getCumulativeBlobGasUsed();
|
||||
context.gasLimitCalculator().currentBlobGasLimit() - cumulativeBlobGasUsed;
|
||||
|
||||
if (remainingBlobGas == 0) {
|
||||
LOG.atTrace()
|
||||
@@ -88,9 +87,11 @@ public class BlobSizeTransactionSelector extends AbstractTransactionSelector {
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
// All necessary checks were done in the pre-processing method, so nothing to do here.
|
||||
final var tx = evaluationContext.getTransaction();
|
||||
if (tx.getType().supportsBlob()) {
|
||||
setWorkingState(getWorkingState() + context.gasCalculator().blobGasCost(tx.getBlobCount()));
|
||||
}
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -29,11 +29,15 @@ import org.slf4j.LoggerFactory;
|
||||
* evaluating transactions based on block size. It checks if a transaction is too large for the
|
||||
* block and determines the selection result accordingly.
|
||||
*/
|
||||
public class BlockSizeTransactionSelector extends AbstractTransactionSelector {
|
||||
public class BlockSizeTransactionSelector extends AbstractStatefulTransactionSelector<Long> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BlockSizeTransactionSelector.class);
|
||||
|
||||
public BlockSizeTransactionSelector(final BlockSelectionContext context) {
|
||||
super(context);
|
||||
private final long blockGasLimit;
|
||||
|
||||
public BlockSizeTransactionSelector(
|
||||
final BlockSelectionContext context, final SelectorsStateManager selectorsStateManager) {
|
||||
super(context, selectorsStateManager, 0L, SelectorsStateManager.StateDuplicator::duplicateLong);
|
||||
this.blockGasLimit = context.pendingBlockHeader().getGasLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,25 +45,23 @@ public class BlockSizeTransactionSelector extends AbstractTransactionSelector {
|
||||
* too large for the block returns a selection result based on block occupancy.
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @param transactionSelectionResults The results of other transaction evaluations in the same
|
||||
* block.
|
||||
* @return The result of the transaction selection.
|
||||
*/
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults transactionSelectionResults) {
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
|
||||
if (transactionTooLargeForBlock(
|
||||
evaluationContext.getTransaction(), transactionSelectionResults)) {
|
||||
final long cumulativeGasUsed = getWorkingState();
|
||||
|
||||
if (transactionTooLargeForBlock(evaluationContext.getTransaction(), cumulativeGasUsed)) {
|
||||
LOG.atTrace()
|
||||
.setMessage("Transaction {} too large to select for block creation")
|
||||
.addArgument(evaluationContext.getPendingTransaction()::toTraceLog)
|
||||
.log();
|
||||
if (blockOccupancyAboveThreshold(transactionSelectionResults)) {
|
||||
if (blockOccupancyAboveThreshold(cumulativeGasUsed)) {
|
||||
LOG.trace("Block occupancy above threshold, completing operation");
|
||||
return TransactionSelectionResult.BLOCK_OCCUPANCY_ABOVE_THRESHOLD;
|
||||
} else if (blockFull(transactionSelectionResults)) {
|
||||
} else if (blockFull(cumulativeGasUsed)) {
|
||||
LOG.trace("Block full, completing operation");
|
||||
return TransactionSelectionResult.BLOCK_FULL;
|
||||
} else {
|
||||
@@ -72,49 +74,42 @@ public class BlockSizeTransactionSelector extends AbstractTransactionSelector {
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
// All necessary checks were done in the pre-processing method, so nothing to do here.
|
||||
final long gasUsedByTransaction =
|
||||
evaluationContext.getTransaction().getGasLimit() - processingResult.getGasRemaining();
|
||||
setWorkingState(getWorkingState() + gasUsedByTransaction);
|
||||
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the transaction is too large for the block.
|
||||
*
|
||||
* @param transaction The transaction to be checked.
|
||||
* @param transactionSelectionResults The results of other transaction evaluations in the same
|
||||
* block.
|
||||
* @param transaction The transaction to be checked. block.
|
||||
* @param cumulativeGasUsed The cumulative gas used by previous txs.
|
||||
* @return True if the transaction is too large for the block, false otherwise.
|
||||
*/
|
||||
private boolean transactionTooLargeForBlock(
|
||||
final Transaction transaction,
|
||||
final TransactionSelectionResults transactionSelectionResults) {
|
||||
final Transaction transaction, final long cumulativeGasUsed) {
|
||||
|
||||
return transaction.getGasLimit()
|
||||
> context.pendingBlockHeader().getGasLimit()
|
||||
- transactionSelectionResults.getCumulativeGasUsed();
|
||||
return transaction.getGasLimit() > blockGasLimit - cumulativeGasUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the block occupancy is above the threshold.
|
||||
*
|
||||
* @param transactionSelectionResults The results of other transaction evaluations in the same
|
||||
* block.
|
||||
* @param cumulativeGasUsed The cumulative gas used by previous txs.
|
||||
* @return True if the block occupancy is above the threshold, false otherwise.
|
||||
*/
|
||||
private boolean blockOccupancyAboveThreshold(
|
||||
final TransactionSelectionResults transactionSelectionResults) {
|
||||
final long gasAvailable = context.pendingBlockHeader().getGasLimit();
|
||||
|
||||
final long gasUsed = transactionSelectionResults.getCumulativeGasUsed();
|
||||
final long gasRemaining = gasAvailable - gasUsed;
|
||||
final double occupancyRatio = (double) gasUsed / (double) gasAvailable;
|
||||
private boolean blockOccupancyAboveThreshold(final long cumulativeGasUsed) {
|
||||
final long gasRemaining = blockGasLimit - cumulativeGasUsed;
|
||||
final double occupancyRatio = (double) cumulativeGasUsed / (double) blockGasLimit;
|
||||
|
||||
LOG.trace(
|
||||
"Min block occupancy ratio {}, gas used {}, available {}, remaining {}, used/available {}",
|
||||
context.miningConfiguration().getMinBlockOccupancyRatio(),
|
||||
gasUsed,
|
||||
gasAvailable,
|
||||
cumulativeGasUsed,
|
||||
blockGasLimit,
|
||||
gasRemaining,
|
||||
occupancyRatio);
|
||||
|
||||
@@ -124,15 +119,11 @@ public class BlockSizeTransactionSelector extends AbstractTransactionSelector {
|
||||
/**
|
||||
* Checks if the block is full.
|
||||
*
|
||||
* @param transactionSelectionResults The results of other transaction evaluations in the same
|
||||
* block.
|
||||
* @param cumulativeGasUsed The cumulative gas used by previous txs.
|
||||
* @return True if the block is full, false otherwise.
|
||||
*/
|
||||
private boolean blockFull(final TransactionSelectionResults transactionSelectionResults) {
|
||||
final long gasAvailable = context.pendingBlockHeader().getGasLimit();
|
||||
final long gasUsed = transactionSelectionResults.getCumulativeGasUsed();
|
||||
|
||||
final long gasRemaining = gasAvailable - gasUsed;
|
||||
private boolean blockFull(final long cumulativeGasUsed) {
|
||||
final long gasRemaining = blockGasLimit - cumulativeGasUsed;
|
||||
|
||||
if (gasRemaining < context.gasCalculator().getMinimumTransactionCost()) {
|
||||
LOG.trace(
|
||||
|
||||
@@ -17,7 +17,6 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
import org.hyperledger.besu.datatypes.Wei;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
|
||||
@@ -37,15 +36,12 @@ public class MinPriorityFeePerGasTransactionSelector extends AbstractTransaction
|
||||
* Evaluates a transaction before processing.
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @param transactionSelectionResults The results of other transaction evaluations in the same
|
||||
* block.
|
||||
* @return TransactionSelectionResult. If the priority fee is below the minimum, it returns an
|
||||
* invalid transient result. Otherwise, it returns a selected result.
|
||||
*/
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults transactionSelectionResults) {
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
if (isPriorityFeePriceBelowMinimum(evaluationContext)) {
|
||||
return TransactionSelectionResult.PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN;
|
||||
}
|
||||
@@ -82,7 +78,6 @@ public class MinPriorityFeePerGasTransactionSelector extends AbstractTransaction
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
|
||||
@@ -40,13 +39,11 @@ public class PriceTransactionSelector extends AbstractTransactionSelector {
|
||||
* minimum, it returns a selection result indicating the reason.
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @param ignored The results of other transaction evaluations in the same block.
|
||||
* @return The result of the transaction selection.
|
||||
*/
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults ignored) {
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
if (transactionCurrentPriceBelowMin(evaluationContext)) {
|
||||
return TransactionSelectionResult.CURRENT_TX_PRICE_BELOW_MIN;
|
||||
}
|
||||
@@ -56,7 +53,6 @@ public class PriceTransactionSelector extends AbstractTransactionSelector {
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
// All necessary checks were done in the pre-processing method, so nothing to do here.
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
|
||||
@@ -16,7 +16,6 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
@@ -41,8 +40,7 @@ public class ProcessingResultTransactionSelector extends AbstractTransactionSele
|
||||
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults) {
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
// All checks depend on processingResult and will be done in the post-processing method, so
|
||||
// nothing to do here.
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
@@ -54,14 +52,12 @@ public class ProcessingResultTransactionSelector extends AbstractTransactionSele
|
||||
* result.
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @param blockTransactionResults The results of other transaction evaluations in the same block.
|
||||
* @param processingResult The processing result of the transaction.
|
||||
* @return The result of the transaction selection.
|
||||
*/
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
|
||||
if (processingResult.isInvalid()) {
|
||||
|
||||
@@ -17,34 +17,41 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
import org.hyperledger.besu.datatypes.Address;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SkipSenderTransactionSelector extends AbstractTransactionSelector {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SkipSenderTransactionSelector.class);
|
||||
private final Set<Address> skippedSenders = new HashSet<>();
|
||||
private final Map<Address, Long> skippedSenderNonceMap = new HashMap<>();
|
||||
|
||||
public SkipSenderTransactionSelector(final BlockSelectionContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this pending tx belongs to a sender with a previous pending tx not selected, in which
|
||||
* case we can safely skip the evaluation due to the nonce gap
|
||||
*
|
||||
* @param evaluationContext The current selection session data.
|
||||
* @return SENDER_WITH_PREVIOUS_TX_NOT_SELECTED if there is a nonce gas for this sender
|
||||
*/
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults ignored) {
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
final var sender = evaluationContext.getTransaction().getSender();
|
||||
if (skippedSenders.contains(sender)) {
|
||||
final var skippedNonce = skippedSenderNonceMap.get(sender);
|
||||
if (nonceGap(evaluationContext, skippedNonce)) {
|
||||
LOG.atTrace()
|
||||
.setMessage("Not selecting tx {} since its sender {} is in the skip list")
|
||||
.setMessage("Not selecting tx {} since its sender {} is in the skip list with nonce {}")
|
||||
.addArgument(() -> evaluationContext.getPendingTransaction().toTraceLog())
|
||||
.addArgument(sender)
|
||||
.addArgument(skippedNonce)
|
||||
.log();
|
||||
|
||||
return TransactionSelectionResult.SENDER_WITH_PREVIOUS_TX_NOT_SELECTED;
|
||||
@@ -52,10 +59,14 @@ public class SkipSenderTransactionSelector extends AbstractTransactionSelector {
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
}
|
||||
|
||||
private static boolean nonceGap(
|
||||
final TransactionEvaluationContext evaluationContext, final Long skippedNonce) {
|
||||
return skippedNonce != null && evaluationContext.getTransaction().getNonce() > skippedNonce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
// All necessary checks were done in the pre-processing method, so nothing to do here.
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
@@ -66,7 +77,7 @@ public class SkipSenderTransactionSelector extends AbstractTransactionSelector {
|
||||
* same sender, since it will never be selected due to the nonce gap, so we add the sender to the
|
||||
* skip list.
|
||||
*
|
||||
* @param evaluationContext The current selection context
|
||||
* @param evaluationContext The current evaluation context
|
||||
* @param transactionSelectionResult The transaction selection result
|
||||
*/
|
||||
@Override
|
||||
@@ -74,7 +85,27 @@ public class SkipSenderTransactionSelector extends AbstractTransactionSelector {
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResult transactionSelectionResult) {
|
||||
final var sender = evaluationContext.getTransaction().getSender();
|
||||
skippedSenders.add(sender);
|
||||
LOG.trace("Sender {} added to the skip list", sender);
|
||||
final var nonce = evaluationContext.getTransaction().getNonce();
|
||||
skippedSenderNonceMap.put(sender, nonce);
|
||||
LOG.trace("Sender {} added to the skip list with nonce {}", sender, nonce);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a transaction is selected we can remove it from the list. This could happen when the same
|
||||
* pending tx is present both in the internal pool and the plugin pool, and for example it is not
|
||||
* selected by the plugin but could be later selected from the internal pool.
|
||||
*
|
||||
* @param evaluationContext The current evaluation context
|
||||
* @param processingResult The transaction processing result
|
||||
*/
|
||||
@Override
|
||||
public void onTransactionSelected(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
final var sender = evaluationContext.getTransaction().getSender();
|
||||
final var skippedNonce = skippedSenderNonceMap.remove(sender);
|
||||
if (skippedNonce != null) {
|
||||
LOG.trace("Sender {} removed from the skip list, skipped nonce was {}", sender, skippedNonce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ import org.hyperledger.besu.plugin.services.MetricsSystem;
|
||||
import org.hyperledger.besu.plugin.services.TransactionSelectionService;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
import org.hyperledger.besu.plugin.services.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.services.TransactionSelectionServiceImpl;
|
||||
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
|
||||
@@ -648,33 +649,41 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
final Transaction notSelectedInvalid = createTransaction(2, Wei.of(10), 21_000, SENDER2);
|
||||
ensureTransactionIsValid(notSelectedInvalid, 21_000, 0);
|
||||
|
||||
final PluginTransactionSelectorFactory transactionSelectorFactory =
|
||||
() ->
|
||||
new PluginTransactionSelector() {
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
if (evaluationContext
|
||||
.getPendingTransaction()
|
||||
.getTransaction()
|
||||
.equals(notSelectedTransient))
|
||||
return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT;
|
||||
if (evaluationContext
|
||||
.getPendingTransaction()
|
||||
.getTransaction()
|
||||
.equals(notSelectedInvalid))
|
||||
return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID;
|
||||
return SELECTED;
|
||||
}
|
||||
final PluginTransactionSelector pluginTransactionSelector =
|
||||
new PluginTransactionSelector() {
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
if (evaluationContext
|
||||
.getPendingTransaction()
|
||||
.getTransaction()
|
||||
.equals(notSelectedTransient))
|
||||
return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT;
|
||||
if (evaluationContext
|
||||
.getPendingTransaction()
|
||||
.getTransaction()
|
||||
.equals(notSelectedInvalid))
|
||||
return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID;
|
||||
return SELECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final org.hyperledger.besu.plugin.data.TransactionProcessingResult processingResult) {
|
||||
return SELECTED;
|
||||
}
|
||||
};
|
||||
|
||||
final PluginTransactionSelectorFactory transactionSelectorFactory =
|
||||
new PluginTransactionSelectorFactory() {
|
||||
@Override
|
||||
public PluginTransactionSelector create(
|
||||
final SelectorsStateManager selectorsStateManager) {
|
||||
return pluginTransactionSelector;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final org.hyperledger.besu.plugin.data.TransactionProcessingResult
|
||||
processingResult) {
|
||||
return SELECTED;
|
||||
}
|
||||
};
|
||||
transactionSelectionService.registerPluginTransactionSelectorFactory(
|
||||
transactionSelectorFactory);
|
||||
|
||||
@@ -717,30 +726,35 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
final Transaction notSelected = createTransaction(1, Wei.of(10), 30_000);
|
||||
ensureTransactionIsValid(notSelected, maxGasUsedByTransaction + 1, 0);
|
||||
|
||||
final Transaction selected3 = createTransaction(3, Wei.of(10), 21_000);
|
||||
ensureTransactionIsValid(selected3, maxGasUsedByTransaction, 0);
|
||||
final PluginTransactionSelector pluginTransactionSelector =
|
||||
new PluginTransactionSelector() {
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
return SELECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final org.hyperledger.besu.plugin.data.TransactionProcessingResult processingResult) {
|
||||
// the transaction with max gas +1 should fail
|
||||
if (processingResult.getEstimateGasUsedByTransaction() > maxGasUsedByTransaction) {
|
||||
return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT;
|
||||
}
|
||||
return SELECTED;
|
||||
}
|
||||
};
|
||||
|
||||
final PluginTransactionSelectorFactory transactionSelectorFactory =
|
||||
() ->
|
||||
new PluginTransactionSelector() {
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext) {
|
||||
return SELECTED;
|
||||
}
|
||||
new PluginTransactionSelectorFactory() {
|
||||
@Override
|
||||
public PluginTransactionSelector create(
|
||||
final SelectorsStateManager selectorsStateManager) {
|
||||
return pluginTransactionSelector;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final org.hyperledger.besu.plugin.data.TransactionProcessingResult
|
||||
processingResult) {
|
||||
// the transaction with max gas +1 should fail
|
||||
if (processingResult.getEstimateGasUsedByTransaction() > maxGasUsedByTransaction) {
|
||||
return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT;
|
||||
}
|
||||
return SELECTED;
|
||||
}
|
||||
};
|
||||
transactionSelectionService.registerPluginTransactionSelectorFactory(
|
||||
transactionSelectorFactory);
|
||||
|
||||
@@ -776,7 +790,7 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
PluginTransactionSelector transactionSelector = mock(PluginTransactionSelector.class);
|
||||
when(transactionSelector.evaluateTransactionPreProcessing(any())).thenReturn(SELECTED);
|
||||
when(transactionSelector.evaluateTransactionPostProcessing(any(), any())).thenReturn(SELECTED);
|
||||
when(transactionSelectorFactory.create()).thenReturn(transactionSelector);
|
||||
when(transactionSelectorFactory.create(any())).thenReturn(transactionSelector);
|
||||
|
||||
transactionSelectionService.registerPluginTransactionSelectorFactory(
|
||||
transactionSelectorFactory);
|
||||
@@ -1070,7 +1084,7 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
|
||||
final PluginTransactionSelectorFactory transactionSelectorFactory =
|
||||
mock(PluginTransactionSelectorFactory.class);
|
||||
when(transactionSelectorFactory.create()).thenReturn(transactionSelector);
|
||||
when(transactionSelectorFactory.create(any())).thenReturn(transactionSelector);
|
||||
|
||||
transactionSelectionService.registerPluginTransactionSelectorFactory(
|
||||
transactionSelectorFactory);
|
||||
@@ -1233,7 +1247,7 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
|
||||
final PluginTransactionSelectorFactory transactionSelectorFactory =
|
||||
mock(PluginTransactionSelectorFactory.class);
|
||||
when(transactionSelectorFactory.create()).thenReturn(transactionSelector);
|
||||
when(transactionSelectorFactory.create(any())).thenReturn(transactionSelector);
|
||||
|
||||
transactionSelectionService.registerPluginTransactionSelectorFactory(
|
||||
transactionSelectorFactory);
|
||||
@@ -1320,6 +1334,7 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
final Wei blobGasPrice,
|
||||
final TransactionSelectionService transactionSelectionService) {
|
||||
|
||||
final var selectorsStateManager = new SelectorsStateManager();
|
||||
final BlockTransactionSelector selector =
|
||||
new BlockTransactionSelector(
|
||||
miningConfiguration,
|
||||
@@ -1336,8 +1351,9 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
new LondonGasCalculator(),
|
||||
GasLimitCalculator.constant(),
|
||||
protocolSchedule.getByBlockHeader(blockHeader).getBlockHashProcessor(),
|
||||
transactionSelectionService.createPluginTransactionSelector(),
|
||||
ethScheduler);
|
||||
transactionSelectionService.createPluginTransactionSelector(selectorsStateManager),
|
||||
ethScheduler,
|
||||
selectorsStateManager);
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOBS_FULL;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_BLOB_GAS;
|
||||
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.hyperledger.besu.crypto.KeyPair;
|
||||
@@ -36,12 +37,13 @@ import org.hyperledger.besu.datatypes.VersionedHash;
|
||||
import org.hyperledger.besu.datatypes.Wei;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.IntStream;
|
||||
@@ -50,6 +52,7 @@ import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
import org.apache.tuweni.bytes.Bytes48;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
@@ -68,86 +71,103 @@ class BlobSizeTransactionSelectorTest {
|
||||
@Mock(answer = RETURNS_DEEP_STUBS)
|
||||
BlockSelectionContext blockSelectionContext;
|
||||
|
||||
@Mock TransactionSelectionResults selectionResults;
|
||||
@Mock TransactionProcessingResult transactionProcessingResult;
|
||||
|
||||
SelectorsStateManager selectorsStateManager;
|
||||
BlobSizeTransactionSelector selector;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(blockSelectionContext.gasLimitCalculator().currentBlobGasLimit()).thenReturn(MAX_BLOB_GAS);
|
||||
when(blockSelectionContext.gasCalculator().blobGasCost(anyLong()))
|
||||
.thenAnswer(iom -> BLOB_GAS_PER_BLOB * iom.getArgument(0, Long.class));
|
||||
|
||||
selectorsStateManager = new SelectorsStateManager();
|
||||
selector = new BlobSizeTransactionSelector(blockSelectionContext, selectorsStateManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void notBlobTransactionsAreSelectedWithoutAnyCheck() {
|
||||
final var selector = new BlobSizeTransactionSelector(blockSelectionContext);
|
||||
|
||||
final var nonBlobTx = createEIP1559PendingTransaction();
|
||||
void notBlobTransactionsAreAlwaysSelected() {
|
||||
// this tx fills all the available blob space
|
||||
final var firstBlobTx = createBlobPendingTransaction(MAX_BLOBS);
|
||||
|
||||
final var txEvaluationContext =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), nonBlobTx, null, null, null);
|
||||
blockSelectionContext.pendingBlockHeader(), firstBlobTx, null, null, null);
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertSelected(txEvaluationContext);
|
||||
|
||||
final var result =
|
||||
selector.evaluateTransactionPreProcessing(txEvaluationContext, selectionResults);
|
||||
assertThat(result).isEqualTo(TransactionSelectionResult.SELECTED);
|
||||
verifyNoInteractions(selectionResults);
|
||||
// this non blob tx is selected regardless the blob space is already filled
|
||||
final var nonBlobTx = createEIP1559PendingTransaction();
|
||||
|
||||
final var nonBlobTxEvaluationContext =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), nonBlobTx, null, null, null);
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertSelected(nonBlobTxEvaluationContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
void firstBlobTransactionIsSelected() {
|
||||
when(blockSelectionContext.gasLimitCalculator().currentBlobGasLimit()).thenReturn(MAX_BLOB_GAS);
|
||||
when(blockSelectionContext.gasCalculator().blobGasCost(anyLong()))
|
||||
.thenAnswer(iom -> BLOB_GAS_PER_BLOB * iom.getArgument(0, Long.class));
|
||||
|
||||
final var selector = new BlobSizeTransactionSelector(blockSelectionContext);
|
||||
|
||||
final var firstBlobTx = createBlobPendingTransaction(MAX_BLOBS);
|
||||
|
||||
final var txEvaluationContext =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), firstBlobTx, null, null, null);
|
||||
|
||||
when(selectionResults.getCumulativeBlobGasUsed()).thenReturn(0L);
|
||||
|
||||
final var result =
|
||||
selector.evaluateTransactionPreProcessing(txEvaluationContext, selectionResults);
|
||||
assertThat(result).isEqualTo(TransactionSelectionResult.SELECTED);
|
||||
verify(selectionResults).getCumulativeBlobGasUsed();
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertSelected(txEvaluationContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsBlobsFullWhenMaxNumberOfBlobsAlreadyPresent() {
|
||||
when(blockSelectionContext.gasLimitCalculator().currentBlobGasLimit()).thenReturn(MAX_BLOB_GAS);
|
||||
|
||||
final var selector = new BlobSizeTransactionSelector(blockSelectionContext);
|
||||
|
||||
final var firstBlobTx = createBlobPendingTransaction(1);
|
||||
|
||||
final var txEvaluationContext =
|
||||
final var blobTx1 = createBlobPendingTransaction(MAX_BLOBS);
|
||||
final var txEvaluationContext1 =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), firstBlobTx, null, null, null);
|
||||
blockSelectionContext.pendingBlockHeader(), blobTx1, null, null, null);
|
||||
|
||||
when(selectionResults.getCumulativeBlobGasUsed()).thenReturn(MAX_BLOB_GAS);
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertSelected(txEvaluationContext1);
|
||||
|
||||
final var result =
|
||||
selector.evaluateTransactionPreProcessing(txEvaluationContext, selectionResults);
|
||||
assertThat(result).isEqualTo(TransactionSelectionResult.BLOBS_FULL);
|
||||
verify(selectionResults).getCumulativeBlobGasUsed();
|
||||
final var blobTx2 = createBlobPendingTransaction(1);
|
||||
final var txEvaluationContext2 =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), blobTx2, null, null, null);
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertNotSelected(txEvaluationContext2, BLOBS_FULL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsTooLargeForRemainingBlobGas() {
|
||||
when(blockSelectionContext.gasLimitCalculator().currentBlobGasLimit()).thenReturn(MAX_BLOB_GAS);
|
||||
when(blockSelectionContext.gasCalculator().blobGasCost(anyLong()))
|
||||
.thenAnswer(iom -> BLOB_GAS_PER_BLOB * iom.getArgument(0, Long.class));
|
||||
|
||||
final var selector = new BlobSizeTransactionSelector(blockSelectionContext);
|
||||
|
||||
final var firstBlobTx = createBlobPendingTransaction(MAX_BLOBS);
|
||||
|
||||
final var txEvaluationContext =
|
||||
// first tx only fill the space for one blob leaving space max MAX_BLOB_GAS-1 blobs to be added
|
||||
// later
|
||||
final var blobTx1 = createBlobPendingTransaction(1);
|
||||
final var txEvaluationContext1 =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), firstBlobTx, null, null, null);
|
||||
blockSelectionContext.pendingBlockHeader(), blobTx1, null, null, null);
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertSelected(txEvaluationContext1);
|
||||
|
||||
when(selectionResults.getCumulativeBlobGasUsed()).thenReturn(MAX_BLOB_GAS - 1);
|
||||
final var blobTx2 = createBlobPendingTransaction(MAX_BLOBS);
|
||||
final var txEvaluationContext2 =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), blobTx2, null, null, null);
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertNotSelected(txEvaluationContext2, TX_TOO_LARGE_FOR_REMAINING_BLOB_GAS);
|
||||
}
|
||||
|
||||
final var result =
|
||||
selector.evaluateTransactionPreProcessing(txEvaluationContext, selectionResults);
|
||||
assertThat(result).isEqualTo(TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_BLOB_GAS);
|
||||
verify(selectionResults).getCumulativeBlobGasUsed();
|
||||
private void evaluateAndAssertSelected(final TransactionEvaluationContext txEvaluationContext) {
|
||||
assertThat(selector.evaluateTransactionPreProcessing(txEvaluationContext)).isEqualTo(SELECTED);
|
||||
assertThat(
|
||||
selector.evaluateTransactionPostProcessing(
|
||||
txEvaluationContext, transactionProcessingResult))
|
||||
.isEqualTo(SELECTED);
|
||||
}
|
||||
|
||||
private void evaluateAndAssertNotSelected(
|
||||
final TransactionEvaluationContext txEvaluationContext,
|
||||
final TransactionSelectionResult preProcessedResult) {
|
||||
assertThat(selector.evaluateTransactionPreProcessing(txEvaluationContext))
|
||||
.isEqualTo(preProcessedResult);
|
||||
}
|
||||
|
||||
private PendingTransaction createEIP1559PendingTransaction() {
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright contributors to Besu.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_FULL;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_OCCUPANCY_ABOVE_THRESHOLD;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_GAS;
|
||||
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.hyperledger.besu.crypto.KeyPair;
|
||||
import org.hyperledger.besu.crypto.SignatureAlgorithm;
|
||||
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
|
||||
import org.hyperledger.besu.datatypes.Address;
|
||||
import org.hyperledger.besu.datatypes.TransactionType;
|
||||
import org.hyperledger.besu.datatypes.Wei;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
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 BlockSizeTransactionSelectorTest {
|
||||
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
|
||||
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
|
||||
private static final KeyPair KEYS = SIGNATURE_ALGORITHM.get().generateKeyPair();
|
||||
private static final long TRANSFER_GAS_LIMIT = 21_000L;
|
||||
private static final long BLOCK_GAS_LIMIT = 1_000_000L;
|
||||
|
||||
@Mock(answer = RETURNS_DEEP_STUBS)
|
||||
BlockSelectionContext blockSelectionContext;
|
||||
|
||||
SelectorsStateManager selectorsStateManager;
|
||||
BlockSizeTransactionSelector selector;
|
||||
MiningConfiguration miningConfiguration;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
miningConfiguration = MiningConfiguration.newDefault();
|
||||
when(blockSelectionContext.pendingBlockHeader().getGasLimit()).thenReturn(BLOCK_GAS_LIMIT);
|
||||
when(blockSelectionContext.miningConfiguration()).thenReturn(miningConfiguration);
|
||||
|
||||
selectorsStateManager = new SelectorsStateManager();
|
||||
selector = new BlockSizeTransactionSelector(blockSelectionContext, selectorsStateManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleTransactionBelowBlockGasLimitIsSelected() {
|
||||
final var tx = createPendingTransaction(TRANSFER_GAS_LIMIT);
|
||||
|
||||
final var txEvaluationContext =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), tx, null, null, null);
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertSelected(txEvaluationContext, remainingGas(0));
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(TRANSFER_GAS_LIMIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleTransactionAboveBlockGasLimitIsNotSelected() {
|
||||
final var tx = createPendingTransaction(BLOCK_GAS_LIMIT + 1);
|
||||
|
||||
final var txEvaluationContext =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), tx, null, null, null);
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertNotSelected(txEvaluationContext, TX_TOO_LARGE_FOR_REMAINING_GAS);
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void correctlyCumulatesOnlyTheEffectiveGasUsedAfterProcessing() {
|
||||
final var tx = createPendingTransaction(TRANSFER_GAS_LIMIT * 2);
|
||||
final long remainingGas = 100;
|
||||
|
||||
final var txEvaluationContext =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), tx, null, null, null);
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
evaluateAndAssertSelected(txEvaluationContext, remainingGas(remainingGas));
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(TRANSFER_GAS_LIMIT * 2 - remainingGas);
|
||||
}
|
||||
|
||||
@Test
|
||||
void moreTransactionsBelowBlockGasLimitAreSelected() {
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
|
||||
final int txCount = 10;
|
||||
|
||||
IntStream.range(0, txCount)
|
||||
.forEach(
|
||||
unused -> {
|
||||
final var tx = createPendingTransaction(TRANSFER_GAS_LIMIT);
|
||||
|
||||
final var txEvaluationContext =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), tx, null, null, null);
|
||||
evaluateAndAssertSelected(txEvaluationContext, remainingGas(0));
|
||||
});
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(TRANSFER_GAS_LIMIT * txCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void moreTransactionsThanBlockCanFitOnlySomeAreSelected() {
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
|
||||
final int txCount = 10;
|
||||
|
||||
IntStream.range(0, txCount)
|
||||
.forEach(
|
||||
unused -> {
|
||||
final var tx = createPendingTransaction(TRANSFER_GAS_LIMIT);
|
||||
|
||||
final var txEvaluationContext =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), tx, null, null, null);
|
||||
evaluateAndAssertSelected(txEvaluationContext, remainingGas(0));
|
||||
});
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(TRANSFER_GAS_LIMIT * txCount);
|
||||
|
||||
// last tx is too big for the remaining gas
|
||||
final long tooBigGasLimit = BLOCK_GAS_LIMIT - (TRANSFER_GAS_LIMIT * txCount) + 1;
|
||||
|
||||
final var bigTxEvaluationContext =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(),
|
||||
createPendingTransaction(tooBigGasLimit),
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
evaluateAndAssertNotSelected(bigTxEvaluationContext, TX_TOO_LARGE_FOR_REMAINING_GAS);
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(TRANSFER_GAS_LIMIT * txCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void identifyWhenBlockOccupancyIsAboveThreshold() {
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
|
||||
// create 2 txs with a gas limit just above the min block occupancy ratio
|
||||
// so the first is accepted while the second not
|
||||
final long justAboveOccupancyRatioGasLimit =
|
||||
(long) (BLOCK_GAS_LIMIT * miningConfiguration.getMinBlockOccupancyRatio()) + 100;
|
||||
final var tx1 = createPendingTransaction(justAboveOccupancyRatioGasLimit);
|
||||
|
||||
final var txEvaluationContext1 =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), tx1, null, null, null);
|
||||
evaluateAndAssertSelected(txEvaluationContext1, remainingGas(0));
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(justAboveOccupancyRatioGasLimit);
|
||||
|
||||
final var tx2 = createPendingTransaction(justAboveOccupancyRatioGasLimit);
|
||||
|
||||
final var txEvaluationContext2 =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), tx2, null, null, null);
|
||||
evaluateAndAssertNotSelected(txEvaluationContext2, BLOCK_OCCUPANCY_ABOVE_THRESHOLD);
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(justAboveOccupancyRatioGasLimit);
|
||||
}
|
||||
|
||||
@Test
|
||||
void identifyWhenBlockIsFull() {
|
||||
when(blockSelectionContext.gasCalculator().getMinimumTransactionCost())
|
||||
.thenReturn(TRANSFER_GAS_LIMIT);
|
||||
|
||||
selectorsStateManager.blockSelectionStarted();
|
||||
|
||||
// allow to completely fill the block
|
||||
miningConfiguration.setMinBlockOccupancyRatio(1.0);
|
||||
|
||||
// create 2 txs, where the first fill the block leaving less gas than the min required by a
|
||||
// transfer
|
||||
final long fillBlockGasLimit = BLOCK_GAS_LIMIT - TRANSFER_GAS_LIMIT + 1;
|
||||
final var tx1 = createPendingTransaction(fillBlockGasLimit);
|
||||
|
||||
final var txEvaluationContext1 =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), tx1, null, null, null);
|
||||
evaluateAndAssertSelected(txEvaluationContext1, remainingGas(0));
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(fillBlockGasLimit);
|
||||
|
||||
final var tx2 = createPendingTransaction(TRANSFER_GAS_LIMIT);
|
||||
|
||||
final var txEvaluationContext2 =
|
||||
new TransactionEvaluationContext(
|
||||
blockSelectionContext.pendingBlockHeader(), tx2, null, null, null);
|
||||
evaluateAndAssertNotSelected(txEvaluationContext2, BLOCK_FULL);
|
||||
|
||||
assertThat(selector.getWorkingState()).isEqualTo(fillBlockGasLimit);
|
||||
}
|
||||
|
||||
private void evaluateAndAssertSelected(
|
||||
final TransactionEvaluationContext txEvaluationContext,
|
||||
final TransactionProcessingResult transactionProcessingResult) {
|
||||
assertThat(selector.evaluateTransactionPreProcessing(txEvaluationContext)).isEqualTo(SELECTED);
|
||||
assertThat(
|
||||
selector.evaluateTransactionPostProcessing(
|
||||
txEvaluationContext, transactionProcessingResult))
|
||||
.isEqualTo(SELECTED);
|
||||
}
|
||||
|
||||
private void evaluateAndAssertNotSelected(
|
||||
final TransactionEvaluationContext txEvaluationContext,
|
||||
final TransactionSelectionResult preProcessedResult) {
|
||||
assertThat(selector.evaluateTransactionPreProcessing(txEvaluationContext))
|
||||
.isEqualTo(preProcessedResult);
|
||||
}
|
||||
|
||||
private PendingTransaction createPendingTransaction(final long gasLimit) {
|
||||
return PendingTransaction.newPendingTransaction(
|
||||
createTransaction(TransactionType.EIP1559, gasLimit), false, false);
|
||||
}
|
||||
|
||||
private Transaction createTransaction(final TransactionType type, final long gasLimit) {
|
||||
|
||||
var tx =
|
||||
new TransactionTestFixture()
|
||||
.to(Optional.of(Address.fromHexString("0x634316eA0EE79c701c6F67C53A4C54cBAfd2316d")))
|
||||
.nonce(0)
|
||||
.gasLimit(gasLimit)
|
||||
.type(type)
|
||||
.maxFeePerGas(Optional.of(Wei.of(1000)))
|
||||
.maxPriorityFeePerGas(Optional.of(Wei.of(100)));
|
||||
|
||||
return tx.createTransaction(KEYS);
|
||||
}
|
||||
|
||||
private TransactionProcessingResult remainingGas(final long remainingGas) {
|
||||
final var txProcessingResult = mock(TransactionProcessingResult.class);
|
||||
when(txProcessingResult.getGasRemaining()).thenReturn(remainingGas);
|
||||
return txProcessingResult;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.hyperledger.besu.ethereum.blockcreation;
|
||||
package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -22,8 +22,6 @@ import static org.mockito.Mockito.when;
|
||||
import org.hyperledger.besu.datatypes.Wei;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.AbstractTransactionSelector;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.MinPriorityFeePerGasTransactionSelector;
|
||||
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
|
||||
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
@@ -85,8 +83,7 @@ public class MinPriorityFeePerGasTransactionSelectorTest {
|
||||
private void assertSelectionResult(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResult expectedResult) {
|
||||
var actualResult =
|
||||
transactionSelector.evaluateTransactionPreProcessing(evaluationContext, null);
|
||||
var actualResult = transactionSelector.evaluateTransactionPreProcessing(evaluationContext);
|
||||
assertThat(actualResult).isEqualTo(expectedResult);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,12 @@ package org.hyperledger.besu.ethereum.core;
|
||||
|
||||
import org.hyperledger.besu.datatypes.Address;
|
||||
import org.hyperledger.besu.datatypes.Wei;
|
||||
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader;
|
||||
import org.hyperledger.besu.plugin.services.TransactionSelectionService;
|
||||
import org.hyperledger.besu.plugin.services.txselection.BlockTransactionSelectionService;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
import org.hyperledger.besu.util.number.PositiveNumber;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -168,10 +171,16 @@ public abstract class MiningConfiguration {
|
||||
public TransactionSelectionService getTransactionSelectionService() {
|
||||
return new TransactionSelectionService() {
|
||||
@Override
|
||||
public PluginTransactionSelector createPluginTransactionSelector() {
|
||||
public PluginTransactionSelector createPluginTransactionSelector(
|
||||
final SelectorsStateManager selectorsStateManager) {
|
||||
return PluginTransactionSelector.ACCEPT_ALL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectPendingTransactions(
|
||||
final BlockTransactionSelectionService selectionService,
|
||||
final ProcessableBlockHeader pendingBlockHeader) {}
|
||||
|
||||
@Override
|
||||
public void registerPluginTransactionSelectorFactory(
|
||||
final PluginTransactionSelectorFactory transactionSelectorFactory) {}
|
||||
|
||||
@@ -71,7 +71,7 @@ Calculated : ${currentHash}
|
||||
tasks.register('checkAPIChanges', FileStateChecker) {
|
||||
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
|
||||
files = sourceSets.main.allJava.files
|
||||
knownHash = 'I2IrN2aLU610031Vw8xNr3hcT8/wb25pDrclwZUggE4='
|
||||
knownHash = 'D2ZMRGb2HS/9FgDE2mcizdxTQhFsGD4LS9lgngG/TnU='
|
||||
}
|
||||
check.dependsOn('checkAPIChanges')
|
||||
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
package org.hyperledger.besu.plugin.services;
|
||||
|
||||
import org.hyperledger.besu.plugin.Unstable;
|
||||
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader;
|
||||
import org.hyperledger.besu.plugin.services.txselection.BlockTransactionSelectionService;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
|
||||
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory;
|
||||
import org.hyperledger.besu.plugin.services.txselection.SelectorsStateManager;
|
||||
|
||||
/** Transaction selection service interface */
|
||||
@Unstable
|
||||
@@ -25,9 +28,23 @@ public interface TransactionSelectionService extends BesuService {
|
||||
/**
|
||||
* Create a transaction selector plugin
|
||||
*
|
||||
* @param selectorsStateManager the selectors state manager
|
||||
* @return the transaction selector plugin
|
||||
*/
|
||||
PluginTransactionSelector createPluginTransactionSelector();
|
||||
PluginTransactionSelector createPluginTransactionSelector(
|
||||
SelectorsStateManager selectorsStateManager);
|
||||
|
||||
/**
|
||||
* Called during the block creation to allow plugins to propose their own pending transactions for
|
||||
* block inclusion
|
||||
*
|
||||
* @param blockTransactionSelectionService the service used by the plugin to evaluate pending
|
||||
* transactions and commit or rollback changes
|
||||
* @param pendingBlockHeader the header of the block being created
|
||||
*/
|
||||
void selectPendingTransactions(
|
||||
BlockTransactionSelectionService blockTransactionSelectionService,
|
||||
final ProcessableBlockHeader pendingBlockHeader);
|
||||
|
||||
/**
|
||||
* Registers the transaction selector factory with the service
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright contributors to Besu.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.hyperledger.besu.plugin.services.txselection;
|
||||
|
||||
/**
|
||||
* This class represents an abstract plugin transaction selector which provides methods to manage
|
||||
* the selector state.
|
||||
*
|
||||
* @param <S> The type of the state used by the selector
|
||||
*/
|
||||
public abstract class AbstractStatefulPluginTransactionSelector<S>
|
||||
implements PluginTransactionSelector {
|
||||
private final SelectorsStateManager selectorsStateManager;
|
||||
|
||||
/**
|
||||
* Initialize the plugin state to an initial value
|
||||
*
|
||||
* @param selectorsStateManager the selectors state manager
|
||||
* @param initialState the initial value of the state
|
||||
* @param stateDuplicator the function that duplicates the state
|
||||
*/
|
||||
public AbstractStatefulPluginTransactionSelector(
|
||||
final SelectorsStateManager selectorsStateManager,
|
||||
final S initialState,
|
||||
final SelectorsStateManager.StateDuplicator<S> stateDuplicator) {
|
||||
this.selectorsStateManager = selectorsStateManager;
|
||||
selectorsStateManager.createSelectorState(this, initialState, stateDuplicator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the working state for this selector. A working state contains changes that have not yet
|
||||
* committed
|
||||
*
|
||||
* @return the working state of this selector
|
||||
*/
|
||||
protected S getWorkingState() {
|
||||
return selectorsStateManager.getSelectorWorkingState(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the working state for this selector. A working state contains changes that have not yet
|
||||
* commited
|
||||
*
|
||||
* @param newState the new working state of this selector
|
||||
*/
|
||||
protected void setWorkingState(final S newState) {
|
||||
selectorsStateManager.setSelectorWorkingState(this, newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the commited state for this selector. A commited state contains changes that have been
|
||||
* commited
|
||||
*
|
||||
* @return the commited state of this selector
|
||||
*/
|
||||
protected S getCommitedState() {
|
||||
return selectorsStateManager.getSelectorCommittedState(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright contributors to Besu.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.hyperledger.besu.plugin.services.txselection;
|
||||
|
||||
import org.hyperledger.besu.datatypes.PendingTransaction;
|
||||
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
|
||||
/**
|
||||
* A service that can be used by plugins to include their pending transactions during block
|
||||
* creation. Proposed pending transactions need to be first evaluated and based on the result of the
|
||||
* evaluation of one or more pending transactions the plugin can apply its logic to commit or not
|
||||
* the inclusion of the evaluated in the block.
|
||||
*
|
||||
* <p>The process of including plugin proposed pending transactions starts when {@link
|
||||
* PluginTransactionSelectorFactory#selectPendingTransactions(BlockTransactionSelectionService,
|
||||
* ProcessableBlockHeader)} is called.
|
||||
*/
|
||||
public interface BlockTransactionSelectionService {
|
||||
|
||||
/**
|
||||
* Evaluate a plugin proposed pending transaction for block inclusion
|
||||
*
|
||||
* @param pendingTransaction the pending transaction proposed by the plugin
|
||||
* @return the result of the evaluation
|
||||
*/
|
||||
TransactionSelectionResult evaluatePendingTransaction(PendingTransaction pendingTransaction);
|
||||
|
||||
/**
|
||||
* Commit the changes applied by all the evaluated pending transactions since the previous commit.
|
||||
* As part of this {@link PluginTransactionSelector} state of commited.
|
||||
*
|
||||
* @return false only if a timeout occurred during the selection of the pending transaction,
|
||||
* meaning that the pending transaction is not included in the current block
|
||||
*/
|
||||
boolean commit();
|
||||
|
||||
/**
|
||||
* Rollback the changes applied by all the evaluated pending transactions since the previous
|
||||
* commit.
|
||||
*
|
||||
* <p>As part of this {@link PluginTransactionSelector} state of rolled back.
|
||||
*/
|
||||
void rollback();
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer;
|
||||
|
||||
/** Interface for the transaction selector */
|
||||
@Unstable
|
||||
public interface PluginTransactionSelector {
|
||||
public interface PluginTransactionSelector extends TransactionSelector {
|
||||
/** Plugin transaction selector that unconditionally select every transaction */
|
||||
PluginTransactionSelector ACCEPT_ALL =
|
||||
new PluginTransactionSelector() {
|
||||
|
||||
@@ -15,15 +15,39 @@
|
||||
package org.hyperledger.besu.plugin.services.txselection;
|
||||
|
||||
import org.hyperledger.besu.plugin.Unstable;
|
||||
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader;
|
||||
|
||||
/** Interface for a factory that creates transaction selectors */
|
||||
/**
|
||||
* Interface for a factory that creates transaction selector and propose pending transaction for
|
||||
* block inclusion
|
||||
*/
|
||||
@Unstable
|
||||
public interface PluginTransactionSelectorFactory {
|
||||
/**
|
||||
* A default factory that does not propose any pending transactions and does not apply any filter
|
||||
*/
|
||||
PluginTransactionSelectorFactory NO_OP_FACTORY = new PluginTransactionSelectorFactory() {};
|
||||
|
||||
/**
|
||||
* Create a transaction selector
|
||||
* Create a plugin transaction selector, that can be used during block creation to apply custom
|
||||
* filters to proposed pending transactions
|
||||
*
|
||||
* @param selectorsStateManager the selectors state manager
|
||||
* @return the transaction selector
|
||||
*/
|
||||
PluginTransactionSelector create();
|
||||
default PluginTransactionSelector create(final SelectorsStateManager selectorsStateManager) {
|
||||
return PluginTransactionSelector.ACCEPT_ALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during the block creation to allow plugins to propose their own pending transactions for
|
||||
* block inclusion
|
||||
*
|
||||
* @param blockTransactionSelectionService the service used by the plugin to evaluate pending
|
||||
* transactions and commit or rollback changes
|
||||
* @param pendingBlockHeader the header of the block being created
|
||||
*/
|
||||
default void selectPendingTransactions(
|
||||
final BlockTransactionSelectionService blockTransactionSelectionService,
|
||||
final ProcessableBlockHeader pendingBlockHeader) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright contributors to Besu.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.hyperledger.besu.plugin.services.txselection;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Manages the state of transaction selectors (including the plugin transaction selector {@link
|
||||
* PluginTransactionSelector}) during the block creation process. Some selectors have a state, for
|
||||
* example the amount of gas used by selected pending transactions so far, and changes made to the
|
||||
* state must be only commited after the evaluated pending transaction has been definitely selected
|
||||
* for inclusion, until that point it will be always possible to rollback the changes to the state
|
||||
* and return the previous commited state.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class SelectorsStateManager {
|
||||
private final List<Map<TransactionSelector, SelectorState<?, ? extends StateDuplicator<?>>>>
|
||||
uncommitedStates = new ArrayList<>();
|
||||
private Map<TransactionSelector, SelectorState<?, ? extends StateDuplicator<?>>> committedState =
|
||||
new HashMap<>();
|
||||
private volatile boolean blockSelectionStarted = false;
|
||||
|
||||
/** Create an empty selectors state manager, here to make javadoc linter happy. */
|
||||
public SelectorsStateManager() {}
|
||||
|
||||
/**
|
||||
* Create, initialize and track the state for a selector.
|
||||
*
|
||||
* <p>Call to this method must be performed before the block selection is stated with {@link
|
||||
* #blockSelectionStarted()}, otherwise it fails.
|
||||
*
|
||||
* @param selector the selector
|
||||
* @param initialState the initial value of the state
|
||||
* @param duplicator the state duplicator
|
||||
* @param <S> the type of the selector state
|
||||
* @param <D> the type of the state duplicator
|
||||
*/
|
||||
public <S, D extends StateDuplicator<S>> void createSelectorState(
|
||||
final TransactionSelector selector, final S initialState, final D duplicator) {
|
||||
checkState(
|
||||
!blockSelectionStarted, "Cannot create selector state after block selection is started");
|
||||
committedState.put(selector, new SelectorState<>(initialState, duplicator));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the start of block selection, when the initialization is done, to prepare a new
|
||||
* working state based on the initial state.
|
||||
*
|
||||
* <p>After this method is called, it is not possible to call anymore {@link
|
||||
* #createSelectorState(TransactionSelector, Object, StateDuplicator)}
|
||||
*/
|
||||
public void blockSelectionStarted() {
|
||||
blockSelectionStarted = true;
|
||||
uncommitedStates.add(duplicateLastState());
|
||||
}
|
||||
|
||||
private Map<TransactionSelector, SelectorState<?, ? extends StateDuplicator<?>>>
|
||||
duplicateLastState() {
|
||||
return getLast().entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().duplicated()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the working state for the specified selector
|
||||
*
|
||||
* @param selector the selector
|
||||
* @return the working state of the selector
|
||||
* @param <T> the type of the selector state
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getSelectorWorkingState(final TransactionSelector selector) {
|
||||
return (T) uncommitedStates.getLast().get(selector).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* set the working state for the specified selector
|
||||
*
|
||||
* @param selector the selector
|
||||
* @param newState the new state
|
||||
* @param <T> the type of the selector state
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> void setSelectorWorkingState(final TransactionSelector selector, final T newState) {
|
||||
((SelectorState<T, StateDuplicator<T>>) uncommitedStates.getLast().get(selector)).set(newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the commited state for the specified selector
|
||||
*
|
||||
* @param selector the selector
|
||||
* @return the commited state of the selector
|
||||
* @param <T> the type of the selector state
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getSelectorCommittedState(final TransactionSelector selector) {
|
||||
return (T) committedState.get(selector).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the current working state and prepare a new working state based on the just commited
|
||||
* state
|
||||
*/
|
||||
public void commit() {
|
||||
committedState = getLast();
|
||||
uncommitedStates.clear();
|
||||
uncommitedStates.add(duplicateLastState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the current working state and prepare a new working state based on the just commited
|
||||
* state
|
||||
*/
|
||||
public void rollback() {
|
||||
uncommitedStates.clear();
|
||||
uncommitedStates.add(duplicateLastState());
|
||||
}
|
||||
|
||||
private Map<TransactionSelector, SelectorState<?, ? extends StateDuplicator<?>>> getLast() {
|
||||
if (uncommitedStates.isEmpty()) {
|
||||
return committedState;
|
||||
}
|
||||
return uncommitedStates.getLast();
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that create a duplicate of the input object. The duplication must be a deep copy.
|
||||
*
|
||||
* @param <T> the type of the object
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface StateDuplicator<T> extends UnaryOperator<T> {
|
||||
/**
|
||||
* Duplicate the input objet
|
||||
*
|
||||
* @param t the input object to duplicate
|
||||
* @return a deep copy of the input object
|
||||
*/
|
||||
T duplicate(T t);
|
||||
|
||||
@Override
|
||||
default T apply(final T t) {
|
||||
return duplicate(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to duplicate a long
|
||||
*
|
||||
* @param l a long
|
||||
* @return a copy of the long
|
||||
*/
|
||||
static long duplicateLong(final long l) {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A selector state object is one that is able to return, update and duplicate the state it
|
||||
* contains
|
||||
*
|
||||
* @param <S> the type of the state
|
||||
*/
|
||||
private static class SelectorState<S, D extends StateDuplicator<S>> {
|
||||
private final D duplicator;
|
||||
private S state;
|
||||
|
||||
/**
|
||||
* Create a selector state with the initial value
|
||||
*
|
||||
* @param initialState the initial initialState
|
||||
*/
|
||||
public SelectorState(final S initialState, final D duplicator) {
|
||||
this.state = initialState;
|
||||
this.duplicator = duplicator;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method that concrete classes must implement to create a deep copy of the state
|
||||
*
|
||||
* @return a new selector state with a deep copy of the state
|
||||
*/
|
||||
private SelectorState<S, D> duplicated() {
|
||||
return new SelectorState<>(duplicator.duplicate(state), duplicator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state
|
||||
*
|
||||
* @return the current state
|
||||
*/
|
||||
public S get() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the current state with the new one
|
||||
*
|
||||
* @param newState the new state
|
||||
*/
|
||||
public void set(final S newState) {
|
||||
this.state = newState;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright contributors to Hyperledger Besu.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.hyperledger.besu.plugin.services.txselection;
|
||||
|
||||
import org.hyperledger.besu.plugin.Unstable;
|
||||
|
||||
/** Interface for the transaction selector */
|
||||
@Unstable
|
||||
public interface TransactionSelector {}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.util.log4j.plugin;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.Logger;
|
||||
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
|
||||
import org.apache.logging.log4j.core.filter.AbstractFilter;
|
||||
import org.apache.logging.log4j.message.Message;
|
||||
|
||||
/** Matches a text in the stack trace */
|
||||
@Plugin(
|
||||
name = "StackTraceMatchFilter",
|
||||
category = "Core",
|
||||
elementType = "filter",
|
||||
printObject = true)
|
||||
public class StackTraceMatchFilter extends AbstractFilter {
|
||||
private final String text;
|
||||
|
||||
private StackTraceMatchFilter(final String text, final Result onMatch, final Result onMismatch) {
|
||||
super(onMatch, onMismatch);
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result filter(
|
||||
final Logger logger,
|
||||
final Level level,
|
||||
final Marker marker,
|
||||
final Object msg,
|
||||
final Throwable t) {
|
||||
return filter(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result filter(
|
||||
final Logger logger,
|
||||
final Level level,
|
||||
final Marker marker,
|
||||
final Message msg,
|
||||
final Throwable t) {
|
||||
return filter(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result filter(final LogEvent event) {
|
||||
return filter(event.getThrown());
|
||||
}
|
||||
|
||||
private Result filter(final Throwable t) {
|
||||
if (t != null) {
|
||||
return Arrays.stream(t.getStackTrace())
|
||||
.map(StackTraceElement::getClassName)
|
||||
.anyMatch(cn -> cn.contains(text))
|
||||
? onMatch
|
||||
: onMismatch;
|
||||
}
|
||||
return Result.NEUTRAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder
|
||||
*
|
||||
* @return a new builder
|
||||
*/
|
||||
@PluginBuilderFactory
|
||||
public static StackTraceMatchFilter.Builder newBuilder() {
|
||||
return new StackTraceMatchFilter.Builder();
|
||||
}
|
||||
|
||||
/** Builder for StackTraceMatchFilter */
|
||||
public static class Builder extends AbstractFilterBuilder<StackTraceMatchFilter.Builder>
|
||||
implements org.apache.logging.log4j.core.util.Builder<StackTraceMatchFilter> {
|
||||
@PluginBuilderAttribute private String text = "";
|
||||
|
||||
/** Default constructor */
|
||||
public Builder() {
|
||||
// here to make javadoc happy
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the string to match in the stack trace
|
||||
*
|
||||
* @param text the match string
|
||||
* @return this builder
|
||||
*/
|
||||
public StackTraceMatchFilter.Builder setMatchString(final String text) {
|
||||
this.text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StackTraceMatchFilter build() {
|
||||
return new StackTraceMatchFilter(this.text, this.getOnMatch(), this.getOnMismatch());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user