test: wait for disconnect in disconnect_p2ps + bloomfilter test followups #19252

pull glozow wants to merge 3 commits into bitcoin:master from glozow:test-disconnect-p2ps-wait changing 5 files +18 −15
  1. glozow commented at 6:03 pm on June 11, 2020: member

    Followup to #19083 which adds bloomfilter-related tests.

    1. Make test_node disconnect_p2ps wait until disconnection is complete to avoid race conditions (and not place the burden on tests) from MarcoFalke’s comment. And clean up any redundant wait_untils in the functional tests.
    2. Clean up style + logging in p2p_filter.py and p2p_nobloomfilter_messages.py and jonatack’s other comments
  2. glozow force-pushed on Jun 11, 2020
  3. DrahtBot added the label Tests on Jun 11, 2020
  4. glozow force-pushed on Jun 12, 2020
  5. glozow force-pushed on Jun 12, 2020
  6. DrahtBot commented at 2:56 am on June 13, 2020: 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:

    • #19134 (test: Replace global wait_until with BitcoinTestFramework.wait_until and mininode.wait_until by dboures)

    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.

  7. glozow marked this as ready for review on Jun 13, 2020
  8. glozow renamed this:
    [wip] test: wait for disconnect in disconnect_p2ps + bloomfilter test followups
    test: wait for disconnect in disconnect_p2ps + bloomfilter test followups
    on Jun 13, 2020
  9. glozow commented at 5:13 pm on June 13, 2020: member
    @jonatack addressed your comments on #19083 in afad631 if you have time to take a look :)
  10. in test/functional/test_framework/test_node.py:556 in 628227ff8b outdated
    549@@ -549,11 +550,17 @@ def p2p(self):
    550         assert self.p2ps, self._node_msg("No p2p connection")
    551         return self.p2ps[0]
    552 
    553+    def num_connected_mininodes(self):
    554+        """Return number of p2ps that are connected to the node. Excludes test nodes."""
    555+        peer_subversions = [peer['subver'] for peer in self.getpeerinfo()]
    556+        return peer_subversions.count(MY_SUBVERSION)
    


    jnewbery commented at 0:47 am on June 14, 2020:

    Alternatively, use a conditional in the list comprehension:

    0return len([peer for peer in self.getpeerinfo() if peer['subver'] == MY_SUBVERSION])
    

    I think that’s slightly clearer, but it’s probably just a personal preference thing.

  11. in test/functional/test_framework/test_node.py:554 in 628227ff8b outdated
    549@@ -549,11 +550,17 @@ def p2p(self):
    550         assert self.p2ps, self._node_msg("No p2p connection")
    551         return self.p2ps[0]
    552 
    553+    def num_connected_mininodes(self):
    554+        """Return number of p2ps that are connected to the node. Excludes test nodes."""
    


    jnewbery commented at 0:48 am on June 14, 2020:

    I think this would be slightly more concise if you say what you are counting (rather than saying what you’re excluding):

    0        """Return number of test framework p2p connections to the node."""
    
  12. jnewbery commented at 0:52 am on June 14, 2020: member

    Code review ACK 628227ff8b0409546a0176a39a5491c069749fef

    A couple of small style suggestions inline. Feel free to take or ignore.

  13. jonatack commented at 4:22 pm on June 14, 2020: member

    Tested ACK with a couple of minor suggestions and a possible concern.

    In f1dbb845d consider adding explanation (why, tradeoffs, etc.) for the change in the commit message (like the content of the comment mentioned in the PR description), so future us searching through git history won’t have to look up the PR description and then find the linked comment on GitHub, which is only GitHub-specific metadata and not stored in git history.

    In the afad631b consider not linking in the commit message to GitHub metadata, e.g. PRs, comments, @-usernames (I used to do it, but it can leave a messy trail).

    Concern: possible slowdown in test run times of ~10% in tests with wait_for_disconnect. See details that follow.

    in test_runner.py

     0BASE_SCRIPTS = [
     1    'feature_maxuploadtarget.py',
     2    'p2p_blockfilters.py',
     3    'p2p_blocksonly.py',
     4    'p2p_dos_header_tree.py',
     5    'p2p_invalid_locator.py',
     6    'p2p_invalid_messages.py',
     7    'p2p_leak.py',
     8    'p2p_nobloomfilter_messages.py',
     9    'p2p_node_network_limited.py',
    10    'p2p_sendheaders.py',
    11    'p2p_unrequested_blocks.py',
    12]
    

    this branch

     0TEST                          | STATUS    | DURATION
     1
     2feature_maxuploadtarget.py    |  Passed  | 92 s
     3p2p_blockfilters.py           |  Passed  | 98 s
     4p2p_blocksonly.py             |  Passed  | 4 s
     5p2p_dos_header_tree.py        |  Passed  | 3 s
     6p2p_invalid_locator.py        |  Passed  | 3 s
     7p2p_invalid_messages.py       |  Passed  | 103 s
     8p2p_leak.py                   |  Passed  | 7 s
     9p2p_nobloomfilter_messages.py |  Passed  | 2 s
    10p2p_node_network_limited.py   |  Passed  | 19 s
    11p2p_sendheaders.py            |  Passed  | 16 s
    12p2p_unrequested_blocks.py     |  Passed  | 10 s
    13
    14ALL                           |  Passed  | 357 s (accumulated)
    15Runtime: 110 s
    16
    17feature_maxuploadtarget.py    |  Passed  | 93 s
    18p2p_blockfilters.py           |  Passed  | 92 s
    19p2p_blocksonly.py             |  Passed  | 7 s
    20p2p_dos_header_tree.py        |  Passed  | 3 s
    21p2p_invalid_locator.py        |  Passed  | 2 s
    22p2p_invalid_messages.py       |  Passed  | 94 s
    23p2p_leak.py                   |  Passed  | 7 s
    24p2p_nobloomfilter_messages.py |  Passed  | 2 s
    25p2p_node_network_limited.py   |  Passed  | 17 s
    26p2p_sendheaders.py            |  Passed  | 18 s
    27p2p_unrequested_blocks.py     |  Passed  | 8 s
    28
    29ALL                           |  Passed  | 343 s (accumulated)
    30Runtime: 100 s
    31
    32feature_maxuploadtarget.py    |  Passed  | 95 s
    33p2p_blockfilters.py           |  Passed  | 102 s
    34p2p_blocksonly.py             |  Passed  | 4 s
    35p2p_dos_header_tree.py        |  Passed  | 3 s
    36p2p_invalid_locator.py        |  Passed  | 3 s
    37p2p_invalid_messages.py       |  Passed  | 100 s
    38p2p_leak.py                   |  Passed  | 7 s
    39p2p_nobloomfilter_messages.py |  Passed  | 2 s
    40p2p_node_network_limited.py   |  Passed  | 18 s
    41p2p_sendheaders.py            |  Passed  | 18 s
    42p2p_unrequested_blocks.py     |  Passed  | 9 s
    43
    44ALL                           |  Passed  | 361 s (accumulated)
    45Runtime: 104 s
    46
    47feature_maxuploadtarget.py    |  Passed  | 97 s
    48p2p_blockfilters.py           |  Passed  | 96 s
    49p2p_blocksonly.py             |  Passed  | 17 s
    50p2p_dos_header_tree.py        |  Passed  | 3 s
    51p2p_invalid_locator.py        |  Passed  | 2 s
    52p2p_invalid_messages.py       |  Passed  | 97 s
    53p2p_leak.py                   |  Passed  | 7 s
    54p2p_nobloomfilter_messages.py |  Passed  | 2 s
    55p2p_node_network_limited.py   |  Passed  | 17 s
    56p2p_sendheaders.py            |  Passed  | 18 s
    57p2p_unrequested_blocks.py     |  Passed  | 9 s
    58
    59ALL                           |  Passed  | 365 s (accumulated)
    60Runtime: 103 s
    61
    62feature_maxuploadtarget.py    |  Passed  | 95 s
    63p2p_blockfilters.py           |  Passed  | 97 s
    64p2p_blocksonly.py             |  Passed  | 7 s
    65p2p_dos_header_tree.py        |  Passed  | 3 s
    66p2p_invalid_locator.py        |  Passed  | 3 s
    67p2p_invalid_messages.py       |  Passed  | 96 s
    68p2p_leak.py                   |  Passed  | 7 s
    69p2p_nobloomfilter_messages.py |  Passed  | 2 s
    70p2p_node_network_limited.py   |  Passed  | 16 s
    71p2p_sendheaders.py            |  Passed  | 17 s
    72p2p_unrequested_blocks.py     |  Passed  | 10 s
    73
    74ALL                           |  Passed  | 353 s (accumulated)
    75Runtime: 103 s
    

    master

     0TEST                          | STATUS    | DURATION
     1
     2feature_maxuploadtarget.py    |  Passed  | 94 s
     3p2p_blockfilters.py           |  Passed  | 95 s
     4p2p_blocksonly.py             |  Passed  | 16 s
     5p2p_dos_header_tree.py        |  Passed  | 3 s
     6p2p_invalid_locator.py        |  Passed  | 3 s
     7p2p_invalid_messages.py       |  Passed  | 80 s
     8p2p_leak.py                   |  Passed  | 7 s
     9p2p_nobloomfilter_messages.py |  Passed  | 2 s
    10p2p_node_network_limited.py   |  Passed  | 16 s
    11p2p_sendheaders.py            |  Passed  | 17 s
    12p2p_unrequested_blocks.py     |  Passed  | 9 s
    13
    14ALL                           |  Passed  | 342 s (accumulated)
    15Runtime: 95 s
    16
    17feature_maxuploadtarget.py    |  Passed  | 96 s
    18p2p_blockfilters.py           |  Passed  | 99 s
    19p2p_blocksonly.py             |  Passed  | 7 s
    20p2p_dos_header_tree.py        |  Passed  | 3 s
    21p2p_invalid_locator.py        |  Passed  | 3 s
    22p2p_invalid_messages.py       |  Passed  | 84 s
    23p2p_leak.py                   |  Passed  | 7 s
    24p2p_nobloomfilter_messages.py |  Passed  | 2 s
    25p2p_node_network_limited.py   |  Passed  | 17 s
    26p2p_sendheaders.py            |  Passed  | 18 s
    27p2p_unrequested_blocks.py     |  Passed  | 9 s
    28
    29ALL                           |  Passed  | 345 s (accumulated)
    30Runtime: 99 s
    31
    32feature_maxuploadtarget.py    |  Passed  | 93 s
    33p2p_blockfilters.py           |  Passed  | 94 s
    34p2p_blocksonly.py             |  Passed  | 7 s
    35p2p_dos_header_tree.py        |  Passed  | 3 s
    36p2p_invalid_locator.py        |  Passed  | 2 s
    37p2p_invalid_messages.py       |  Passed  | 79 s
    38p2p_leak.py                   |  Passed  | 7 s
    39p2p_nobloomfilter_messages.py |  Passed  | 2 s
    40p2p_node_network_limited.py   |  Passed  | 16 s
    41p2p_sendheaders.py            |  Passed  | 18 s
    42p2p_unrequested_blocks.py     |  Passed  | 9 s
    43
    44ALL                           |  Passed  | 330 s (accumulated)
    45Runtime: 94 s
    46
    47feature_maxuploadtarget.py    |  Passed  | 94 s
    48p2p_blockfilters.py           |  Passed  | 97 s
    49p2p_blocksonly.py             |  Passed  | 15 s
    50p2p_dos_header_tree.py        |  Passed  | 3 s
    51p2p_invalid_locator.py        |  Passed  | 2 s
    52p2p_invalid_messages.py       |  Passed  | 80 s
    53p2p_leak.py                   |  Passed  | 7 s
    54p2p_nobloomfilter_messages.py |  Passed  | 2 s
    55p2p_node_network_limited.py   |  Passed  | 17 s
    56p2p_sendheaders.py            |  Passed  | 17 s
    57p2p_unrequested_blocks.py     |  Passed  | 9 s
    58
    59ALL                           |  Passed  | 343 s (accumulated)
    60Runtime: 97 s
    
  14. jonatack commented at 4:28 pm on June 14, 2020: member
    (The number of test run samples is too small, but it might be interesting to look further into it.)
  15. glozow commented at 5:05 pm on June 15, 2020: member

    Concern: possible slowdown in test run times of ~10% in tests with wait_for_disconnect.

    Hm :/ that slowdown seems pretty significant. I’ve thought of 2 possible solutions:

    1. What about an optional argument wait_for_disconnect, then allowing tests to decide if waiting isn’t necessary (similar to wait_for_verack in add_p2p_connection)?
    2. I’ve also noticed that disconnect_p2ps is pretty over-used. For example, looks like p2p_invalid_messages.py slowed down by 23 seconds, probably because it calls disconnect_p2ps 10+ times. But actually, that test (and many others) wouldn’t need to wait or even call disconnect_p2ps if it just didn’t use node.p2p. Here is a refactored p2p_invalid_messages.py without any disconnect_p2ps.

    It’s a common pattern to test using multiple mininodes, one at a time, and call disconnect_p2ps at the end to make sure they don’t affect each other. In general, the p2p property is supposed to be a shortcut for node.p2ps[0] when tests only use 1 mininode.

    Thus, I think it would be better to use conn = node.add_p2p_connection() and then use conn to refer to the mininode. We could take out a lot of unnecessary disconnect_p2ps to reduce the performance issue. But it would be a pretty big diff.

    What I suggest, for this PR, is to add a default wait_for_disconnect=False and then make it True for all the tests that need to wait (i.e. the ones I’m already touching here, and I’ll search through the tests to see if any others need it). Would that be a good idea?

  16. jonatack commented at 5:29 pm on June 15, 2020: member
    @gzhao408 I’ve been reworking p2p_invalid_messages.py lately in #19272. I’ll apply this patch to it and test avoiding disconnect_p2ps.
  17. glozow force-pushed on Jun 15, 2020
  18. glozow commented at 10:48 pm on June 15, 2020: member
    Ok, I made waiting optional/default False which should theoretically make a good dent in the performance problems. Went through and added a couple wait_for_disconnect=Trues to where I think it’s needed. I accidentally left the PR link in the https://github.com/bitcoin/bitcoin/pull/19252/commits/81e82070b7c37b2a73e985bdef11e2bcb902d48c commit message, so I’ll fix with next round of edits. Otherwise it’s ready for another look :)
  19. MarcoFalke commented at 11:29 pm on June 15, 2020: member

    Maybe the node.p2p property should be removed if it is causing so much trouble and complexity. There should be no downside to removing it unless I am missing something. Moreover, writing conn = add_conn() everywhere (even in the case of only one peer) even seems preferable for consistency anyway. Also, it is making code more clear at negative cost in complexity, since a reviewer/author doesn’t have to look up what the property does.

    If a connection is used in multiple sub-tests, a member variable could be used like self.conn = add_conn().

  20. jnewbery commented at 0:59 am on June 16, 2020: member

    @jonatack your test runs seem extraordinarily slow. Are you running under bitcoind with some additional debug instrumentation?

    Here are the same tests in my vm for comparison:

    Master:

     01/11 - p2p_dos_header_tree.py passed, Duration: 2 s
     12/11 - p2p_blocksonly.py passed, Duration: 3 s
     23/11 - p2p_invalid_locator.py passed, Duration: 2 s
     34/11 - p2p_leak.py passed, Duration: 6 s
     45/11 - p2p_blockfilters.py passed, Duration: 11 s
     56/11 - p2p_nobloomfilter_messages.py passed, Duration: 1 s
     67/11 - p2p_node_network_limited.py passed, Duration: 9 s
     7Remaining jobs: [feature_maxuploadtarget.py, p2p_invalid_messages.py, p2p_sendheaders.py, p2p_unrequested_blocks.py]
     88/11 - p2p_unrequested_blocks.py passed, Duration: 4 s
     9Remaining jobs: [feature_maxuploadtarget.py, p2p_invalid_messages.py, p2p_sendheaders.py]
    109/11 - p2p_sendheaders.py passed, Duration: 13 s
    11Remaining jobs: [feature_maxuploadtarget.py, p2p_invalid_messages.py]
    1210/11 - p2p_invalid_messages.py passed, Duration: 28 s
    13Remaining jobs: [feature_maxuploadtarget.py]
    1411/11 - feature_maxuploadtarget.py passed, Duration: 69 s                
    15
    16TEST                          | STATUS    | DURATION
    17
    18feature_maxuploadtarget.py    |  Passed  | 69 s
    19p2p_blockfilters.py           |  Passed  | 11 s
    20p2p_blocksonly.py             |  Passed  | 3 s
    21p2p_dos_header_tree.py        |  Passed  | 2 s
    22p2p_invalid_locator.py        |  Passed  | 2 s
    23p2p_invalid_messages.py       |  Passed  | 28 s
    24p2p_leak.py                   |  Passed  | 6 s
    25p2p_nobloomfilter_messages.py |  Passed  | 1 s
    26p2p_node_network_limited.py   |  Passed  | 9 s
    27p2p_sendheaders.py            |  Passed  | 13 s
    28p2p_unrequested_blocks.py     |  Passed  | 4 s
    29
    30ALL                           |  Passed  | 148 s (accumulated) 
    

    This branch (with the default wait_for_disconnect set to True):

     0TEST                          | STATUS    | DURATION
     1
     2feature_maxuploadtarget.py    |  Passed  | 62 s
     3p2p_blockfilters.py           |  Passed  | 13 s
     4p2p_blocksonly.py             |  Passed  | 6 s
     5p2p_dos_header_tree.py        |  Passed  | 3 s
     6p2p_invalid_locator.py        |  Passed  | 2 s
     7p2p_invalid_messages.py       |  Passed  | 27 s
     8p2p_leak.py                   |  Passed  | 7 s
     9p2p_nobloomfilter_messages.py |  Passed  | 1 s
    10p2p_node_network_limited.py   |  Passed  | 9 s
    11p2p_sendheaders.py            |  Passed  | 13 s
    12p2p_unrequested_blocks.py     |  Passed  | 4 s
    13
    14ALL                           |  Passed  | 147 s (accumulated) 
    15Runtime: 62 s
    

    so with a sample size of 1, there’ no observable difference.

    If your bitcoinds are generally slow, then disconnecting peers may be slow, but generally it should be pretty quick and not contribute to test times significantly.

    I think this needs more testing, but if others are able to replicate my results, then I’d vote for not adding the wait_for_disconnect parameter and keeping this simple.

  21. jonatack commented at 4:55 am on June 16, 2020: member

    Isolated the issue :facepalm: Rebasing this PR onto 49236be or current master, p2p_invalid_messages runs much faster, with or without this change. John’s PR made a huge improvement in its run time.

    re-ACK 628227ff8b0409546a0176a39a5491c069749fef (without the extra param)

  22. DrahtBot added the label Needs rebase on Jun 16, 2020
  23. [test] logging and style followups for bloomfilter tests
    -Use peer to refer to mininodes instead of node
    because they are not bitcoind nodes.
    -Use log.debug for logs that give helpful but
    not super necessary information.
    -Adhere to style guidelines (newlines, capitalization).
    e81942d2e1
  24. [test] wait for disconnect_p2ps to be reflected in getpeerinfo
    -Waiting is important to avoid race conditions,
    especially if testing peer info through rpc later.
    -Wait for mininodes to be disconnected only, even
    though it's more complex, because we may still want
    to be connected to test nodes.
    aeb9fb414e
  25. [refactor] use waiting inside disconnect_p2ps
    -Use wait_for_disconnect instead of manual
    wait after calling disconnect_p2ps.
    9a40cfc558
  26. in test/functional/test_framework/test_node.py:557 in 8386ad52e9 outdated
    549@@ -549,11 +550,17 @@ def p2p(self):
    550         assert self.p2ps, self._node_msg("No p2p connection")
    551         return self.p2ps[0]
    552 
    553-    def disconnect_p2ps(self):
    554+    def num_connected_mininodes(self):
    555+        """Return number of test framework p2p connections to the node."""
    556+        return len([peer for peer in self.getpeerinfo() if peer['subver'] == MY_SUBVERSION])
    557+
    558+    def disconnect_p2ps(self, wait_for_disconnect=False):
    


    MarcoFalke commented at 9:52 am on June 16, 2020:
    Also agree with John that this should not be a configurable parameter to keep the complexity down

    glozow commented at 3:19 pm on June 16, 2020:
    Ok! 👍 will update with the rebase since it seems like the performance issue isn’t too bad
  27. glozow force-pushed on Jun 16, 2020
  28. DrahtBot removed the label Needs rebase on Jun 16, 2020
  29. glozow commented at 9:35 pm on June 16, 2020: member

    Rebased and addressed comments. Got rid of wait_for_disconnect 🙂

    Master:

     0TEST                          | STATUS    | DURATION
     1
     2feature_maxuploadtarget.py    |  Passed  | 66 s
     3p2p_blockfilters.py           |  Passed  | 28 s
     4p2p_blocksonly.py             |  Passed  | 9 s
     5p2p_dos_header_tree.py        |  Passed  | 6 s
     6p2p_invalid_locator.py        |  Passed  | 2 s
     7p2p_invalid_messages.py       |  Passed  | 10 s
     8p2p_leak.py                   |  Passed  | 7 s
     9p2p_nobloomfilter_messages.py |  Passed  | 4 s
    10p2p_node_network_limited.py   |  Passed  | 10 s
    11p2p_sendheaders.py            |  Passed  | 17 s
    12p2p_unrequested_blocks.py     |  Passed  | 5 s
    13
    14ALL                           |  Passed  | 164 s (accumulated) 
    15Runtime: 66 s
    

    This branch:

     0TEST                          | STATUS    | DURATION
     1
     2feature_maxuploadtarget.py    |  Passed  | 67 s
     3p2p_blockfilters.py           |  Passed  | 18 s
     4p2p_blocksonly.py             |  Passed  | 9 s
     5p2p_dos_header_tree.py        |  Passed  | 6 s
     6p2p_invalid_locator.py        |  Passed  | 3 s
     7p2p_invalid_messages.py       |  Passed  | 11 s
     8p2p_leak.py                   |  Passed  | 7 s
     9p2p_nobloomfilter_messages.py |  Passed  | 3 s
    10p2p_node_network_limited.py   |  Passed  | 11 s
    11p2p_sendheaders.py            |  Passed  | 15 s
    12p2p_unrequested_blocks.py     |  Passed  | 5 s
    13
    14ALL                           |  Passed  | 155 s (accumulated) 
    15Runtime: 67 s
    
  30. jonatack commented at 8:49 am on June 17, 2020: member
    Code review ACK 9a40cfc from re-reviewing the diff and git range-diff 5cafb46 8386ad5 9a40cfc
  31. MarcoFalke commented at 10:19 am on June 17, 2020: member

    ACK 9a40cfc558b3f7fa4fff1270f969582af17479a5 🐂

    Signature:

     0-----BEGIN PGP SIGNED MESSAGE-----
     1Hash: SHA512
     2
     3ACK 9a40cfc558b3f7fa4fff1270f969582af17479a5 🐂
     4-----BEGIN PGP SIGNATURE-----
     5
     6iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
     7pUi2dgv+OVPePBmXNQ90EcBVrvDet1CHNODyKbSS713qREEOfhwi9KN+2pU3eAcy
     8dLfsOWCEe1QSlT+OXDXmjYnQon7OI4U3+KMIiaPW3wV/jcPMkP6TcrwIRzlDYmt9
     9Kw81TTq6rjKByYnib5MNy4JP/tFBxcw822JxfkqV8lCwWyiPg6LANzP8fIg8kU9L
    10eb8bmDN1nzDrPEHdNrcWJG7byWNFIwCuScCZGXUlWnuDVj+HKU1n6vLN+v5O4Gj+
    11NHYKWvSMa/+rLfyh/d13/f92HYObTnrNuHG4/6Wk4hszXgA16SivFfwrTIeewGMG
    120jcr49IcOAmMxnUiSLCEYba1f/weM4Kt0iDC6ryRoLyYsnEoQ3TJ8tEEQQCOKng1
    13y8VvvHeI3r4Y3A0ELUWNe3qVtySozLDo+zR3dop04iBUD/DIZBOlUNIqZLGYn9Xa
    1422FEWJbQF2EpTJX+vze9Wz9OfhnZTiVJRcqCoTCUcdBqPov9mES+xt0x0il7CsWv
    15FwlNXkKL
    16=60oC
    17-----END PGP SIGNATURE-----
    

    Timestamp of file with hash 60c563acc039d017e59fc85afcc06dea3c4ec236d394ece2f361f3fc7252dfb6 -

  32. MarcoFalke merged this on Jun 17, 2020
  33. MarcoFalke closed this on Jun 17, 2020

  34. MarcoFalke referenced this in commit 67881de0e3 on Jun 24, 2020
  35. glozow deleted the branch on Jul 25, 2020
  36. deadalnix referenced this in commit f1c2bb088d on May 13, 2021
  37. DrahtBot locked this on Feb 15, 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-11-23 18:12 UTC

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