net: reimplement private broadcast as jobs isolated from the node #36628

pull instagibbs wants to merge 16 commits into bitcoin:master from instagibbs:2026-10-pb_reimpl changing 78 files +6078 −3044
  1. instagibbs commented at 4:45 PM on October 23, 2026: member

    Split out of #36608, the overall private broadcast rework to support ease of review for like-for-like feature.

    Differences from #36608 :

    1. No tool support or prep refactors for the tool
    2. No package relay
    3. Slimmed down test profile focusing on protecting aginst IP address linkage and not crashing the node on adversarial inputs (which is a leak in itself)

    With -privatebroadcast, sendrawtransaction queues a job instead of handing the transaction to the connection manager. Each job runs on its own thread, shares no state with the node, and reaches the network only through the Tor proxy: it resolves the chain's DNS seeds, then announces over BIP324 to recipients on a schedule drawn up front, each connection with fresh isolation credentials. Nothing a recipient does, and nothing the node sees of the transaction coming back, changes the plan, and the transaction enters the node's mempool only when it returns from the network. The old private broadcast connections and relay are removed.

    The invariants are specified in doc/design/private-broadcast.md.

    The rest of the tests are in a follow-up to be considered later: https://github.com/instagibbs/bitcoin/tree/2026-10-pb_reimpl_followup

  2. DrahtBot added the label P2P on Oct 23, 2026
  3. DrahtBot commented at 4:45 PM on October 23, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  4. instagibbs force-pushed on Oct 23, 2026
  5. DrahtBot added the label CI failed on Oct 23, 2026
  6. instagibbs force-pushed on Oct 23, 2026
  7. DrahtBot removed the label CI failed on Oct 23, 2026
  8. instagibbs force-pushed on Oct 23, 2026
  9. instagibbs force-pushed on Oct 23, 2026
  10. doc: add the private broadcast specification
    This is the design the following commits implement: what a private
    broadcast job guarantees, the values peers and observers see, and the
    node's interface. Later commits cite its items by number.
    e869d0d529
  11. privbcast: add the parameters and the schedule
    Start src/privbcast, the code that will run the node's private
    broadcast jobs, built into bitcoin_node. params.h holds the values of
    the specification. DrawPlan draws a job's plan from the job's own
    randomness: the schedule, the order of the DNS seeds and the seed of
    the order in which each DNS seed hands out its candidates.
    MIN_DNS_SEEDS is the floor R3 sets for the mainnet seed list, and a
    unit test holds the list to it, so a release that drops seeds below
    what fills a job fails the test rather than leaving opportunities
    empty.
    
    Implements C1 and R3; the values of Parameters.
    8123fbfa93
  12. privbcast: add candidate assignment
    Assign fixes every opportunity's candidate once discovery ends, from
    the plan and the discovery result alone. An endpoint that several
    seeds returned is credited to the seed that comes first in the plan's
    seed order, through the lowest query set that returned it. Each seed
    hands out its candidates in an order drawn at random by a generator of
    its own, seeded from the plan and the seed's place in that order. Slot
    j's first attempt comes from query set j mod 4 whenever a seed it may
    draw from has one, so a single exit supplies at most two first
    attempts. Neither which seed answers first nor the order of a seed's
    answers changes the assignment.
    
    Implements C2, R2, R4, R5a, R5b and R5c.
    2da3f3b367
  13. privbcast: add a byte-driven SOCKS5 client
    A job reaches recipients and resolves DNS seed names through the proxy,
    on streams its event loop drives without blocking. Socks5Client holds
    no socket: it offers the bytes to write, consumes the proxy's replies,
    and leaves bytes past the final reply to the P2P transport. It offers
    only username and password authentication, with the credentials it is
    given or fresh ones. It sends every destination as a name, numeric
    addresses included, and resolves nothing: the node's own connections
    do the same, and Tor's SafeSocks refuses a request that carries only
    an address. Its errors name the proxy's reply with netbase's
    Socks5ErrorString, now exported.
    
    Implements B2 and the Proxy interface section.
    59fa9a570b
  14. privbcast: add the attempt state machine
    An attempt is one connection to a recipient once the proxy connected
    it: a BIP324 session as initiator, the handshake with the job's
    VERSION, one INV, the transaction served at most once, and the PING.
    It holds no socket and reads no clock: the job's loop passes the bytes
    read and the time; each call records what happened (a completed write,
    the connection, bytes received) and then enforces the attempt's
    deadlines, so that nothing is acted on at or after one.
    
    The attempt draws its VERSION and PING nonces from the random context
    it is given, and takes its BIP324 key, ellswift entropy and garbage
    from a key source. Without one, as in production, V2Transport draws
    them from the strong RNG. The fuzz target injects a source
    that draws from its seeded context, so that an attempt's bytes are a
    function of that context and of what the peer sent.
    
    Implements E1-E8, A2, B4, D3 and the attempt part of D2.
    14329acbf1
  15. privbcast: add proxy streams and discovery
    A job reaches the network only through streams to the SOCKS5 proxy,
    which its event loop drives without blocking. ProxyStream connects to
    the proxy by address or unix socket path, resolving nothing, and runs
    the exchange of its Socks5Client; each stage, the connect and each
    step of the exchange, times out on the steady clock. Once the proxy
    has connected the stream, its socket carries the destination's bytes.
    
    Discovery launches every RESOLVE query at job start, each on a stream
    of its own, in four query sets: each set has credentials of its own
    and one query of every seed, so Tor builds four circuits rather than
    one per query, and each seed is still asked through four exits.
    Discovery ends when every query has ended or when the discovery window
    does, whatever is still pending. It keeps the answers that are public
    routable IPv4 or IPv6 addresses, with the chain's default port, and
    counts the others; its result gives each seed's answers by the query
    set they came through, whatever order they arrived in. An exception
    thrown while it acts on one query ends only that query, as a failed
    stream would.
    
    Implements B1, B2, C3, D1, R1, R2 and C7 for the queries.
    74e527f169
  16. privbcast: add the job
    A job is one event loop on its caller's thread. Step() runs the
    deadlines and the dials that are due, waits on every open stream until
    the next deadline, and does the I/O; Run() steps to the end. The plan
    is drawn when the job is made, before any socket exists, and discovery
    starts with the first step. When discovery ends the assignment is
    fixed, and each slot dials its opportunities at their scheduled times,
    one attempt at a time, until one reaches the announcement point or
    none is left. The slots share only the loop, so a stalled stream holds
    up no other slot, and the cancel flag ends everything at the next step.
    
    An exception thrown while the loop acts on one attempt ends only that
    attempt, as a failed stream would. One thrown anywhere else in a step
    fails the job: it stops as a cancelled job does, and its report gives
    the error.
    
    The report mirrors Interface/Report, every time an offset from job
    start on the plan clock. Snapshot() gives the progress the node is to
    show, from any thread.
    
    Implements A1, A3, A4, C1-C8, D1, D2, H1-H3 and the report interface.
    f54a6358c7
  17. test: extend the harness for private broadcast jobs
    The SOCKS5 server gains Tor's RESOLVE command, reply delays for a slow
    or stalled proxy, a mode that offers only unauthenticated access and a
    mode that behaves as an ordinary non-Tor proxy. A client that hangs up
    before its greeting, as a cancelled job's streams can, and a connection
    that Windows reports as aborted count as closed, not as errors. The P2P
    network thread judges a failed protocol by its own reconnect flag, so
    that several listeners can share one loop. test_framework/privbcast.py
    holds the release's parameters, a scripted recipient, and the checks of
    a job's wire profile, schedule, report summary and proxy credentials.
    
    Prepares the functional tests of the node's jobs.
    6890e531b7
  18. node: add the private broadcast job queue
    The node runs each private broadcast as a privbcast::Job on a thread of
    its own. PrivbcastQueue queues the submitted transactions, starts and
    cancels the jobs from one scheduler thread, and keeps in memory the
    records that getprivatebroadcastinfo and abortprivatebroadcast are to
    show. It reaches the node only through callbacks for networking, the
    onion proxy and the mempool, and takes the job runner as a parameter,
    so that tests can drive it with stub jobs and mocked clocks. A job that
    failed, or whose runner threw, ends aborted with the error in its
    record.
    
    The scheduler's start rule is a pure function, StartPolicy::Decide(),
    which each pass of the scheduler applies.
    
    Implements N3-N7, N9, N10, the node's D2 and D3, U3. Not yet wired:
    the next commit switches -privatebroadcast to it.
    f5dfaa2836
  19. node: run private broadcast as jobs
    With -privatebroadcast, sendrawtransaction now queues a job on the
    node's PrivbcastQueue instead of handing the transaction to the
    connection manager, getprivatebroadcastinfo lists the jobs with their
    progress and reports, and abortprivatebroadcast aborts them. Startup
    requires onion to be able to become reachable, refuses the regtest
    seed and time divisor overrides on other chains, -signetseednode,
    -signetchallenge, and off regtest a non-zero -mocktime, and refuses to
    start rather than trim connections when the jobs' file descriptors are
    not available. -connect and -proxyrandomize no longer matter.
    
    -privatebroadcasttimedivisor, regtest only, divides every duration of
    a job's plan, so that functional tests can run jobs in real time.
    
    The old private broadcast connections and relay stay in the tree, no
    longer fed, until the commits that remove them.
    
    Implements N1, N2, N8, N10, N11, U1, U2 and Interface/Node; replaces
    the old implementation's tests with the specification's.
    ace5bed91e
  20. test: run private broadcast jobs end to end through the node
    p2p_private_broadcast.py drives jobs on a mocked clock, which moves a
    job's schedule but not the steady clock that times its proxy exchanges
    and connections. These tests run jobs in real time instead, shortened
    by -privatebroadcasttimedivisor: a recipient that floods a job is cut
    off at the receive cap, and a proxy that refuses authentication or is
    not Tor gets nothing to carry.
    
    test_framework/privbcast.py gains the harness: it restarts the node
    behind each scenario's SOCKS5 server with its seeds and time divisor,
    and reads the job's report from getprivatebroadcastinfo.
    ffe5d1c6e2
  21. net: remove the old private broadcast relay
    Since -privatebroadcast runs jobs, nothing hands PeerManager a
    transaction for private broadcast any more. Remove its queue
    (src/private_broadcast.{h,cpp}) with the methods, the retry task and
    the private_broadcast option that served it, and its handling of
    private broadcast connections: the fixed VERSION, the INV after
    VERACK, the GETDATA and PONG handling, the incoming message filter,
    the lifetime limit and the exemption from discouragement. The unit and
    fuzz tests of that code go with it, including the net_tests case for
    the addrman exception and the denialofservice_tests cases for the
    discouragement exemption.
    
    Delete-only: besides whole lines, conditions and arguments are cut out
    of lines, and the lines of a removed branch's alternative move out one
    indentation level. git show -w --word-diff-regex='[A-Za-z0-9_]+|[^[:space:]]'
    shows deletions only, apart from the size of the node array in
    denialofservice_tests, which goes back from 5 to 3.
    f988c2b278
  22. net: remove the old private broadcast connections
    With the relay gone, nothing asks CConnman for private broadcast
    connections. Remove the connection type, the thread that opened such
    connections with its counters and choice of network, and the places
    that treated them specially: the incoming nonce check, the fresh I2P
    session, the guard against direct connections, the outbound message
    filter, the fixed protocol version, the exception in disconnection by
    subnet and their term in the orphanage's static_assert. The type's
    names go from getpeerinfo's help, bitcoin-cli -netinfo and the GUI. The
    proxy override of ConnectNode() and OpenNetworkConnection(), which only
    that thread set, goes too, with its copy in CNode and in the
    reconnection record and the fuzz input for it.
    
    Delete-only: besides whole lines, conditions, arguments and list
    separators are cut out of lines, what followed a cut argument joins
    its line, and one line moves out an indentation level with the if
    around it. git show -w --word-diff-regex='[A-Za-z0-9_]+|[^[:space:]]'
    shows deletions only.
    d7c7ebf49c
  23. refactor: drop the trivial AdvertisedVersion()
    Without private broadcast connections, a node advertises
    PROTOCOL_VERSION on every connection, and CNode::AdvertisedVersion()
    only returns it. Use the constant again where the method was called.
    5d3f26e429
  24. doc: describe bitcoind -privatebroadcast
    Add the user guide to private broadcast: which Tor proxy the jobs use,
    how sendrawtransaction queues them and when they start, what is not
    queued again, how to follow and abort jobs, what disabling networking
    and shutdown do, which node settings do not apply, what the default
    log shows, the file descriptors the jobs need, and the limits of what
    a job protects.
    565bbd1b3f
  25. doc: release notes for private broadcast jobs 19f9cd7c4d
  26. instagibbs force-pushed on Oct 24, 2026

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-10-24 07:51 UTC

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