eBPF Linux tracepoints #19866

pull jb55 wants to merge 2 commits into bitcoin:master from jb55:usdt-probes changing 3 files +64 −0
  1. jb55 commented at 3:54 am on September 4, 2020: member

    Instead of writing ad-hoc logging everywhere (eg: #19509), we can take advantage of linux user static defined traces, aka. USDTs ( not the stablecoin :sweat_smile: )

    The linux kernel can hook into these tracepoints at runtime, but otherwise they have little to no performance impact. Traces can pass data which can be printed externally via tools such as bpftrace. For example, here’s one that prints incoming and outgoing network messages:

    Examples

    Network Messages

     0#!/usr/bin/env bpftrace
     1
     2BEGIN
     3{
     4  printf("bitcoin net msgs\n"); [@start](/bitcoin-bitcoin/contributor/start/) = nsecs;
     5}
     6
     7usdt:./src/bitcoind:net:push_message
     8{
     9  $ip = str(arg0);
    10  $peer_id = (int64)arg1;
    11  $command = str(arg2);
    12  $data_len = arg3;
    13  $data = buf(arg3,arg4);
    14  $t = (nsecs - [@start](/bitcoin-bitcoin/contributor/start/)) / 100000;
    15
    16  printf("%zu outbound %s %s %zu %d %r\n", $t, $command, $ip, $peer_id, $data_len, $data); [@outbound](/bitcoin-bitcoin/contributor/outbound/)[$command]++;
    17}
    18
    19usdt:./src/bitcoind:net:process_message
    20{
    21  $ip = str(arg0);
    22  $peer_id = (int64)arg1;
    23  $command = str(arg2);
    24  $data_len = arg3;
    25  $data = buf(arg3,arg4);
    26  $t = (nsecs - [@start](/bitcoin-bitcoin/contributor/start/)) / 100000;
    27
    28  printf("%zu inbound %s %s %zu %d %r\n", $t, $command, $ip, $peer_id, $data_len, $data); [@inbound](/bitcoin-bitcoin/contributor/inbound/)[$ip, $command]++;
    29}
    
    $ sudo bpftrace netmsg.bt
    

    output: https://jb55.com/s/b11312484b601fb3.txt

    if you look at the bottom of the output you can see a histogram of all the messages grouped by message type and IP. nice!

    IBD Benchmarking

     0#!/usr/bin/env bpftrace
     1BEGIN
     2{
     3  printf("IBD to 500,000 bench\n");
     4}
     5
     6usdt:./src/bitcoind:CChainState:ConnectBlock
     7{
     8  $height = (uint32)arg0;
     9
    10  if ($height == 1) {
    11    printf("block 1 found, starting benchmark\n"); [@start](/bitcoin-bitcoin/contributor/start/) = nsecs;
    12  }
    13
    14  if ($height >= 500000) { [@end](/bitcoin-bitcoin/contributor/end/) = nsecs; [@duration](/bitcoin-bitcoin/contributor/duration/) = [@end](/bitcoin-bitcoin/contributor/end/) - [@start](/bitcoin-bitcoin/contributor/start/);
    15    exit();
    16  }
    17}
    18
    19END {
    20  printf("duration %d ms\n", [@duration](/bitcoin-bitcoin/contributor/duration/) / 1000000)
    21}
    

    This one hooks into ConnectBlock and prints the IBD time to height 500,000 starting from the first call to ConnectBlock

    Userspace static tracepoints give lots of flexibility without invasive logging code. It’s also more flexible than ad-hoc logging code, allowing you to instrument many different aspects of the system without having to enable per-subsystem logging.

    Other ideas: tracepoints for lock contention, threads, what else?

    Let me know what ya’ll think and if this is worth adding to bitcoin.

    TODO

    • docs?
    • Integrate systemtap-std-dev/libsystemtap into build (provides the <sys/sdt.h> header)
    • dtrace macos support? (is this still a thing?) going to focus on linux for now
  2. DrahtBot added the label P2P on Sep 4, 2020
  3. DrahtBot added the label Validation on Sep 4, 2020
  4. jb55 force-pushed on Sep 4, 2020
  5. fanquake removed the label P2P on Sep 4, 2020
  6. fanquake removed the label Validation on Sep 4, 2020
  7. fanquake added the label Utils/log/libs on Sep 4, 2020
  8. jb55 force-pushed on Sep 4, 2020
  9. in src/trace.h:3 in 1b4286c6ac outdated
    0@@ -0,0 +1,27 @@
    1+
    2+
    3+#ifndef BITCOIN_TRACE_H
    


    laanwj commented at 6:29 am on September 4, 2020:

    nit:

    • copyright header
    • redundant empty lines here
    • also i think we want this file under src/util
  10. in src/net_processing.cpp:3897 in 1b4286c6ac outdated
    3890@@ -3890,6 +3891,12 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter
    3891     }
    3892     CNetMessage& msg(msgs.front());
    3893 
    3894+    TRACE4(PeerLogicValidation, ProcessMessages,
    3895+           pfrom->addr.ToString().c_str(),
    3896+           msg.m_command.c_str(),
    3897+           MakeUCharSpan(msg.m_recv).data(),
    


    laanwj commented at 6:29 am on September 4, 2020:
    Why create a span just to take its data field?

    jb55 commented at 2:31 pm on September 4, 2020:
    Think I copied this over from the peer logging PR and didn’t simplify. Will fix.
  11. laanwj commented at 6:37 am on September 4, 2020: member
    Concept ACK, thanks for working on this, I think this is a great way to have more visibility into internal metrics (for diagnosing issues, and statistics), without intrusive changes, or having to hand-roll all kind of custom tooling.
  12. practicalswift commented at 6:53 am on September 4, 2020: contributor
    Concept ACK: more tooling for allowing visibility into internals for power users is good. I don’t think this Linux only improvement removes the need for #19509 at all: I would use both in different contexts :)
  13. DrahtBot commented at 8:26 am on September 4, 2020: member

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

    Conflicts

    No conflicts as of last run.

  14. jonatack commented at 2:53 pm on September 4, 2020: member
    Concept ACK, will review/test.
  15. jb55 commented at 2:54 pm on September 4, 2020: member
    @jonatack thanks! you need systemtap-sdt-dev if you’re on debian-like which I still need to add to the build. This just provides the sdt header file.
  16. jb55 renamed this:
    RFC: eBPF Linux tracepoints
    eBPF Linux tracepoints
    on Sep 4, 2020
  17. jb55 marked this as a draft on Sep 4, 2020
  18. jonatack commented at 2:58 pm on September 4, 2020: member

    @jonatack thanks! you need systemtap-sdt-dev if you’re on debian-like which I still need to add to the build. This just provides the sdt header file.

    Thanks for the heads-up! which probably saved me some time even though you mentioned it in the PR description. Done: sudo apt install systemtap-sdt-dev

  19. jb55 force-pushed on Sep 4, 2020
  20. in src/util/trace.h:8 in 629d119f90 outdated
    0@@ -0,0 +1,29 @@
    1+// Copyright (c) 2020 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+#ifndef BITCOIN_UTIL_TRACE_H
    6+#define BITCOIN_UTIL_TRACE_H
    7+
    8+#if /* !defined(ENABLE_TRACING) || */ defined(WIN32)
    


    laanwj commented at 5:44 pm on September 4, 2020:
    ENABLE_TRACING is probably intended be provided a configure option (e.g. --enable-ebpf) later on?

    jb55 commented at 5:48 pm on September 4, 2020:
    correct. I think having runtime probes would be neat to have in release builds, then you could tap into them in production. This shouldn’t be a problem since traces are basically no-ops if there are no probes attached. This is what linux does of course. perhaps it would be a thing you could explicitly disable if you wanted to, or if configure couldn’t find the sdt header.

    jb55 commented at 5:51 pm on September 4, 2020:
    although it’s not technically a no-op if the macro arg does some amount of work, like serializing an ip address to a string. so trace invocations shouldn’t try to serialize anything or do heavy work. you can always write parsers on the bpf side.

    laanwj commented at 6:02 pm on September 4, 2020:

    correct. I think having runtime probes would be neat to have in release builds,

    Agree, I’m fine with enabling it by default when the prerequisites are installed (and the platform is Linux),

    although it’s not technically a no-op if the macro arg does some amount of work,

    Yes, this is something to be careful with. The LogPrintfmacro avoids this by evaluating its arguments only when debugging is enabled. But I think in most cases it’s not a problem, we’ll only want to trace values that are readily available anyway.


    jb55 commented at 5:12 pm on September 5, 2020:
    I’ve added a commit that detects sys/sdt.h and enables tracing if it finds it
  21. laanwj commented at 6:21 pm on September 4, 2020: member

    Could list the probe points in gdb:

    0(gdb) info probes
    1Type Provider            Name            Where              Semaphore Object                                     
    2stap CChainState         ConnectBlock    0x0000000000294636           /…/bitcoin/src/bitcoind 
    3stap CConnman            PushMessage     0x000000000009ab40           /…/bitcoin/src/bitcoind 
    4stap PeerLogicValidation ProcessMessages 0x00000000000d1387           /…/bitcoin/bitcoin/src/bitcoind 
    

    $ sudo bpftrace netmsg.bt -o nettrace.txt -c “./src/bitcoind -datadir=/run/bitcoin "

    Is it really necessary to run this as root? I read @eklitzke’s blog post back in the day and he doesn’t use it there. But it looks like he uses a different tool, stap instead of bpftrace.

    Edit: yes it is necessary to run bpftrace as root,because it uses some privileged kernel functionality. It’s probably not necessary to run bitcoind as root though.

  22. laanwj commented at 6:41 pm on September 4, 2020: member

    I’m trying the first example, but it complains about a missing function buf. Any idea? Maybe too old bptrace version?

    0# uname -r
    15.4.0-42-generic
    2# bpftrace --version
    3bpftrace v0.9.4
    4# bpftrace -p $(cat ~user/.bitcoin/bitcoind.pid) ../network-messages.bt 
    5../network-messages.bt:13:11-14: ERROR: Unknown function: buf
    6  $data = buf(arg1,arg2);
    7          ~~~
    
  23. jb55 commented at 6:58 pm on September 4, 2020: member

    “Wladimir J. van der Laan” notifications@github.com writes:

    I’m trying the first example, but it complains about a missing function buf. Any idea? Maybe too old bptrace version?

    yeah buf is a new thing from bpftrace 0.11 and bcc 0.16.0, you can just remove that if you’re testing.

    bpftrace is a pretty high level thing, you can write lower level bpf programs with bcc, I haven’t tried that yet but it looks like its more powerful. bpftrace is great for simple stuff though.

  24. jb55 commented at 7:04 pm on September 4, 2020: member

    Edit: yes it is necessary to run bpftrace as root,because it uses some privileged kernel functionality. It’s probably not necessary to run bitcoind as root though.

    right, you can run bitcoin without root and then attach to it with bpftrace afterwards.

  25. jb55 commented at 7:08 pm on September 4, 2020: member

    Could list the probe points in gdb

    I wasn’t sure how we should name the probes, right now I just have the mapped to class::method but perhaps they could be more abstract like validation:connect_block, net:push_message, net:process_message ?

  26. laanwj commented at 7:24 pm on September 4, 2020: member

    yeah buf is a new thing from bpftrace 0.11 and bcc 0.16.0, you can just remove that if you’re testing.

    Good to know! I’ve built bpftrace from git and it works now (which is version bpftrace v0.11.0, a lot newer!), thanks.

    I wasn’t sure how we should name the probes, right now I just have the mapped to class::method but perhaps they could be more abstract like validation:connect_block, net:push_message, net:process_message ?

    I think I prefer that. It has the probe point name describe more accurately the action taken and data that is traced. We’ll want to list and document these at some point.

    Other ideas: tracepoints for lock contention, threads, what else?

    I think these would be good to have too:

    • Peer connected
    • Peer disconnected

    BTW: is it possible to do binary logging with bpftrace or bcc? (e.g. to provide a similar output to #19509). Or is it always text?

  27. in src/net_processing.cpp:3894 in 629d119f90 outdated
    3890@@ -3890,6 +3891,12 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter
    3891     }
    3892     CNetMessage& msg(msgs.front());
    3893 
    3894+    TRACE4(PeerLogicValidation, ProcessMessages,
    


    laanwj commented at 7:30 pm on September 4, 2020:
    I think we’ll want to add the peer id as well here (same for outgoing)

    jb55 commented at 5:13 pm on September 5, 2020:
    done
  28. laanwj commented at 12:13 pm on September 5, 2020: member
    For some extra inspiration with regard to tracepoints, we might want to look at the statoshi diff. (I think we can convert most of the statsd use to tracepoints, or sets of two tracepoints in the case of timing ones)
  29. jb55 force-pushed on Sep 5, 2020
  30. jb55 force-pushed on Sep 5, 2020
  31. jb55 marked this as ready for review on Sep 5, 2020
  32. jb55 commented at 5:29 pm on September 5, 2020: member

    I think I prefer that. It has the probe point name describe more accurately the action taken and data that is traced.

    done, renamed to net:process_message,push_message and net:connect_block

    BTW: is it possible to do binary logging with bpftrace or bcc? (e.g. to provide a similar output to #19509). Or is it always text?

    I haven’t been able to figure that out with bpftrace but I’m going to try put together a bcc examples of this. Perhaps we should have a docs/tracing.md that documents the tracepoints with some examples? We could also start collecting bpftrace or bcc scripts somewhere, not sure what the best place for those would be. For now a bunch of bpftrace one liner examples in some docs would be neat.

  33. laanwj commented at 12:30 pm on September 6, 2020: member

    I haven’t been able to figure that out with bpftrace but I’m going to try put together a bcc examples of this. Perhaps we should have a docs/tracing.md that documents the tracepoints with some examples?

    Great idea. It definitely makes sense to document this well so that people know how to use it. eBPF use with applications is not that common yet, we can assume people to just know.

    Having a directory or repository of ready-to-use scripts for common statistics ways to dump/analyse data makes sense too. I also think the Python binary log analysis tooling from #19509 can be useful, even if we don’t merge that dumping method.

  34. jb55 commented at 10:07 pm on September 8, 2020: member

    So this is pretty incredible: We may only need to use static traces in a few places. For things like ConnectBlock we can use uprobes (no code changes required!):

    0$ objdump -CtT src/bitcoind | grep ConnectBlock
    100000000002db020 g     F .text	0000000000002a66              CChainState::ConnectBlock(CBlock const&, BlockValidationState&, CBlockIndex*, CCoinsViewCache&, CChainParams const&, bool)
    
    0$ sudo bpftrace -e '
    1
    2struct CBlockIndex { u8 *hash; struct CBlockIndex *pprev; struct CBlockIndex *pskip; int height; };
    3
    4uprobe:./src/bitcoind:"CChainState::ConnectBlock(CBlock const&, BlockValidationState&, CBlockIndex*, CCoinsViewCache&, CChainParams const&, bool)" 
    5{ 
    6  printf("connect block %d\n", ((struct CBlockIndex *)arg3)->height); 
    7}
    8'
    
    0connect block 7796
    1connect block 7797
    2connect block 7798
    3connect block 7799
    4connect block 7800
    5connect block 7801
    6...
    

    🤯

  35. jb55 marked this as a draft on Sep 9, 2020
  36. laanwj added this to the milestone 22.0 on Oct 27, 2020
  37. laanwj commented at 10:03 am on October 27, 2020: member

    That’s neat!

    But I think the nice thing about documented static probes is that it relies less on the specific code structure and more on conceptual events. This would allow for e.g. collecting statistics (things like statoshi) with a (semi) stable interface, without constantly rebasing.

  38. jb55 commented at 4:28 pm on October 27, 2020: member

    That’s neat!

    But I think the nice thing about documented static probes is that it relies less on the specific code structure and more on conceptual events. This would allow for e.g. collecting statistics (things like statoshi) with a (semi) stable interface, without constantly rebasing.

    yup agreed, uprobes are a neat trick, but semi-stable static probes that you can depend on in scripts would be much more useful to build on top of. uprobes are a nice supplement/fallback in case a static probe is not available.

  39. build: detect sys/sdt.h for eBPF tracing 933ab8a720
  40. tracing: add tracing framework
    Signed-off-by: William Casarin <jb55@jb55.com>
    22eb7930a6
  41. laanwj commented at 11:01 am on December 7, 2020: member
  42. in src/net.cpp:2980 in 83171997dc outdated
    2838@@ -2838,7 +2839,10 @@ bool CConnman::NodeFullyConnected(const CNode* pnode)
    2839 void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
    2840 {
    2841     size_t nMessageSize = msg.data.size();
    2842-    LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n",  SanitizeString(msg.m_type), nMessageSize, pnode->GetId());
    2843+    auto sanitizedType = SanitizeString(msg.m_type);
    2844+
    2845+    LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", sanitizedType, nMessageSize, pnode->GetId());
    2846+    TRACE4(net, push_message, sanitizedType.c_str(), msg.data, nMessageSize, pnode->GetId());
    


    laanwj commented at 11:02 am on December 7, 2020:

    I’m not sure we want to pass the sanitized packet type to the probe here or simply the raw data.

    Edit: Nah, I guess anyone that wants the raw data already has a pointer to the packet, which is passed too. It’s extra information.

  43. laanwj commented at 11:43 am on December 7, 2020: member
  44. jb55 force-pushed on Dec 7, 2020
  45. jb55 commented at 4:33 pm on December 7, 2020: member

    @laanwj writes:

    I’m not sure we want to pass the sanitized packet type to the probe here or simply the raw data.

    I’m not super familiar with the network packet format, is this easy to pull from the raw data? If so we probably don’t need it. I wasn’t really happy with the current binary capabilities of bpftrace but I imagine this will get better over time once the tooling matures.

    Rebased version here: https://github.com/laanwj/bitcoin/tree/usdt-probes

    Thanks for moving this along. I reviewed the range diff and looks ok.

    I’ve also made a start with documentation doc/tracing.md: https://github.com/laanwj/bitcoin/blob/35cad1adbf2bdf98d437e499ea10904d967b0083/doc/tracing.md

    Amazing, thank you!

  46. laanwj commented at 3:56 pm on December 8, 2020: member

    I’m not super familiar with the network packet format, is this easy to pull from the raw data?

    Yea the header is very simple, given that eBPF is used for complex packet fltering and pre-processing setups I’m a bit surprised the binary handling capabilities are still a weak point but it should be able to handle it:

    00…3    u8[4]  Start characters
    14…15   u8[20] Type (zero padded text)
    216…19  u32    Size in bytes
    320     u8     Checksum
    
  47. in doc/tracing.md:31 in d283230e5e outdated
    26+
    27+The current tracepoints are listed here.
    28+
    29+### net
    30+
    31+- `net:process_message(char* addr_str, int node_id, char* msgtype_str, uint8_t* rx_data, size_t rx_size)`
    


    laanwj commented at 4:00 pm on December 8, 2020:

    I would prefer to rearrange the arguments to push_message to be in the same order as process_message (eg move node_id up front before msgtype_str):

    0net:push_message(int node_id, char* msgtype_str, uint8_t* tx_data, size_t tx_size)
    
  48. laanwj commented at 4:14 pm on December 8, 2020: member
    I think this is ready to go out of draft and get review for the build system changes. It adds the basic infrastructure for tracing. Further tracepoints and examples can be added in later PRs.
  49. jb55 commented at 11:56 pm on December 8, 2020: member

    I think this is ready to go out of draft and get review for the build system changes. It adds the basic infrastructure for tracing. Further tracepoints and examples can be added in later PRs.

    ok I’ll push just those for now

  50. jb55 force-pushed on Dec 9, 2020
  51. jb55 marked this as ready for review on Dec 9, 2020
  52. jb55 commented at 0:05 am on December 9, 2020: member

    Given that eBPF is used for complex packet fltering and pre-processing setups I’m a bit surprised the binary handling capabilities are still a weak point

    This was really a comment on the bpftrace tool, not the capabilities of ebpf itself. You can always write a C/BCC file and load the low level probes that way, but I’m lazy and haven’t tried that yet.

  53. RandyMcMillan commented at 1:55 am on December 9, 2020: contributor
    I remember when you posted about this on Twitter. Exciting… Concept ACK
  54. laanwj commented at 12:36 pm on December 10, 2020: member

    You can always write a C/BCC file and load the low level probes that way, but I’m lazy and haven’t tried that yet.

    Yes, that would be interesting. But yes, we can experiment with that later, bpftrace is the most user friendly way to use this so it’s good to document it first.

    Tested ACK 22eb7930a6ae021438aa0b8e750170534944f296

  55. 0xB10C commented at 11:00 pm on December 29, 2020: member

    Tested ACK 22eb7930a6ae021438aa0b8e750170534944f296

    Played around with the IBD example and added a TRACE1(validation, mempool_added, fee) to MemPoolAccept::AcceptSingleTransaction to log the fee of incoming transactions via bpftrace. I agree with the earlier discussion on enabling it by default if prerequisites are installed.

    I did try to extend the IBD example to log the hash of the connected block, but haven’t had any luck getting this to work with bpftrace yet. Might try with bcc later.

  56. jb55 commented at 11:57 am on December 30, 2020: member

    0xB10C notifications@github.com writes:

    I did try to extend the IBD example to log the hash of the connected block, but haven’t had any luck getting this to work with bpftrace yet. Might try with bcc later.

    I had the same experience. perhaps it will be easier once bpftrace has better tooling and more control around printing arbitrary data. This feature was only added recently:

    https://github.com/iovisor/bpftrace/pull/1107

  57. 0xB10C commented at 12:02 pm on December 31, 2020: member

    Example bcc python script for block-connected printing the block height and hash via USDT probes:

     0from __future__ import print_function
     1from bcc import BPF, USDT
     2import sys
     3from datetime import datetime
     4
     5if len(sys.argv) < 2:
     6    print("USAGE:", sys.argv[0], "path/to/bitcoind")
     7    exit()
     8path = sys.argv[1]
     9debug = False
    10
    11bpf_text = """
    12#include <uapi/linux/ptrace.h>
    13struct connect_block_t
    14{
    15    u32 height;
    16    u8 hash[32];
    17};
    18BPF_PERF_OUTPUT(connected_blocks);
    19int do_trace(struct pt_regs *ctx) {
    20    struct connect_block_t cb = {};
    21    bpf_usdt_readarg(1, ctx, &cb.height);
    22    bpf_usdt_readarg_p(2, ctx, &cb.hash, sizeof(cb.hash));
    23    connected_blocks.perf_submit(ctx, &cb, sizeof(cb));
    24    return 0;
    25};
    26"""
    27
    28u = USDT(path=str(path))
    29u.enable_probe(probe="connect_block", fn_name="do_trace")
    30
    31if debug:
    32    print(u.get_text())
    33
    34b = BPF(text=bpf_text, usdt_contexts=[u])
    35
    36def print_connected_blocks(cpu, data, size):
    37    event = b["connected_blocks"].event(data)
    38    height = event.height
    39    hash_bytes = bytearray(event.hash)
    40    hash_bytes.reverse()
    41    hash = hash_bytes.hex()
    42    print(datetime.now(), "height:", height, "hash:", hash)
    43
    44b["connected_blocks"].open_perf_buffer(print_connected_blocks)
    45
    46while 1:
    47    try:
    48        b.perf_buffer_poll()
    49    except KeyboardInterrupt:
    50        exit()
    

    Output on testnet:

    02020-12-31 12:05:40.634456 height: 1901575 hash: 0000000000000012e5bad3eb72856bc8cee92b4da47c7d0ee49db1463345a1f8
    12020-12-31 12:10:11.215210 height: 1901576 hash: 00000000000000107e1cef35dc0557a49f5642b0b9b289ebc1cee454797ed1c3
    22020-12-31 12:14:00.349253 height: 1901577 hash: 0000000000000004c7abf45ba560c28e7bfa39c19e28284f8a640e33ee4069d1
    32020-12-31 12:17:02.398484 height: 1901578 hash: 00000000000000027b2f88e8cc4e4b0f8a7ad9d0142f282d272827201875e7c4
    42020-12-31 12:17:17.796756 height: 1901579 hash: 000000000000001a64e5d01f08c3183165fa2d8225d881081c4b80bb1ffba5b8
    52020-12-31 12:18:40.852506 height: 1901580 hash: 0000000000000010da1398d31de9d46eac62caf2a2c4ae1f7b91041ba612a0ae
    

    or gist

  58. laanwj merged this on Jan 18, 2021
  59. laanwj closed this on Jan 18, 2021

  60. laanwj referenced this in commit 9c2658170e on Jan 18, 2021
  61. laanwj referenced this in commit 3accbc906a on Jan 18, 2021
  62. sidhujag referenced this in commit d35db2baaa on Jan 20, 2021
  63. laanwj referenced this in commit 61cefde7a7 on Jul 27, 2021
  64. sidhujag referenced this in commit 1f479d9c1a on Jul 28, 2021
  65. DrahtBot locked this on Aug 18, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-18 15:12 UTC

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