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

fix: never read reserved txs from the mempool #1267

Closed
wants to merge 2 commits into from

Conversation

evan-forbes
Copy link
Member

closes #1266

see details there.

Comment on lines 48 to 56
func (s *store) has(txKey types.TxKey) bool {
s.mtx.RLock()
defer s.mtx.RUnlock()
_, has := s.txs[txKey]
tx, has := s.txs[txKey]
if has && tx.isReserved() {
return false
}
return has
}
Copy link
Member Author

Choose a reason for hiding this comment

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

wasn't sure about this one @cmwaters

do we want to return true even if a tx is reserved? does this defeat the purpose of reserving in the first place?

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 this is correct. We don't want to mark the node as having the transaction until we have verified it. We might want to revisit this solution however when we add the tx status endpoint. There could be several states:

  • Processing
  • Pending
  • Proposed
  • Committed
  • Evicted
  • Rejected

Copy link
Collaborator

@rootulp rootulp left a comment

Choose a reason for hiding this comment

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

@@ -34,3 +34,5 @@ func newWrappedTx(tx types.Tx, key types.TxKey, height, gasWanted, priority int6

// Size reports the size of the raw transaction in bytes.
func (w *wrappedTx) size() int64 { return int64(len(w.tx)) }

func (w *wrappedTx) isReserved() bool { return w.height == -1 }
Copy link
Collaborator

Choose a reason for hiding this comment

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

[question] what is a reserved transaction? How does it relate to height == -1? Any links to docs would be helpful.

Also this function could use a Go doc.

Copy link
Member Author

Choose a reason for hiding this comment

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

reserved tx is a cat thing, I'm not sure yet if we need it as afaik it's an optimization

if !txmp.store.reserve(key) {

Copy link
Contributor

Choose a reason for hiding this comment

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

It's basically a way of not processing the same transaction in parallel. Say I receive transaction x and while processing it I receive tx x again I will verify it again because I haven't yet added it to the mempool

Copy link
Collaborator

Choose a reason for hiding this comment

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

Rephrasing that: isReserved is true when a transaction has been received but hasn't been added to the mempool yet because it's still being processed? Does that mean isReserved == isProcessing?

Are these the exhaustive states a transaction could be in: https://github.com/celestiaorg/celestia-core/pull/1267/files#r1526067986

If yes, would it make sense to consolidate on one term either: isProcessing or isReserved?

@evan-forbes
Copy link
Member Author

evan-forbes commented Mar 15, 2024

CI is failing here

yeah thanks, I'm waiting on this comment as I'm not sure if this is correct property or not #1267 (comment) and the test is failing because its testing for that property

cmwaters
cmwaters previously approved these changes Mar 15, 2024
Copy link
Contributor

@cmwaters cmwaters left a comment

Choose a reason for hiding this comment

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

Looks good to me

Comment on lines 48 to 56
func (s *store) has(txKey types.TxKey) bool {
s.mtx.RLock()
defer s.mtx.RUnlock()
_, has := s.txs[txKey]
tx, has := s.txs[txKey]
if has && tx.isReserved() {
return false
}
return has
}
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 this is correct. We don't want to mark the node as having the transaction until we have verified it. We might want to revisit this solution however when we add the tx status endpoint. There could be several states:

  • Processing
  • Pending
  • Proposed
  • Committed
  • Evicted
  • Rejected

Copy link
Collaborator

@staheri14 staheri14 left a comment

Choose a reason for hiding this comment

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

Looks good to me, though I have a question regarding the reasoning behind storing reserved transactions.

@@ -27,7 +27,7 @@ func (s *store) set(wtx *wrappedTx) bool {
}
s.mtx.Lock()
defer s.mtx.Unlock()
if tx, exists := s.txs[wtx.key]; !exists || tx.height == -1 {
if tx, exists := s.txs[wtx.key]; !exists || tx.isReserved() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

[Question]Why should we store a reserved transaction if it will not be processed due to being a duplicate?

Copy link
Member Author

Choose a reason for hiding this comment

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

idk that's a great question cc @cmwaters

this was just adding a method for syntactic sugar, but it does seem like we should remove that check entirely

Comment on lines +42 to +44
if tx != nil && tx.isReserved() {
return nil
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

[Question] In which cases a reserved tx exists, while the original tx does not?

Copy link
Member Author

Choose a reason for hiding this comment

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

if its currently being processed by the mempool

Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it mean then there are situations in which the original tx is removed from the mempool, but the reserved one is still in the mempool?

Copy link
Contributor

Choose a reason for hiding this comment

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

The original tx and the reserved tx are the same barring the fact that the height we received the transaction has not yet been set.

If a transaction fails to be added it is removed. If it succeeds the height is added and it's no longer considered reserved

@evan-forbes
Copy link
Member Author

while separate from this change, I'm fine with not merging until we resolve #1267 (comment)

@evan-forbes evan-forbes disabled auto-merge March 18, 2024 15:41
@rootulp
Copy link
Collaborator

rootulp commented Mar 18, 2024

I don't understand this change enough to approve.

@evan-forbes
Copy link
Member Author

I don't understand this change enough to approve.

@rootulp Does this sentence in the original issue help, or perhpaps we can sync on it quickly to get to the bottom it?

CAT will return txs that are reserved but not yet stored. This can result in weird behavior, such as passing the application a zero length transaction during PrepareProposal.

The fundemental problem is just that we can't pass len 0 txs to the application, as it will panic. Since the mempool processes things async, atm its possible to reap a reserved tx (a tx that is in the process of being added to the mempool, but only has a hash)

Copy link
Collaborator

@rootulp rootulp left a comment

Choose a reason for hiding this comment

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

LGTM. Sync'ed with @evan-forbes offline. My rough notes:

  • isReserved is unique to the CAT pool. It doesn't exist in other mempool implementations.
  • isReserved is a mechanism used to label transactions that are in the process of being added to the mempool.
  • We don't want a block proposer to reap an isReserved transaction and include it in a block yet. Therefore, methods like getAllKeys and getAllTxs were modified in this PR to return all non-reserved transactions.
  • Evan and I discussed two potential refactors:
  1. Modify the conditional in
    if !txmp.store.reserve(key) {
    to avoid a double negative.
  2. Consider adding a separate map for reserved transactions

@cmwaters
Copy link
Contributor

  1. Consider adding a separate map for reserved transactions

This seems plausible as well. We essentially just want to check this map when a new transaction comes in, in case we are already processing it. It could just be a map of the tx hashes

@evan-forbes
Copy link
Member Author

closing in favor of #1272

cmwaters added a commit that referenced this pull request Mar 25, 2024
)

This is an alternative to #1267 that solves the current problem with
reserving transactions by using a separate lookup map as suggested by
@rootulp.<hr>This is an automatic backport of pull request #1272 done by
[Mergify](https://mergify.com).

Co-authored-by: Callum Waters <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S:backport-to-v0.34.x T:Bug Type: Bug (confirmed)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CAT can return reserved txs
4 participants