-
Notifications
You must be signed in to change notification settings - Fork 40
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
feat(block-producer): support transaction expiration #582
base: next
Are you sure you want to change the base?
Conversation
ea27cbe
to
6298e05
Compare
6298e05
to
79fb353
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! Looks good! I left some comments inline - mostly nits. A couple of more general questions:
First, should we not also account for expiring batches? Basically, if we have a batch that contains a transaction that is now expired, we should probably not try to put it into a block. For this, we may need to add expires_at
field to transaction batch and it would be set to the min of all transaction expiration times for this batch.
Also, an alternative approach to handling this would be to move the transaction expiration logic into the transaction graph. Specifically, in TransactionGraph::select_batch()
we could check if a selected transaction has expired or not. If it has, we'd discard it and remove all of its dependents, and then move to selecting the next transaction. This approach could have the following benefits:
- We don't really need to track transaction expirations in a separate struct.
- The
Mempool
struct doesn't need to know anything about transaction expiration logic.
The latter could be useful once we decided to optimize transaction selection logic to give some priority to transactions which could expire soon and this logic will be fully encapsulated in the transaction graph.
/// | ||
/// Unlike [remove_batches](Self::remove_batches), this has no error condition as batches are | ||
/// derived internally. | ||
pub fn remove_transactions<'a>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: since we are technically removing batches (and not transactions), should we rename this into something like remove_batches_with_transactions()
?
// Check that the transaction hasn't already expired. | ||
if tx.expires_at() <= self.chain_tip { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be a bit more aggressive here? For example, a transaction which expires in the next block is almost certainly not going to make it as we need to put it into the batch first and then the batch needs to make it into the block. Maybe it could be something like:
if tx.expires.at() <= self.chain_tip + 1 {
...
}
Instead of hard-coding +1
, we could also have a constant for it.
@@ -166,6 +174,8 @@ pub struct Mempool { | |||
/// Inflight transactions. | |||
transactions: TransactionGraph, | |||
|
|||
expirations: TransactionExpirations, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a brief doc comment here as well.
// BATCH FAILED TESTS | ||
// ================================================================================================ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should we move the tests into a separate file? (e.g., src/mempool/tests.rs
)
// Revert all transactions. This is the nuclear (but simplest) solution. | ||
// | ||
// We currently don't have a way of determining why this block failed so take the safe route | ||
// and just nuke all associated transactions. | ||
// | ||
// TODO: improve this strategy, e.g. count txn failures (as well as in e.g. batch failures), | ||
// and only revert upon exceeding some threshold. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's create an issue to address this in the future (unless we already have one).
// and only revert upon exceeding some threshold. | ||
let txs = batches | ||
.into_iter() | ||
.flat_map(|batch| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should we name this variable batch_id
to make thins a bit more explicit?
This should be handled automatically since the expiration tracking is done for all transactions, even those in batches. For every transaction that expires we:
So (2) handles the batch expiring.
We could do something like this sure. Then we do need separate transaction and batch expiration tracking, and the graphs will have to be aware of the chain tip. The downside is that selecting a batch/block suddenly comes with additional side effects, now you also need to handle reverting things in the mempool. Additionally, Ideally we would keep the expiration check in This does improve selection options in the future so maybe its worth trying out. I'll code this up and diff it against the current PR. Overall I wonder if its enough to abstract the key selection information within each graph separately, or if one day we'll want the selection strategy to have access to the entire mempool's data. As in, batch selection will want to know about the transaction graph data and vice versa. |
Ah! Makes sense - somehow I missed this.
Do you mean that if we detect an expired transaction, we'd also need to remove the associated batches from the batch graph? If so, it does complicate things, but I wonder if there is a way to keep these graphs disjointed.
We should be able to address this by "materializing" transaction expiration block in |
This PR adds support for transaction expirations.
(Additionally also removes a dangling file)
Transaction expiration is tracked in a separate struct which bidirectionally maps between transactions and their expiration height.
Closes #508.