validation: mempool validation and submission for packages of 1 child + parents #22674

pull glozow wants to merge 8 commits into bitcoin:master from glozow:package-child-with-parents changing 9 files +567 −21
  1. glozow commented at 12:06 pm on August 10, 2021: member
    This is 1 chunk of Package Mempool Accept; it restricts packages to 1 child with its parents, doesn’t allow conflicts, and doesn’t have CPFP (yet). Future PRs (see #22290) will add RBF and CPFP within packages.
  2. glozow force-pushed on Aug 10, 2021
  3. glozow added the label Validation on Aug 10, 2021
  4. glozow added the label RPC/REST/ZMQ on Aug 10, 2021
  5. in src/rpc/rawtransaction.cpp:1026 in 0ab20f3581 outdated
    1022@@ -1023,6 +1023,131 @@ static RPCHelpMan testmempoolaccept()
    1023     };
    1024 }
    1025 
    1026+static RPCHelpMan submitrawpackage()
    


    unknown commented at 12:48 pm on August 10, 2021:

    I have no clue about details of packages and related PRs. Interested in this RPC which can be helpful in my tests. Is this like broadcasting transactions in batches?

    0for (int i = 0; i < MAX; i++) {
    1  if (consensus and mempool policies) {
    2  // add to broadcast queue
    3} else {
    4  // Error
    5}
    6}
    7
    8//BroadcastTransaction(node, queue);
    

    Is it possible to select peer for broadcasting this package? Not sure if enough people will ever agree to add such option in sendrawtransaction but we can experiment with test RPCs if its easy to implement. Context: #21876

    So RPC can have one argument to mention peer address.


    glozow commented at 12:53 pm on August 10, 2021:

    Is this like broadcasting transactions in batches?

    No, but you can make multiple calls to sendrawtransaction

    Is it possible to select peer for broadcasting this package?

    No, I don’t think that would be very helpful here. If you’re interested in enabling specific functionality, perhaps open a PR?


    unknown commented at 1:10 pm on August 10, 2021:
    Thanks
  6. michaelfolkson commented at 1:18 pm on August 10, 2021: contributor

    Concept ACK

    Just reviewing my notes from the L2 onchain support workshops. It was agreed that restricting to 1 child in a package would be sufficient for discussed L2 use cases (at least for now) and this PR implements multiple parents, 1 child packages rather than restricting to 1 parent, 1 child because of minimal increased implementation complexity.

  7. glozow commented at 2:59 pm on August 10, 2021: member
    Thank you @michaelfolkson. Indeed you are correct, we discussed that multiple parent + 1 child would be best but 1 parent + 1 child sufficient for addressing L2 use cases. After speaking with @TheBlueMatt offline and getting further into the implementation, I realized that the multi-parent case would be significantly more helpful without being that much more complex.
  8. DrahtBot commented at 6:55 pm on August 10, 2021: member

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #23448 (refactor, consensus: remove calls to global Params() in validation layer by lsilva01)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  9. TheBlueMatt commented at 8:37 pm on August 12, 2021: member

    After speaking with @TheBlueMatt offline and getting further into the implementation, I realized that the multi-parent case would be significantly more helpful without being that much more complex.

    This came, in part, out of a discussion between @ariard and I about an implementation @ariard had to implement the lightning “anchor output” protocol in LDK[1], and later a discussion between @sdaftuar and I had about how practical it is to avoid some of the complexity @ariard suggested there. Specifically, in order to work around issues with potential package relay systems, @ariard suggested a somewhat complicated approach involving chaining spends and broadcasting conflicts to see which one confirms first. This avoids us having to implement the “one on-chain output for future feerate bumping per open channel” logic that others have, which is incredibly wasteful of on-chain space and also adds a lot of complexity for LDK’s downstream users. If we can do replacement and multiple (no-onchain-parent) parents without a lot of complexity, it will save us a lot of heartache later. @sdaftuar suggested that it likely won’t add a lot of complexity, so I suggested that to @glozow, who seemed to agree.

    [1] https://github.com/rust-bitcoin/rust-lightning/issues/989

  10. ariard commented at 2:33 pm on August 17, 2021: member

    @TheBlueMatt

    If we can do replacement and multiple (no-onchain-parent) parents without a lot of complexity, it will save us a lot of heartache later.

    No, I’m going to stick to the domino-bumping approach we previously discussed.

    Multiple parents aren’t safe for lightning as it does allow a counterparty to delay the confirmation of a specific channel commitment by overbidding on any bundled commitment inside the same package. Let’s say you broadcast the package A+B+C+D+E, where A,B,C,D are commitment transactions and E a common CPFP. Previously, your malicious counterparty have submitted a better-feerate package A’+F to the network mempools. When your package {A,B,C,D,E} is sumbitted for acceptance, it will failed on the better-feerate requirement : https://github.com/bitcoin/bitcoin/blob/fdd80b0a53b4af0b29cb6e03118e2456d053a757/src/validation.cpp#L830 (fwiw, not documented in bip125).

    As you don’t know the failure reason from the honest LN node viewpoint, it’s hard to decide on the next broadcast strategy. Either splitting the former package in X isolated one parent+child ones, because you suspect your counterparty to meddle with your transaction confirmation (similar to https://github.com/lightningnetwork/lightning-rfc/pull/803). Or attribute the failure to feerate efficiency w.r.t to top of of the mempool transactions.

    Note, your counterparty doesn’t have to be malicious for your multiple parents package to fail confirmation. A honest counterparty can just have decided to go on-chain concurrently, with a better fee-estimation than you.

    Of course, I think this unsafety is only concerning in case of time-sensitive confirmations. If all your commitments are devoid of HTLC outputs, it’s not time-sensitive anymore, and that’s okay to have confirmation failure on the edge cases, not funds are loss, beyond the timevalue of liquidity.

    Further, endorsing a Core dev hat, I would be pretty conservative with multiple-transaction p2p package. AFAIK, we already have DoS concerns to validate a single transaction that would finally lead to mempool acceptance failure (see #20277 (comment)). IIRC we have not improved on this area due to a lack of an agreement on an efficient anti-DoS strategy against malicious transaction submission (see #21224)

    If we either make progress in this direction and start to penalize or slow-down heavy-package propagation, we might in the future break LN softwares which would have made assumptions on multiple-parent support. Note, this can also in a disaster scenario if we discover DoS vulns we would have thought out and the Core project don’t have a formalized process in case of internal wreckage with significant security implications for downstream projects.

    So back to a LN dev hat, I prefer to be conservative with the design of LDK anchor output backend and not make assumptions on potential multiple-parents support. For now, I think we already make far too assumptions on Core mempools behavior, without fully understanding all the implications.

    Apart of the multi-parents point, I think the change proposed are okay. Though I would recall once again, imho it would be better to present a BIP draft on the ml for this serie of PRs, once for all to agree on a consistent v0.1 package model, instead to all waste time on local discussions splitted across projects issues, slacks and irc…

  11. michaelfolkson commented at 7:49 pm on August 23, 2021: contributor

    @ariard: Core could support multiple parents, 1 child packages whilst the Lightning protocol could only support 1 parent, 1 child packages (assuming your safety assessment above for Lightning is correct)? If Lightning needs only a subset of what Core offers there is generally less of a problem. It is when Lightning needs a superset of what Core offers where there is a problem. This would need a separation of validation and submission with the Lightning protocol/implementations enforcing different submission rules to Core’s submission rules. Unless you are worried about a rogue Lightning implementation not following a future Lightning spec and creating a multiple parent, 1 child package when it shouldn’t?

    Though I would recall once again, imho it would be better to present a BIP draft on the ml for this serie of PRs, once for all to agree on a consistent v0.1 package model, instead to all waste time on local discussions splitted across projects issues, slacks and irc…

    I agree on the BIP draft.

  12. ariard commented at 10:02 pm on August 24, 2021: member

    Core could support multiple parents, 1 child packages whilst the Lightning protocol could only support 1 parent, 1 child packages

    Yes I think I’ve made the comment already in #16401, about the batching use-case for LN. I think it’s okay as long as you don’t have time-sensitive unilateral closure, where a counterparty might interfere with the spending of one funding output also covered by the package to decrease the confirmation odds of all other channels closure.

    (assuming your safety assessment above for Lightning is correct)?

    Please, would be great to have more eyes on this point. Note, iirc “anchor output” is only deployed by lnd so far and the last version (option_zero_htlc_tx_fee) isn’t yet finalized. I’ve not look in-depth on their anchored transaction broadcast strategy though avoiding to introduce yet-another-vector of transaction-relay jamming that’s something I’ve in mind for the LDK implementation.

    Unless you are worried about a rogue Lightning implementation not following a future Lightning spec and creating a multiple parent, 1 child package when it shouldn’t?

    Note, the BOLTs loosely specify the safe propagation of LN time-sensitive transactions. It’s left to the knowledge of implementors for the best part (dust threshold, standard script flags, min tx size, …) What I’m worried of is uninformed Lightning or L2 developers extending even further than today the scope of assumptions on mempool/p2p Core’s behavior with safety implications for their protocols. Especially when it’s a behavior potentially frail that we might heavily revise in the future such as handling of DoSy transactions.

    Again, with the lack of a clear project philosophy on the valid set of assumptions one can make on the stability of what Core offers at the mempool/p2p level, I’m learning the lesson, better to be conservative as an upper layer dev!

  13. michaelfolkson commented at 7:38 am on August 25, 2021: contributor

    Again, with the lack of a clear project philosophy on the valid set of assumptions one can make on the stability of what Core offers at the mempool/p2p level, I’m learning the lesson, better to be conservative as an upper layer dev!

    If you, Lightning devs etc said to @glozow, Core etc that Lightning can’t cope with multiple parent, 1 child packages and it can’t enforce the restriction of 1 parent, 1 child packages at Layer 2 (assuming Core goes with multiple parent, 1 child) I am pretty sure Core will implement 1 parent, 1 child. This package relay project afaiu is primarily for Lightning (although there are other use cases). There are clearly some tricky things as we move between layers but I think this particular one is easy to navigate assuming Lightning devs can assess what it is comfortable with Core doing.

  14. glozow force-pushed on Sep 2, 2021
  15. DrahtBot added the label Needs rebase on Sep 9, 2021
  16. glozow force-pushed on Sep 24, 2021
  17. glozow removed the label RPC/REST/ZMQ on Sep 24, 2021
  18. glozow added the label TX fees and policy on Sep 24, 2021
  19. glozow commented at 4:17 pm on September 24, 2021: member
    Rebased since #22675 was merged. I’ve reduced this PR’s size a little bit. It no longer adds the submitrawpackage RPC, but has unit tests. Mailing list post is up.
  20. DrahtBot removed the label Needs rebase on Sep 24, 2021
  21. DrahtBot added the label Needs rebase on Sep 27, 2021
  22. glozow force-pushed on Sep 28, 2021
  23. glozow commented at 9:25 am on September 28, 2021: member
    Rebased on master and squashed a couple commits
  24. DrahtBot removed the label Needs rebase on Sep 28, 2021
  25. DrahtBot added the label Needs rebase on Oct 7, 2021
  26. t-bast commented at 7:47 am on October 19, 2021: member

    Concept ACK.

    I agree with @ariard: I personally will use a (1 parent - 1 child) package for the current lightning anchor outputs scheme because it is time-sensitive and subject to attacks from a malicious counterparty.

    However, since it doesn’t make the bitcoind code much more complex, I believe that allowing (N parents - 1 child) can be very beneficial to other off-chain schemes, so we shouldn’t restrict them to (1 parent - 1 child).

    For example, I would use it in lightning to bump multiple funding transactions (or mutual close transactions) that have a low feerate at once. That feels like a very reasonable use of (N parents - 1 child) that has no drawbacks whatsoever.

  27. glozow force-pushed on Oct 19, 2021
  28. DrahtBot removed the label Needs rebase on Oct 19, 2021
  29. glozow force-pushed on Oct 20, 2021
  30. in src/policy/packages.h:26 in 97124603ad outdated
    25  */
    26 enum class PackageValidationResult {
    27     PCKG_RESULT_UNSET = 0,        //!< Initial value. The package has not yet been rejected.
    28-    PCKG_POLICY,                  //!< The package itself is invalid (e.g. too many transactions).
    29-    PCKG_TX,                      //!< At least one tx is invalid.
    30+    PCKG_BAD,                     //!< The package itself is invalid or malformed.
    


    ariard commented at 3:53 pm on October 20, 2021:
    What’s your thinking to introduce a PCKG_BAD ? I don’t see how it provides a supplemental or more restrictive information to the caller instead of relying on PCKG_POLICY and it’s a bit confusing as the checks failed with PCKG_BAD are already located in src/policy/packages.cpp.

    glozow commented at 5:44 pm on October 20, 2021:

    At some point we will have a P2P protocol for packages which we want to punish peers for violating, so we definitely want to distinguish between a network-wide rule violation and mempool rejection based on our local policy rules. In other words, this distinction mainly informs the net processing layer about the misbehavior of peers.

    Within this PR, we obviously haven’t defined P2P stuff yet, but I am attempting to formally specify what the rules are.

    So these are all PCKG_BAD and, in the future, we’d want to punish a peer that gives us something like this:

    • more than 25 transactions
    • transactions aren’t sorted
    • there are conflicts within the package

    But these would be PCKG_POLICY:

    • package mempool ancestor/descendants exceed our limits
    • package feerate is below our mempool minimum

    ariard commented at 0:09 am on October 25, 2021:

    So these are all PCKG_BAD and, in the future, we’d want to punish a peer that gives us something like this:

    I think we’re never discouraging based on tx-relay or mempool policy violations because our peers can run a policy far different than ours ? Like a future version of Core could have package with more than 25 component, a old peer connected would punish that peer for a behavior valid according to the relay rules of the former.

    Discouraring/punishing on the rational of policy violations could trigger netsplits and I think that’s why we’re never marking a peer as Misbehaving in MaybePunishNodeForPeer for policy failure reasons.

    However, I could see that a ill-crafted package could be punished in the sense that MAX_PEER_PACKAGE_ANNOUNCEMENTS is dynamically constrained. But that would mean we have a stable package format across p2p package versions, which sounds a bit early to commit on IMO.

    Likely we need to think more about it, I would say for this PR it’s a bit premature to introduce PCKG_BAD though if you think otherwise maybe better to document it’s for future p2p purposes.


    glozow commented at 12:59 pm on October 28, 2021:
    Yeah you’re right, I was getting a little too excited :P will remove and put them all under PCKG_POLICY.
  31. in src/policy/packages.h:22 in 97124603ad outdated
    17@@ -18,13 +18,15 @@ static constexpr uint32_t MAX_PACKAGE_SIZE{101};
    18 static_assert(MAX_PACKAGE_SIZE * WITNESS_SCALE_FACTOR * 1000 >= MAX_STANDARD_TX_WEIGHT);
    19 
    20 /** A "reason" why a package was invalid. It may be that one or more of the included
    21- * transactions is invalid or the package itself violates our rules.
    22- * We don't distinguish between consensus and policy violations right now.
    23+ * transactions is invalid or the package itself fails. It's possible for failures to arise from
    24+ * rule violations or mempool policy.
    


    ariard commented at 3:54 pm on October 20, 2021:
    nit: “consensus rules”

    glozow commented at 4:34 pm on October 21, 2021:
    Done
  32. in src/policy/packages.h:47 in 7a66557831 outdated
    40@@ -41,4 +41,12 @@ class PackageValidationState : public ValidationState<PackageValidationResult> {
    41  */
    42 bool CheckPackage(const Package& txns, PackageValidationState& state);
    43 
    44+/** Context-free check that a package is exactly one child and at least one of its parents. It is
    45+ * expected to be sorted, which means the last transaction must be the child. The package cannot
    46+ * contain any transactions that are not the child's parents.
    47+ * @param[in]   exact   When true, return whether this package is exactly one child and all of its
    


    ariard commented at 4:34 pm on October 20, 2021:

    How are you aiming to use exact=true ?

    I understand that if you’re aiming to submit the package to your local mempool, you know the state of it and you can adjust the package components to only what is needed (exact=false).

    However, if you submit a package over the p2p network, one can’t assume a knowledge of the network mempools. A missing or replaced parent fails the propagation of the package. For this reason, a package broadcaster should always submit exact packages

    I’m just thinking we should keep the acceptance paths unified between submitting a package over RPC and submitting a package over p2p.


    glozow commented at 5:57 pm on October 20, 2021:

    Correct, I expect all unconfirmed parents to be present, and P2P should be talking about full packages.

    At some point, I was hoping to be able to statically verify that a transaction is exactly child-with-parents by requiring transactions for all of the child’s inputs to be present, but I realized we have to allow the parents to confirm earlier than the child, and it’s not reasonable to expect those confirmed parents to continue being relayed.

    Sooooo yeah I’ll remove it


    glozow commented at 1:14 pm on October 22, 2021:
    Removed exact parameter
  33. in src/validation.cpp:534 in 1a80d27a98 outdated
    502@@ -503,6 +503,10 @@ class MemPoolAccept
    503         std::unique_ptr<CTxMemPoolEntry> m_entry;
    504         std::list<CTransactionRef> m_replaced_transactions;
    505 
    506+        bool m_replacement_transaction;
    


    ariard commented at 4:45 pm on October 20, 2021:
    Note to reviewers, this commits also cache if the candidate is a replacement transaction based on the presence of conflicting transactions.

    glozow commented at 5:34 pm on October 20, 2021:
    Ah, that came from a merge conflict with #22539 which removed it.
  34. glozow commented at 4:54 pm on October 20, 2021: member
    Rebased, fixed a small bug caught by t-bast offline, and added a formal-ish doc that specifies the set of definitions and rules for packages.
  35. in doc/package_mempool_acceptance.md:1 in 84215d4a17 outdated
    0@@ -0,0 +1,57 @@
    1+# Package Mempool Accept
    


    ariard commented at 5:02 pm on October 20, 2021:

    Maybe doc/policy or doc/txrelay ? I guess we’ll have another doc for RBF revamp.

    Related #22806


    glozow commented at 4:34 pm on October 21, 2021:
    makes sense, I’ve created a doc/policy folder. More documentation of policy to come :D
  36. ariard commented at 5:09 pm on October 20, 2021: member

    Concept ACK

    Great to see doc/package_mempool_acceptance.md :)

    W.r.t unsafety of multiple-commitments-one-child for lightning, I can propose a bolt/blip document explaining the pitfalls for anchor outputs. Other L2 protocols with time-sensitive requirements (e.g DLC/Revault) could be able to do so. I think that’s better ecosystem-wise rather than a “Motivation” section in package_mempool_acceptance that we would need to update anytime a new use-case of package acceptance is designed (or just link to upper layer docs ?)

    Started to reviewed until 78d3665

  37. in doc/package_mempool_acceptance.md:11 in 35dd8dfcaf outdated
     6+directed edge exists between a transaction that spends the output of another transaction).
     7+
     8+For every transaction `t` in a **topologically sorted** package, if any of its parents are present
     9+in the package, they appear somewhere in the list before `t`.
    10+
    11+A **child-with-unconfirmed-parents** package is a toplogically sorted package that consists of
    


    t-bast commented at 7:01 am on October 21, 2021:

    nit:

    0A **child-with-unconfirmed-parents** package is a topologically sorted package that consists of
    

    glozow commented at 4:33 pm on October 21, 2021:
    Fixed
  38. in doc/package_mempool_acceptance.md:14 in 35dd8dfcaf outdated
     9+in the package, they appear somewhere in the list before `t`.
    10+
    11+A **child-with-unconfirmed-parents** package is a toplogically sorted package that consists of
    12+exactly one child and all of its unconfirmed parents (no other transactions may be present).
    13+The last transaction in the package is the child; each of its inputs must refer to a UTXO in the
    14+current chain tip or some preceding transaction in the package.
    


    t-bast commented at 7:05 am on October 21, 2021:

    Does that mean that if I have:

    • tx1 in the mempool
    • tx2 and tx3 not yet published
    • childTx spending tx1, tx2 and tx3

    A package containing [tx2, tx3, childTx] is invalid? Do I need to put tx1 in that package? I plan on potentially funding CPFP children with unconfirmed inputs, that means I’d need an extra step to insert them in the package: but if they made it to the mempool on their own, it seems unnecessary, isn’t it?

    If the package [tx2, tx3, childTx] is valid, we should probably update the phrasing here to:

    0The last transaction in the package is the child; each of its inputs must refer to a UTXO in the
    1current chain tip, the mempool or some preceding transaction in the package.
    

    glozow commented at 10:54 am on October 21, 2021:

    Yes, based on these rules, you need to put tx1 in the package. I believe this is better because we can relay packages across the network without worrying about differences in mempool contents.

    I can see how it’s inconvenient to need to track the other parents when you’re submitting to your node. One thing I can offer is adding a bit of code to our client interface code to “fill in” the package using mempool contents before submitting via ProcessNewPackage - that should be easy enough to do.


    t-bast commented at 11:47 am on October 21, 2021:

    I can see how it’s inconvenient to need to track the other parents when you’re submitting to your node. One thing I can offer is adding a bit of code to our client interface code to “fill in” the package using mempool contents before submitting via ProcessNewPackage - that should be easy enough to do.

    That would be great!

  39. in doc/package_mempool_acceptance.md:38 in 35dd8dfcaf outdated
    33+   the same inputs. Packages cannot have duplicate transactions. (#20833)
    34+
    35+* No transaction in a package can conflict with a mempool transaction. BIP125 Replace By Fee is
    36+  disabled for packages. (#20833)
    37+
    38+   - RBF in packages may be enabled in the future.
    


    t-bast commented at 7:10 am on October 21, 2021:

    Just to make sure I didn’t mess something up: I was able to do package RBF from eclair based on your package-mempool-accept branch, was that expected?

    I had (localCommitTx, localAnchorTx) in my mempool, and I was able to replace it with (remoteCommitTx, remoteAnchorTx) that paid more fees (with both commit txs paying 0 fee).


    glozow commented at 7:31 am on October 21, 2021:
    Yes! That’s just not in this PR yet so I didn’t want an inaccurate doc
  40. glozow force-pushed on Oct 21, 2021
  41. laanwj added this to the "Blockers" column in a project

  42. glozow added the label Review club on Oct 22, 2021
  43. in src/validation.cpp:811 in f3f4370401 outdated
    806@@ -796,8 +807,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
    807         // to be secure by simply only having two immediately-spendable
    808         // outputs - one for each counterparty. For more info on the uses for
    809         // this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
    810-        if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
    811-                !m_pool.CalculateMemPoolAncestors(*entry, setAncestors, 2, m_limit_ancestor_size, m_limit_descendants + 1, m_limit_descendant_size + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) {
    812+        if (ws.m_vsize >  EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
    813+            !m_pool.CalculateMemPoolAncestors(*entry, setAncestors, /* limitancestors */ 2,
    


    ariard commented at 10:14 pm on October 24, 2021:
    If we want to take opportunity to clarify the code, we can constify this magic value as CARVEOUT_ANCESTOR_TX_LIMIT.

    glozow commented at 10:00 am on October 26, 2021:
    Good suggestion. I think I would want to do this in a separate PR, along with adding documentation in the doc/policy/ folder for mempool ancestor/descendant limits and CPFP carve out.
  44. in src/validation.cpp:833 in f025db33a3 outdated
    826@@ -819,6 +827,19 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
    827         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-spends-conflicting-tx", *err_string);
    828     }
    829 
    830+    return true;
    831+}
    832+
    833+bool MemPoolAccept::MempoolChecks(Workspace& ws)
    


    ariard commented at 10:44 pm on October 24, 2021:
    I think all the checks in this method are related to replacement maybe name could reflect that like ReplacementChecks ? MempoolChecks isn’t really verbose w.r.t class MemPoolAccept.

    glozow commented at 1:00 pm on October 28, 2021:
    Yeah that’s a better name, done in #23381
  45. in src/validation.h:64 in 147199cc1a outdated
    59@@ -60,6 +60,13 @@ static const unsigned int DEFAULT_ANCESTOR_SIZE_LIMIT = 101;
    60 static const unsigned int DEFAULT_DESCENDANT_LIMIT = 25;
    61 /** Default for -limitdescendantsize, maximum kilobytes of in-mempool descendants */
    62 static const unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT = 101;
    63+
    64+// It doesn't make sense for package limits to exceed default mempool ancestor/descendant limits.
    


    ariard commented at 10:55 pm on October 24, 2021:
    “Package evaluation if successful leads to acceptance of the component in the mempool, therefore the package limits must always bind to the mempool ancestor/descendant limits”, better ?

    glozow commented at 8:18 pm on November 9, 2021:
    Edited to be similar to this
  46. in src/validation.cpp:1339 in 7f8ba8db73 outdated
    1215+        if (test_accept) {
    1216+            auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache);
    1217+            return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args);
    1218+        } else {
    1219+            auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(chainparams, GetTime(), coins_to_uncache);
    1220+            return MemPoolAccept(pool, active_chainstate).AcceptPackage(package, args);
    


    ariard commented at 11:22 pm on October 24, 2021:

    I think we should keep the package API “hard-to-misuse” for the user.

    IIUC, this PR introduce a second definition of a package, divergent of the testmempoolaccept/AcceptMultipleTransactions one. This second definition is more restrictive as the package must be a child with all of its unconfirmed parents and sorted.

    If a user sanitizes a package through testmempoolaccept with a 2-parent, 2-child to verify policy correctness. Then calls submitpackage, this second RPC should fail while the package topology isn’t changed.

    I think this can be concerning for L2 with pre-signed transactions, as this illegal package might be the only valid state owned by the L2 node to enforce her balance.


    glozow commented at 8:03 pm on November 9, 2021:
    Yeah, I agree completely that we will end up with 3 flavors of packages. When we add submitrawpackage, it will be best to have TestMultipleTransactions, PackageChildWithParents, PackageChildWithParents(test_accept=true). These would correspond with user-facing interfaces testmempoolaccept, submitrawpackage, and testpackage (?)

    jnewbery commented at 8:10 am on December 21, 2021:
    Totally agree that these interfaces should be split up. We can :bike: :hut: the naming in a future PR.
  47. in src/validation.cpp:1129 in 7f8ba8db73 outdated
    1124+    }
    1125+
    1126+    LOCK(m_pool.cs);
    1127+    const auto& child = package[package.size() - 1];
    1128+    // The child is never allowed to conflict with the mempool.
    1129+    if (m_pool.exists(child->GetHash())) {
    


    ariard commented at 11:33 pm on October 24, 2021:

    I wonder if this check doesn’t wrongly block some replacement.

    Let’s say you have A + B already in the mempool, where B is a child of A. Then I submit A’ + B, where A’ is a same-txid-different-witness replacement candidate of A, with an improved feerate. A’+B should replace A + B, however it will be rejected as B is already in the mempool ?


    glozow commented at 8:20 pm on November 9, 2021:
    In this case, B is already in the mempool and should be deduplicated (removed) from the package. That logic will be added in a future PR.

    glozow commented at 4:24 pm on November 29, 2021:
    Ok I’ve added the deduplication logic into this PR (see “[validation] de-duplicate package transactions already in mempool” commit). Now, if a same-txid-different-witness transaction is in the mempool, we’ll treat it as already-in-mempool. We can add witness replacement later when we have that.
  48. in src/validation.cpp:1201 in 7f8ba8db73 outdated
    1112+
    1113+    // Check that the package is well-formed. If it isn't, we won't try to validate any of the
    1114+    // transactions and thus won't return any MempoolAcceptResults, just a package-wide error.
    1115+
    1116+    // Static package checks.
    1117+    if (!CheckPackage(package, package_state)) return PackageMempoolAcceptResult(package_state, {});
    


    ariard commented at 11:36 pm on October 24, 2021:
    Duplicated checks with AcceptMultipleTransactions ? I think it’s better to keep an unified package evaluation path between testmempoolaccept/submitpackage.

    glozow commented at 7:58 pm on November 9, 2021:
    Right, duplicated for now. I think it’s okay since this is a very cheap, context-free check. In the future, when AcceptPackage modifies the package (i.e. deduplicates transactions), it will make more sense to call CheckPackage() once again on the new set of transactions.

    glozow commented at 4:25 pm on November 29, 2021:
    Added deduplication logic, so the input to AcceptMultipleTransactions() is not necessarily the same input to AcceptPackage().
  49. in src/validation.cpp:1221 in 7f8ba8db73 outdated
    1129+    if (m_pool.exists(child->GetHash())) {
    1130+        package_state.Invalid(PackageValidationResult::PCKG_POLICY, "conflicts-with-mempool");
    1131+        return PackageMempoolAcceptResult(package_state, {});
    1132+    }
    1133+    // The package must be 1 child with all of its unconfirmed parents.
    1134+    std::unordered_set<uint256, SaltedTxidHasher> parent_txids;
    


    ariard commented at 11:44 pm on October 24, 2021:
    Can be unconfirmed_parent_txids.

    glozow commented at 8:17 pm on November 9, 2021:
    Done
  50. in src/validation.cpp:509 in eff3e48fae outdated
    504@@ -497,9 +505,9 @@ class MemPoolAccept
    505     MempoolAcceptResult AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
    506 
    507     /**
    508-    * Multiple transaction acceptance. Transactions may or may not be interdependent,
    509-    * but must not conflict with each other. Parents must come before children if any
    510-    * dependencies exist.
    511+    * Multiple transaction acceptance. Transactions may or may not be interdependent, but must not
    512+    * conflict with each other, and the transactions cannot already be in the mempool. Parents must
    


    ariard commented at 11:51 pm on October 24, 2021:
    “and the transactions cannot already be in the mempool” are you referring the check L1219 in AcceptPackage about child disallowed to conflict with the mempool ?

    glozow commented at 11:40 am on October 28, 2021:

    Note that this is AcceptMultipleTransactions and not AcceptPackage.

    I’m referring to the check inPreChecks(), where it returns failure if the transaction is already in the mempool. So AcceptMultipleTransactions expects the txns passed in to not already be in the mempool. I think it’s important to state this precondition in the function documentation.

    On the other hand, AcceptPackage will allow transactions to already be in the mempool, and will de-duplicate them before passing them to AcceptMultipleTransactions (not included in this PR, but see #22290).


    ariard commented at 11:40 pm on November 1, 2021:

    That’s right, it’s AcceptMultipleTransactions. I agree it’s important to state requirements in function documentation.

    Note as a nit outside the scope of this PR, testmempoolaccept documentation doesn’t seem to reflect the not-already-in-mempool condition “If multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other”


    glozow commented at 7:56 pm on November 9, 2021:

    “package policies apply: the transactions cannot conflict with any mempool transactions or each other”

    https://github.com/bitcoin/bitcoin/blob/8ae4ba481ce8f7da173bef24432729c87a36cb70/src/rpc/rawtransaction.cpp#L875-L876

  51. in src/validation.cpp:1010 in eff3e48fae outdated
    1006@@ -992,22 +1007,90 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
    1007 
    1008     // This transaction should only count for fee estimation if:
    1009     // - it's not being re-added during a reorg which bypasses typical mempool fee limits
    1010+    // - it's not part of a package, which would likely mean its base feerate is not an accurate
    


    ariard commented at 11:54 pm on October 24, 2021:

    Well, the base feerate of a package component is still offered to the miner, even if this component should not have been accepted in the mempool as a single tx ?

    I think a stronger rational is the lack of p2p packages for now, preventing a package to effectively propagate to miner mempools and as such be considered as a valid blockspace demand ?


    glozow commented at 8:17 pm on November 9, 2021:
    Good point, edited
  52. ariard commented at 11:56 pm on October 24, 2021: member
    Reviewed until 5ab8cb23
  53. in src/validation.h:230 in 5ab8cb23e4 outdated
    236-*/
    237+ * Validate a package for submission to the mempool.
    238+ * @param[in]   test_accept     When true, run validation checks but don't submit to mempool.
    239+ * @param[in]   txns            Package to be validated.
    240+ *
    241+ * See package_mempool_accept.md for documentation of package validation rules.
    


    jnewbery commented at 9:49 am on October 27, 2021:

    Outdated name:

    0 * See doc/policy/packages.md for documentation of package validation rules.
    
  54. in src/validation.cpp:1207 in 5ab8cb23e4 outdated
    1215+
    1216+    // Static package checks.
    1217+    if (!CheckPackage(package, package_state)) return PackageMempoolAcceptResult(package_state, {});
    1218+
    1219+    // All transactions in the package must be a parent of the last transaction. This is just an
    1220+    // opportunity for us to fail fast on a static check without taking the mempool lock.
    


    jnewbery commented at 9:56 am on October 27, 2021:

    I’d suggest not using the word “static” here, which already has multiple meanings in c++ that don’t apply here. I think “context-free” would be better.

    I’d also suggest changing the commit log from [packages] add static IsChildWithParents function to [packages] add context-free IsChildWithParents function


    glozow commented at 7:58 pm on November 9, 2021:
    Good point, done
  55. in src/policy/packages.cpp:66 in 5ab8cb23e4 outdated
    59@@ -60,3 +60,28 @@ bool CheckPackage(const Package& txns, PackageValidationState& state)
    60     }
    61     return true;
    62 }
    63+
    64+bool IsChildWithParents(const Package& package)
    65+{
    66+    assert(!package.empty());
    


    jnewbery commented at 9:58 am on October 27, 2021:
    Does this need to assert? Why not just fail the if (package.size() < 2) return false; test below and return false? The calling code already needs to handle that failure, and by definition the empty set is not a ChildWithParents.
  56. in src/policy/packages.cpp:71 in 5ab8cb23e4 outdated
    66+    assert(!package.empty());
    67+    assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;}));
    68+
    69+    if (package.size() < 2) return false;
    70+
    71+    const auto& child = package[package.size() - 1];
    


    jnewbery commented at 10:02 am on October 27, 2021:
    Maybe use std::vector::back()? That returns a reference to the last element in the vector. You’ve already established that exists by returning if package.size < 2.

    glozow commented at 8:11 pm on November 9, 2021:
    Thanks, done.
  57. in src/policy/packages.cpp:81 in 5ab8cb23e4 outdated
    76+                   [](const auto& input) { return input.prevout.hash; });
    77+
    78+    std::unordered_set<uint256, SaltedTxidHasher> parent_txids;
    79+    std::transform(package.cbegin(), package.cbegin() + (package.size() - 1),
    80+                   std::inserter(parent_txids, parent_txids.end()),
    81+                   [](const auto& tx) { return tx->GetHash(); });
    


    jnewbery commented at 10:11 am on October 27, 2021:
    You don’t need to construct this parent_txids set. You just need to pass once through package from the start to the penultimate entry, checking that the hash of each transaction is in input_txids (and exiting early with failure if it’s not found).

    glozow commented at 8:11 pm on November 9, 2021:
    Thanks, done
  58. in src/policy/packages.cpp:71 in 5ab8cb23e4 outdated
    68+
    69+    if (package.size() < 2) return false;
    70+
    71+    const auto& child = package[package.size() - 1];
    72+
    73+    std::unordered_set<uint256, SaltedTxidHasher> input_txids;
    


    jnewbery commented at 10:13 am on October 27, 2021:
    Is there any advantage to this being an unordered set instead of a set? The size is small enough that the insertion/lookup complexity doesn’t matter.

    glozow commented at 8:04 pm on November 9, 2021:
    No advantage, but no disadvantage afaik?

    laanwj commented at 4:29 pm on November 23, 2021:
    Defaulting to unordered set unless there’s a reason to need ordering for determinism makes sense, imo.
  59. in src/test/txpackage_tests.cpp:140 in 5ab8cb23e4 outdated
    36+                                                       CAmount(48 * COIN), /* submit */ false);
    37+        CTransactionRef tx_child = MakeTransactionRef(mtx_child);
    38+
    39+        PackageValidationState state;
    40+        BOOST_CHECK(CheckPackage({tx_parent, tx_child}, state));
    41+        BOOST_CHECK(!CheckPackage({tx_child, tx_parent}, state));
    


    jnewbery commented at 10:27 am on October 27, 2021:
    Can you check the state.GetResult() and state.GetRejectReason() are what you’re expecting ( PackageValidationResult::PCKG_BAD and "package-not-sorted")
  60. in src/test/txpackage_tests.cpp:164 in 5ab8cb23e4 outdated
    58+        BOOST_CHECK(!IsChildWithParents(package));
    59+
    60+        // The parents can be in any order.
    61+        FastRandomContext rng;
    62+        Shuffle(package.begin(), package.end(), rng);
    63+        package.push_back(MakeTransactionRef(child));
    


    jnewbery commented at 10:29 am on October 27, 2021:
    0
    1        // Now add the child to the package
    2        package.push_back(MakeTransactionRef(child));
    

    glozow commented at 8:03 pm on November 9, 2021:
    I feel like that comment is unnecessary?
  61. in src/test/txpackage_tests.cpp:159 in 5ab8cb23e4 outdated
    53+            child.vin.push_back(CTxIn(COutPoint(parent->GetHash(), 0)));
    54+        }
    55+        child.vout.push_back(CTxOut(47 * COIN, spk2));
    56+
    57+        // The child must be in the package.
    58+        BOOST_CHECK(!IsChildWithParents(package));
    


    jnewbery commented at 10:29 am on October 27, 2021:
    check failure reason

    glozow commented at 11:35 am on October 28, 2021:
    IsChildWithParents() just returns a bool and doesn’t use a PackageValidationState so there is no failure reason.

    jnewbery commented at 12:05 pm on October 28, 2021:
    :facepalm:
  62. in src/test/txpackage_tests.cpp:175 in 5ab8cb23e4 outdated
    69+        package.erase(package.begin());
    70+        BOOST_CHECK(IsChildWithParents(package));
    71+
    72+        // The package cannot have unrelated transactions.
    73+        package.insert(package.begin(), m_coinbase_txns[0]);
    74+        BOOST_CHECK(!IsChildWithParents(package));
    


    jnewbery commented at 10:30 am on October 27, 2021:
    check failure reason

    glozow commented at 11:36 am on October 28, 2021:
    IsChildWithParents() just returns a bool and doesn’t use a PackageValidationState so there is no failure reason.

    jnewbery commented at 12:05 pm on October 28, 2021:
    :facepalm: :facepalm:
  63. in src/test/txpackage_tests.cpp:85 in 5ab8cb23e4 outdated
    80+        mtx_parent.vin.push_back(CTxIn(COutPoint(m_coinbase_txns[0]->GetHash(), 0)));
    81+        mtx_parent.vout.push_back(CTxOut(20 * COIN, spk));
    82+        mtx_parent.vout.push_back(CTxOut(20 * COIN, spk2));
    83+        CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
    84+
    85+        CMutableTransaction mtx_parent2;
    


    jnewbery commented at 10:31 am on October 27, 2021:
    0        CMutableTransaction mtx_parent_also_child;
    

    to match the naming below


    glozow commented at 8:17 pm on November 9, 2021:
    Sure, done
  64. in src/test/txpackage_tests.cpp:200 in 5ab8cb23e4 outdated
    93+        mtx_child.vout.push_back(CTxOut(49 * COIN, spk));
    94+        CTransactionRef tx_child = MakeTransactionRef(mtx_child);
    95+
    96+        PackageValidationState state;
    97+        BOOST_CHECK(CheckPackage({tx_parent, tx_parent_also_child, tx_child}, state));
    98+        BOOST_CHECK(IsChildWithParents({tx_parent, tx_parent_also_child, tx_child}));
    


    jnewbery commented at 10:33 am on October 27, 2021:
    Perhaps also check {tx_parent_also_child, tx_parent, tx_child}. I think it’ll fail CheckPackage() since it’s not sorted, but will succeed IsChildWithParents().

    glozow commented at 7:52 pm on November 9, 2021:
    Good idea, added
  65. in src/test/txpackage_tests.cpp:19 in 5ab8cb23e4 outdated
    14+
    15+#include <boost/test/unit_test.hpp>
    16+
    17+BOOST_AUTO_TEST_SUITE(txpackage_tests)
    18+
    19+BOOST_FIXTURE_TEST_CASE(static_package_tests, TestChain100Setup)
    


    jnewbery commented at 10:35 am on October 27, 2021:
    0BOOST_FIXTURE_TEST_CASE(non_contextual_package_tests, TestChain100Setup)
    

    glozow commented at 8:16 pm on November 9, 2021:
    Done
  66. in src/test/txpackage_tests.cpp:207 in 5ab8cb23e4 outdated
    94+        CTransactionRef tx_child = MakeTransactionRef(mtx_child);
    95+
    96+        PackageValidationState state;
    97+        BOOST_CHECK(CheckPackage({tx_parent, tx_parent_also_child, tx_child}, state));
    98+        BOOST_CHECK(IsChildWithParents({tx_parent, tx_parent_also_child, tx_child}));
    99+    }
    


    jnewbery commented at 10:37 am on October 27, 2021:

    Other possible unit tests (not necessarily in this PR):

    • too large package (26 txs)
    • too heavy package
    • conflicting txs in package
    • duplicate txs in package (with same and different witnesses)

    jnewbery commented at 12:39 pm on October 27, 2021:
    Oops. I see now that these are in txvalidation_tests.cpp. Why are some of the package unit tests in txpackage_tests.cpp and some in txvalidation_tests.cpp?

    glozow commented at 1:09 pm on October 27, 2021:
    When I first added these tests in #20833, all the checks in CheckPackage() were inside AcceptMultipleTransactions() (which is why I’m calling ProcessNewPackage() to hit those errors). Now that they’ve been moved to their own function in packages.h, we can make the tests in txvalidation_tests.cpp simpler unit tests and move them to txpackage_tests.cpp.

    glozow commented at 1:00 pm on October 28, 2021:

    Now that they’ve been moved to their own function in packages.h, we can make the tests in txvalidation_tests.cpp simpler unit tests and move them to txpackage_tests.cpp.

    Added this in #23381

  67. in doc/policy/packages.md:9 in 5ab8cb23e4 outdated
    0@@ -0,0 +1,57 @@
    1+# Package Mempool Accept
    2+
    3+## Definitions
    4+
    5+A **package** is an ordered list of transactions, representable by a Directed Acyclic Graph (a
    6+directed edge exists between a transaction that spends the output of another transaction).
    7+
    8+For every transaction `t` in a **topologically sorted** package, if any of its parents are present
    9+in the package, they appear somewhere in the list before `t`.
    


    jnewbery commented at 10:39 am on October 27, 2021:
    This seems sufficient for now. In the future, we might want to define a canonically sorted package, where the txs are sorted by generation, then by txid.
  68. in doc/policy/packages.md:6 in 5ab8cb23e4 outdated
    0@@ -0,0 +1,57 @@
    1+# Package Mempool Accept
    2+
    3+## Definitions
    4+
    5+A **package** is an ordered list of transactions, representable by a Directed Acyclic Graph (a
    6+directed edge exists between a transaction that spends the output of another transaction).
    


    jnewbery commented at 10:41 am on October 27, 2021:
    It’s unclear to me whether this definition implies that the graph is fully connected (i.e. every transaction in the package is (transitively) connected to every other transaction in the package through parent/child relationships).

    glozow commented at 8:16 pm on November 9, 2021:
    I’ve added “connected.” I hesitate to write “fully” because I have sometimes seen “fully connected” used interchangeably with complete.
  69. in doc/policy/packages.md:14 in 5ab8cb23e4 outdated
     9+in the package, they appear somewhere in the list before `t`.
    10+
    11+A **child-with-unconfirmed-parents** package is a topologically sorted package that consists of
    12+exactly one child and all of its unconfirmed parents (no other transactions may be present).
    13+The last transaction in the package is the child; each of its inputs must refer to a UTXO in the
    14+current chain tip or some preceding transaction in the package.
    


    jnewbery commented at 10:43 am on October 27, 2021:
    Perhaps highlight that this means that a child-with-unconfirmed-parents is contextual on the block chain. It can only be defined uniquely in the context of a UTXO set (which can be represented by a chain tip block hash).

    glozow commented at 8:13 pm on November 9, 2021:
    Clarified
  70. jnewbery commented at 10:53 am on October 27, 2021: member

    I’ve reviewed the first 3 commits. Looking good so far!

    I suggest changing the word “static” in the first two commits to “non-contextual”.

  71. in src/validation.cpp:875 in 5ab8cb23e4 outdated
    876+    const CTransaction& tx = *ws.m_ptx;
    877+    const uint256& hash = ws.m_hash;
    878+    TxValidationState& state = ws.m_state;
    879+    const CTxMemPool::setEntries setIterConflicting = m_pool.GetIterSet(ws.m_conflicts);
    880+
    881+    ws.m_replacement_transaction = ws.m_conflicts.size();
    


    jnewbery commented at 1:09 pm on October 27, 2021:

    I think it’s better to continue using std::set::empty rather than implicitly casting the int to a bool:

    0    ws.m_replacement_transaction = !ws.m_conflicts.empty();
    

    glozow commented at 1:00 pm on October 28, 2021:
    Done in #23381
  72. in src/validation.cpp:593 in 5ab8cb23e4 outdated
    590+    // Try to add all transactions to the mempool, atomically (either all of the transactions are
    591+    // added or none). Returns true if all of the transactions are in the mempool after size
    592+    // limiting is performed, false otherwise.
    593+    bool FinalizePackage(const ATMPArgs& args, std::vector<Workspace>& workspaces, PackageValidationState& package_state,
    594+                         std::map<const uint256, const MempoolAcceptResult>& results)
    595+                         EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
    


    jnewbery commented at 1:29 pm on October 27, 2021:

    nit: the lock annotation shouldn’t be aligned with the open parens (since it’s not contained in the parens):

    0    bool FinalizePackage(const ATMPArgs& args, std::vector<Workspace>& workspaces, PackageValidationState& package_state,
    1                         std::map<const uint256, const MempoolAcceptResult>& results)
    2        EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
    

    glozow commented at 8:13 pm on November 9, 2021:
    Fixed
  73. hernanmarino commented at 4:53 pm on October 27, 2021: contributor
    Concept ACK
  74. glozow commented at 12:53 pm on October 28, 2021: member
    It seems that this PR is a bit large for reviewers, so I’ve opened #23381 with all of the no-behavior-change commits from this PR. Reviewing that one should be more mechanical. I will address review comments, but am marking this PR as a draft until that one is merged.
  75. glozow marked this as a draft on Oct 28, 2021
  76. jnewbery removed this from the "Blockers" column in a project

  77. laanwj referenced this in commit e70fb87a4f on Nov 9, 2021
  78. jnewbery commented at 4:34 pm on November 9, 2021: member
    rebase plz
  79. glozow force-pushed on Nov 9, 2021
  80. glozow force-pushed on Nov 9, 2021
  81. glozow marked this as ready for review on Nov 9, 2021
  82. glozow commented at 1:22 am on November 10, 2021: member
    Rebased, ready for review :)
  83. in src/validation.cpp:1220 in af762281be outdated
    1215+    // The child is never allowed to conflict with the mempool.
    1216+    if (m_pool.exists(GenTxid::Txid(child->GetHash()))) {
    1217+        package_state.Invalid(PackageValidationResult::PCKG_POLICY, "conflicts-with-mempool");
    1218+        return PackageMempoolAcceptResult(package_state, {});
    1219+    }
    1220+    // The package must be 1 child with all of its unconfirmed parents.
    


    t-bast commented at 1:33 pm on November 10, 2021:

    nit: I found the similar comment in packages.cpp clearer: “The package is expected to be sorted, so the last transaction is the child.”

    I would clarify it to:

    0    // The package is expected to be sorted, so all transactions except the last one are the unconfirmed parents
    

    glozow commented at 4:11 pm on November 29, 2021:
    Added to the comment
  84. t-bast commented at 1:49 pm on November 10, 2021: member

    Code Review ACK https://github.com/bitcoin/bitcoin/pull/22674/commits/af762281be1a26dc89fa8edcae14eb187c31a185

    There are subtleties with the m_view chain backend that are outside of my reach, and cache state as well, so worth having experienced mempool people look into it.

  85. glozow added this to the "Blockers" column in a project

  86. in src/validation.cpp:1119 in 40d3f98e7b outdated
    1114+
    1115+    LOCK(m_pool.cs);
    1116+    const auto& child = package[package.size() - 1];
    1117+    // The child is never allowed to conflict with the mempool.
    1118+    if (m_pool.exists(GenTxid::Txid(child->GetHash()))) {
    1119+        package_state.Invalid(PackageValidationResult::PCKG_POLICY, "conflicts-with-mempool");
    


    achow101 commented at 8:39 pm on November 10, 2021:

    In 40d3f98e7b3d9630f99efe06fba1f6a6a21794b5 “[policy] require submitted packages to be child-with-unconfirmed-parents”

    ISTM this isn’t that the transaction conflicts with the mempool but rather that it already exists in the mempool? A similar check is done in PreChecks where the wtxid is also checked. Should the wtxid be checked here as well?


    glozow commented at 4:13 pm on November 29, 2021:
    Right - that comment didn’t match the code. I’ve removed this now, since it’s not relevant until package RBF
  87. in src/validation.cpp:1218 in 40d3f98e7b outdated
    1123+    std::unordered_set<uint256, SaltedTxidHasher> unconfirmed_parent_txids;
    1124+    std::transform(package.cbegin(), package.cbegin() + (package.size() - 1),
    1125+                   std::inserter(unconfirmed_parent_txids, unconfirmed_parent_txids.end()),
    1126+                   [](const auto& tx) { return tx->GetHash(); });
    1127+
    1128+    // All child inputs must refer to a preceding package transaction or a confirmed UTXO. The only
    


    achow101 commented at 8:49 pm on November 10, 2021:

    In 40d3f98e7b3d9630f99efe06fba1f6a6a21794b5 “[policy] require submitted packages to be child-with-unconfirmed-parents”

    Why must non-package inputs be confirmed? From my understanding of AcceptMultipleTransactions, it would be fine to have transactions which have unconfirmed inputs so long as they are already in the mempool.


    glozow commented at 9:39 pm on November 10, 2021:
    We would still be able to validate the package if it was missing parents that were already in our mempool, but it means our peer sent us a malformed package (in the future when we have package relay).
  88. in src/validation.cpp:483 in fd3dbcc7c7 outdated
    479@@ -474,6 +480,7 @@ class MemPoolAccept
    480                             /* m_coins_to_uncache */ coins_to_uncache,
    481                             /* m_test_accept */ true,
    482                             /* m_allow_bip125_replacement */ false,
    483+                            /* m_package_submission */ false,
    


    achow101 commented at 8:56 pm on November 10, 2021:

    In fd3dbcc7c74eb9da6631c217893c325266bbf2e1 “[validation] full package accept + mempool submission”

    Perhaps mention that this value is unused, so it is ok for it to be false? At first, this seemed incorrect since testing package acceptance should have the same parameters as actual package acceptance (modulo adding to the mempool).


    glozow commented at 4:21 pm on November 29, 2021:
    Added a comment for this
  89. in src/validation.cpp:1041 in fd3dbcc7c7 outdated
    1038+    // ConsensusScriptChecks adds to the script cache and is therefore consensus-critical;
    1039+    // CheckInputsFromMempoolAndCache asserts that transactions only spend coins available from the
    1040+    // mempool or UTXO set. Submit each transaction to the mempool immediately after calling
    1041+    // ConsensusScriptChecks to make the outputs available for subsequent transactions.
    1042+    for (Workspace& ws : workspaces) {
    1043+        // Since PolicyScriptChecks() passed, this should never fail.
    


    achow101 commented at 9:02 pm on November 10, 2021:

    In fd3dbcc7c74eb9da6631c217893c325266bbf2e1 “[validation] full package accept + mempool submission”

    If these are never supposed to fail, then shouldn’t these be an assertion rather than returning a failure?


    glozow commented at 2:22 pm on November 29, 2021:
    This would only fail if there is some serious bug in our consensus/policy code such that policy script checks are looser than consensus. If such a bug exists and an attacker finds it before we do, having an assertion here allows the attacker to crash nodes by sending out transactions exploiting it. However, it’d definitely be good to throw in debug mode so that we can hope to catch it in testing/fuzzing: see #23590

    glozow commented at 4:19 pm on November 29, 2021:
    Added Assume(false) to all of the “should never fail” checks so they throw in debug mode.
  90. achow101 commented at 9:15 pm on November 10, 2021: member
    Code Review ACK af762281be1a26dc89fa8edcae14eb187c31a185
  91. in src/policy/packages.cpp:78 in af762281be outdated
    73+                   std::inserter(input_txids, input_txids.end()),
    74+                   [](const auto& input) { return input.prevout.hash; });
    75+
    76+    // Every transaction must be a parent of the last transaction in the package.
    77+    return std::all_of(package.cbegin(), package.cbegin() + package.size() - 1,
    78+                       [&input_txids](const auto& ptx) { return input_txids.count(ptx->GetHash()) > 0; });
    


    stickies-v commented at 11:49 pm on November 10, 2021:

    nit: since we’ve already asserted that package.size() >= 2, I think this can be slightly simplified to

    0    return std::all_of(package.cbegin(), package.cend() - 1,
    1                       [&input_txids](const auto& ptx) { return input_txids.count(ptx->GetHash()) > 0; });
    

    also in validation.cpp


    glozow commented at 4:18 pm on November 29, 2021:
    Changed both
  92. in src/test/txpackage_tests.cpp:148 in af762281be outdated
    143+        BOOST_CHECK(IsChildWithParents({tx_parent, tx_child}));
    144+    }
    145+
    146+    // 24 Parents and 1 Child
    147+    {
    148+        std::vector<CTransactionRef> package;
    


    stickies-v commented at 0:07 am on November 11, 2021:

    nit: is there a reason for not just using the Package alias?

    0        Package package;
    

    glozow commented at 4:13 pm on November 29, 2021:
    Done
  93. in src/test/txpackage_tests.cpp:194 in af762281be outdated
    189+        CTransactionRef tx_parent_also_child = MakeTransactionRef(mtx_parent_also_child);
    190+
    191+        CMutableTransaction mtx_child;
    192+        mtx_child.vin.push_back(CTxIn(COutPoint(tx_parent->GetHash(), 1)));
    193+        mtx_child.vin.push_back(CTxIn(COutPoint(tx_parent_also_child->GetHash(), 0)));
    194+        mtx_child.vout.push_back(CTxOut(49 * COIN, spk));
    


    stickies-v commented at 0:36 am on November 11, 2021:
    nit: mtx_child spends 49, but only has 20+20 vin. It doesn’t affect the tests, but it confused me for a while.

    glozow commented at 4:18 pm on November 29, 2021:
    Changed to spend 39
  94. in src/test/txpackage_tests.cpp:218 in af762281be outdated
    213+    unsigned int expected_pool_size = m_node.mempool->size();
    214+    CKey parent_key;
    215+    parent_key.MakeNewKey(true);
    216+    CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
    217+
    218+    // Unrelated packages are not allowed in package submission.
    


    stickies-v commented at 2:59 pm on November 11, 2021:

    nit: I think you meant to say “Unrelated transactions”?

    0    // Unrelated transactions are not allowed in package submission.
    

    glozow commented at 4:13 pm on November 29, 2021:
    Yes, good catch :)
  95. stickies-v commented at 3:17 pm on November 11, 2021: member

    Code review ACK af762281be1a26dc89fa8edcae14eb187c31a185

    I’m new and don’t have enough understanding on the interactions of this PR with the rest of the codebase, so this is as far as my review will go. I added some nits, apologies if they’re too nitty . Not sure yet where the threshold is, so feel free to ignore them of course.

    Thanks for the detailed and consistent documentation.

  96. in doc/policy/packages.md:56 in af762281be outdated
    50+
    51+* Packages must be child-with-unconfirmed-parents packages. (#22674)
    52+
    53+   - *Rationale*: This allows for fee-bumping by CPFP. Allowing multiple parents makes it possible
    54+     to fee-bump a batch of transactions. Restricting packages to a defined topology is easier to
    55+     reason about and simplifies the validation logic greatly.
    


    ariard commented at 0:25 am on November 16, 2021:

    I checked the rules present in ProcessPackage/AcceptPackage/AcceptMultipleTransactions. There are 2 package policies I can see missing.

    The first one is the “package memory usage too high”. That being said this check is host-dependent so it would be too artificial to expect users to respect it. In practice, you will hit first MAX_PACKAGE_SIZE.

    The second one is the package must be at least 2-txn sized, otherwise you will fail on IsChildWithParents first check. It might be obvious from the “child-with-unconfirmed-parents packages” definition, though good to explicit requirement.

    Do you want to add to this specification that it’s “unstable” or “work-in-progress” and shouldn’t be strongly rely on by users for now ?

    Also I recommend to add a safety warning for the “Packages must be child-with-unconfirmed-parents packages” rule, such as “Please note the usage of multiple-parents one-child might be unsafe for your use-case and should be weigh in with care” (see previous comment).


    glozow commented at 4:21 pm on November 29, 2021:
    1. I’ve removed the “package memory usage too high” check
    2. Added a comment that packages must contain at least 2 transactions
    3. Added a disclaimer that the policy docs aren’t exhaustive
    4. Added a warning that multi-parent-1-child may be unsafe for some use cases
  97. in src/validation.cpp:1042 in af762281be outdated
    1039+    // This is different from the total virtual size of the transactions.
    1040+    const unsigned int total_memory_usage = std::accumulate(workspaces.cbegin(), workspaces.cend(), 0,
    1041+        [](unsigned int sum, const auto& ws) { return sum + ws.m_entry->DynamicMemoryUsage(); });
    1042+    if (total_memory_usage > MAX_DISCONNECTED_TX_POOL_SIZE * 1000) {
    1043+        return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package memory usage too high");
    1044+    }
    


    ariard commented at 1:41 am on November 16, 2021:

    IIUC the design rational behind this check is to prevent unbounded mempool growth in-between individual transaction acceptance and final call to LimitMempoolSize ?

    Intuitively, I would say package memory usage is already bounded by MAX_PACKAGE_SIZE and MAX_STANDARD_TX_WEIGHT, even if we have to assume malloc overhead, total_memory_usage should stay correlated enough to total vsize, to not have to introduce a new check ?


    glozow commented at 4:14 pm on November 29, 2021:
    Yeah you’re right, this is probably overkill. I’ve removed it.
  98. in src/validation.cpp:1055 in af762281be outdated
    1056+
    1057+        // Mempool ancestors may have changed since the last calculation done in PreChecks, since
    1058+        // package ancestors have already been submitted. Since PreChecks() and
    1059+        // PackageMempoolChecks() both enforce ancestor/descendant limits, this should never fail.
    1060+        std::string err_string;
    1061+        if(!m_pool.CalculateMemPoolAncestors(*ws.m_entry, ws.m_ancestors, m_limit_ancestors,
    


    ariard commented at 1:45 am on November 16, 2021:
    If this check should never fail, is it introduced as an additional belt-and-suspender of PreChecks, in case we asserted package ancestors/descendants limits with wrong relaxation ?

    glozow commented at 4:15 pm on November 29, 2021:
    I’ve added a comment to explain why we need to do this - addUnchecked needs a setAncestors. We need to recalculate it in here because the transaction might have new ancestors (i.e. preceding transactions in the package that were submitted).
  99. in src/validation.cpp:1084 in af762281be outdated
    1084+    if (!all_submitted) return false;
    1085+
    1086+    // Find the wtxids of the transactions that made it into the mempool. Allow partial submission,
    1087+    // but don't report success unless they all made it into the mempool.
    1088+    for (Workspace& ws : workspaces) {
    1089+        if (m_pool.exists(GenTxid::Wtxid(ws.m_ptx->GetWitnessHash()))) {
    


    ariard commented at 1:51 am on November 16, 2021:

    What are the reasons for a package transaction to not exist in the mempool at that step ?

    If it has failed the script checks, we should have marked all_submitted as false and returned early. Asking the question because FinalizePackage comment explicitly says that package acceptance should have been atomically or not (“either all of the transactions are added or none”) .


    jnewbery commented at 8:35 am on December 21, 2021:
    Transactions could have been removed in the LimitMempoolSize() call. Perhaps “either all of the transactions are added or none” is from an earlier branch? The function comment now says “The package may end up partially-submitted after size limitting”.
  100. in src/validation.cpp:1185 in af762281be outdated
    1181@@ -1074,9 +1182,76 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
    1182         }
    1183     }
    1184 
    1185+    if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, std::move(results));
    1186+
    1187+    if (!FinalizePackage(args, workspaces, package_state, results)) {
    1188+        package_state.Invalid(PackageValidationResult::PCKG_TX, "submission failed");
    


    ariard commented at 1:59 am on November 16, 2021:
    I think you’re overriding the m_result of package_state, which could have been set first in FinalizePackage ?

    glozow commented at 4:16 pm on November 29, 2021:
    Good point. Removed the package_state stuff from FinalizePackage().
  101. ariard commented at 2:01 am on November 16, 2021: member
    Still reviewing, I’ve not browse tests coverage so far.
  102. laanwj commented at 3:40 pm on November 23, 2021: member

    Thanks for adding documentation on the P2P transaction policy. Some small suggestions:

    • Please create a doc/policy/README.md that lists the files in the directory (this can be very brief right now, clearly, but can serve as an overview in the future when more documents are added)
    • Please add a link to doc/policy/README.md to doc/README.md so that everything can be found from the main README
  103. in doc/policy/packages.md:14 in af762281be outdated
     9+in the package, they appear somewhere in the list before `t`.
    10+
    11+A **child-with-unconfirmed-parents** package is a topologically sorted package that consists of
    12+exactly one child and all of its unconfirmed parents (no other transactions may be present).
    13+The last transaction in the package is the child, and its package can be canonically defined based
    14+on the current state: each of its inputs must refer to a UTXO in the current chain tip or some
    


    laanwj commented at 4:11 pm on November 23, 2021:
    I was a bit confused by “UTXO in the current chain tip” here. I understand that you mean “UTXO in the UTXO set as of the current chain tip”, but i first interpreted it as “a UTXO in the chain-tip block”.

    glozow commented at 4:16 pm on November 29, 2021:
    Changed to “each of its inputs must be available in the UTXO set as of the current chain tip”
  104. in src/validation.h:66 in 73fe6a19a9 outdated
    59@@ -60,6 +60,15 @@ static const unsigned int DEFAULT_ANCESTOR_SIZE_LIMIT = 101;
    60 static const unsigned int DEFAULT_DESCENDANT_LIMIT = 25;
    61 /** Default for -limitdescendantsize, maximum kilobytes of in-mempool descendants */
    62 static const unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT = 101;
    63+
    64+// If a package is submitted, it must be within the mempool's ancestor/descendant limits. Since a
    65+// submitted package must be child-with-unconfirmed-parents (all of the transactions are an ancestor
    66+// of the child), it doesn't make sense for package limits to exceed default mempool package limits.
    


    ariard commented at 2:11 am on November 24, 2021:

    Maybe the sentence could be better phrased than “make sense”, like “As submitted package are ultimately upper bounded by in-mempool package limits, ensure that package acceptance limits are encompassed by the in-mempool ones” ?

    Note, the mempool package limits can be adjusted by the node settings, e.g size=100. In that case, we refuse package, of which the size has been considered as acceptable by the node operator. I think it’s not a big deal, as those settings are rarely tweaked, but maybe batching applications are concerned ?


    glozow commented at 4:17 pm on November 29, 2021:

    Changed to:

    // If a package is submitted, it must be within the mempool’s ancestor/descendant limits. Since a // submitted package must be child-with-unconfirmed-parents (all of the transactions are an ancestor // of the child), package limits are ultimately bounded by mempool package limits. Ensure that the // defaults reflect this constraint.

  105. in src/test/txpackage_tests.cpp:271 in af762281be outdated
    266+    // 3 Generations is not allowed.
    267+    auto result_3gen_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
    268+                                                package_3gen, /* test_accept */ false);
    269+    BOOST_CHECK(result_3gen_submit.m_state.IsInvalid());
    270+    BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
    271+    BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
    


    ariard commented at 2:23 am on November 24, 2021:
    Do you have test coverage for the AcceptPackage-specific checks “conflicts-with-mempool” and “package-not-child-with-unconfirmed-parents” ? They might be already exercised elsewhere.

    glozow commented at 4:18 pm on November 29, 2021:
    I’ve removed the conflicts-with-mempool logic, since it’s not relevant right now. Added a test for package-not-child-with-unconfirmed-parents.
  106. ariard commented at 2:23 am on November 24, 2021: member
    Finished a whole first parse. Overall good.
  107. [packages] add static IsChildWithParents function 9b2fdca7f0
  108. [unit test] context-free package checks ba26169f60
  109. [packages/doc] define and document package rules
    Central place for putting package-related info. This document or parts
    of it can also be easily ported to other places if deemed appropriate.
    d59ddc5c3d
  110. [policy] require submitted packages to be child-with-unconfirmed-parents
    Note that this code path is not ever executed yet, because
    ProcessNewPackage asserts test_accept=true.
    144a29099a
  111. [validation] full package accept + mempool submission be3ff151a1
  112. [packages] add sanity checks for package vs mempool limits 8310d942e0
  113. [validation] de-duplicate package transactions already in mempool
    As node operators are free to set their mempool policies however they
    please, it's possible for package transaction(s) to already be in the
    mempool. We definitely don't want to reject the entire package in that
    case (as that could be a censorship vector).
    
    We should still return the successful result to the caller, so add
    another result type to MempoolAcceptResult.
    e12fafda2d
  114. [unit test] package submission 046e8ff264
  115. glozow force-pushed on Nov 29, 2021
  116. glozow commented at 4:29 pm on November 29, 2021: member
    Thanks for the reviews! I’ve addressed comments and added the commit handling transactions already in mempool since it was the answer to a lot of review questions. Also added some tests and a doc/policy/README.md + link in doc/README.md.
  117. laanwj commented at 2:35 pm on December 2, 2021: member
    Code review ACK 046e8ff264be6b888c0f9a9d822e32aa74e19b78 Changes since af76228 look good to me. Thanks for adding tests and documentation.
  118. laanwj merged this on Dec 15, 2021
  119. laanwj closed this on Dec 15, 2021

  120. laanwj removed this from the "Blockers" column in a project

  121. sidhujag referenced this in commit 45a1be76bd on Dec 15, 2021
  122. in src/validation.cpp:1237 in 046e8ff264
    1232+    m_view.SetBackend(m_active_chainstate.CoinsTip());
    1233+    const auto package_or_confirmed = [this, &unconfirmed_parent_txids](const auto& input) {
    1234+         return unconfirmed_parent_txids.count(input.prevout.hash) > 0 || m_view.HaveCoin(input.prevout);
    1235+    };
    1236+    if (!std::all_of(child->vin.cbegin(), child->vin.cend(), package_or_confirmed)) {
    1237+        package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-unconfirmed-parents");
    


    ariard commented at 0:09 am on December 16, 2021:

    Note, I think the error string is a little bit confusing here.

    The error sounds to suggest the package is invalid due to not being a child with unconfirmed parents. However, a package can be valid if parents are in different state than unconfirmed transactions in the mempool, namely already included in the chain tip. Reading back packages.md, the fact that the parents inputs can be already available in the UTXO set is laid out in the “Definitions” section, but not in the “Rules” ones. A hurried user could think a package must only be made of unconfirmed parents, a restriction on the utility of the feature.

    To reduce risks of confusion, I think the confirmed-parents allowance could be mentioned in the “Rules” section for redundancy and the message error here could be “missing-parents-from-package-or-utxo-set”.


    glozow commented at 12:10 pm on December 16, 2021:

    However, a package can be valid if parents are in different state than unconfirmed transactions in the mempool, namely already included in the chain tip.

    This is not the case. If the package contains a parent that is already confirmed, it is an invalid package. This is because we don’t want to rescan previous blocks for the parent transaction (we might not have them if we’re pruning); we only use the UTXO set.

    A hurried user could think a package must only be made of unconfirmed parents, a restriction on the utility of the feature.

    This would be correct. A package must only have unconfirmed parents.


    ariard commented at 11:53 pm on December 16, 2021:

    This is not the case. If the package contains a parent that is already confirmed, it is an invalid package.

    My mistake, I should have precised, “a package can be valid if the child parents are in different state than unconfirmed transactions in the mempool, namely already included in the chain tip” so we agree here.

    This would be correct. A package must only have unconfirmed parents.

    Yes, though the package child is allowed to have not-in-the-package-confirmed-parents. I was intending to hightlight that packages.md is not explicit on that. I think it can be precised later.

  123. in src/validation.cpp:1248 in 046e8ff264
    1243+
    1244+    LOCK(m_pool.cs);
    1245+    std::map<const uint256, const MempoolAcceptResult> results;
    1246+    // As node operators are free to set their mempool policies however they please, it's possible
    1247+    // for package transaction(s) to already be in the mempool, and we don't want to reject the
    1248+    // entire package in that case (as that could be a censorship vector).  Filter the transactions
    


    ariard commented at 1:22 am on December 16, 2021:

    While I agree on the end goal to avoid introducing tx-relay censorship vector, I disagree on assigning mempool policies heterogeneity as the root cause. If a user decides for a highly-restrictive policy (e.g bumping mintxrelayfee), as protocol developers, there is not that much we can do to prevent such “self”-censorship.

    What we would like to avoid is offering a naive pinning trick to a malicious counterparty in the context of time-sensitive multi-party protocols. E.g, in Lightning, a counterparty catches at early propagation a commitment transaction A from the holder, attaches a low-feerate child B’ on its own anchor output, then propagates widely this malicious package by mass-connecting. When the honest package A + B is reaching the majority of the network, it fails package acceptance if we don’t have a relaxation to re-evaluate package even if a subset of components are already in the mempool.


    glozow commented at 12:14 pm on December 16, 2021:

    Would this be a more comprehensive comment?

    “Node operators are free to set their mempool policies however they please, nodes may receive transactions in different orders, and malicious counterparties may try to take advantage of policy differences to pin transactions. As such, it’s possible for some package transaction(s) to already be in the mempool, and we don’t want to reject the entire package in that case (as that could be a censorship vector).”


    ariard commented at 11:57 pm on December 16, 2021:

    may try to take advantage of policy differences to pin transactions

    “or transactions propagation delays” (i.e bypass _INVENTORY_BROADCAST_INTERVAL timers)"

    Otherwise, far better, good to me.


    glozow commented at 4:17 pm on December 17, 2021:
    Applied your suggestion in #23804
  124. in src/validation.cpp:1267 in 046e8ff264
    1262+        } else if (m_pool.exists(GenTxid::Txid(txid))) {
    1263+            // Transaction with the same non-witness data but different witness (same txid,
    1264+            // different wtxid) already exists in the mempool.
    1265+            //
    1266+            // We don't allow replacement transactions right now, so just swap the package
    1267+            // transaction for the mempool one. Note that we are ignoring the validity of the
    


    ariard commented at 1:39 am on December 16, 2021:

    I think this is a behavior unsafe for the unwarranted user.

    In the context of multi-party protocol, a malicious counterparty broadcast a transaction A’ with an inflated witness. When the package A+B issued by a honest participant is entering into the mempool, A’ is swapped in place of A. However, A has a far smallest witness, thus swapping for A’ effectively downgrades the package feerate. This breaks the fee-bumping expectation of the honest participant, as a malleated package is now propagating on the network, instead of the higher one.

    As of today, I don’t know deployed protocols which would be affected by this package feerate-downgrade issue. That said, with the activation of Taproot it’s now far easier to introduce applications/protocols vulnerable, as shared-owners of a utxo might have witnesses unfairly asymmetric in weight due to the script path spend control block.

    I assume this behavior should be corrected if witness replacement is implemented. However, I think we shouldn’t make assumptions it’s done before package relay. In the meantime, I think it’s better to not introduce unsafe or obscure behaviors, of which the curation might be missed by future reviewers. I would say it’s better to just fail package acceptance if a same txid, different wtxid already exists in the mempool (same behavior than single-transaction acceptance in PreChecks).


    glozow commented at 12:34 pm on December 16, 2021:

    In the context of multi-party protocol, a malicious counterparty broadcast a transaction A’ with an inflated witness. When the package A+B issued by a honest participant is entering into the mempool, A’ is swapped in place of A. However, A has a far smallest witness, thus swapping for A’ effectively downgrades the package feerate.

    As specified in the proposal: “We use the package feerate of the package after deduplication” which is intended to avoid an issue like this, and others. More rationale is included in the proposal.

    This breaks the fee-bumping expectation of the honest participant, as a malleated package is now propagating on the network, instead of the higher one.

    Given that the feerate of A’ was sufficient to enter the mempool, this is made no worse or better with the introduction of package mempool accept, as we’re treating A’ vs A identically to how we treat individual transactions. We will deduplicate A from the package and assess B individually; its package feerate only includes the transactions that are not already in the mempool. Of course this means the ancestor score of B once everything is submitted is lower, but the danger has not been made worse by package mempool acceptance, as we would have the same result if B was received as an individual transaction.

    I assume this behavior should be corrected if witness replacement is implemented.

    Yes exactly. I still stipulate that package RBF and witness replacements can be worked on in parallel; both are improvements but neither requires the other. I’ve tried to document the limitation (known to both single transactions and packages) in the code, as seen in this diff.


    glozow commented at 12:38 pm on December 16, 2021:

    I would say it’s better to just fail package acceptance if a same txid, different wtxid already exists in the mempool (same behavior than single-transaction acceptance in PreChecks).

    I think this is worse than the bloated-witness problem acknowledged above. If A’ is already in the mempool and:

    • A and B are received separately as individual transactions -> A is rejected and B is accepted
    • A+B is received as a package -> package fails, B is rejected.

    This makes package acceptance more strict than individual transaction acceptance, which is something we want to avoid.


    t-bast commented at 5:48 pm on December 16, 2021:

    I agree with @glozow’s proposed behavior to use the existing mempool transaction (for now) in case it has a different wtxid. From what I see, the worse that can happen if that the sender proposed a package with feerate F1, and it ends up having feerate F2 < F1.

    A sender that doesn’t see its transaction included should keep bumping the child, so it should eventually confirm, it will just cost more than expected, but I don’t think it introduces a fundamental attack vector (at least I currently don’t see one).

    And we can later implement replacement to choose between A and A’ the one with the smallest weight (which is incentives compatible as it pays a higher feerate).


    ariard commented at 0:58 am on December 17, 2021:

    As specified in the proposal: “We use the package feerate of the package after deduplication” which is intended to avoid an issue like this, and others. More rationale is included in the proposal.

    Yes, I’ve browsed against the gist and it doesn’t precise in details how the deduplication is operated, at least on the witness replacement case (I think ?).

    Given that the feerate of A’ was sufficient to enter the mempool, this is made no worse or better with the introduction of package mempool accept, as we’re treating A’ vs A identically to how we treat individual transactions. We will deduplicate A from the package and assess B individually; its package feerate only includes the transactions that are not already in the mempool. Of course this means the ancestor score of B once everything is submitted is lower, but the danger has not been made worse by package mempool acceptance, as we would have the same result if B was received as an individual transaction.

    Yes, I agree package mempool accept hasn’t worsen that case compared to the individual transaction processing.

    Note, my comment was about the future when package relay is supported, if we forget to operate incentives compatibles witness replacement in the case of incoming A’ smaller than already in-mempool A. If we p2p relay the with-dedup-transactions package, that would constitute an attack vector as A + B have a smaller feerate than A’ + B.

    Currently, this is the code behavior though I think we all agree to correct that in the future. I think it’s nice to either update the gist or leave a more detailed comment in the code, and thus ease future review/implementation work.

    Yes exactly. I still stipulate that package RBF and witness replacements can be worked on in parallel

    I don’t fundamentally disagree on working them in parallel, beyond the scarcity of review bandwidth.

    This makes package acceptance more strict than individual transaction acceptance, which is something we want to avoid.

    Actually, I think package acceptance is stricter than individual transaction acceptance, at least at package limits evaluation (CheckPackageLimits). User-wise, I still think it’s better to fail loudly than silently accept a mutant, though if we can’t agree here, maybe at least warn back the user about the wtxid swap ?

  125. in src/validation.cpp:1282 in 046e8ff264
    1277+    }
    1278+
    1279+    // Nothing to do if the entire package has already been submitted.
    1280+    if (txns_new.empty()) return PackageMempoolAcceptResult(package_state, std::move(results));
    1281+    // Validate the (deduplicated) transactions as a package.
    1282+    auto submission_result = AcceptMultipleTransactions(txns_new, args);
    


    ariard commented at 2:15 am on December 16, 2021:

    As future package components should be evaluated on the union of the fees instead of the transaction ones to decide acceptance, uncareful deduplication could provoke unexpected package submission failures.

    Let’s say you have the package A+B+C, where A and B are parents of C, A and C are low-feerate, B is high-feerate. If B is already in the mempool, when the package A+B+C is received, B is dedup, then the union of fees of A+C is realized, it’s too low to satisfy the mempool min fee, the packages is rejected.

    To avoid this behavior, we should compute the union of fees pre-dedup in AcceptPackage, then pass down the result to AcceptMultipleTransactions. It’s open if future testmempoolaccept of multiple transaction should be also based on the union of fees, or we think it’s an acceptable divergence w.r.t package submission. I think we should have a TODO pointing to that context.

    Further, I think deduplication is also introducing issues w.r.t ancestors/descendants limits. If we decide to relay the package on the positive result from AcceptMultipleTransactions, a dedup package might have lower ancestors/descendants limits than the complete one we announce to our peers.


    glozow commented at 12:42 pm on December 16, 2021:

    Let’s say you have the package A+B+C, where A and B are parents of C, A and C are low-feerate, B is high-feerate. If B is already in the mempool, when the package A+B+C is received, B is dedup, then the union of fees of A+C is realized, it’s too low to satisfy the mempool min fee, the packages is rejected. To avoid this behavior, we should compute the union of fees pre-dedup in AcceptPackage, then pass down the result to AcceptMultipleTransactions.

    It is actually incentive-compatible to reject this package. B’s fees should not supplement A’s fees, because B does not depend on A; including B in the package feerate would be “sibling-pays-for-sibling” behavior. I’ve illustrated this in examples Q1 and Q2.


    glozow commented at 12:56 pm on December 16, 2021:

    Further, I think deduplication is also introducing issues w.r.t ancestors/descendants limits. If we decide to relay the package on the positive result from AcceptMultipleTransactions, a dedup package might have lower ancestors/descendants limits than the complete one we announce to our peers.

    No, we’ll still enforce ancestor/descendant limits on the package in the same way. The package transactions that are already in the mempool will be included in ancestor counts. The nice thing about a child-with-parents package is that all package transactions are ancestors of the child transaction so we won’t under/overestimate :).

    Since we always have all of the unconfirmed parents in the package, mempool contents won’t affect whether our package is within the limits specified by the protocol.

    For example, if we can’t have more than 25 in a package but somebody has a package where the child has 30 in-mempool ancestors, regardless of the size of the package after deduplication, the package is defined as child-with-all-unconfirmed-parents. Thus, since it has 30 unconfirmed ancestors, the defined package has a size of 31 and is too big.


    t-bast commented at 5:51 pm on December 16, 2021:

    It is actually incentive-compatible to reject this package. B’s fees should not supplement A’s fees, because B does not depend on A; including B in the package feerate would be “sibling-pays-for-sibling” behavior.

    I agree with that. @ariard if you also agree with that, is the rest of your comment resolved? Or is there a part I’m missing?


    ariard commented at 1:23 am on December 17, 2021:

    It is actually incentive-compatible to reject this package. B’s fees should not supplement A’s fees, because B does not depend on A; including B in the package feerate would be “sibling-pays-for-sibling” behavior. I’ve illustrated this in examples Q1 and Q2.

    You’re right, that wouldn’t be incentive-compatible. It also points that a naive implementation of fee union would be flawed, and that packages fees should be computed in topological descending order.

    No, we’ll still enforce ancestor/descendant limits on the package in the same way. The package transactions that are already in the mempool will be included in ancestor counts. The nice thing about a child-with-parents package is that all package transactions are ancestors of the child transaction so we won’t under/overestimate :).

    Right, at the individual transaction level. At the package-level, in CheckPackageLimits, which is processed after deduplication, a parent transaction is accounted both as the child ancestor and as a package member in the check against limitAncestorCount (L271, in src/txmempool.cpp). If we prune the package size at deduplication, we might pass that check, which would be fail by a peer with a mempool, lacking the dedup elements ?


    glozow commented at 11:15 am on December 17, 2021:

    At the package-level, in CheckPackageLimits, which is processed after deduplication, a parent transaction is accounted both as the child ancestor and as a package member in the check against limitAncestorCount (L271, in src/txmempool.cpp). If we prune the package size at deduplication, we might pass that check, which would be fail by a peer with a mempool, lacking the dedup elements ?

    Nope, this is a fair concern, but I made sure to make it impossible for a package transaction to be double-counted after de-duplication. See this assertion that transactions passed in to CheckPackageLimits() must not be in the mempool:

    https://github.com/bitcoin/bitcoin/blob/8c0bd871fcf6c5ff5851ccb18a7bc7554a0484b0/src/validation.cpp#L931-L936


    ariard commented at 0:28 am on December 20, 2021:

    Yes my concern is if we relay the de-dup transactions as part of the p2p package we announce to our peer. A package A is evaluated as valid by Alice because she is dedup’ing half-of-the-package due to already in-mempool transaction. The whole package is relayed to Bob, who has an empty mempool. The whole package is evaluated and fails against CheckPackageLimits.

    That said, I’m making assumptions on the p2p package mechanism, which has not been yet formalized. I’m making a mental note and I would say let’s think about it more when p2p is introduced. We can still correct AcceptPackage back then.

  126. ariard commented at 2:24 am on December 16, 2021: member

    I think it’s better either to revert e12fafd (or past reviewers to look on it)

    This commit is introducing a good chunk of context with implications for the rest of package relay support, it was not included in the branches previously reviewed and it has only been ACK’ed by 1 reviewer before merge.

  127. glozow commented at 12:45 pm on December 16, 2021: member

    Thanks @ariard for the prompt re-review!

    I think it’s better either to revert e12fafd (or past reviewers to look on it)

    (And re: IRC convos): I personally think e12fafda2dfbbdf63f125e5af797ecfaa6488f66 made sense for this PR and would have included it in the next one anyway, but I’m obviously biased as the author. I definitely welcome more review on it and can hold off on opening the next package mempool accept PR until it seems people are comfortable. If you have time @jnewbery @t-bast @stickies-v @achow101, we’re requesting a look at e12fafda2dfbbdf63f125e5af797ecfaa6488f66 which was added between your ACKs and the merge. Thanks! :)

  128. in src/validation.h:214 in 046e8ff264
    210@@ -191,7 +211,7 @@ struct PackageMempoolAcceptResult
    211 {
    212     const PackageValidationState m_state;
    213     /**
    214-    * Map from wtxid to finished MempoolAcceptResults. The client is responsible
    215+    * Map from (w)txid to finished MempoolAcceptResults. The client is responsible
    


    t-bast commented at 5:54 pm on December 16, 2021:
    I think this change can be problematic. Now we don’t know what the keys are for this map (txid or wtxid). Are callers expected to just try both?

    glozow commented at 6:09 pm on December 16, 2021:

    Essentially yes, so that we can handle same-txid-different-witness. It’s “if you can’t find it by wtxid then try txid because it may have been swapped out.”

    I also considered adding a special result type like ResultType::WITNESS_SWAPPED to communicate “try again using txid” to the caller (would that be better?).

    Another approach is to include the entire transaction in the MempoolAcceptResult, but it seems weird if the result transaction’s wtxid doesn’t match the key wtxid.

    It’s a little ugly, but I’m not sure if there’s a cleaner way to do this?


    t-bast commented at 6:18 pm on December 16, 2021:

    I also considered adding a special result type like ResultType::WITNESS_SWAPPED to communicate “try again using txid” to the caller (would that be better?).

    It does feel better to me, but there’s a part of your answer I don’t understand. This case only happens when the mempool contain a “same txid, different wtxid” entry, right? Let’s call A our proposed tx and A’ the mempool “same txid, different wtxid” tx. Why can’t you simply use A wtxid as the key in the m_tx_results map (even if A’ is actually used)? Why do you want the caller to try again? If the caller sees that the result type is ResultType::WITNESS_SWAPPED this should give them all the information they need?


    achow101 commented at 6:32 pm on December 16, 2021:
    I agree with @t-bast, I think it should be the original wtxid with a witness swapped result type.

    glozow commented at 11:04 am on December 17, 2021:
    I will open a PR to change this. Thanks very much for your reviews! :)

    glozow commented at 4:17 pm on December 17, 2021:
    Addressed in #23804
  129. t-bast commented at 5:55 pm on December 16, 2021: member
    https://github.com/bitcoin/bitcoin/pull/22674/commits/e12fafda2dfbbdf63f125e5af797ecfaa6488f66 looks good to me, I’m just unsure about the change in the tx results key set…
  130. in src/validation.cpp:1270 in e12fafda2d outdated
    1266+            //
    1267+            // We don't allow replacement transactions right now, so just swap the package
    1268+            // transaction for the mempool one. Note that we are ignoring the validity of the
    1269+            // package transaction passed in.
    1270+            // TODO: allow witness replacement in packages.
    1271+            auto iter = m_pool.GetIter(wtxid);
    


    achow101 commented at 6:25 pm on December 16, 2021:

    In e12fafda2dfbbdf63f125e5af797ecfaa6488f66 “[validation] de-duplicate package transactions already in mempool”

    Shouldn’t this be txid since if wtxid were in the mempool, the previous if would be the path taken?

    0            auto iter = m_pool.GetIter(txid);
    

    There also does not appear to be any tests for this case.


    glozow commented at 4:17 pm on December 17, 2021:
    Addressed in #23804
  131. in src/validation.cpp:1210 in 046e8ff264
    1205+    if (!IsChildWithParents(package)) {
    1206+        package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-parents");
    1207+        return PackageMempoolAcceptResult(package_state, {});
    1208+    }
    1209+
    1210+    const auto& child = package[package.size() - 1];
    


    jnewbery commented at 7:50 am on December 21, 2021:

    Perhaps:

    0    assert(package.size() > 1);
    1    const auto& child = package.back();
    

    The size of the package has been checked in IsChildWithParents(), but good to check here as a reminder to the developer and in case IsChildWithParents() is ever changed. Alternatively:

    0    // package size must be > 1 from check in `IsChildWithParents()`
    1    const auto& child = package.back();
    

    glozow commented at 5:48 pm on January 7, 2022:
    Done in #23804
  132. in src/validation.cpp:1214 in 046e8ff264
    1209+
    1210+    const auto& child = package[package.size() - 1];
    1211+    // The package must be 1 child with all of its unconfirmed parents. The package is expected to
    1212+    // be sorted, so the last transaction is the child.
    1213+    std::unordered_set<uint256, SaltedTxidHasher> unconfirmed_parent_txids;
    1214+    std::transform(package.cbegin(), package.end() - 1,
    


    jnewbery commented at 7:51 am on December 21, 2021:
    0    std::transform(package.cbegin(), package.cend() - 1,
    

    glozow commented at 5:48 pm on January 7, 2022:
    Done in #23804
  133. in src/validation.cpp:1067 in 046e8ff264
    1064+        // If we call LimitMempoolSize() for each individual Finalize(), the mempool will not take
    1065+        // the transaction's descendant feerate into account because it hasn't seen them yet. Also,
    1066+        // we risk evicting a transaction that a subsequent package transaction depends on. Instead,
    1067+        // allow the mempool to temporarily bypass limits, the maximum package size) while
    1068+        // submitting transactions individually and then trim at the very end.
    1069+        if (!Finalize(args, ws)) {
    


    jnewbery commented at 8:28 am on December 21, 2021:

    I don’t like this call into Finalize() from FinalizePackage(). You’ve essentially added a boolean parameter to Finalize() (by adding m_package_submission to ATMPArgs), that controls which parts of Finalize() are run. That’s usually a bit of a red flag.

    Currently, the only part of Finalize() that you need is the call to addUnchecked() (transaction replacement is currently disabled for packages, and validForFeeEstimation and LimitMempoolSize() are gated on m_package_submission). I think you should just bring the call to addUnchecked() directly into FinalizePackage().


    jnewbery commented at 8:56 am on December 21, 2021:

    It looks to me like calling addUnchecked() (via Finalize()) for a transaction that’s already in the mempool will cause problems (in addUnchecked(), mapTx.insert() will return the existing mempool transaction, and then various state will be incorrectly updated).

    I think the later commit e12fafda2dfbbdf63f125e5af797ecfaa6488f66 [validation] de-duplicate package transactions already in mempool prevents this by deduplicating transactions in the package. However, that’s not at all obvious since the checking code is quite removed from this call. I’d suggest:

    • Assume() that the transaction is not in the mempool here (and don’t call addUnchecked() if it is)
    • inside addUnchecked(), assert that the transaction is not in the mempool.

    glozow commented at 4:12 pm on January 7, 2022:

    Thanks for your feedback! I actually don’t think it’d be cleaner to move addUnchecked() to that function even though it’s the only package-related thing that’s done in Finalize() right now. It repeats code, and will be worse in the future: we’ll also need to remove replacements later with package RBF (and flag valid for fee estimation with package relay). The boolean has 2 uses now, and I intend to use it later for fee-related logic too.

    That being said, I can see how the calling relationships are different from expectations, so I’ll renamed FinalizePackage to SubmitPackage in the followup. Hopefully that makes it more clear that it’s not supposed to be analogous to Finalize().


    glozow commented at 4:42 pm on January 7, 2022:

    It’s actually prevented from the get-go, since AcceptMultipleTransactions() rejects the whole list of transactions if there are any that already exist in the mempool. The de-duplication logic allows us to only pass the not-already-in-mempool transactions.

    I agree it’s helpful and more future-proof to add some sanity checks, since calling addUnchecked() on a transaction that’s already in the mempool would be awful.


    glozow commented at 5:48 pm on January 7, 2022:
    Added assertions in #23804
  134. in src/validation.cpp:1046 in 046e8ff264
    1043+    // ConsensusScriptChecks adds to the script cache and is therefore consensus-critical;
    1044+    // CheckInputsFromMempoolAndCache asserts that transactions only spend coins available from the
    1045+    // mempool or UTXO set. Submit each transaction to the mempool immediately after calling
    1046+    // ConsensusScriptChecks to make the outputs available for subsequent transactions.
    1047+    for (Workspace& ws : workspaces) {
    1048+        if (!ConsensusScriptChecks(args, ws)) {
    


    jnewbery commented at 8:32 am on December 21, 2021:

    Is there a good reason that ConsensusScriptChecks() is inside FinalizePackage() when validating a package, but called before Finalize() when validating a single transaction? I think it’d be clearer if there was symmetry between the two (i.e. ConsensusScriptChecks() is called directly from AcceptMultipleTransactions()).

    There’s also the inconsistency that testmempoolaccept for a single transaction will call ConsensusScriptChecks(), but won’t call ConsensusScriptChecks() for packages.


    glozow commented at 11:39 am on January 7, 2022:

    We need to call ConsensusScriptChecks() + submit for each transaction in sequence (and cannot call all the ConsensusScriptChecks()s together, then submit them all together), because of the sanity checks inside ConsensusScriptChecks() that all coins are available. So unless we put all of the logic from FinalizePackage() into AcceptMultipleTransactions(), I don’t think it’s possible to make these patterns the same.

    However, I’m happy to rename it to SubmitPackage() or something so it doesn’t seem like there’s supposed to be a pattern. AFAIK ConsensusScriptChecks() is really part of submission logic.


    glozow commented at 5:48 pm on January 7, 2022:
    Renamed in #23804
  135. in src/validation.cpp:1054 in 046e8ff264
    1051+            all_submitted = Assume(false);
    1052+        }
    1053+
    1054+        // Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the
    1055+        // last calculation done in PreChecks, since package ancestors have already been submitted.
    1056+        std::string err_string;
    


    jnewbery commented at 8:33 am on December 21, 2021:

    Maybe:

    0        std::string unused_err_string;
    

    glozow commented at 5:48 pm on January 7, 2022:
    Done in #23804
  136. in src/validation.cpp:1259 in 046e8ff264
    1254+        // There are 3 possibilities: already in mempool, same-txid-diff-wtxid already in mempool,
    1255+        // or not in mempool. An already confirmed tx is treated as one not in mempool, because all
    1256+        // we know is that the inputs aren't available.
    1257+        if (m_pool.exists(GenTxid::Wtxid(wtxid))) {
    1258+            // Exact transaction already exists in the mempool.
    1259+            auto iter = m_pool.GetIter(wtxid);
    


    jnewbery commented at 8:44 am on December 21, 2021:
    I think this also needs to be m_pool.GetIter(txid) as below (https://github.com/bitcoin/bitcoin/pull/22674/files#r770804029). CTxMemPool::GetIter() takes a txid, so this lookup will fail if wtxid != txid.

    jnewbery commented at 10:44 am on December 21, 2021:
    In fact, I think for now you can just search for the txid using m_pool.GetIter(txid) and collapse the two cases (already in mempool, same-txid-different-wtxid in mempool) into one.

    glozow commented at 4:19 pm on January 7, 2022:

    CTxMemPool::GetIter() takes a txid, so this lookup will fail if wtxid != txid.

    Does it? I think since mapTx indexes by txid and wtxid, it might allow both? The test passes when txid != wtxid. Either way, thanks for the good catch, should probably make that interface clearer…

    In fact, I think for now you can just search for the txid using m_pool.GetIter(txid) and collapse the two cases (already in mempool, same-txid-different-wtxid in mempool) into one.

    Even though the two cases are treated the same, they probably won’t be in the future, which is why I separated the branches in this PR. Now, given that they’re already separate, I don’t think it makes much sense to collapse them and then separate them again when we need to.

  137. in src/validation.cpp:594 in 046e8ff264
    588@@ -564,6 +589,14 @@ class MemPoolAccept
    589     // limiting is performed, false otherwise.
    590     bool Finalize(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
    591 
    592+    // Submit all transactions to the mempool and call ConsensusScriptChecks to add to the script
    593+    // cache - should only be called after successful validation of all transactions in the package.
    594+    // The package may end up partially-submitted after size limitting; returns true if all
    


    jnewbery commented at 10:14 am on December 21, 2021:
    0    // The package may end up partially-submitted after size limiting; returns true if all
    

    glozow commented at 5:48 pm on January 7, 2022:
    Done in #23804
  138. in src/validation.cpp:1079 in 046e8ff264
    1076+    // It may or may not be the case that all the transactions made it into the mempool. Regardless,
    1077+    // make sure we haven't exceeded max mempool size.
    1078+    LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip(),
    1079+                     gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000,
    1080+                     std::chrono::hours{gArgs.GetIntArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
    1081+    if (!all_submitted) return false;
    


    jnewbery commented at 10:24 am on December 21, 2021:

    I’m not sure about this all_submitted variable. It seems like it’s being used to try to gracefully handle logic errors (eg the package size calculations were incorrect, or Finalize() unexpectedly returns false). However, I don’t think it really is graceful. If one of those conditions fails, then you’ll have called addUnchecked() for the transactions, but you’ll never make it to GetMainSignals().TransactionAddedToMempool() below (i.e. transactions will be added to the mempool without the relevant signal being fired).

    I think it may be best to remove this line and rely on the logic below to correctly set the mempool accept results and fire the right signal.


    glozow commented at 4:59 pm on January 7, 2022:
    Yikes! You’re right, we shouldn’t quit early here, because then the notifications don’t fire in the (rare) case we have a partial submission.

    glozow commented at 5:47 pm on January 7, 2022:
    Done in #23804
  139. in src/validation.cpp:1185 in 046e8ff264
    1178@@ -1074,9 +1179,114 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
    1179         }
    1180     }
    1181 
    1182+    if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, std::move(results));
    1183+
    1184+    if (!FinalizePackage(args, workspaces, package_state, results)) {
    1185+        package_state.Invalid(PackageValidationResult::PCKG_TX, "submission failed");
    


    jnewbery commented at 10:28 am on December 21, 2021:

    “submission failed” doesn’t add any new information. I think FinalizePackage() can only fail due to:

    • logic error
    • mempool full (package partially accepted or not accepted)

    Can we distinguish those and return the information in the PackageValidationState object?


    glozow commented at 5:47 pm on January 7, 2022:
    Done in #23804
  140. in src/validation.cpp:1344 in 046e8ff264
    1344+    }();
    1345 
    1346     // Uncache coins pertaining to transactions that were not submitted to the mempool.
    1347-    for (const COutPoint& hashTx : coins_to_uncache) {
    1348-        active_chainstate.CoinsTip().Uncache(hashTx);
    1349+    // Ensure the coins cache is still within limits.
    


    jnewbery commented at 10:31 am on December 21, 2021:
    Should this comment be above the BlockValidationState state_dummy; line?

    glozow commented at 5:47 pm on January 7, 2022:
    Done in #23804
  141. in src/validation.cpp:1246 in 046e8ff264
    1241+    // coins_to_uncache. The backend will be connected again when needed in PreChecks.
    1242+    m_view.SetBackend(m_dummy);
    1243+
    1244+    LOCK(m_pool.cs);
    1245+    std::map<const uint256, const MempoolAcceptResult> results;
    1246+    // As node operators are free to set their mempool policies however they please, it's possible
    


    jnewbery commented at 10:41 am on December 21, 2021:
    I don’t think this is necessarily due to node operators setting different policies. It may just be that a different peer sent us one of the parent transactions individually.

    glozow commented at 3:31 pm on January 7, 2022:
    yes, that’s stated right after the comma.
  142. in src/rpc/rawtransaction.cpp:978 in 046e8ff264
    973@@ -974,6 +974,8 @@ static RPCHelpMan testmempoolaccept()
    974             continue;
    975         }
    976         const auto& tx_result = it->second;
    977+        // Package testmempoolaccept doesn't allow transactions to already be in the mempool.
    978+        CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
    


    jnewbery commented at 10:53 am on December 21, 2021:
    How is this enforced? More specifically, what happens if testmempoolaccept is called with multiple transactions where one or more is already in the mempool? It looks to me like the assert in PackageMempoolChecks() could be triggered.

    glozow commented at 3:32 pm on January 7, 2022:
    testmempoolaccept doesn’t allow already-in-mempool transactions, so it will be rejected
  143. in src/test/txpackage_tests.cpp:310 in 046e8ff264
    305+        BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
    306+        BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent->GetHash())));
    307+        BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child->GetHash())));
    308+    }
    309+
    310+    // Already-in-mempool transactions should be detected and de-duplicated.
    


    jnewbery commented at 10:59 am on December 21, 2021:
    This tests the result when both transactions in the package are already in the mempool. It would be nice to test what happens when a package is a mix of transactions already in the mempool and new transactions.

    glozow commented at 12:41 pm on January 17, 2022:
    Added in #23804
  144. glozow deleted the branch on Jan 17, 2022
  145. fanquake referenced this in commit 417e7503f8 on Jan 25, 2022
  146. sidhujag referenced this in commit 81e7f6528e on Jan 28, 2022
  147. fanquake referenced this in commit bc49650b7c on Feb 22, 2022
  148. sidhujag referenced this in commit b2aae5647b on Feb 22, 2022
  149. Fabcien referenced this in commit 3addc7fab2 on Nov 14, 2022
  150. Fabcien referenced this in commit 8608818eaa on Nov 16, 2022
  151. Fabcien referenced this in commit 5b41420a65 on Nov 17, 2022
  152. Fabcien referenced this in commit fddc6b49b6 on Nov 17, 2022
  153. DrahtBot locked this on Jan 17, 2023

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-01 10:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me