Skip to content

Commit

Permalink
[Plugin API] - TransactionSelector - Send TransactionSelectionResult …
Browse files Browse the repository at this point in the history
…to the plugin when not transaction is not selected (hyperledger#6010)

Signed-off-by: Gabriel-Trintinalia <[email protected]>
  • Loading branch information
Gabriel-Trintinalia authored Oct 10, 2023
1 parent e946276 commit a56a35f
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ public BlockTransactionSelector(
.orElse(AllAcceptingTransactionSelector.INSTANCE);
}

private List<AbstractTransactionSelector> createTransactionSelectors(
final BlockSelectionContext context) {
return List.of(
new BlockSizeTransactionSelector(context),
new PriceTransactionSelector(context),
new BlobPriceTransactionSelector(context),
new ProcessingResultTransactionSelector(context));
}

/**
* Builds a list of transactions for a block by iterating over all transactions in the
* PendingTransactions pool. This operation can be long-running and, if executed in a separate
Expand All @@ -139,16 +148,7 @@ public TransactionSelectionResults buildTransactionListForBlock() {
.setMessage("Transaction pool stats {}")
.addArgument(blockSelectionContext.transactionPool().logStats())
.log();
blockSelectionContext
.transactionPool()
.selectTransactions(
pendingTransaction -> {
final var res = evaluateTransaction(pendingTransaction);
if (!res.selected()) {
updateTransactionRejected(pendingTransaction, res);
}
return res;
});
blockSelectionContext.transactionPool().selectTransactions(this::evaluateTransaction);
LOG.atTrace()
.setMessage("Transaction selection result {}")
.addArgument(transactionSelectionResults::toTraceLog)
Expand All @@ -167,105 +167,40 @@ public TransactionSelectionResults buildTransactionListForBlock() {
*/
public TransactionSelectionResults evaluateTransactions(final List<Transaction> transactions) {
transactions.forEach(
transaction -> {
var pendingTransaction = new PendingTransaction.Local(transaction);
final var res = evaluateTransaction(pendingTransaction);
if (!res.selected()) {
updateTransactionRejected(pendingTransaction, res);
}
});
transaction -> evaluateTransaction(new PendingTransaction.Local(transaction)));
return transactionSelectionResults;
}

