This adds a low-level fuzz target for orphan transaction handling by creating random transactions and calling all functions in TxOrphanage.
It cannot simulate real-world orphan/unorphan scenarios effectively since it does not maintain any state about the node and the chain. A high-level fuzz target which construct well-designed transaction graphs will be added later.
fuzz: add low-level target for txorphanage #25447
pull chinggg wants to merge 1 commits into bitcoin:master from chinggg:fuzz-txorphan changing 3 files +145 −0-
chinggg commented at 12:57 PM on June 22, 2022: contributor
-
in src/test/fuzz/orphanage.cpp:1 in f7c8ec6d1f outdated
0 | @@ -0,0 +1,77 @@ 1 | +// Copyright (c) 2020-2021 The Bitcoin Core developers
MarcoFalke commented at 1:08 PM on June 22, 2022:nit: year
in src/test/fuzz/orphanage.cpp:5 in f7c8ec6d1f outdated
0 | @@ -0,0 +1,77 @@ 1 | +// Copyright (c) 2020-2021 The Bitcoin Core developers 2 | +// Distributed under the MIT software license, see the accompanying 3 | +// file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 | + 5 | +#include <random.h>
MarcoFalke commented at 1:11 PM on June 22, 2022:nondeterminism does not work nicely with whitebox and greybox fuzzing. It makes it impossible for fuzz engines to track coverage precisely. Ideally the full program execution is fully predetermined by the fuzz input exclusively.
Please remove this and use
fuzzed_data_providerinstead.in src/test/fuzz/orphanage.cpp:27 in f7c8ec6d1f outdated
22 | + { 23 | + // construct transaction 24 | + const CTransaction tx = [&] { 25 | + CMutableTransaction tx_mut; 26 | + tx_mut.nVersion = CTransaction::CURRENT_VERSION; 27 | + tx_mut.nLockTime = 0;
MarcoFalke commented at 1:11 PM on June 22, 2022:nit: I think they are default initialized, so can remove them?
in src/test/fuzz/orphanage.cpp:21 in f7c8ec6d1f outdated
16 | + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 17 | + 18 | + TxOrphanage orphanage; 19 | + std::set<uint256> orphan_work_set; 20 | + 21 | + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), COINBASE_MATURITY)
MarcoFalke commented at 1:13 PM on June 22, 2022:any reason to use
COINBASE_MATURITYhere? Shouldn't this be a constant derived fromTxOrphanageinternals? For example a bit larger than the maximum set size?
chinggg commented at 4:28 PM on June 22, 2022:Already changed to
DEFAULT_MAX_ORPHAN_TRANSACTIONSin src/test/fuzz/orphanage.cpp:31 in f7c8ec6d1f outdated
26 | + tx_mut.nVersion = CTransaction::CURRENT_VERSION; 27 | + tx_mut.nLockTime = 0; 28 | + const auto num_in = GetRandInternal(COINBASE_MATURITY); 29 | + const auto num_out = GetRandInternal(COINBASE_MATURITY); 30 | + for (unsigned i = 0; i < num_in; i++) { 31 | + CTxIn in(GetRandHash(), i);
MarcoFalke commented at 1:15 PM on June 22, 2022:It might be better to pick either:
- an existing prevout of a previously created transaction
- a random hash
Otherwise you are only ever testing one generation of txs.
chinggg commented at 4:41 PM on June 22, 2022:Thanks for your advice. You mean we'd better consider parent relationships when constructing transactions even they are not completely valid? I have changed the code to keep transactions (actually
CTransactionRef) in a set, from whichprevoutwill be picked for later inputs. However, now it has two differences compared totx_pool_standardtarget.tx_pool_standardkeep a set of outpoints, while I keep transactionstx_pool_standardfill the outpoints initially byMineBlockresults, while I construct initial transactions using random hashes.
I am not sure which strategy is better.
MarcoFalke commented at 11:06 AM on June 27, 2022:I think a set of outpoints is better. In your current approach you create
first_tx-> (second tx) -> (third tx) ... This is a plain chain. Ideally there are some more complicated graphs.MarcoFalke approvedMarcoFalke commented at 1:16 PM on June 22, 2022: membernice
DrahtBot added the label Build system on Jun 22, 2022chinggg force-pushed on Jun 22, 2022MarcoFalke removed the label Build system on Jun 22, 2022MarcoFalke added the label Tests on Jun 22, 2022chinggg renamed this:fuzz: add low level target for txorphanage
fuzz: add low-level target for txorphanage
on Jun 23, 2022chinggg force-pushed on Jun 27, 2022in src/test/fuzz/orphanage.cpp:20 in 98a82551df outdated
19 | + 20 | +void initialize_orphanage() 21 | +{ 22 | + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); 23 | + g_setup = testing_setup.get(); 24 | + SyncWithValidationInterfaceQueue();
MarcoFalke commented at 11:07 AM on June 27, 2022:I think a basic testing setup (args and logging) is sufficient. No need for a chain here in this simple test.
chinggg commented at 12:38 PM on June 27, 2022:I also want to keep the setup minimal. The chain used here is to construct the first tx by calling
chainman->GetParams().GenesisBlock().vtx[0], maybe I should just construct it manually instead of getting it from the genesis block.
MarcoFalke commented at 12:45 PM on June 27, 2022:Yeah, I think it might be preferable to just construct it manually. However, if you switch to an outpoints approach, you'll probably need to create a few outpoints manually instead.
MarcoFalke commented at 12:54 PM on June 29, 2022:Please keep the
MakeNoLogFileContextin init. It is not strictly needed, but I think it is nice to have to have a logger and argsmanager initialized for testing.The reason is that they are currently globals, so they may be used at any time during runtime or in future code changes without the fuzz test noticing. And if they are used, we want them to be initialized for testing and not accidentally corrupt the main datadir (or cause OOMs) or other bugs.
chinggg force-pushed on Jun 29, 2022in src/test/fuzz/orphanage.cpp:28 in 2ae40873c7 outdated
23 | + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); 24 | + g_setup = testing_setup.get(); 25 | + // initialize outpoints to construct transactions later 26 | + for (int i = 0; i < 4; ++i) { 27 | + CTxIn in = MineBlock(g_setup->m_node, P2WSH_OP_TRUE); 28 | + g_outpoints.insert(in.prevout);
MarcoFalke commented at 7:08 AM on June 29, 2022:I think you can just create a few outpoints by calling the
COutPointconstructor and passing a txid of 0 or 1 or so. This should be so cheap that it doesn't have to be done in the global setup, but can be done local in the fuzz target itself.chinggg force-pushed on Jun 29, 2022DrahtBot commented at 12:23 PM on June 29, 2022: member<!--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:
- #25527 ([kernel 3c/n] Decouple validation cache initialization from
ArgsManagerby dongcarl) - #25324 (refactor: add most of src/util to iwyu by fanquake)
- #25227 (Handle invalid hex encoding in ParseHex by MarcoFalke)
- #25112 (util: Move error message formatting of NonFatalCheckError to cpp by MarcoFalke)
- #24974 (refactor: Make FEELER_SLEEP_WINDOW type safe (std::chrono) by MarcoFalke)
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.
in src/test/fuzz/orphanage.cpp:14 in dda28c8f42 outdated
9 | +#include <test/fuzz/util.h> 10 | +#include <test/util/mining.h> 11 | +#include <test/util/script.h> 12 | +#include <test/util/setup_common.h> 13 | +#include <txorphanage.h> 14 | +#include <validation.h>
MarcoFalke commented at 12:55 PM on June 29, 2022:nit: I think this is no longer needed?
in src/test/fuzz/orphanage.cpp:5 in dda28c8f42 outdated
0 | @@ -0,0 +1,97 @@ 1 | +// Copyright (c) 2022 The Bitcoin Core developers 2 | +// Distributed under the MIT software license, see the accompanying 3 | +// file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 | + 5 | +#include <net_processing.h>
MarcoFalke commented at 12:55 PM on June 29, 2022:Is this needed right now?
chinggg commented at 1:22 PM on June 29, 2022:DEFAULT_MAX_ORPHAN_TRANSACTIONSis defined innet_processing.hin src/test/fuzz/orphanage.cpp:36 in dda28c8f42 outdated
31 | + // construct transaction 32 | + const CTransactionRef tx = [&] { 33 | + CMutableTransaction tx_mut; 34 | + const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); 35 | + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); 36 | + // pick unique outpoints from g_outpoints as input
MarcoFalke commented at 12:56 PM on June 29, 2022:// pick unique outpoints from outpoints as inputnit
in src/test/fuzz/orphanage.cpp:46 in dda28c8f42 outdated
41 | + tx_mut.vin.emplace_back(prevout); 42 | + outpoints.erase(pop); 43 | + } 44 | + // output amount will not affect txorphanage 45 | + for (uint32_t i = 0; i < num_out; i++) { 46 | + tx_mut.vout.emplace_back(CAmount{0}, P2WSH_OP_TRUE);
MarcoFalke commented at 1:02 PM on June 29, 2022:tx_mut.vout.emplace_back(CAmount{}, CScript{});nit: No need for any script
in src/test/fuzz/orphanage.cpp:48 in dda28c8f42 outdated
43 | + } 44 | + // output amount will not affect txorphanage 45 | + for (uint32_t i = 0; i < num_out; i++) { 46 | + tx_mut.vout.emplace_back(CAmount{0}, P2WSH_OP_TRUE); 47 | + } 48 | + // restore previously removed outpoints
MarcoFalke commented at 1:02 PM on June 29, 2022:why?
chinggg commented at 2:22 PM on June 29, 2022:The loop are used to fill the tx's
vout, you mean it is not necessary since we don't have any code that verifies content ofvout? Should we replace it withtx.vout.resize(num_out)or just keepvoutempty?
MarcoFalke commented at 2:38 PM on June 29, 2022:Oh, I meant why it is needed or what the goal is to restore previously removed outpoints
chinggg commented at 2:48 PM on June 29, 2022:The comment actually refers to the loop below.
// restore previously removed outpoints for (auto& in : tx_mut.vin) { outpoints.insert(in.prevout); }Since we want outpoints used by
vinto be unique, whenever an outpoint is used, it is erased from the set temporarily. Aftervinis constructed, we insert them back to the sets. The logic and the comment here is the same astx_pool_standardtarget https://github.com/bitcoin/bitcoin/blob/master/src/test/fuzz/tx_pool.cpp#L221-L224
MarcoFalke commented at 3:05 PM on June 29, 2022:This is because the tx_pool_standard target does the accounting later on, depending on whether the tx was accepted or not https://github.com/bitcoin/bitcoin/blob/cc22bd7f708fc3f5e793bf0138cd340f71c0feb9/src/test/fuzz/tx_pool.cpp#L296
However, your fuzz target does not have a concept of "rejected" transactions right now. Does it?
If you want to "restore" the outpoints, it might be better to not delete them in the first place.
chinggg commented at 3:27 PM on June 29, 2022:Thanks for your explanation. There is no "rejected" transaction right now. The outpoint is "deleted" from the set whenever it is picked as an input of the tx to ensure it will not be selected again. Once the construction of the tx is finished, all "deleted" transactions, which are exactly the inputs of the constructed tx, are "recovered". All the process happens within the construction of tx.
So it seems has nothing to do with the later accounting which is considered in
tx_pool_standard.in src/test/fuzz/orphanage.cpp:61 in dda28c8f42 outdated
56 | + } 57 | + return new_tx; 58 | + }(); 59 | + 60 | + // trigger orphanage functions 61 | + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 2 * DEFAULT_MAX_ORPHAN_TRANSACTIONS)
MarcoFalke commented at 1:04 PM on June 29, 2022:LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS)nit: I think this can be larger, unless it takes a long time?
chinggg commented at 2:54 PM on June 29, 2022:There are two
LIMITED_WHILEloops in the file, the outer loop determines how many transactions can be constructed, and the inner loop (the one you suggested) determines how many times canTxOrphangemethods be triggered. I am not sure about the exact value to be set. I think it can be bigger, though the probability of actually reaching the limit may be very low.in src/test/fuzz/orphanage.cpp:77 in dda28c8f42 outdated
72 | + orphanage.HaveTx(GenTxid::Txid(tx->GetHash())); 73 | + orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash())); 74 | + }, 75 | + [&] { 76 | + LOCK(g_cs_orphans); 77 | + orphanage.GetTx(tx->GetHash());
MarcoFalke commented at 1:07 PM on June 29, 2022:could assert that the return value is equal to the one of
HaveTxhere?in src/test/fuzz/orphanage.cpp:81 in dda28c8f42 outdated
76 | + LOCK(g_cs_orphans); 77 | + orphanage.GetTx(tx->GetHash()); 78 | + }, 79 | + [&] { 80 | + LOCK(g_cs_orphans); 81 | + orphanage.AddTx(tx, PeerID);
MarcoFalke commented at 1:07 PM on June 29, 2022:could assert that the return value of
HaveTxis true here?in src/test/fuzz/orphanage.cpp:85 in dda28c8f42 outdated
80 | + LOCK(g_cs_orphans); 81 | + orphanage.AddTx(tx, PeerID); 82 | + }, 83 | + [&] { 84 | + LOCK(g_cs_orphans); 85 | + orphanage.EraseTx(tx->GetHash());
MarcoFalke commented at 1:07 PM on June 29, 2022:could assert that the return value of HaveTx is false here?
in src/test/fuzz/orphanage.cpp:93 in dda28c8f42 outdated
88 | + LOCK(g_cs_orphans); 89 | + orphanage.EraseForPeer(PeerID); 90 | + }, 91 | + [&] { 92 | + LOCK(g_cs_orphans); 93 | + orphanage.LimitOrphans(fuzzed_data_provider.ConsumeIntegral<unsigned int>());
MarcoFalke commented at 1:11 PM on June 29, 2022:Could assert that the new size is less than the passed value?
MarcoFalke approvedin src/test/fuzz/orphanage.cpp:23 in dda28c8f42 outdated
18 | +{ 19 | + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 20 | + 21 | + TxOrphanage orphanage; 22 | + std::set<uint256> orphan_work_set; 23 | + std::set<COutPoint> outpoints;
MarcoFalke commented at 3:06 PM on June 29, 2022:I think this might be better as std::vector, to get a more stable, predictable sorting
chinggg commented at 3:29 PM on June 29, 2022:vectoris better if we only want to add outpoints to the end, but it will be slow if we want to randomly delete elements.
MarcoFalke commented at 3:46 PM on June 29, 2022:Ah, I see. I think you can also use the
std::swap(*pop, vec.back())and thenvec.pop_back()to achieve deletion inO(1), which should be faster than deletion instd::set?chinggg force-pushed on Jun 29, 2022chinggg force-pushed on Jun 29, 2022in src/test/fuzz/orphanage.cpp:13 in a5b4de3823 outdated
8 | +#include <test/fuzz/util.h> 9 | +#include <test/util/setup_common.h> 10 | +#include <txorphanage.h> 11 | + 12 | +namespace { 13 | +const TestingSetup* g_setup;
MarcoFalke commented at 6:58 PM on June 29, 2022:is this needed?
in src/test/fuzz/orphanage.cpp:18 in a5b4de3823 outdated
13 | +const TestingSetup* g_setup; 14 | +} // namespace 15 | + 16 | +void initialize_orphanage() 17 | +{ 18 | + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
MarcoFalke commented at 6:59 PM on June 29, 2022:I think BasicTestingSetup should be enough
in src/test/fuzz/orphanage.cpp:20 in a5b4de3823 outdated
15 | + 16 | +void initialize_orphanage() 17 | +{ 18 | + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); 19 | + g_setup = testing_setup.get(); 20 | + SyncWithValidationInterfaceQueue();
MarcoFalke commented at 6:59 PM on June 29, 2022:Is this needed?
in src/test/fuzz/orphanage.cpp:120 in a5b4de3823 outdated
116 | + orphanage.EraseForPeer(peer_id); 117 | + }, 118 | + [&] { 119 | + auto size_before = orphanage.Size(); 120 | + auto limit = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); 121 | + auto n_evicted = WITH_LOCK(g_cs_orphans, return orphanage.LimitOrphans(limit));
MarcoFalke commented at 7:18 PM on June 29, 2022:I think you'll need to use SetMockTime throughout the test to expire txs
MarcoFalke commented at 7:20 PM on June 29, 2022:Also there is a non-deterministic FastRandomContext in this function, but I guess it won't be possible to fix that without touching non-fuzz code.
MarcoFalke approvedMarcoFalke commented at 7:20 PM on June 29, 2022: memberIf you want you can submit fuzz inputs to the qa-assets repo
in src/test/fuzz/orphanage.cpp:46 in a5b4de3823 outdated
41 | + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); 42 | + // pick unique outpoints from outpoints as input 43 | + for (uint32_t i = 0; i < num_in; i++) { 44 | + auto& prevout = PickValue(fuzzed_data_provider, outpoints); 45 | + tx_mut.vin.emplace_back(prevout); 46 | + // pop the picked outpoint to ensure it will not be chosen again
MarcoFalke commented at 7:24 PM on June 29, 2022:I am thinking that it might be interesting to fuzz duplicate inputs?
Maybe a bool (determined at the beginning of the fuzz input) can indicate whether this run may have duplicate inputs in txs or not?
chinggg force-pushed on Jun 30, 2022in src/test/fuzz/orphanage.cpp:20 in 21e2d52f5b outdated
15 | +} 16 | + 17 | +FUZZ_TARGET_INIT(orphanage, initialize_orphanage) 18 | +{ 19 | + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 20 | + SetMockTime(ConsumeTime(fuzzed_data_provider));
chinggg commented at 3:06 PM on July 5, 2022:I find most targets set mocktime at the beginning so I put it here. How can we achieve better coverage by controlling mocktime/expiry?
MarcoFalke commented at 3:10 PM on July 5, 2022:This will just set it once and keep it constant. To change it you'd have to set it in the loop below
in src/test/fuzz/orphanage.cpp:17 in 21e2d52f5b outdated
12 | +void initialize_orphanage() 13 | +{ 14 | + static const auto testing_setup = MakeNoLogFileContext(); 15 | +} 16 | + 17 | +FUZZ_TARGET_INIT(orphanage, initialize_orphanage)
MarcoFalke commented at 3:27 PM on July 5, 2022:FUZZ_TARGET_INIT(txorphanage, initialize_orphanage)Maybe rename the file and target to
txorphanage?
chinggg commented at 4:00 PM on July 5, 2022:Sure.
orphanageis not so explicit. But the source file has the same nametxorphanage.cpp, istxorphanbetter?chinggg force-pushed on Jul 6, 2022in src/test/fuzz/txorphan.cpp:15 in 43f1e95ca8 outdated
5 | +#include <net_processing.h> 6 | +#include <test/fuzz/FuzzedDataProvider.h> 7 | +#include <test/fuzz/fuzz.h> 8 | +#include <test/fuzz/util.h> 9 | +#include <test/util/setup_common.h> 10 | +#include <txorphanage.h>
MarcoFalke commented at 8:53 AM on July 6, 2022:nit: I ran iwyu on this to add missing includes. Feel free to pick:
diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh index bdb68e0f6..32f0ea5e4 100755 --- a/ci/test/06_script_b.sh +++ b/ci/test/06_script_b.sh @@ -46,6 +46,7 @@ if [ "${RUN_TIDY}" = "true" ]; then " src/policy/settings.cpp"\ " src/rpc/fees.cpp"\ " src/rpc/signmessage.cpp"\ + " src/test/fuzz/txorphan.cpp"\ " -p . ${MAKEJOBS} -- -Xiwyu --cxx17ns -Xiwyu --mapping_file=${BASE_BUILD_DIR}/bitcoin-$HOST/contrib/devtools/iwyu/bitcoin.core.imp" fi diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp index 8a39ea685..d318baa6a 100644 --- a/src/test/fuzz/txorphan.cpp +++ b/src/test/fuzz/txorphan.cpp @@ -2,12 +2,27 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <consensus/amount.h> +#include <net.h> #include <net_processing.h> +#include <primitives/transaction.h> +#include <script/script.h> +#include <sync.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> #include <test/util/setup_common.h> #include <txorphanage.h> +#include <uint256.h> +#include <util/check.h> +#include <util/time.h> + +#include <algorithm> +#include <cstdint> +#include <memory> +#include <set> +#include <utility> +#include <vector> void initialize_orphanage() {
MarcoFalke commented at 8:56 AM on July 6, 2022:For reference the output was:
test/fuzz/txorphan.cpp should add these lines: #include <stdint.h> // for uint32_t, uint8_t #include <algorithm> // for max #include <memory> // for __shared_ptr_access, opera... #include <set> // for set #include <utility> // for swap, pair #include <vector> // for vector #include "consensus/amount.h" // for CAmount #include "net.h" // for NodeId #include "primitives/transaction.h" // for COutPoint, CTxIn, CTxOut #include "script/script.h" // for CScript #include "sync.h" // for LOCK, WITH_LOCK #include "uint256.h" // for uint256 #include "util/check.h" // for inline_assertion_check #include "util/time.h" // for SetMockTimeMarcoFalke approvedMarcoFalke commented at 8:55 AM on July 6, 2022: memberApproach ACK.
While the existing process_messages target can achieve line coverage in this module, it fails to generate any meaningful path/branch coverage. So it makes sense to add a "unit" fuzz test.
fuzz: add low-level target for txorphanage 6eb0909cb7chinggg force-pushed on Jul 6, 2022MarcoFalke commented at 6:39 PM on July 6, 2022: memberreview ACK 6eb0909cb7d5883a258f76ad6cf2c989fc6f892f 🐈
<details><summary>Show signature</summary>
Signature:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 review ACK 6eb0909cb7d5883a258f76ad6cf2c989fc6f892f 🐈 -----BEGIN PGP SIGNATURE----- iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p pUhrmAwAye+c8glh2rwa+Qreh97bToMj6AF9aCUG1yCSIc08TsEKPCDujBF8LgIx 10NMTZuUrqTclBHZcyi7Sjs7XXfCO/aRVRHm5eFa78LIWRIVsP7QHSYo18MehZ51 f+/iDkM6G3e4AjUMXRIzNKiHnt9DQj8gh/NgqCa4c2LelJtIMvntTjBjDymUQKgO sVvyTYuKBKTLWJsqSOD6d76oOlZxsZx6dHXjQhmtfXaDxBX2SFC9h6+MulEDMGmq aq402tH3uwLOpVmPbJ+qEXrrDLKrG+Fd1O3me15gB1Oy8kEh14gXVlLbHGBzmbCF NB21clLX5JYmCbFgmfXHiAQBNhBhPzS8ELkgHuW9Mv52pLv+nu7832koD7WTe9yj cphjGYKK3nxiZ9VtLtBA/nWMAJVybjXtXRRcut6Hjt70n3RirY27eezc8EYSNdXI 37DBKxQtnyIJf0ul+A+QsJuPKFpbZmwX7Ch1y2oWeZE6v9PCKbU9zM6SAUdyBq4R 3sHS9w5D =Bf9G -----END PGP SIGNATURE-----</details>
MarcoFalke merged this on Jul 7, 2022MarcoFalke closed this on Jul 7, 2022sidhujag referenced this in commit 8fbeebb581 on Jul 11, 2022DrahtBot locked this on Jul 7, 2023ContributorsLabels
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-04-22 09:13 UTC
This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me