wallet: Improve AvailableCoins performance by reducing duplicated operations #24699

pull achow101 wants to merge 5 commits into bitcoin:master from achow101:faster-available-coins changing 9 files +62 −36
  1. achow101 commented at 10:08 PM on March 28, 2022: member

    While running my coin selection simulations, I noticed that towards the end of the simulation, the wallet would become slow to make new transactions. The wallet generally performs much more slowly when there are a large number of transactions and/or a large number of keys. The improvements here are focused on wallets with a large number of transactions as that is what the simulations produce.

    Most of the slowdown I observed was due to DescriptorScriptPubKeyMan::GetSigningProvider re-deriving keys every time it is called. To avoid this, it will now cache the SigningProvider produced so that repeatedly fetching the SigningProvider for the same script will not result in the same key being derived over and over. This has a side effect of making the function non-const, which makes a lot of other functions non-const as well. This helps with wallets with lots of address reuse (as my coin selection simulations are), but not if addresses are not reused as keys will end up needing to be derived the first time GetSigningProvider is called for a script.

    The GetSigningProvider problem was also exacerbated by unnecessarily fetching a SigningProvider for the same script multiple times. A SigningProvider is retrieved to be used inside of IsSolvable. A few lines later, we use GetTxSpendSize which fetches a SigningProvider and then calls CalculateMaximumSignedInputSize. We can avoid a second call to GetSigningProvider by using CalculateMaximumSignedInputSize directly with the SigningProvider already retrieved for IsSolvable.

    There is an additional slowdown where ProduceSignature with a dummy signer is called twice for each output. The first time is IsSolvable checks that ProduceSignature succeeds, thereby informing whether we have solving data. The second is CalculateMaximumSignedInputSize which returns -1 if ProduceSignature fails, and returns the input size otherwise. We can reduce this to one call of ProduceSignature by using CalculateMaximumSignedInputSize's result to set solvable.

    Lastly, a lot of time is spent looking in mapWallet and mapTxSpends to determine whether an output is already spent. The performance of these lookups is slightly improved by changing those maps to use std::unordered_map and std::unordered_multimap respectively.

  2. DrahtBot added the label RPC/REST/ZMQ on Mar 28, 2022
  3. DrahtBot added the label Wallet on Mar 28, 2022
  4. achow101 force-pushed on Mar 29, 2022
  5. DrahtBot commented at 2:12 PM on March 29, 2022: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #25695 (tidy: add modernize-use-using by fanquake)
    • #25664 (refactor: Redefine IsSolvable() using descriptors by darosior)
    • #25659 (wallet: simplify ListCoins implementation by furszy)
    • #23417 (wallet, spkm: Move key management from DescriptorScriptPubKeyMan to wallet level KeyManager by achow101)
    • #22693 (RPC/Wallet: Add "use_txids" to output of getaddressinfo by luke-jr)

    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.

  6. josibake commented at 4:19 PM on March 29, 2022: member

    Concept ACK

    very nice, cursory glance looks good. do you have any benchmarks on how much this improves performance when dealing with large wallets with many transactions?

  7. DrahtBot cross-referenced this on Mar 29, 2022 from issue wallet: avoid mixing different `OutputTypes` during coin selection by josibake
  8. DrahtBot cross-referenced this on Mar 29, 2022 from issue Improve display address handling for external signer by Sjors
  9. achow101 commented at 10:10 PM on March 29, 2022: member

    do you have any benchmarks on how much this improves performance when dealing with large wallets with many transactions?

    One of the simulation scenarios I ran has 10050 deposits and 4950 spends. It's runtime went from 4 hr 18 min to 38 min after the changes in this PR (plus one additional change to the simulation script which would apply a speedup that I'm not sure about, but would not dominate the total runtime).

  10. DrahtBot cross-referenced this on Mar 30, 2022 from issue BIP-322 basic support by kallewoof
  11. DrahtBot cross-referenced this on Mar 30, 2022 from issue wallet, spkm: Move key management from DescriptorScriptPubKeyMan to wallet level KeyManager by achow101
  12. DrahtBot cross-referenced this on Mar 30, 2022 from issue RPC/Wallet: Add "use_txids" to output of getaddressinfo by luke-jr
  13. DrahtBot cross-referenced this on Mar 30, 2022 from issue rpc: add getxpub by Sjors
  14. DrahtBot cross-referenced this on Mar 30, 2022 from issue Implement BIP-119 Validation (CheckTemplateVerify) by JeremyRubin
  15. in src/wallet/spend.cpp:190 in 7d64540c22 outdated
     186 | @@ -187,10 +187,13 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
     187 |  
     188 |              std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(wtx.tx->vout[i].scriptPubKey);
     189 |  
     190 | -            bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
    


    promag commented at 5:13 PM on April 5, 2022:

    7d64540c22b0d0e0776f4e146f36d8b5ac462d09

    This ternary was unnecessary, right?



    Empact commented at 6:59 PM on April 6, 2022:

    achow101 commented at 8:07 PM on April 10, 2022:

    It probably was.


    furszy commented at 2:07 PM on April 20, 2022:

    Self-note for 7d64540c:

    The difference, aside from the clear speedup, is that we are no longer going to call VerifyScript after producing the dummy signature (which if would had failed in the past, the node would had crashed for the IsSolvable assertion).

  16. in src/script/signingprovider.cpp:91 in a1299a9a5a outdated
      86 | +{
      87 | +    a.scripts.insert(b.scripts.begin(), b.scripts.end());
      88 | +    a.pubkeys.insert(b.pubkeys.begin(), b.pubkeys.end());
      89 | +    a.keys.insert(b.keys.begin(), b.keys.end());
      90 | +    a.origins.insert(b.origins.begin(), b.origins.end());
      91 | +    a.tr_spenddata = a.tr_spenddata;
    


    promag commented at 5:19 PM on April 5, 2022:

    a1299a9a5aee964ed75acac38e6d99d86952f4e4

    👀


    promag commented at 8:31 PM on April 5, 2022:

    That’s not correct


    w0xlt commented at 9:26 PM on April 5, 2022:

    Yes, I agree. That is really not correct. b.tr_spenddata data is merged into a.tr_spenddata on the next line. But I do not see the point of a.tr_spenddata = a.tr_spenddata;.


    promag commented at 10:27 PM on April 5, 2022:

    Should be removed


    achow101 commented at 8:07 PM on April 10, 2022:

    Fixed


    achow101 commented at 8:08 PM on April 10, 2022:

    Fixed

  17. in src/wallet/scriptpubkeyman.h:549 in a1299a9a5a outdated
     545 | @@ -546,12 +546,14 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
     546 |  
     547 |      KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
     548 |  
     549 | +    std::map<int32_t, FlatSigningProvider> m_map_signing_providers;
    


    promag commented at 5:43 PM on April 5, 2022:

    a1299a9a5aee964ed75acac38e6d99d86952f4e4

    Have you considered mutable instead of dropping const everywhere?

    An annotation would be nice BTW.


    achow101 commented at 8:07 PM on April 10, 2022:

    Changed to mutable

  18. in src/wallet/interfaces.cpp:326 in d3a6bba3fd outdated
     325 | -        std::vector<WalletTx> result;
     326 | -        result.reserve(m_wallet->mapWallet.size());
     327 | +        std::set<WalletTx> result;
     328 |          for (const auto& entry : m_wallet->mapWallet) {
     329 | -            result.emplace_back(MakeWalletTx(*m_wallet, entry.second));
     330 | +            result.emplace(MakeWalletTx(*m_wallet, entry.second));
    


    promag commented at 6:08 PM on April 5, 2022:

    d3a6bba3fdeba44a630f14755f73a9cb1a301b09

    This seems to be a bad change for the GUI when loading a big wallet.

    The order by hash requirement comes from TransactionTablePriv::updateWallet implementation, definitely something to improve/refactor.


    achow101 commented at 8:08 PM on April 10, 2022:

    I think it won't be too bad, but fixing that seems like a bigger change than suitable for this PR.

  19. promag changes_requested
  20. promag commented at 6:08 PM on April 5, 2022: member

    Concept ACK

  21. w0xlt approved
  22. w0xlt commented at 8:29 PM on April 5, 2022: contributor

    Code Review ACK e6e77b3

  23. DrahtBot cross-referenced this on Apr 8, 2022 from issue wallet: increase BnB upper limit by S3RK
  24. achow101 force-pushed on Apr 10, 2022
  25. w0xlt approved
  26. w0xlt commented at 4:43 PM on April 11, 2022: contributor

    reACK 91c3f77

  27. DrahtBot cross-referenced this on Apr 14, 2022 from issue wallet: return error msg for "too-long-mempool-chain" by furszy
  28. in src/wallet/spend.cpp:195 in af54709713 outdated
     188 | @@ -189,7 +189,7 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
     189 |  
     190 |              bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
     191 |              bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
     192 | -            int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
     193 | +            int input_bytes = CalculateMaximumSignedInputSize(wtx.tx->vout[i], provider.get(), /*use_max_sig=*/(coinControl && coinControl->fAllowWatchOnly));
    


    furszy commented at 1:52 PM on April 20, 2022:

    Seeing af547097: nit: would be good to extract wtx.tx->vout[I] into its own ref variable at the top of the vout for loop (line 165). We are accessing the same value in the vector several times.


    achow101 commented at 10:36 PM on April 20, 2022:

    Will do this if I have to touch again.

  29. furszy commented at 3:43 PM on April 20, 2022: member

    Code reviewed 91c3f774.

    Two things to add:

    • About the signing providers cache (fcc2160d):

      ~Seems that this going to cache the private keys when the wallet is unencrypted/unlocked and then keep them available in memory when the wallet gets encrypted/locked.~ --> update: meh nah.. could actually assert that the provider in the cache does not have a sk stored.

      Even when the changes are small, wouldn't hurt to add test coverage for it (like getting the signing provider without including the private keys, then get it with them, check cache update, etc). Maybe, if you are ok, I could work on it (not sure how much it will take me to do it but.. I can give it a shot to start getting deeper over the descriptors architecture).

    • I do agree with @promag comment for 3b8b47b7 but.. yeah, better to work on it on a future PR as well.

  30. achow101 commented at 7:18 PM on April 20, 2022: member

    I wonder how this would perform by splitting up the COutPoint with this data structure:

    using TxSpends = std::unordered_map<uint256, std::unordered_multimap<uint32_t, uint256>, SaltedOutpointHasher>;
    

    This would get rid of the O(n) iterations of the whole vout.size(), which should therefor have a lot less lookups.

    Also I'm not sure how fast the unordered_multimap is, maybe it's better to use a vector:

    using TxSpends = std::unordered_map<uint256, std::unordered_map<uint32_t, std::vector<uint256>>, SaltedOutpointHasher>;
    

    Hmm, that would probably be better. I'll try that. I don't think there's really a benchmark for this so it'll be hard to measure the effect. I think the second solution is also faster as getting the range from the multimap appears to be O(n) on average too.

    ~Seems that this going to cache the private keys when the wallet is unencrypted/unlocked and then keep them available in memory when the wallet gets encrypted/locked.~ --> update: meh nah.. could actually assert that the provider in the cache does not have a sk stored.

    This was considered and it specifically only caches the SigningProvider before private keys are added by making a copy.

    Even when the changes are small, wouldn't hurt to add test coverage for it (like getting the signing provider without including the private keys, then get it with them, check cache update, etc). Maybe, if you are ok, I could work on it (not sure how much it will take me to do it but.. I can give it a shot to start getting deeper over the descriptors architecture).

    Feel free to try. You can reach out to me if you have any questions.

  31. achow101 commented at 10:19 PM on April 20, 2022: member

    I wonder how this would perform by splitting up the COutPoint with this data structure:

    Actually there are a lot of other places that mapTxSpends is used and splitting it up requires making a ton of changes throughout the wallet to deal with it. The vast majority of the usage of mapTxSpends is to do lookups by outpoint.

  32. furszy commented at 10:57 PM on April 26, 2022: member
  33. furszy cross-referenced this on Apr 27, 2022 from issue wallet: remove extra wtx lookup in 'AvailableCoins' + several code cleanups. by furszy
  34. furszy approved
  35. furszy commented at 7:20 PM on April 27, 2022: member

    code ACK 91c3f774

  36. DrahtBot cross-referenced this on May 8, 2022 from issue Set effective_value when initializing a COutput by ishaanam
  37. DrahtBot cross-referenced this on May 19, 2022 from issue wallet: unify “allow/block other inputs“ concept by furszy
  38. DrahtBot cross-referenced this on May 21, 2022 from issue rpc: Filter inputs by type during CoinSelection by aureleoules
  39. furszy cross-referenced this on May 28, 2022 from issue bench: add benchmark for wallet 'AvailableCoins' function. by furszy
  40. DrahtBot cross-referenced this on Jun 2, 2022 from issue wallet: re-activate the not triggered "AmountWithFeeExceedsBalance" error by furszy
  41. DrahtBot cross-referenced this on Jun 16, 2022 from issue refactor: Split util/system into exception, shell, and fs-specific files by Empact
  42. achow101 referenced this in commit 8be652e439 on Jun 17, 2022
  43. DrahtBot added the label Needs rebase on Jun 17, 2022
  44. fjahr commented at 1:24 PM on June 19, 2022: contributor

    utACK 91c3f774c7ce55cb81d8e238d9469b1cacb10674 modulo rebase

  45. achow101 force-pushed on Jun 20, 2022
  46. achow101 force-pushed on Jun 20, 2022
  47. DrahtBot removed the label Needs rebase on Jun 21, 2022
  48. in src/wallet/spend.cpp:201 in 008d1178c8 outdated
     197 | @@ -198,7 +198,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
     198 |              // Filter by spendable outputs only
     199 |              if (!spendable && only_spendable) continue;
     200 |  
     201 | -            int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
     202 | +            int input_bytes = CalculateMaximumSignedInputSize(wtx.tx->vout[i], provider.get(), /*use_max_sig=*/(coinControl && coinControl->fAllowWatchOnly));
    


    furszy commented at 12:51 PM on June 27, 2022:
                int input_bytes = CalculateMaximumSignedInputSize(output, provider.get(), /*use_max_sig=*/(coinControl && coinControl->fAllowWatchOnly));
    

    achow101 commented at 3:40 PM on July 6, 2022:

    Done

  49. DrahtBot cross-referenced this on Jun 27, 2022 from issue wallet: unify max signature logic by S3RK
  50. DrahtBot cross-referenced this on Jul 5, 2022 from issue wallet: cleanup cached amount and input mine check code by furszy
  51. achow101 force-pushed on Jul 6, 2022
  52. achow101 force-pushed on Jul 6, 2022
  53. DrahtBot added the label Needs rebase on Jul 8, 2022
  54. in src/script/signingprovider.h:91 in 6f9e59143d outdated
      87 | @@ -88,6 +88,7 @@ struct FlatSigningProvider final : public SigningProvider
      88 |  };
      89 |  
      90 |  FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b);
      91 | +void MergeInto(FlatSigningProvider& a, const FlatSigningProvider& b);
    


    murchandamus commented at 6:43 PM on July 12, 2022:

    Is it possible that "Merge" should have been replaced with "MergeInto" and a return of the merged value? It seems odd that we would both merge a and b, and then also merge b into a.


    achow101 commented at 9:51 PM on July 13, 2022:

    Looking at this further, it's actually possible to do just use Merge, so I've removed MergeInto and now only use the preexisting Merge.

  55. murchandamus commented at 7:22 PM on July 12, 2022: contributor

    Concept ACK, code looks good to me, one question

  56. achow101 force-pushed on Jul 12, 2022
  57. DrahtBot removed the label Needs rebase on Jul 12, 2022
  58. achow101 force-pushed on Jul 13, 2022
  59. DrahtBot cross-referenced this on Jul 16, 2022 from issue rpc/wallet: Add details and duplicate section for simulaterawtransaction by anibilthare
  60. DrahtBot added the label Needs rebase on Jul 21, 2022
  61. in src/wallet/spend.cpp:216 in 45736de91e outdated
     186 | @@ -187,13 +187,15 @@ CoinsResult AvailableCoins(const CWallet& wallet,
     187 |  
     188 |              std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(output.scriptPubKey);
     189 |  
     190 | -            bool solvable = provider ? IsSolvable(*provider, output.scriptPubKey) : false;
    


    josibake commented at 5:31 PM on July 21, 2022:

    in https://github.com/bitcoin/bitcoin/pull/24699/commits/45736de91e9350258994183c7032d51bf2e1142e:

    not sure if this is super important, but IsSolvable does a static_assert to check for STANDARD_SCRIPT_VERIFY_FLAGS and SCRIPT_VERIFY_WITNESS_PUBKEYTYPE and also runs VerifyScript, neither of which are happening in CalculateMaximumSignedInputSize


    josibake commented at 5:51 PM on July 21, 2022:

    maybe it's worth adding these checks to DummySignInput so there is no behavior change?


    achow101 commented at 6:44 PM on July 21, 2022:

    static_assert is a compile time check so it doesn't matter where it is done.

    Both IsSolvable and CalculateMaximumSignedInputSize call ProduceSignature, which does VerifyScript at the very end of it if the rest of it was successful. So IsSolvable doing VerifyScript is actually redundant, and both IsSolvable and CalculatemaximumSignedInputSize are doing the same checks.


    josibake commented at 6:58 PM on July 21, 2022:

    ah, i missed the VerifyScript call in ProduceSignature, thanks for the explanation!

  62. josibake approved
  63. josibake commented at 5:56 PM on July 21, 2022: member

    ACK https://github.com/bitcoin/bitcoin/pull/24699/commits/4323943403aa45333e415086bad735e0e7066da0 mod rebase

    worth mentioning that I (and others) have been using this code to run wallet simulations based on real usage scenarios, so the code has been tested "in the wild". left one comment re: IsSolvable but could just be my own lack of understanding. I also noticed the feature_backwards_compatibility.py --descriptors test is falling in CI , but i ran it locally without any failures

  64. achow101 force-pushed on Jul 21, 2022
  65. josibake commented at 7:06 PM on July 21, 2022: member

    reACK 1f61630

  66. DrahtBot removed the label Needs rebase on Jul 21, 2022
  67. DrahtBot cross-referenced this on Jul 22, 2022 from issue refactor: Redefine `IsSolvable()` using descriptors by darosior
  68. DrahtBot cross-referenced this on Jul 22, 2022 from issue wallet: simplify ListCoins implementation by furszy
  69. furszy approved
  70. furszy commented at 6:22 PM on July 22, 2022: member

    Code review ACK 1f616304

    Something that has been floating around my head is the lack of TTL for each entry in the cache. So the map doesn't grow indefinitely but.. not blocking, could tackle it in a follow-up work.

  71. achow101 commented at 3:33 PM on July 23, 2022: member

    @fjahr @w0xlt re-review?

  72. fjahr commented at 8:05 PM on July 26, 2022: contributor

    Code review re-ACK 1f61630497fd52089886903fdfc8e75b1c7f4d37

  73. w0xlt approved
  74. DrahtBot cross-referenced this on Jul 28, 2022 from issue wallet: Check max transaction weight in CoinSelection by aureleoules
  75. DrahtBot added the label Needs rebase on Jul 28, 2022
  76. wallet: Use CalculateMaximumSignedInputSize to indicate solvability
    In AvailableCoins, we need to know whether we can solve for an output.
    This was done by using IsSolvable, which just calls ProduceSignature and
    produces a dummy signature. However, we already do that in order to get
    the size of the input by using CalculateMaximumSignedInputSize. As this
    function returns -1 if ProduceSignature fails, we can just remove the
    use of IsSolvable and check that input_bytes is not -1 to determine
    the solvability of an output.
    8a105ecd1a
  77. achow101 force-pushed on Jul 29, 2022
  78. josibake commented at 3:28 PM on July 29, 2022: member

    reACK https://github.com/bitcoin/bitcoin/commit/38ead65dae9c6bb14ef95b1405510d02317f520e

    verified rebase with git range-diff master 1f61630 38ead65

  79. DrahtBot removed the label Needs rebase on Jul 29, 2022
  80. DrahtBot cross-referenced this on Jul 29, 2022 from issue tidy: add modernize-use-using by fanquake
  81. fjahr commented at 10:56 PM on July 29, 2022: contributor

    re-ACK 38ead65dae9c6bb14ef95b1405510d02317f520e

  82. furszy approved
  83. furszy commented at 1:09 AM on August 3, 2022: member

    diff ACK 38ead65d

  84. in src/wallet/scriptpubkeyman.cpp:2091 in 7bb08eb91d outdated
    2089 | +        // Get the scripts, keys, and key origins for this script
    2090 | +        std::vector<CScript> scripts_temp;
    2091 | +        if (!m_wallet_descriptor.descriptor->ExpandFromCache(index, m_wallet_descriptor.cache, scripts_temp, *out_keys)) return nullptr;
    2092 | +
    2093 | +        // Cache SigningProvider so we don't need to re-derive if we need this SigningProvider again
    2094 | +        m_map_signing_providers[index] = Merge(m_map_signing_providers[index], *out_keys);
    


    murchandamus commented at 6:45 PM on August 3, 2022:

    Since m_map_signing_providers.find(index) == m_map_signing_providers.end() in the else branch here, why isn't this just:

            m_map_signing_providers[index] = *out_keys;
    

    Isn't this merging with an empty object?


    achow101 commented at 7:22 PM on August 3, 2022:

    IIRC the compiler complained about doing it that way, presumably because out_keys is a unique_ptr


    achow101 commented at 7:38 PM on August 3, 2022:

    Apparently I remember incorrectly. Changed.

  85. wallet: Cache SigningProviders
    In order to avoid constantly re-deriving the same keys in
    DescriptorScriptPubKeyMan, cache the SigningProviders generated inside
    of GetSigningProvider.
    1f798fe85b
  86. Change mapTxSpends to be a std::unordered_multimap 97532867cf
  87. Change getWalletTxs to return a set instead of a vector
    For some reason, the primary consumer of getWalletTxs requires the
    transactions to be in hash order when it is processing them. std::map
    will iterate in hash order so the transactions end up in that order when
    placed into the vector. To ensure this order when mapWallet is no longer
    ordered, the vector is replaced with a set which will maintain the hash
    order.
    272356024d
  88. Change mapWallet to be a std::unordered_map bc886fcb31
  89. achow101 force-pushed on Aug 3, 2022
  90. murchandamus commented at 7:42 PM on August 3, 2022: contributor

    ACK bc886fcb31e1afa7bbf7b86bfd93e51da7076ccf

  91. furszy approved
  92. furszy commented at 10:13 PM on August 3, 2022: member

    diff re-reACK bc886fcb

  93. achow101 merged this on Aug 5, 2022
  94. achow101 closed this on Aug 5, 2022

  95. sidhujag referenced this in commit a9fe50175b on Aug 6, 2022
  96. bitcoinhodler cross-referenced this on Oct 28, 2022 from issue Instability in `listunspent` after #24699 by bitcoinhodler
  97. bitcoinhodler commented at 12:30 AM on October 28, 2022: contributor

    I filed #26406 to report an instability triggered by bc886fcb3

  98. murchandamus commented at 5:48 PM on October 28, 2022: contributor

    I filed #26406 to report an instability triggered by bc886fc

    Reading the linked issue, this has meanwhile been resolved.

  99. achow101 referenced this in commit 139ba2bf12 on Jan 4, 2023
  100. sidhujag referenced this in commit b23c80f51c on Jan 4, 2023
  101. john-moffett cross-referenced this on Jan 27, 2023 from issue doc: Fix comment about how wallet txs are sorted by john-moffett
  102. hebasto referenced this in commit daebf9ebb0 on Feb 3, 2023
  103. sidhujag referenced this in commit c665392cad on Feb 3, 2023
  104. bitcoin locked this on Oct 28, 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: 2026-05-20 06:53 UTC