p2p: Refactor sock to add I2P fuzz and unit tests #21387

pull vasild wants to merge 8 commits into bitcoin:master from vasild:i2p_tests changing 12 files +344 −87
  1. vasild commented at 11:17 am on March 8, 2021: member

    Change the networking code and the I2P code to be fully mockable and use FuzzedSocket to fuzz the I2P methods Listen(), Accept() and Connect().

    Add a mocked Sock implementation that returns a predefined data on reads and use it for a regression unit test for the bug fixed in #21407.

  2. fanquake added the label Tests on Mar 8, 2021
  3. fanquake added the label P2P on Mar 8, 2021
  4. jonatack commented at 11:20 am on March 8, 2021: member
    Concept ACK
  5. in src/netbase.cpp:575 in d59cb9b3d3 outdated
    651-            struct timeval timeout = MillisToTimeval(nTimeout);
    652-            fd_set fdset;
    653-            FD_ZERO(&fdset);
    654-            FD_SET(hSocket, &fdset);
    655-            int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout);
    656-#endif
    


    vasild commented at 11:23 am on March 8, 2021:

    Note to reviewers: this old code was inconsistent with the events it requested depending on which function was used - it requested “in” or “out” (POLLIN | POLLOUT) when using poll() but only “out” when using select() (did not pass fdset as 2’nd arg to select()).

    This is now replaced by Sock::Wait(..., requested Sock::RECV | Sock::SEND) which, if ends up calling select(), will ask it for both “in” and “out”.

    I think this is ok.


    MarcoFalke commented at 11:41 am on March 8, 2021:
    it might be good to split the non-fuzz “refactors” out into their own pull

    vasild commented at 12:43 pm on March 8, 2021:

    Lets see which are the non-fuzz refactors:

    0[7] i2p: add fuzz tests on the I2P Session public interface
    1[6] i2p: use pointers to Sock to accommodate mocking
    2[5] net: change ConnectSocketDirectly() to take a Sock argument
    3[4] net: add connect() and getsockopt() wrappers to Sock
    4[3] fuzz: avoid FuzzedSock::Recv() repeated errors with EAGAIN
    5[2] fuzz: extend FuzzedSock::Recv() to support MSG_PEEK
    6[1] fuzz: implement unimplemented FuzzedSock methods
    

    1-3 are obviously fuzz. 4 touches FuzzedSocket. 7 adds fuzz tests. So only 5 and 6 remain. 5 depends on the previous commits. 6 is non-fuzz, does not depend on the previous commits and can be extracted, but 7 depends on it. So a split can be: PR-A: 6 PR-B: 1-5, 7

    I think it is not worth to split, given that 6 is a small mechanical change.

  6. in src/util/sock.h:159 in ed7af727d3 outdated
    154@@ -155,6 +155,8 @@ class Sock
    155      * Contained socket. `INVALID_SOCKET` designates the object is empty.
    156      */
    157     SOCKET m_socket;
    158+
    159+    friend class FuzzedSock;
    


    MarcoFalke commented at 11:37 am on March 8, 2021:
    style-nit in the first commit: Wouldn’t it be better to change private to protected? This would allow other tests to mock m_socket without having to enumerate all “friend” test classes here

    vasild commented at 12:43 pm on March 8, 2021:
    Done.
  7. vasild force-pushed on Mar 8, 2021
  8. in src/test/fuzz/util.h:563 in a6c20d2b88 outdated
    533@@ -534,36 +534,37 @@ class FuzzedSock : public Sock
    534 {
    535     FuzzedDataProvider& m_fuzzed_data_provider;
    536 
    537+    /**
    538+     * Data to return when `MSG_PEEK` is used as a `Recv()` flag.
    539+     * If `MSG_PEEK` is used, then our `Recv()` returns some random data as usual, but on the next
    540+     * `Recv()` call we must return the same data, thus we remember it here.
    541+     */
    542+    mutable std::optional<uint8_t> m_peek_data;
    


    vasild commented at 12:52 pm on March 8, 2021:
    Either this should be mutable or the const qualifier should be removed from Sock::Recv() (and Sock::Send() for consistency). I don’t have a strong opinion. I chose mutable because it is a smaller change and did not require modifying existing code.

    vasild commented at 5:44 pm on March 12, 2021:
    Removing const is not feasible (an avalanche of changes, not worth it).
  9. practicalswift commented at 5:46 pm on March 8, 2021: contributor

    Concept ACK

    Thanks for improving FuzzedSock with connect, getsockopt and MSG_PEEK (recv) support! :)

    Very happy to see the I2P code being thoroughly fuzzed before landing in a release! “Fuzz before release” is a nice high bar I think we should try to aim for going forward for new features :)

  10. in src/test/fuzz/util.h:703 in a6c20d2b88 outdated
    698+        }
    699+        if (opt_val == nullptr) {
    700+            return 0;
    701+        }
    702+        const auto rnd = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(*opt_len);
    703+        std::memcpy(opt_val, rnd.data(), rnd.size());
    


    jonatack commented at 6:53 pm on March 8, 2021:

    Builds cleanly at a6c20d2b88ea92, but I’m seeing a UBSan runtime error when starting the fuzzer (same error for each of 3 times):

     0$ clang --version
     1clang version 9.0.1-16 
     2Target: x86_64-pc-linux-gnu
     3Thread model: posix
     4
     5$ FUZZ=i2p src/test/fuzz/fuzz ../qa-assets/fuzz_seed_corpus/
     6INFO: Seed: 3673367385
     7INFO: Loaded 1 modules   (642733 inline 8-bit counters): 642733 [0x55abe383e4c8, 0x55abe38db375), 
     8INFO: Loaded 1 PC tables (642733 PCs): 642733 [0x55abe38db378,0x55abe42a9e48), 
     9INFO:   132706 files found in ../qa-assets/fuzz_seed_corpus/
    10INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 1048576 bytes
    11INFO: seed corpus: files: 132706 min: 1b max: 3986616b total: 1882953565b rss: 284Mb
    12[#4096](/bitcoin-bitcoin/4096/)	pulse  cov: 1773 ft: 2176 corp: 11/47b exec/s: 2048 rss: 366Mb
    13
    14test/fuzz/util.h:703:30: runtime error: null pointer passed as argument 2, which is declared to never be null
    15/usr/include/string.h:44:28: note: nonnull attribute specified here
    16SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior test/fuzz/util.h:703:30 in 
    17
    18[#8192](/bitcoin-bitcoin/8192/)	pulse  cov: 2600 ft: 3813 corp: 32/219b exec/s: 2730 rss: 441Mb
    19[#16384](/bitcoin-bitcoin/16384/)	pulse  cov: 2655 ft: 4890 corp: 70/750b exec/s: 2730 rss: 586Mb
    20[#32768](/bitcoin-bitcoin/32768/)	pulse  cov: 2773 ft: 6429 corp: 111/2011b exec/s: 2730 rss: 649Mb
    21[#65536](/bitcoin-bitcoin/65536/)	pulse  cov: 2794 ft: 7344 corp: 142/4922b exec/s: 2849 rss: 649Mb
    22[#131072](/bitcoin-bitcoin/131072/)	pulse  cov: 2890 ft: 8189 corp: 162/47Kb exec/s: 2473 rss: 1271Mb
    23[#132707](/bitcoin-bitcoin/132707/)	INITED cov: 2890 ft: 8189 corp: 162/47Kb exec/s: 2249 rss: 1271Mb
    24[#132896](/bitcoin-bitcoin/132896/)	REDUCE cov: 2890 ft: 8189 corp: 162/46Kb lim: 14309 exec/s: 2214 rss: 1271Mb L: 13800/13800 MS: 4 ChangeBinInt-ChangeByte-ChangeByte-EraseBytes-
    25.../...
    26[#204032](/bitcoin-bitcoin/204032/)	REDUCE cov: 2912 ft: 9195 corp: 194/29Kb lim: 14413 exec/s: 2147 rss: 1271Mb L: 62/2551 MS: 2 ChangeBinInt-EraseBytes-
    27[#206627](/bitcoin-bitcoin/206627/)	REDUCE cov: 2912 ft: 9195 corp: 194/29Kb lim: 14426 exec/s: 2152 rss: 1271Mb L: 66/2551 MS: 5 InsertByte-PersAutoDict-ChangeByte-EraseBytes-PersAutoDict- DE: "\x00\x00\x00\x00\x00\x00\x00\x1a"-"\x1a\x00\x00\x00\x00\x00\x00\x00"-
    28[#207638](/bitcoin-bitcoin/207638/)	REDUCE cov: 2912 ft: 9195 corp: 194/29Kb lim: 14426 exec/s: 2162 rss: 1271Mb L: 102/2551 MS: 1 EraseBytes-
    

    vasild commented at 9:52 am on March 9, 2021:

    Fixed, thanks! I did not see this because my memcpy() does not have the nonnull attribute.

    Off-topic: the nonnull attribute has this speciality - the compiler assumes that the parameter will never be null and may eliminate branches in the function that check for that. For example:

    0void func(char* buf) __nonnull(1)
    1{
    2    if (buf == nullptr) {
    3        // take action
    4        return;
    5    }
    6    buf[0] = 1;
    7}
    

    Compiler optimization may reduce this to:

    0void func(char* buf) __nonnull(1)
    1{
    2    buf[0] = 1;
    3}
    

    It looks reasonable, given that code like func(nullptr); will not compile. However, if the value of the argument is determined at runtime and may still end up being nullptr, then the compiler has no way to detect this during compilation. So the line buf[0] = 1; may still be executed with buf being nullptr even though the source code checks for that just above, leading to some nice “wtf” moments.

    Ubsan alleviates this.


    jonatack commented at 12:09 pm on March 10, 2021:
    Yes. Runs without the error now.
  11. DrahtBot commented at 7:23 pm on March 8, 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:

    • #21506 (p2p, refactor: make NetPermissionFlags an enum class by jonatack)

    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.

  12. vasild force-pushed on Mar 9, 2021
  13. vasild commented at 9:53 am on March 9, 2021: member
    a6c20d2b8...933d181a8: avoid calling memcpy() with NULL argument even if size is 0
  14. jonatack commented at 12:17 pm on March 10, 2021: member

    Saw this OOM error three times, but only with ../qa-assets/fuzz_seed_corpus/ (git pulled the latest head)

     0$ FUZZ=i2p src/test/fuzz/fuzz ../qa-assets/fuzz_seed_corpus/
     1INFO: Seed: 2309095707
     2INFO: Loaded 1 modules   (642728 inline 8-bit counters): 642728 [0x55bbf8e17668, 0x55bbf8eb4510), 
     3INFO: Loaded 1 PC tables (642728 PCs): 642728 [0x55bbf8eb4510,0x55bbf9882f90), 
     4INFO:   237105 files found in ../qa-assets/fuzz_seed_corpus/
     5INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 1048576 bytes
     6INFO: seed corpus: files: 237105 min: 1b max: 3986616b total: 4147898382b rss: 359Mb
     7[#4096](/bitcoin-bitcoin/4096/)	pulse  cov: 1461 ft: 1820 corp: 8/29b exec/s: 2048 rss: 445Mb
     8[#8192](/bitcoin-bitcoin/8192/)	pulse  cov: 1862 ft: 2571 corp: 23/136b exec/s: 2048 rss: 520Mb
     9[#16384](/bitcoin-bitcoin/16384/)	pulse  cov: 2655 ft: 4120 corp: 38/278b exec/s: 2340 rss: 659Mb
    10[#32768](/bitcoin-bitcoin/32768/)	pulse  cov: 2749 ft: 5425 corp: 81/926b exec/s: 2730 rss: 722Mb
    11[#65536](/bitcoin-bitcoin/65536/)	pulse  cov: 2813 ft: 6711 corp: 132/2841b exec/s: 2849 rss: 722Mb
    12[#131072](/bitcoin-bitcoin/131072/)	pulse  cov: 2911 ft: 8428 corp: 169/7173b exec/s: 2730 rss: 722Mb
    13==16390== ERROR: libFuzzer: out-of-memory (used: 2191Mb; limit: 2048Mb)
    14   To change the out-of-memory limit use -rss_limit_mb=<N>
    15
    16Live Heap Allocations: 89301481 bytes in 242997 chunks; quarantined: 245829231 bytes in 30252 chunks; 1909049 other chunks; total chunks: 2182298; showing top 95% (at most 8 unique contexts)
    1722746576 byte(s) (25%) in 237105 allocation(s)
    18    [#0](/bitcoin-bitcoin/0/) 0x55bbf4cf94fd in malloc (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2d444fd)
    19    [#1](/bitcoin-bitcoin/1/) 0x55bbf4c0bde7 in operator new(unsigned long) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c56de7)
    20    [#2](/bitcoin-bitcoin/2/) 0x55bbf4c23aac in fuzzer::ReadCorpora(std::Fuzzer::vector<std::Fuzzer::basic_string<char, std::Fuzzer::char_traits<char>, std::Fuzzer::allocator<char> >, fuzzer::fuzzer_allocator<std::Fuzzer::basic_string<char, std::Fuzzer::char_traits<char>, std::Fuzzer::allocator<char> > > > const&, std::Fuzzer::vector<std::Fuzzer::basic_string<char, std::Fuzzer::char_traits<char>, std::Fuzzer::allocator<char> >, fuzzer::fuzzer_allocator<std::Fuzzer::basic_string<char, std::Fuzzer::char_traits<char>, std::Fuzzer::allocator<char> > > > const&) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c6eaac)
    21    [#3](/bitcoin-bitcoin/3/) 0x55bbf4c23672 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c6e672)
    22    [#4](/bitcoin-bitcoin/4/) 0x55bbf4c4cb12 in main (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c97b12)
    23    [#5](/bitcoin-bitcoin/5/) 0x7f929ca83d09 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26d09)
    24
    2521499328 byte(s) (24%) in 12 allocation(s)
    26    [#0](/bitcoin-bitcoin/0/) 0x55bbf4cf94fd in malloc (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2d444fd)
    27    [#1](/bitcoin-bitcoin/1/) 0x55bbf4c0bde7 in operator new(unsigned long) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c56de7)
    28    [#2](/bitcoin-bitcoin/2/) 0x55bbf4c4cb12 in main (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c97b12)
    29    [#3](/bitcoin-bitcoin/3/) 0x7f929ca83d09 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26d09)
    30
    3116777216 byte(s) (18%) in 1 allocation(s)
    32    [#0](/bitcoin-bitcoin/0/) 0x55bbf4d28c3d in operator new(unsigned long) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2d73c3d)
    33    [#1](/bitcoin-bitcoin/1/) 0x55bbf4e228c5 in __gnu_cxx::new_allocator<uint256>::allocate(unsigned long, void const*) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/ext/new_allocator.h:115:27
    34    [#2](/bitcoin-bitcoin/2/) 0x55bbf4e228c5 in std::allocator_traits<std::allocator<uint256> >::allocate(std::allocator<uint256>&, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/alloc_traits.h:460:20
    35    [#3](/bitcoin-bitcoin/3/) 0x55bbf4e228c5 in std::_Vector_base<uint256, std::allocator<uint256> >::_M_allocate(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/stl_vector.h:346:20
    36    [#4](/bitcoin-bitcoin/4/) 0x55bbf575d27a in std::vector<uint256, std::allocator<uint256> >::_M_default_append(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/vector.tcc:635:34
    37    [#5](/bitcoin-bitcoin/5/) 0x55bbf575cdd2 in std::vector<uint256, std::allocator<uint256> >::resize(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/stl_vector.h:940:4
    38    [#6](/bitcoin-bitcoin/6/) 0x55bbf575c623 in CuckooCache::cache<uint256, SignatureCacheHasher>::setup(unsigned int) /home/jon/projects/bitcoin/bitcoin/src/./cuckoocache.h:344:15
    39    [#7](/bitcoin-bitcoin/7/) 0x55bbf59809e6 in CuckooCache::cache<uint256, SignatureCacheHasher>::setup_bytes(unsigned long) /home/jon/projects/bitcoin/bitcoin/src/./cuckoocache.h:368:16
    40    [#8](/bitcoin-bitcoin/8/) 0x55bbf59809e6 in InitScriptExecutionCache() /home/jon/projects/bitcoin/bitcoin/src/validation.cpp:1468:44
    41    [#9](/bitcoin-bitcoin/9/) 0x55bbf607ba7f in BasicTestingSetup::BasicTestingSetup(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<char const*, std::allocator<char const*> > const&) /home/jon/projects/bitcoin/bitcoin/src/test/util/setup_common.cpp:110:5
    42    [#10](/bitcoin-bitcoin/10/) 0x55bbf4dc2187 in std::_MakeUniq<BasicTestingSetup const>::__single_object std::make_unique<BasicTestingSetup const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<char const*, std::allocator<char const*> > const&>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<char const*, std::allocator<char const*> > const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/unique_ptr.h:962:34
    43    [#11](/bitcoin-bitcoin/11/) 0x55bbf4dbe382 in std::unique_ptr<BasicTestingSetup const, std::default_delete<BasicTestingSetup const> > MakeNoLogFileContext<BasicTestingSetup const>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<char const*, std::allocator<char const*> > const&) /home/jon/projects/bitcoin/bitcoin/src/./test/util/setup_common.h:170:12
    44    [#12](/bitcoin-bitcoin/12/) 0x55bbf4fe4723 in initialize_i2p() /home/jon/projects/bitcoin/bitcoin/src/test/fuzz/i2p.cpp:17:39
    45    [#13](/bitcoin-bitcoin/13/) 0x55bbf4d2f80c in void std::__invoke_impl<void, void (*&)()>(std::__invoke_other, void (*&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/invoke.h:60:14
    46    [#14](/bitcoin-bitcoin/14/) 0x55bbf4d2f80c in std::enable_if<is_invocable_r_v<void, void (*&)()>, void>::type std::__invoke_r<void, void (*&)()>(void (*&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/invoke.h:110:2
    47    [#15](/bitcoin-bitcoin/15/) 0x55bbf4d2f80c in std::_Function_handler<void (), void (*)()>::_M_invoke(std::_Any_data const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/std_function.h:291:9
    48    [#16](/bitcoin-bitcoin/16/) 0x55bbf531c54c in std::function<void ()>::operator()() const /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/std_function.h:622:14
    49    [#17](/bitcoin-bitcoin/17/) 0x55bbf66a3c92 in initialize() /home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz.cpp:44:5
    50    [#18](/bitcoin-bitcoin/18/) 0x55bbf66a51cd in LLVMFuzzerInitialize /home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz.cpp:70:5
    51    [#19](/bitcoin-bitcoin/19/) 0x55bbf4c218ac in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c6c8ac)
    52    [#20](/bitcoin-bitcoin/20/) 0x55bbf4c4cb12 in main (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c97b12)
    53    [#21](/bitcoin-bitcoin/21/) 0x7f929ca83d09 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26d09)
    54
    5516777216 byte(s) (18%) in 1 allocation(s)
    56    [#0](/bitcoin-bitcoin/0/) 0x55bbf4d28c3d in operator new(unsigned long) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2d73c3d)
    57    [#1](/bitcoin-bitcoin/1/) 0x55bbf4e228c5 in __gnu_cxx::new_allocator<uint256>::allocate(unsigned long, void const*) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/ext/new_allocator.h:115:27
    58    [#2](/bitcoin-bitcoin/2/) 0x55bbf4e228c5 in std::allocator_traits<std::allocator<uint256> >::allocate(std::allocator<uint256>&, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/alloc_traits.h:460:20
    59    [#3](/bitcoin-bitcoin/3/) 0x55bbf4e228c5 in std::_Vector_base<uint256, std::allocator<uint256> >::_M_allocate(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/stl_vector.h:346:20
    60    [#4](/bitcoin-bitcoin/4/) 0x55bbf575d27a in std::vector<uint256, std::allocator<uint256> >::_M_default_append(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/vector.tcc:635:34
    61    [#5](/bitcoin-bitcoin/5/) 0x55bbf575cdd2 in std::vector<uint256, std::allocator<uint256> >::resize(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/stl_vector.h:940:4
    62    [#6](/bitcoin-bitcoin/6/) 0x55bbf575c623 in CuckooCache::cache<uint256, SignatureCacheHasher>::setup(unsigned int) /home/jon/projects/bitcoin/bitcoin/src/./cuckoocache.h:344:15
    63    [#7](/bitcoin-bitcoin/7/) 0x55bbf5759dc2 in CuckooCache::cache<uint256, SignatureCacheHasher>::setup_bytes(unsigned long) /home/jon/projects/bitcoin/bitcoin/src/./cuckoocache.h:368:16
    64    [#8](/bitcoin-bitcoin/8/) 0x55bbf5759dc2 in (anonymous namespace)::CSignatureCache::setup_bytes(unsigned long) /home/jon/projects/bitcoin/bitcoin/src/script/sigcache.cpp:80:25
    65    [#9](/bitcoin-bitcoin/9/) 0x55bbf5759dc2 in InitSignatureCache() /home/jon/projects/bitcoin/bitcoin/src/script/sigcache.cpp:100:36
    66    [#10](/bitcoin-bitcoin/10/) 0x55bbf607ba6f in BasicTestingSetup::BasicTestingSetup(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<char const*, std::allocator<char const*> > const&) /home/jon/projects/bitcoin/bitcoin/src/test/util/setup_common.cpp:109:5
    67    [#11](/bitcoin-bitcoin/11/) 0x55bbf4dc2187 in std::_MakeUniq<BasicTestingSetup const>::__single_object std::make_unique<BasicTestingSetup const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<char const*, std::allocator<char const*> > const&>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<char const*, std::allocator<char const*> > const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/unique_ptr.h:962:34
    68    [#12](/bitcoin-bitcoin/12/) 0x55bbf4dbe382 in std::unique_ptr<BasicTestingSetup const, std::default_delete<BasicTestingSetup const> > MakeNoLogFileContext<BasicTestingSetup const>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<char const*, std::allocator<char const*> > const&) /home/jon/projects/bitcoin/bitcoin/src/./test/util/setup_common.h:170:12
    69    [#13](/bitcoin-bitcoin/13/) 0x55bbf4fe4723 in initialize_i2p() /home/jon/projects/bitcoin/bitcoin/src/test/fuzz/i2p.cpp:17:39
    70    [#14](/bitcoin-bitcoin/14/) 0x55bbf4d2f80c in void std::__invoke_impl<void, void (*&)()>(std::__invoke_other, void (*&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/invoke.h:60:14
    71    [#15](/bitcoin-bitcoin/15/) 0x55bbf4d2f80c in std::enable_if<is_invocable_r_v<void, void (*&)()>, void>::type std::__invoke_r<void, void (*&)()>(void (*&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/invoke.h:110:2
    72    [#16](/bitcoin-bitcoin/16/) 0x55bbf4d2f80c in std::_Function_handler<void (), void (*)()>::_M_invoke(std::_Any_data const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/std_function.h:291:9
    73    [#17](/bitcoin-bitcoin/17/) 0x55bbf531c54c in std::function<void ()>::operator()() const /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/std_function.h:622:14
    74    [#18](/bitcoin-bitcoin/18/) 0x55bbf66a3c92 in initialize() /home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz.cpp:44:5
    75    [#19](/bitcoin-bitcoin/19/) 0x55bbf66a51cd in LLVMFuzzerInitialize /home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz.cpp:70:5
    76    [#20](/bitcoin-bitcoin/20/) 0x55bbf4c218ac in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c6c8ac)
    77    [#21](/bitcoin-bitcoin/21/) 0x55bbf4c4cb12 in main (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c97b12)
    78    [#22](/bitcoin-bitcoin/22/) 0x7f929ca83d09 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26d09)
    79
    808388608 byte(s) (9%) in 1 allocation(s)
    81    [#0](/bitcoin-bitcoin/0/) 0x55bbf4cf94fd in malloc (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2d444fd)
    82    [#1](/bitcoin-bitcoin/1/) 0x55bbf4c0bde7 in operator new(unsigned long) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c56de7)
    83    [#2](/bitcoin-bitcoin/2/) 0x55bbf4c2f807 in fuzzer::GetSizedFilesFromDir(std::Fuzzer::basic_string<char, std::Fuzzer::char_traits<char>, std::Fuzzer::allocator<char> > const&, std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >*) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c7a807)
    84    [#3](/bitcoin-bitcoin/3/) 0x55bbf4c23aac in fuzzer::ReadCorpora(std::Fuzzer::vector<std::Fuzzer::basic_string<char, std::Fuzzer::char_traits<char>, std::Fuzzer::allocator<char> >, fuzzer::fuzzer_allocator<std::Fuzzer::basic_string<char, std::Fuzzer::char_traits<char>, std::Fuzzer::allocator<char> > > > const&, std::Fuzzer::vector<std::Fuzzer::basic_string<char, std::Fuzzer::char_traits<char>, std::Fuzzer::allocator<char> >, fuzzer::fuzzer_allocator<std::Fuzzer::basic_string<char, std::Fuzzer::char_traits<char>, std::Fuzzer::allocator<char> > > > const&) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c6eaac)
    85    [#4](/bitcoin-bitcoin/4/) 0x55bbf4c23672 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c6e672)
    86    [#5](/bitcoin-bitcoin/5/) 0x55bbf4c4cb12 in main (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x2c97b12)
    87    [#6](/bitcoin-bitcoin/6/) 0x7f929ca83d09 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26d09)
    88
    89MS: 0 ; base unit: 0000000000000000000000000000000000000000
    90
    91
    92artifact_prefix='./'; Test unit written to ./oom-da39a3ee5e6b4b0d3255bfef95601890afd80709
    93Base64: 
    94SUMMARY: libFuzzer: out-of-memory
    
  15. jonatack commented at 7:23 pm on March 11, 2021: member
    Had the same OOM issue today with another fuzz PR, so I’m proceeding on the assumption that it’s a regression that is not related to this PR.
  16. in src/test/fuzz/util.h:631 in 933d181a8c outdated
    628-            m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len));
    629+        std::vector<uint8_t> random_bytes;
    630+        bool pad_to_len_bytes{m_fuzzed_data_provider.ConsumeBool()};
    631+        if (m_peek_data.has_value()) {
    632+            // `MSG_PEEK` was used in the preceding `Recv()` call, return `m_peek_data`.
    633+            random_bytes.assign(1, m_peek_data.value());
    


    jonatack commented at 7:53 pm on March 11, 2021:

    e944970 nit, can alternatively omit the count argument with an initializer list

    0            random_bytes.assign({m_peek_data.value()});
    

    vasild commented at 5:00 pm on March 12, 2021:
    Done.
  17. in src/util/sock.h:104 in 933d181a8c outdated
     99+    /**
    100+     * getsockopt(2) wrapper. Equivalent to
    101+     * `getsockopt(this->Get(), level, opt_name, opt_val, opt_len)`. Code that uses this
    102+     * wrapper can be unit-tested if this method is overridden by a mock Sock implementation.
    103+     */
    104+    virtual int Getsockopt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const;
    


    jonatack commented at 8:16 pm on March 11, 2021:

    3e5afb3b

    member function naming: s/Getsockopt/GetSockOpt/

    nit, in the Doxygen docs: s/unit-tested/unit tested/ lines 95 and 102


    vasild commented at 5:00 pm on March 12, 2021:
    Done.
  18. in src/test/fuzz/util.h:688 in 933d181a8c outdated
    663@@ -642,6 +664,47 @@ class FuzzedSock : public Sock
    664         return random_bytes.size();
    665     }
    666 
    667+    int Connect(const sockaddr*, socklen_t) const override
    


    jonatack commented at 8:23 pm on March 11, 2021:

    3e5afb3bf0db02ad3fc00afe431f64f3dcd0d3b1

    0    int Connect(const sockaddr* addr, socklen_t addr_len) const override
    

    vasild commented at 5:04 pm on March 12, 2021:

    Why? It is a bit shorter without the parameter names. Omitting parameter names if they are not used I think is a good practice because it makes it obvious that they are not used.

    Also, specifying parameter names and not using those parameters in the function will produce a compiler warning with -Wunused-parameter (which is explicitly disabled in Bitcoin Core, I guess because of so many functions that violate it that it is hard to fix).


    jonatack commented at 5:58 pm on March 12, 2021:
    Thanks for the explanation.
  19. in src/netbase.cpp:636 in 933d181a8c outdated
    632@@ -633,7 +633,7 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i
    633     }
    634 
    635     // Connect to the addrConnect service on the hSocket socket.
    636-    if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR)
    637+    if (sock.Connect((struct sockaddr*)&sockaddr, len) == SOCKET_ERROR)
    


    jonatack commented at 8:47 pm on March 11, 2021:

    34c199db11b36eee91e56cdd046c090f62da18d6

    0    if (sock.Connect(reinterpret_cast<struct sockaddr*>(&sockaddr), len) == SOCKET_ERROR)
    

    vasild commented at 5:04 pm on March 12, 2021:
    Done.
  20. in src/netbase.cpp:663 in 933d181a8c outdated
    686-            if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR)
    687-            {
    688+            // sockerr here.
    689+            int sockerr;
    690+            socklen_t sockerr_len = sizeof(sockerr);
    691+            if (sock.Getsockopt(SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&sockerr, &sockerr_len) ==
    


    jonatack commented at 8:56 pm on March 11, 2021:

    34c199db11b36eee91e56cdd046c090f62da18d6

    0            if (sock.Getsockopt(SOL_SOCKET, SO_ERROR, static_cast<sockopt_arg_type>(&sockerr), &sockerr_len) ==
    

    vasild commented at 5:04 pm on March 12, 2021:
    Done.

    vasild commented at 5:20 pm on March 12, 2021:

    Broke the windows build. Reverted.

    0netbase.cpp:663:81: error: invalid static_cast from type ‘int*’ to type ‘sockopt_arg_type’ {aka ‘char*’}
    1  663 |                     SOL_SOCKET, SO_ERROR, static_cast<sockopt_arg_type>(&sockerr), &sockerr_len) ==
    2      |                                                                                 ^
    

    jonatack commented at 5:58 pm on March 12, 2021:
    We should probably use reinterpret_cast to signal that this is an ugly and non-portable cast (based on typedefs in compat.h). It doesn’t have to be done in this PR. There are related cases.
  21. in src/netbase.cpp:580 in 933d181a8c outdated
    685-            socklen_t nRetSize = sizeof(nRet);
    686-            if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR)
    687-            {
    688+            // sockerr here.
    689+            int sockerr;
    690+            socklen_t sockerr_len = sizeof(sockerr);
    


    jonatack commented at 8:59 pm on March 11, 2021:
    34c199db11b36eee91e56cdd046c090f62da18d6 Should sockerr be initialized with a value?

    vasild commented at 5:06 pm on March 12, 2021:
    I don’t think so - getsockopt() will set it (if it returns 0). The previous code abused nRet which contained a leftover value from the call to select().

    jonatack commented at 6:48 pm on March 12, 2021:

    Yes, it looks like getsockopt will set both sockerr and sockerr_len. I was curious what sizeof would return on an uninitialized int on my implementation/compiler:

     0#include <iostream>
     1#include <sys/socket.h>
     2
     3int main()
     4{
     5    int sockerr;
     6    socklen_t sockerr_len{sizeof(sockerr)};
     7
     8    std::cout << "sockerr: " << sockerr << '\n';
     9    std::cout << "size_of(sockerr): " << sizeof(sockerr) << '\n';
    10    std::cout << "sockerr_len: " << sockerr_len << '\n';
    11
    12    return 0;
    13}
    
    0sockerr: 0
    1size_of(sockerr): 4
    2sockerr_len: 4
    

    vasild commented at 5:33 am on March 13, 2021:
    sizeof() does not read the value, can be used safely without triggering any ubsan or valgrind warnings on uninitialized variables.
  22. jonatack commented at 9:02 pm on March 11, 2021: member

    Built and reviewed each commit up to 34c199db11b36eee91e56cdd046c090f62da18d6.

    In the 3rd commit, “avoid repeated errors,” it looks like the commit message should be “to retry the operation.” e.g. s/to/the/

  23. vasild commented at 8:12 am on March 12, 2021: member

    Had the same OOM issue today with another fuzz PR

    Can you reproduce it? E.g. FUZZ=i2p src/test/fuzz/fuzz ./oom-da39a3ee5e6b4b0d3255bfef95601890afd80709

  24. jonatack commented at 1:32 pm on March 12, 2021: member
    0INFO: Seed: 3949145381
    1INFO: Loaded 1 modules   (643885 inline 8-bit counters): 643885 [0x56188117c288, 0x5618812195b5), 
    2INFO: Loaded 1 PC tables (643885 PCs): 643885 [0x5618812195b8,0x561881bec888), 
    3src/test/fuzz/fuzz: Running 1 inputs 1 time(s) each.
    4Running: oom-da39a3ee5e6b4b0d3255bfef95601890afd80709
    5Executed oom-da39a3ee5e6b4b0d3255bfef95601890afd80709 in 0 ms
    6***
    7*** NOTE: fuzzing was not performed, you have only
    8***       executed the target code on a fixed set of inputs.
    9***
    
  25. in src/i2p.cpp:377 in 933d181a8c outdated
    371@@ -370,12 +372,12 @@ void Session::CreateIfNotCreatedAlready()
    372               m_my_addr.ToString());
    373 }
    374 
    375-Sock Session::StreamAccept()
    376+std::unique_ptr<Sock> Session::StreamAccept()
    377 {
    378-    Sock sock = Hello();
    379+    auto sock = Hello();
    


    jonatack commented at 2:04 pm on March 12, 2021:
    5ac2bc1 Verified this line and line 352 compile when replaced with explicit typing and braced initialization std::unique_ptr<Sock> sock{Hello()}; (which would save people time in looking up the type)
  26. in src/test/fuzz/i2p.cpp:30 in 933d181a8c outdated
    25+    auto CreateSockOrig = CreateSock;
    26+    CreateSock = [&fuzzed_data_provider](const CService&) {
    27+        return std::make_unique<FuzzedSock>(fuzzed_data_provider);
    28+    };
    29+
    30+    CService sam_proxy;
    


    jonatack commented at 2:14 pm on March 12, 2021:
    933d181a nit, sam_proxy here and to on line 43 below can both be const (to show intention)

    vasild commented at 5:06 pm on March 12, 2021:
    Done.
  27. jonatack commented at 2:21 pm on March 12, 2021: member
    ACK 933d181a8c0c41318204b1657765d582418a80a1 modulo a few thoughts/questions above
  28. vasild force-pushed on Mar 12, 2021
  29. vasild commented at 5:07 pm on March 12, 2021: member
    933d181a8...7c6fc2de5: address suggestions and also fuzz the IsConnected() method of Sock / FuzzedSock.
  30. vasild force-pushed on Mar 12, 2021
  31. vasild commented at 5:20 pm on March 12, 2021: member
    7c6fc2de5...23c861d6f: fix Windows CI
  32. jonatack commented at 6:50 pm on March 12, 2021: member
    re-ACK 23c861d6ff995b7e6034d4bec6af2f6a3d595dca
  33. DrahtBot added the label Needs rebase on Mar 16, 2021
  34. fuzz: implement unimplemented FuzzedSock methods
    We want `Get()` to always return the same value, otherwise it will look
    like the `FuzzedSock` implementation itself is broken. So assign
    `m_socket` a random number in the `FuzzedSock` constructor.
    
    There is nothing to fuzz about the `Get()` and `Release()` methods, so
    use the ones from the base class `Sock`.
    
    `Reset()` is just setting our socket to `INVALID_SOCKET`. We don't want
    to use the base `Reset()` because it will close `m_socket` and given
    that our `m_socket` is just a random number it may end up closing a real
    opened file descriptor if it coincides with our random `m_socket`.
    9b05c49ade
  35. fuzz: extend FuzzedSock::Recv() to support MSG_PEEK
    A conforming `recv(2)` call is supposed to return the same data on a
    call following `recv(..., MSG_PEEK)`. Extend `FuzzedSock::Recv()` to do
    that.
    
    For simplicity we only return 1 byte when `MSG_PEEK` is used. If we
    would return a buffer of N bytes, then we would have to keep track how
    many of them were consumed on subsequent non-`MSG_PEEK` calls.
    3088f83d01
  36. fuzz: avoid FuzzedSock::Recv() repeated errors with EAGAIN
    If `recv(2)` returns an error (`-1`) and sets `errno` to a temporary
    error like `EAGAIN` a proper application code is expected to retry the
    operation.
    
    If the fuzz data is exhausted, then `FuzzedSock::Recv()` will keep
    returning `-1` and setting `errno` to the first element of
    `recv_errnos[]` which happened to be `EAGAIN`. This may continue forever
    or cause the fuzz test to run for a long time before some higher level
    application "receive timeout" is triggered.
    
    Thus, put `ECONNREFUSED` as first element of `recv_errnos[]`.
    5a887d49b2
  37. net: add connect() and getsockopt() wrappers to Sock
    Extend the `Sock` class with wrappers to `connect()` and `getsockopt()`.
    
    This will make it possible to mock code which uses those.
    b5861100f8
  38. net: change ConnectSocketDirectly() to take a Sock argument
    Change `ConnectSocketDirectly()` to take a `Sock` argument instead of a
    bare `SOCKET`. With this, use the `Sock`'s (possibly mocked) methods
    `Connect()`, `Wait()` and `GetSockOpt()` instead of calling the OS
    functions directly.
    82d360b5a8
  39. i2p: use pointers to Sock to accommodate mocking
    Change the types of `i2p::Connection::sock` and
    `i2p::sam::Session::m_control_sock` from `Sock` to
    `std::unique_ptr<Sock>`.
    
    Using pointers would allow us to sneak `FuzzedSock` instead of `Sock`
    and have the methods of the former called.
    
    After this change a test only needs to replace `CreateSock()` with
    a function that returns `FuzzedSock`.
    9947e44de0
  40. fuzz: add tests for the I2P Session public interface 2d8ac77970
  41. test: add I2P test for a runaway SAM proxy
    Add a regression test for https://github.com/bitcoin/bitcoin/pull/21407.
    
    The test creates a socket that, upon read, returns some data, but never
    the expected terminator `\n`, injects that socket into the I2P code and
    expects `i2p::sam::Session::Connect()` to fail, printing a specific
    error message to the log.
    40316a37cb
  42. vasild force-pushed on Mar 16, 2021
  43. vasild commented at 2:02 pm on March 16, 2021: member

    23c861d6f...40316a37c:

    • rebase due to conflicts
    • expect that Sock methods RecvUntilTerminator() and SendComplete() could throw an exception when using fuzzed socket. After all a fuzzed socket may return an error at any time.
    • add a regression test for the bug fixed in #21407.
  44. vasild renamed this:
    fuzz: test the I2P Session public interface
    test: add I2P fuzz and unit tests
    on Mar 16, 2021
  45. DrahtBot removed the label Needs rebase on Mar 16, 2021
  46. jonatack approved
  47. jonatack commented at 4:02 pm on March 16, 2021: member

    Thanks for adding the regression test.

    re-ACK 40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5 reviewed git range-diff 01bb3afb 23c861d 40316a3 and the new unit test commit, debug built, ran unit tests, ran bitcoind with an I2P service and network operation with seven I2P peers (2 in, 5 out) is looking nominal

  48. MarcoFalke removed the label P2P on Mar 18, 2021
  49. MarcoFalke removed the label Tests on Mar 18, 2021
  50. DrahtBot added the label Build system on Mar 18, 2021
  51. DrahtBot added the label P2P on Mar 18, 2021
  52. DrahtBot added the label Utils/log/libs on Mar 18, 2021
  53. MarcoFalke removed the label Build system on Mar 18, 2021
  54. MarcoFalke removed the label Utils/log/libs on Mar 18, 2021
  55. MarcoFalke added the label Tests on Mar 18, 2021
  56. practicalswift commented at 11:31 pm on March 20, 2021: contributor

    Tested ACK 40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5

    Great fuzzing work @vasild!

  57. MarcoFalke renamed this:
    test: add I2P fuzz and unit tests
    p2p: Refactor sock to add I2P fuzz and unit tests
    on Mar 22, 2021
  58. MarcoFalke commented at 6:55 am on March 22, 2021: member
    (Changed title, because this is not test-only)
  59. MarcoFalke commented at 6:55 am on March 22, 2021: member
    Concept ACK 40316a37cb
  60. MarcoFalke requested review from laanwj on Mar 30, 2021
  61. laanwj approved
  62. laanwj commented at 3:39 pm on March 30, 2021: member

    Code review ACK 40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5

    I like how this gets rid of a #ifdef USE_POLL in the connect logic.

  63. laanwj merged this on Mar 30, 2021
  64. laanwj closed this on Mar 30, 2021

  65. vasild deleted the branch on Mar 30, 2021
  66. sidhujag referenced this in commit 47d2859a47 on Mar 30, 2021
  67. Fabcien referenced this in commit de38a2d80d on Feb 15, 2022
  68. in src/test/fuzz/i2p.cpp:51 in 40316a37cb
    46+    const CService to;
    47+    bool proxy_error;
    48+
    49+    if (sess.Connect(to, conn, proxy_error)) {
    50+        try {
    51+            conn.sock->SendComplete("verack\n", 10ms, interrupt);
    


    MarcoFalke commented at 9:42 am on June 9, 2022:

    vasild commented at 2:55 pm on June 9, 2022:

    Well, it would be executed, but for Connect() to return true the fuzzed sock must return some meaningful data, which I guess has a very very low chance of happening.

    Edit: now I remember I was considering crafting a custom fuzz-corpus data input which would contain the meaningful data at the right offsets so that Connect() will succeed. I gave up because that would be difficult to maintain because the binary “magic” corpus data would have to be adjusted every time this test is modified to add or remove calls that consume fuzz data before Connect() is called. Also, that would not be fuzzing so much, but rather some kind of unit testing.


    MarcoFalke commented at 3:16 pm on June 9, 2022:
    There’d also be a middle way to teach the fuzz engine a meaningful format or snippets. This can be achieved with a dict, a custom mutator, or with some code in the fuzz target (CallOneOf(…))
  69. kittywhiskers referenced this in commit 77ec468229 on Apr 16, 2023
  70. kittywhiskers referenced this in commit 380149f2c3 on Apr 16, 2023
  71. kittywhiskers referenced this in commit bde3fa9129 on Apr 17, 2023
  72. kittywhiskers referenced this in commit b449c6dc60 on Apr 17, 2023
  73. kittywhiskers referenced this in commit 308095791d on Apr 17, 2023
  74. kittywhiskers referenced this in commit ab652a7a18 on Apr 17, 2023
  75. kittywhiskers referenced this in commit 8a3fac549f on Apr 17, 2023
  76. kittywhiskers referenced this in commit 6d012ced63 on Apr 17, 2023
  77. Empact referenced this in commit b095ac573b on Apr 25, 2023
  78. DrahtBot locked this on Jun 9, 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-03 10:13 UTC

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