Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow plugins to propose transactions during block creation #8268

Merged
merged 4 commits into from
Feb 12, 2025

Conversation

fab-10
Copy link
Contributor

@fab-10 fab-10 commented Feb 7, 2025

PR description

With this PR, plugins that could already apply custom rules during the evaluation of pending transactions for block inclusion, can now directly propose pending transactions for block inclusion. Of course plugin proposed pending transactions must pass all the filters and be processed successfully.

In order to propose transactions a plugin implements its selection logic extending PluginTransactionSelectorFactory::selectPendingTransactions method, via that method it has access to the pending block header and the BlockTransactionSelectionService that is the core if the workflow, since it allows for evaluating pending transactions and depending on the result of the evaluations, the plugin can decide to either commit or rollback them.

The process of evaluation a pending transaction, not only changes the world state, but also affected the internal state of some selectors, for example the selector that tracks the cumulative gas used in the block or the blobs numbers, we can refer to these selectors as stateful, and a common unified strategy has been introduce to support stateful selectors, via the SelectorsStateManager, that is now responsible to keep and manage the working and commited state for each stateful selector.

Fixed Issue(s)

Thanks for sending a pull request! Have you done the following?

  • Checked out our contribution guidelines?
  • Considered documentation and added the doc-change-required label to this PR if updates are required.
  • Considered the changelog and included an update if required.
  • For database changes (e.g. KeyValueSegmentIdentifier) considered compatibility and performed forwards and backwards compatibility tests

Locally, you can run these tests to catch failures early:

  • spotless: ./gradlew spotlessApply
  • unit tests: ./gradlew build
  • acceptance tests: ./gradlew acceptanceTest
  • integration tests: ./gradlew integrationTest
  • reference tests: ./gradlew ethereum:referenceTests:referenceTests

@fab-10 fab-10 force-pushed the plugin-proposed-txs branch from d85013c to 4de05eb Compare February 11, 2025 09:23
@fab-10 fab-10 marked this pull request as ready for review February 11, 2025 09:44
selectedTransactions.add(transaction);
transactionsByType
.computeIfAbsent(transaction.getType(), type -> new ArrayList<>())
.add(transaction);
receipts.add(receipt);
cumulativeGasUsed += gasUsed;
cumulativeBlobGasUsed += blobGasUsed;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we are removing cumulativeBlobGasUsed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this class was used to both (1)track selection results and (2)managed some state for the selectors, now the 2 tasks have been separated and in this class are only kept the selection results(1) and since the cumulativeBlobGasUsed is not needed as result, but only useful for the stateful selector it has been moved into the BlobSizeTransactionSelector

return selectorsStateManager.getSelectorWorkingState(this);
}

protected void setWorkingState(final S newState) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not completly sure about this name, for me it was the WorldState and after I saw that it's a long 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is about the selector specific state, for example the BlobSizeTransactionSelector that needs to keep the cumulative count of how many blobs have been already added by previous txs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it's better but maybe WorkingContext? SelectionContext?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, we already have BlockSelectionContext and TransactionEvaluationContext here so it can also create confusion.
Do you think using state could be confusing just at a first look, and after reviewing the code for longer the distinction is clearer?

I can also rename this method to setSelectorWorkingState, I thought it was redundant since it is in a selector class already, but if it helps readability then no problem

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok, maybe just add a comment to explain what is this state

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -29,37 +29,39 @@
* 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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AbstractStatefulTransactionSelector. I feel that this name should be in conflict with Statefull and stateless worldstate. Just thniking I don't have a strong opinion about that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that state can refer to different things here, so one should read it along with transaction selector to fully qualify, not sure what could be another naming for this

category = "Core",
elementType = "filter",
printObject = true)
public class StackTraceMatchFilter extends AbstractFilter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this class needed for this PR ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really, it is an helper to configure log4j to avoid logging some exception that are not relevant during the tests, do you prefer to have it in a separate PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No if It is necessary for your tests I think it's OK

Copy link
Contributor

@ahamlat ahamlat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't finished the review, but this is my first round of comments

package org.hyperledger.besu.plugin.services.txselection;

/**
* This class represents an abstract plugin transaction selector which provides manage the selector
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small typo to fix : which provides manage the selector


/**
* Get the working state for this selector. A working state contains changes that have not yet
* commited
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • typo fix : committed

transaction -> evaluateTransaction(new PendingTransaction.Local.Priority(transaction)));
selectorsStateManager.blockSelectionStarted();

transactions.stream()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Streams are not good for performance especially when using map, so I prefer the the previous version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted

final PendingTransaction pendingTransaction) {
@Override
public TransactionSelectionResult evaluatePendingTransaction(
final org.hyperledger.besu.datatypes.PendingTransaction pendingTransaction) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to import PendingTransaction with the whole package here ? is there another PendingTransaction ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, for example when we create the actual pending txs in the evaluateTransactions method, while this method is from the interface and uses the PendingTransaction interface

processTransaction(evaluationContext.getTransaction(), txWorldStateUpdater);
processTransaction(evaluationContext.getTransaction());

txWorldStateUpdater.markTransactionBoundary();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate on why this is needed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is needed in case more that one tx is processed before the commit, for example a plugin can only decide to commit only if txA and txB are both selected, so in this case you use the same world state updater first to execute txA and then txB, but without marking the tx boundary, it is possible that when executing txB there are some addresses already warm-up by txA, thus breaking the gas calculation.
This has no effect without plugin, since here we are committing after every selected, but provides flexibility for plugin to implement selection logic.

processTransaction(evaluationContext.getTransaction(), txWorldStateUpdater);
processTransaction(evaluationContext.getTransaction());

txWorldStateUpdater.markTransactionBoundary();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this called before the commit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is called on StackedUpdater so if I understood correctly it should be fine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok just wanted to be sure we are not using it for Bonsai accumulator

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
@fab-10 fab-10 force-pushed the plugin-proposed-txs branch from de8c049 to 4ac97a7 Compare February 12, 2025 09:41
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
Copy link
Contributor

@ahamlat ahamlat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, it would be great if we can have more coverage on BlockSizeTransactionSelector class, as this PR changes the way cumulative gas is updated.

txWorldStateUpdater.commit();
blockWorldStateUpdater.commit();
for (final var pendingAction : selectedTxPendingActions) {
pendingAction.run();
Copy link
Contributor

@ahamlat ahamlat Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the executions will be sequential here, otherwise a use of an executor is more convenient if we want to have asynchronous executions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this must be sequential and all pending actions must be completed before leaving the synchronized block

* @return a copy of the long
*/
static long duplicateLong(final long l) {
return l;
Copy link
Contributor

@ahamlat ahamlat Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is a bit confusing for me as it returns the input param, I dont see why we need to call it in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is like the UnaryOperator::identity method, since basically there is no need to duplicate a long, it is used by selectors that have a Long as state like BlockSelectionContext

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
@fab-10 fab-10 merged commit 428a638 into hyperledger:main Feb 12, 2025
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants