Note that there is a difference between orphan transactions and orphan blocks.
There are a number of known orphan-handling problems
which have existed for quite some time. While they represent inefficiencies when transactions are
relayed individually, these problems are even more relevant in package relay, where the orphanage is
part of a transaction’s “critical path.”
Currently, we only attempt to resolve orphans with the peer who provided them. If this doesn’t
work out (e.g. they send a notfound or don’t respond), we do not try again. In fact, we can’t,
because we’ve already forgotten who else could help.
The TxRequestTracker, introduced in PR #19988,
is a data structure that tracks hashes of transactions to download and the candidate peers for
requesting those transactions. It schedules requests to send, helps avoid duplicate in-flight
transaction requests, and encodes prioritization of peers.
In transaction download, outbound peers are preferred over inbound peers, though the
TxRequestTracker does not have internal knowledge of connection directions; the preference is
expressed by adding a delay to the announcement entry. In orphan resolution, there are similar
reasons for preferring outbound over inbound peers.
When a transaction is rejected from or accepted to mempool, we call ForgetTxHash on the
TxRequestTracker, indicating that we have successfully downloaded the transaction. Downloading
it from a different peer will not make the transaction valid, so we do not need to keep it around.
Prior to this PR, what are the steps for orphan resolution, starting from when we notice that the
transaction has missing inputs? Where in the code does this happen?
What are the ways we may fail to resolve an orphan with the peer we request its parents from?
What are some reasons things this may happen, honest or otherwise? (Hint: one honest example is
that the peer disconnects before responding to our request, perhaps because they are going offline)
Can you come up with an attack to prevent a node from downloading a 1p1c package by exploiting
today’s orphan resolution behavior? Temporary and/or probabilistic censorship are ok.
What is the PR’s solution to this problem?
Can you think of any alternatives - does BIP 331 fix any of this?
In this PR, which peers do we identify as potential candidates for orphan resolution, and why?
What does the node do if a peer announces a transaction that is currently a to-be-resolved orphan?
Prior to this PR, what are all the TxOrphanage methods that remove an orphan, and where in
the code are they called? (Hint: you should end up with 5 links to code)
Take a look at the
logic
for adding a transaction to the orphanage and m_orphan_resolution_tracker. Is it possible that
there are candidate peers but AddTx is not called - how? Why is that important?
This PR edits some of the erasures to potentially keep the transaction in the orphanage and only
modify the announcers list instead. Which places are these, and why? (Hint: for example,
EraseForPeer is called when a peer disconnects)
Commit
7badc73
stores missing parent txids in the TxOrphanage entry. Another approach could be to re-calculate
the missing parents prior to sending out requests; the missing parents may change over time as well.
Which approach do you prefer, and why?
Should it be possible for a transaction to be in m_orphanage but not
m_orphan_resolution_tracker? Why or why not? How might you test this?
Why might we prefer to resolve orphans with outbound peers over inbound peers?
What are the disadvantages of resolving orphans by requesting parent txids? (Hint: see BIP 331). Does this PR change that?
Commit 3e16a36
edits the 1p1c logic to no longer try packages of transactions from different peers. What was the
original motivation for this section, and why is removing it ok?
<premitive2> It's when a node receives a transaction that contains inputs referencing a transaction it doesn't have and then performs actions to find the parent
<marcofleon> getting txs that are orphans to possibly get into the mempool by finding their ancestors. process starts when you get a tx that has missing inputs
<glozow> Great answers! Yes, we're trying to find the missing inputs of an unconfirmed transaction. It's called an orphan because it's missing at least one parent.
<glozow> Prior to this PR, what are the steps for orphan resolution, starting from when we notice that the transaction has missing inputs? Bonus points if you can get code links
<instagibbs> if another peer advertises the parent via INV, we can still ask them for it, or if we get another orphan with the same missing parent, we will try them after a timeout?
<chinggg> In MempoolRejectedTx, we check if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS), where we notice that the transaction has missing inputs. The link is already in notes?
<glozow> marcofleon: yes kind of! If we receive the parent somehow and it's low feerate, we might pick this orphan back up in `Find1P1CPackage` that way
<glozow> What are the ways we may fail to resolve an orphan with the peer we request its parents from? What are some reasons things this may happen, honest or otherwise?
<glozow> marcofleon: specifically by "fake parent" I'm thinking the tx with a malleated witness. Given that the txid can be the same, they have responded to our request, but since it's invalid it doesn't help us resolve the orphan.
<premitive2> Is there a way to overrun the orphan store with fake child txs from different peers? I know there's a limit to the orphaned txs kept around, but I don't know the conditions in which new orphans can replace old ones
<marcofleon> you'd have to target the node somehow. but yeah I'm not actually sure how a peer would ensure that they were the first ones to send you the orphan
<chinggg> instead of just querying and waiting for the peer who provided the tx, refactor the code and introduce m_orphan_resolution_tracker to query from multiple peer candidates with some scheduling
<instagibbs> we add the orphan itself to a new orphan tx request tracker, so when the timer goes off for that, we "immediately" add the missing parents to the regular request tracker
<dergoegge> I was wondering why we need another txrequest tracker instance for this? couldn’t we just keep track of announcers in the orphanage (same as in the PR) and then add requests for the parents to the existing tracker on future announcements?
<dergoegge> I guess I'm wondering why we need the concept of tracking the resolution by orphan id, as opposed to just putting the requests for the parents in the existing tracker
<glozow> Perhaps that could work, where you're treating it as if all of them just announced the missing parents? I don't know how you'd add `ancpkginfo` orphan resolution easily this way though.
<glozow> I think it's possible it works? My main questions would be (1) what is the cost of having a second tracker? Because it's the perfect data structure for this. (2) how do we make it extensible to package relay still.
<marcofleon> The fact that there are candidates that be added or not added to that new tracker is why it made sense to me in the first place I guess is what i was saying
<glozow> btw, we're not even halfway through the questions. Lmk if y'all would like another session tomorrow. We can make it happen if there's 3+ people interested.
<glozow> I think it's really important to consider new announcers as orphan reso candidates, for the case where somebody frontruns and sends you an orphan unsolicited.
<glozow> We can pick up where we left off and ofc bring up any questions you have: Prior to this PR, what are all the `TxOrphanage` methods that remove an orphan, and where in the code are they called?
<marcofleon> I wasn't too sure how to interpret the question but I thought no it's not possible. Whenver there's a peer that passes OrphanResolutionCandidate the orphan tx gets added
<glozow> instagibbs: maybe the link doesn't render well. we're looking at the code block in `MempoolRejectedTx` where we're `add_orphan_reso_candidate` ing
<glozow> This PR edits some of the erasures to potentially keep the transaction in the orphanage and only modify the announcers list instead. Which places are these, and why?
<premitive2> I was a little confused about what you meant with 'the missing parent may change over time as well, although I like the approach and the fact that GetChildrenFromDifferentPeer was dropped
<glozow> what instagibbs said. I'm not sure the best way to phrase this but my mental model is to just assume that all inbounds are trying to attack you
<glozow> dergoegge: I don't think it's a 2x increase, but it can be large. it's a function of the tx's size. oh wow i just realized we could just store a bit vector that corresponds to its inputs.
<glozow> If you have multiple missing parents as well, downloading them one by one seems really inefficient as well. You'd re-evaluate the orphan over and over again (always still getting missing inputs) until the last parent gets in
<premitive2> The parent was used to find other peers from the children to request, like @monlovesmango it now can do everything it did without that code
<glozow> You could imagine that a peer can give you an infinite number of children for any given transaction. So let's not sign up to 1p1c all of them haha
<marcofleon> if we have the child from someone else and they don't have the parent yet... but we have the parent from another peer we just won't form a 1p1c package?