wallet: Mark replaced tx to not be in the mempool anymore #18842

pull MarcoFalke wants to merge 1 commits into bitcoin:master from MarcoFalke:2004-walletBumpFeeReplacedTxNoMempool changing 3 files +15 −0
  1. MarcoFalke commented at 3:31 pm on May 1, 2020: member

    The wallet does not mark the replaced tx as out-of-mempool. This causes failures in user scripts, because later RPCs may depend on this state change from bumpfee.

    For example, the following might fail on current master:

    0txid = sendtoaddress(...)
    1bumpfee(txid)
    2abandontransaction(txid)  # fails because txid is still marked as "in mempool"
    

    Fixes #18831

  2. MarcoFalke commented at 3:35 pm on May 1, 2020: member
    This is an alternative to my preferred fix #18840
  3. DrahtBot added the label Wallet on May 1, 2020
  4. ryanofsky commented at 5:45 pm on May 1, 2020: member

    re: #18842 (comment)

    This is an alternative to my preferred fix #18840

    fa3fdbfa89719797bba3b044b96caa984c5ff4be does seem a little fragile. I guess I was thinking of something more like:

     0diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp
     1index 0e7641ae320..8b288e62ff7 100644
     2--- a/src/interfaces/chain.cpp
     3+++ b/src/interfaces/chain.cpp
     4@@ -309,6 +309,11 @@ public:
     5         LOCK(::mempool.cs);
     6         return IsRBFOptIn(tx, ::mempool);
     7     }
     8+    bool isInMempool(const uint256& txid) override
     9+    {
    10+        LOCK(::mempool.cs);
    11+        return ::mempool.exists(txid);
    12+    }
    13     bool hasDescendantsInMempool(const uint256& txid) override
    14     {
    15         LOCK(::mempool.cs);
    16diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
    17index fb77c81cdcb..c76a361f261 100644
    18--- a/src/interfaces/chain.h
    19+++ b/src/interfaces/chain.h
    20@@ -185,6 +185,9 @@ public:
    21     //! Check if transaction is RBF opt in.
    22     virtual RBFTransactionState isRBFOptIn(const CTransaction& tx) = 0;
    23 
    24+    //! Check if transaction is in mempool.
    25+    virtual bool isInMempool(const uint256& txid) = 0;
    26+
    27     //! Check if transaction has descendants in mempool.
    28     virtual bool hasDescendantsInMempool(const uint256& txid) = 0;
    29 
    30diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
    31index 7a6deab1a8c..833414d4880 100644
    32--- a/src/wallet/wallet.cpp
    33+++ b/src/wallet/wallet.cpp
    34@@ -707,6 +707,12 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
    35 
    36     wtx.mapValue["replaced_by_txid"] = newHash.ToString();
    37 
    38+    // Refresh mempool status without waiting for transactionRemovedFromMempool
    39+    // notification so the wallet is in an internally consistent state and
    40+    // immediately knows the old transaction should not be considered trusted
    41+    // and is eligible to be abandoned
    42+    wtx.fInMempool = chain().isInMempool(originalHash);
    43+
    44     WalletBatch batch(*database, "r+");
    45 
    46     bool success = true;
    
  5. promag commented at 0:13 am on August 15, 2020: member

    Concept ACK.

    This looks a bug to me, a test would be nice. @ryanofsky how could chain().isInMempool(originalHash) be true?

    I find odd that mapValue["replaces_txid"] is updated in feebumper and mapValue["replaced_by_txid"] is updated in wallet. Maybe we could move CWallet::MarkReplaced to feebumper.

  6. DrahtBot commented at 4:22 am on September 20, 2020: member

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

    Conflicts

    No conflicts as of last run.

  7. DrahtBot added the label Needs rebase on Oct 14, 2020
  8. Sjors commented at 1:51 pm on March 3, 2021: member
    Apparently the backwards compatibility test is still intermittently failing. Needs rebase.
  9. MarcoFalke force-pushed on Mar 5, 2021
  10. MarcoFalke commented at 4:14 pm on March 5, 2021: member
    Rebased. Not addressed any feedback, because the question by @promag hasn’t been answered: “@ryanofsky how could chain().isInMempool(originalHash) be true?”
  11. ryanofsky commented at 4:38 pm on March 5, 2021: member

    Rebased. Not addressed any feedback, because the question by @promag hasn’t been answered: “@ryanofsky how could chain().isInMempool(originalHash) be true?”

    I think it should be true because MarkReplaced should be called after the new transaction is committed and the old transaction is no longer in the mempool. As I understand it, the race happens because transactionRemovedFromMempool notification hasn’t been processed yet, not because the old transaction is actually in the mempool.

  12. ryanofsky commented at 5:00 pm on March 5, 2021: member

    Oh sorry, I answered why the question how could it be false, not how could it be true. It could be true if MarkReplaced was called at unexpected time on a transaction that wasn’t successfully replaced, or maybe restored after a replacement was abandoned. So the change was suggested to make MarkReplaced safer. An alternative to:

    0wtx.fInMempool = chain().isInMempool(originalHash);
    

    could be:

    0assert(!chain().isInMempool(originalHash));
    1wtx.fInMempool = false;
    

    and I think either of these would be more safe. Adding the comment from #18842 (comment) could also be helpful.

  13. ryanofsky commented at 5:01 pm on March 5, 2021: member
    Code review ACK faeedff5c87091fd83d2fb2b29eb49c948363f29 or for any of the suggested alternatives.
  14. DrahtBot removed the label Needs rebase on Mar 5, 2021
  15. MarcoFalke commented at 5:49 pm on March 5, 2021: member

    assert(!chain().isInMempool(originalHash));

    This seems fragile, as it (in theory) may crash the node. Imagine calling bumpfee, then deprioritizing the tx with prioritizetransaction, then adding back the original tx (before the interface call can complete).

  16. ryanofsky commented at 5:55 pm on March 5, 2021: member

    assert(!chain().isInMempool(originalHash));

    This seems fragile, as it (in theory) may crash the node. Imagine calling bumpfee, then deprioritizing the tx with prioritizetransaction, then adding back the original tx (before the interface call can complete).

    Right, that’s why I suggested wtx.fInMempool = chain().isInMempool(originalHash); to do the correct thing in this case. But crashing seems better than doing the wrong thing if we don’t want to handle this case.

  17. ryanofsky commented at 5:58 pm on March 5, 2021: member
    Or you could replace crashing assert with throwing CHECK. Basically #18842 (comment) is still my preferred alternative, but all alternatives discussed and current PR seem fine.
  18. MarcoFalke commented at 6:16 pm on March 5, 2021: member

    to do the correct thing in this case

    I don’t think it is possible to do a thing that is correct in all scenarios. The wallet is an async subscriber of the mempool, so if there are any mempool RPCs called concurrently (and conflicting) with wallet RPCs there is no way to “do the correct thing”. The design here is that the wallet will eventually catch up with the correct data if you give it enough time to eat all previous in-flight notifications.

  19. ryanofsky commented at 6:28 pm on March 5, 2021: member
    Is there something not correct about wtx.fInMempool = chain().isInMempool(originalHash); with cs_wallet held?
  20. MarcoFalke commented at 6:37 pm on March 5, 2021: member

    It won’t crash the node, but adding it doesn’t guarantee that the in-mempool status is correct at all times.

    Imagine calling bumpfee, concurrently you deprioritize the new tx with prioritizetransaction, then adding back the original tx with sendrawtransaction. Then wtx.fInMempool = chain().isInMempool(originalHash); assigns true. But immediately afterward you remove the deprioritization and call sendrawtransaction with the bumped-fee tx. sendrawtransaction is not a wallet RPC, so the status will incorrectly be fInMempool=true for the original tx, when it should be false.

  21. ryanofsky commented at 7:01 pm on March 5, 2021: member

    Imagine calling bumpfee, concurrently you deprioritize the new tx with prioritizetransaction, then adding back the original tx with sendrawtransaction. Then wtx.fInMempool = chain().isInMempool(originalHash); assigns true

    I think can you avoid problems here just holding cs_wallet throughout the bumpfee call. I haven’t dug into the details of what we’re doing, but clearly what we’re doing now is incorrect.

    I’m not sure if you prefer blindly assigning wtx.fInMempool = false over assigning wtx.fInMempool = isInMempool(wtx). If you do, I guess I have a different preference and think that latter would be more correct in long run even if it doesn’t fix every problem right now. But either choice seems fine and would be better than current behavior.

  22. wallet: Mark replaced tx to not be in the mempool anymore fa4e088cba
  23. MarcoFalke force-pushed on Mar 7, 2021
  24. MarcoFalke commented at 11:19 am on March 7, 2021: member
    Thanks, force pushed your patch (needed rebased)
  25. meshcollider commented at 9:51 pm on March 8, 2021: contributor
    utACK fa4e088cbac035b8029a10b492849540150d0622
  26. ryanofsky approved
  27. ryanofsky commented at 2:09 am on March 9, 2021: member
    Code review ACK fa4e088cbac035b8029a10b492849540150d0622, and previous ACK faeedff5c87091fd83d2fb2b29eb49c948363f29 is also still valid in case there’s a preference for the original fix
  28. MarcoFalke commented at 6:53 am on March 9, 2021: member
    This variant has two ACKs, so I think it is good to go in
  29. MarcoFalke merged this on Mar 9, 2021
  30. MarcoFalke closed this on Mar 9, 2021

  31. MarcoFalke deleted the branch on Mar 9, 2021
  32. sidhujag referenced this in commit af176b46bc on Mar 9, 2021
  33. luke-jr referenced this in commit ff35c52a29 on Jun 29, 2021
  34. DrahtBot locked this on Aug 16, 2022

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-12-18 12:12 UTC

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