/*
/**
* 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 called until the block under construction is suitably
* full (in terms of gasLimit) and the provided transaction's gasLimit does not fit within
* the space remaining in the block.
* transactions are found which fill a block worth of gas. This function will continue to be
* called until the block under construction is suitably full (in terms of gasLimit) and the
* provided transaction's gasLimit does not fit within the space remaining in the block.
*
* @param pendingTransaction The transaction to be evaluated.
* @return The result of the transaction evaluation process.
* @throws CancellationException if the transaction selection process is cancelled.
*/
private TransactionSelectionResult evaluateTransaction(
final PendingTransaction pendingTransaction) {
if (isCancelled.get()) {
throw new CancellationException("Cancelled during transaction selection.");
}

final Transaction transaction = pendingTransaction.getTransaction();
checkCancellation();

TransactionSelectionResult selectionResult =
evaluateTransactionPreProcessing(pendingTransaction);
TransactionSelectionResult selectionResult = evaluatePreProcessing(pendingTransaction);
if (!selectionResult.selected()) {
return selectionResult;
return handleTransactionNotSelected(pendingTransaction, selectionResult);
}

final WorldUpdater worldStateUpdater = worldState.updater();
final BlockHashLookup blockHashLookup =
new CachingBlockHashLookup(blockSelectionContext.processableBlockHeader(), blockchain);

final TransactionProcessingResult effectiveResult =
transactionProcessor.processTransaction(
blockchain,
worldStateUpdater,
blockSelectionContext.processableBlockHeader(),
transaction,
blockSelectionContext.miningBeneficiary(),
blockHashLookup,
false,
TransactionValidationParams.mining(),
blockSelectionContext.blobGasPrice());
final TransactionProcessingResult processingResult =
processTransaction(pendingTransaction, worldStateUpdater);

var transactionWithProcessingContextResult =
evaluateTransactionPostProcessing(pendingTransaction, effectiveResult);
if (!transactionWithProcessingContextResult.selected()) {
return transactionWithProcessingContextResult;
var postProcessingSelectionResult =
evaluatePostProcessing(pendingTransaction, processingResult);
if (!postProcessingSelectionResult.selected()) {
return handleTransactionNotSelected(pendingTransaction, postProcessingSelectionResult);
}

final long gasUsedByTransaction = transaction.getGasLimit() - effectiveResult.getGasRemaining();
final long cumulativeGasUsed =
transactionSelectionResults.getCumulativeGasUsed() + gasUsedByTransaction;

worldStateUpdater.commit();
final TransactionReceipt receipt =
transactionReceiptFactory.create(
transaction.getType(), effectiveResult, worldState, cumulativeGasUsed);

final long blobGasUsed =
blockSelectionContext.gasCalculator().blobGasCost(transaction.getBlobCount());

updateTransactionSelected(pendingTransaction, receipt, gasUsedByTransaction, blobGasUsed);

LOG.atTrace()
.setMessage("Selected {} for block creation")
.addArgument(transaction::toTraceLog)
.log();

return TransactionSelectionResult.SELECTED;
}

private void updateTransactionSelected(
final PendingTransaction pendingTransaction,
final TransactionReceipt receipt,
final long gasUsedByTransaction,
final long blobGasUsed) {

transactionSelectionResults.updateSelected(
pendingTransaction.getTransaction(), receipt, gasUsedByTransaction, blobGasUsed);

// notify external selector if any
externalTransactionSelector.onTransactionSelected(pendingTransaction);
}

private void updateTransactionRejected(
final PendingTransaction pendingTransaction,
final TransactionSelectionResult processingResult) {

transactionSelectionResults.updateNotSelected(
pendingTransaction.getTransaction(), processingResult);

// notify external selector if any
externalTransactionSelector.onTransactionRejected(pendingTransaction);
return handleTransactionSelected(pendingTransaction, processingResult, worldStateUpdater);
}

/**
Expand All @@ -277,15 +212,13 @@ private void updateTransactionRejected(
* @param pendingTransaction The transaction to be evaluated.
* @return The result of the transaction selection process.
*/
private TransactionSelectionResult evaluateTransactionPreProcessing(
private TransactionSelectionResult evaluatePreProcessing(
final PendingTransaction pendingTransaction) {

// Process the transaction through internal selectors
for (var selector : transactionSelectors) {
TransactionSelectionResult result =
selector.evaluateTransactionPreProcessing(
pendingTransaction, transactionSelectionResults);
// If the transaction is not selected by any internal selector, return the result
if (!result.equals(TransactionSelectionResult.SELECTED)) {
return result;
}
Expand All @@ -303,16 +236,14 @@ private TransactionSelectionResult evaluateTransactionPreProcessing(
* @param processingResult The result of the transaction processing.
* @return The result of the transaction selection process.
*/
private TransactionSelectionResult evaluateTransactionPostProcessing(
private TransactionSelectionResult evaluatePostProcessing(
final PendingTransaction pendingTransaction,
final TransactionProcessingResult processingResult) {

// Process the transaction through internal selectors
for (var selector : transactionSelectors) {
TransactionSelectionResult result =
selector.evaluateTransactionPostProcessing(
pendingTransaction, transactionSelectionResults, processingResult);
// If the transaction is not selected by any selector, return the result
if (!result.equals(TransactionSelectionResult.SELECTED)) {
return result;
}
Expand All @@ -321,12 +252,94 @@ private TransactionSelectionResult evaluateTransactionPostProcessing(
pendingTransaction, processingResult);
}

private List<AbstractTransactionSelector> createTransactionSelectors(
final BlockSelectionContext context) {
return List.of(
new BlockSizeTransactionSelector(context),
new PriceTransactionSelector(context),
new BlobPriceTransactionSelector(context),
new ProcessingResultTransactionSelector(context));
/**
* Processes a transaction
*
* @param pendingTransaction The transaction to be processed.
* @param worldStateUpdater The world state updater.
* @return The result of the transaction processing.
*/
private TransactionProcessingResult processTransaction(
final PendingTransaction pendingTransaction, final WorldUpdater worldStateUpdater) {
final BlockHashLookup blockHashLookup =
new CachingBlockHashLookup(blockSelectionContext.processableBlockHeader(), blockchain);
return transactionProcessor.processTransaction(
blockchain,
worldStateUpdater,
blockSelectionContext.processableBlockHeader(),
pendingTransaction.getTransaction(),
blockSelectionContext.miningBeneficiary(),
blockHashLookup,
false,
TransactionValidationParams.mining(),
blockSelectionContext.blobGasPrice());
}

/**
* Handles a selected transaction by committing the world state updates, creating a transaction
* receipt, updating the TransactionSelectionResults with the selected transaction, and notifying
* the external transaction selector.
*
* @param pendingTransaction The pending transaction.
* @param processingResult The result of the transaction processing.
* @param worldStateUpdater The world state updater.
* @return The result of the transaction selection process.
*/
private TransactionSelectionResult handleTransactionSelected(
final PendingTransaction pendingTransaction,
final TransactionProcessingResult processingResult,
final WorldUpdater worldStateUpdater) {
worldStateUpdater.commit();
final Transaction transaction = pendingTransaction.getTransaction();

final long gasUsedByTransaction =
transaction.getGasLimit() - processingResult.getGasRemaining();
final long cumulativeGasUsed =
transactionSelectionResults.getCumulativeGasUsed() + gasUsedByTransaction;
final long blobGasUsed =
blockSelectionContext.gasCalculator().blobGasCost(transaction.getBlobCount());

final TransactionReceipt receipt =
transactionReceiptFactory.create(
transaction.getType(), processingResult, worldState, cumulativeGasUsed);

logTransactionSelection(pendingTransaction.getTransaction());

transactionSelectionResults.updateSelected(
pendingTransaction.getTransaction(), receipt, gasUsedByTransaction, blobGasUsed);
externalTransactionSelector.onTransactionSelected(pendingTransaction);

return TransactionSelectionResult.SELECTED;
}

/**
* Handles the scenario when a transaction is not selected. It updates the
* TransactionSelectionResults with the unselected transaction, and notifies the external
* transaction selector.
*
* @param pendingTransaction The unselected pending transaction.
* @param selectionResult The result of the transaction selection process.
* @return The result of the transaction selection process.
*/
private TransactionSelectionResult handleTransactionNotSelected(
final PendingTransaction pendingTransaction,
final TransactionSelectionResult selectionResult) {
transactionSelectionResults.updateNotSelected(
pendingTransaction.getTransaction(), selectionResult);
externalTransactionSelector.onTransactionNotSelected(pendingTransaction, selectionResult);
return selectionResult;
}

private void checkCancellation() {
if (isCancelled.get()) {
throw new CancellationException("Cancelled during transaction selection.");
}
}

private void logTransactionSelection(final Transaction transaction) {
LOG.atTrace()
.setMessage("Selected {} for block creation")
.addArgument(transaction::toTraceLog)
.log();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -674,9 +674,10 @@ public void transactionSelectionPluginShouldBeNotifiedWhenTransactionSelectionCo
final Transaction transaction = createTransaction(0, Wei.of(10), 21_000);
ensureTransactionIsValid(transaction, 21_000, 0);

final TransactionInvalidReason invalidReason =
TransactionInvalidReason.PLUGIN_TX_VALIDATOR_INVALIDATED;
final Transaction invalidTransaction = createTransaction(1, Wei.of(10), 21_000);
ensureTransactionIsInvalid(
invalidTransaction, TransactionInvalidReason.PLUGIN_TX_VALIDATOR_INVALIDATED);
ensureTransactionIsInvalid(invalidTransaction, invalidReason);
transactionPool.addRemoteTransactions(List.of(transaction, invalidTransaction));

createBlockSelectorWithTxSelPlugin(
Expand All @@ -692,11 +693,16 @@ public void transactionSelectionPluginShouldBeNotifiedWhenTransactionSelectionCo
ArgumentCaptor<PendingTransaction> argumentCaptor =
ArgumentCaptor.forClass(PendingTransaction.class);

// selected transaction must be notified to the selector
verify(transactionSelector).onTransactionSelected(argumentCaptor.capture());
PendingTransaction selected = argumentCaptor.getValue();
assertThat(selected.getTransaction()).isEqualTo(transaction);

verify(transactionSelector).onTransactionRejected(argumentCaptor.capture());
// unselected transaction must be notified to the selector with correct reason
verify(transactionSelector)
.onTransactionNotSelected(
argumentCaptor.capture(),
eq(TransactionSelectionResult.invalid(invalidReason.toString())));
PendingTransaction rejectedTransaction = argumentCaptor.getValue();
assertThat(rejectedTransaction.getTransaction()).isEqualTo(invalidTransaction);
}
Expand Down
2 changes: 1 addition & 1 deletion plugin-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,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 = '+7wo9cABKEFyYvjtpDFAOXqVKBAkffdnb433hT0VQ7I='
knownHash = 'Pfql+wKH6qarEvlb9TXZGVV/xwJuKWK5egKHt9uCpzE='
}
check.dependsOn('checkAPIChanges')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ TransactionSelectionResult evaluateTransactionPostProcessing(
*/
default void onTransactionSelected(final PendingTransaction pendingTransaction) {}
/**
* Method called when a transaction is rejected to be added to a block.
* Method called when a transaction is not selected to be added to a block.
*
* @param pendingTransaction The transaction that has been rejected.
* @param pendingTransaction The transaction that has not been selected.
* @param transactionSelectionResult The transaction selection result
*/
default void onTransactionRejected(final PendingTransaction pendingTransaction) {}
default void onTransactionNotSelected(
final PendingTransaction pendingTransaction,
final TransactionSelectionResult transactionSelectionResult) {}
}

0 comments on commit a56a35f

Please sign in to comment.