Reimplement private broadcast as node state-less jobs + package relay support #36608

pull instagibbs wants to merge 35 commits into bitcoin:master from instagibbs:privbcast-4-package changing 121 files +17211 −4442
  1. instagibbs commented at 2:47 PM on October 22, 2026: member

    Goals

    Goal 1: Remove reliance on, and probes about, internal node state from private broadcast

    Goal 2: Specify observable behavior to aid reimplementation and reduce profile fingerprints across versions

    Layout

    Four parts, each starting with an empty marker commit:

    1. support: moves shared code into the common library; no behavior change
    2. bitcoin-privbcast: the specification and the standalone tool
    3. bitcoind -privatebroadcast as jobs: the node runs the tool's job; the old relay and connections are removed
    4. package mode: one parent and its child. Its validation commits (saturating fee sum, test-accept as submission, self-paying parent) serve only this part.

    For the v33 release, I think 1 through 3 should be the goal, but the roadmap could be trimmed down to remove tool-only code if the conceptual support for the standalone tool is not there.

    The tool itself introduces about 600 loc of logic, and also necessitates some of the prep move-only commits putting things in common. The vast majority is the shared Job machinery.

    Node-less state

    The primary failure mode of the in-master private broadcast is that it relies on node state for operation, and this has led to a number of "careful coding" fixes:

    1. The v2->v1 retry connected directly, without the proxy: #35319 (merged in #35410)
    2. Discouraging a misbehaving peer gave a clearnet oracle: #36312
    3. Eager job completion via mempool inspection: #36277
    4. Private broadcast connections wrote to addrman: #35032

    There are many other sources of fingerprints as well, some harder than others to exploit, and to fix. The solution: remove reliance on node state entirely to drive attempts. The spec states the goals as:

    • A job reveals neither the sender's IP address, onion address nor node identity to any party.
    • A hostile recipient, DNS seed or Tor exit learns no more than an honest one. It can only affect delivery.

    Jobs, slots and opportunities

    • Job: one broadcast of one transaction, from submission to its report. A job spends its first 12 s finding candidates (IPv4/IPv6 addresses from the DNS seeds, resolved through Tor exits), then delivers. It runs for at most 10 min.
    • Slot: one of a job's 6 independent delivery targets. Each slot tries to announce the transaction to one peer and serve it when asked. Slots 0–2 open when delivery starts; slot 3 opens 35–180 s later; slots 4–5 open 185–240 s later, at least 5 s apart. Each of these times is drawn at random within its range.
    • Opportunity: one of a slot's 4 scheduled connection times: a first attempt and up to 3 fallbacks, each 50–60 s after the previous. A fallback runs only if the slot's previous attempt failed before announcing; once a slot announces, its remaining opportunities are dropped.

    The times are drawn at job start, and each opportunity's candidate is fixed when discovery ends, before any peer is contacted. Nothing a peer does moves them (C1–C4). A job makes at most 24 connections, at most 6 of them at once.

    Differences at a glance

    master this PR
    Where it runs Inside bitcoind only bitcoind -privatebroadcast, or the standalone bitcoin-privbcast with no node; the same job either way (U3), with a JSON report of every connection
    Recipients addrman.Select() over onion, I2P, and IPv4/IPv6 through the proxy; networks follow -onlynet DNS seed names resolved through Tor exits (RESOLVE), IPv4/IPv6 only; no node state used (R1, R6)
    Connections CConnman peers of type PRIVATE_BROADCAST, visible in getpeerinfo The job's own proxy streams, each with fresh SOCKS credentials, so each gets its own circuit; not peers of the node
    Plan 3 connections, then more whenever the tx is stale (5 min, then 1 min) until it comes back from the network 6 slots of 4 opportunities, all scheduled at job start; no retries; at most 10 min
    Transport v2 when addrman advertises it, otherwise v1, with a v2→v1 retry BIP324 v2 only, with one constant VERSION per release (B4, E1)
    Node settings Shape which networks and peers are used Peer-selection settings don't apply to jobs (U2)
    Packages — submitpackage with one parent and one child: announce the child, serve the parent on request

    No addrman

    Under master, the node's private broadcast destinations are drawn from the node's addrman. Other parties write to that table through addr gossip, which makes it a unique fingerprint vector, and inherently attacker influenced. This rules out drawing recipients from anything the node or its peers can write to.

    Its replacement is querying DNS seeds in a predetermined way under a static profile. This is inherently less robust than the p2p network in terms of liveness, but leaves no fingerprint within a release. The marginal fingerprint is the set of DNS seeds per release, which affects who is queried and exactly how. Each RESOLVE returns one address and exits cache answers, so each seed is asked through several exits: 4 Tor circuits, each querying all 7 seeds, for up to 28 results, which are assigned to the 6x4 opportunities before any dialing happens. A circuit per query, 28 in all, kept Tor busy enough that overlapping discoveries lost answers.

    If the list shrinks, grows, or is modified, the release is revealed to the Tor exits carrying the queries, which see every seed name queried. A DNS seed learns only that it is on the list, and only when the exit has no cached answer.

    The fingerprint potential also precludes fixed seeds, which change greatly each release, and which degrade in connectivity over time regardless.

    Minimal release fingerprint

    Once a release is cut, behavior profiles are intended to be identical per user. This precludes user knobs and per-instance parameters, and requires a constant user agent and VERSION.

    Across releases, the reimplemented p2p message state machine allows better control of unnecessary profile breaks going forward.

    Specification

    doc/design/private-broadcast-tool.md comes before any code in part 2. It has goals, a threat model per party (recipient, exit, seed, network observer, guard), 54 numbered invariants (A–U, N), parameters, interface, assumptions, non-goals and a rationale. Code comments and tests cite the invariant IDs, so review can check code against a stated property. Concept review of the goals, threat model and non-goals is most useful first, then review of the implementation against the invariants.

    This implementation is actually the second implementation, initially implemented solely via the spec and black box tests, which were handed to an otherwise blind agent.

    Tradeoffs

    • Needs Tor, including Tor's RESOLVE extension. I2P is gone, and so are onion recipients. To re-add these, we need a connect->GETADDR step, and then have to filter out most of the addresses that do not answer when dialed.
    • DNS seeds and Tor exits learn that a job ran, in addition to end recipients.
    • No retries beyond the scheduled fallbacks. Retrying would act on whether the transaction comes back, which the adversary controls; the user resubmits instead.
    • getprivatebroadcastinfo now reports jobs, and the connections no longer show in getpeerinfo.
  2. --- part 1/4: support ---
    Empty commit marking where the parts of this pull request begin. Part 1 moves code that bitcoin-privbcast shares with the node into the common library and exports what it needs from net and netbase. No behaviour change.
    bcd21b632c
  3. build: move bip324 to the common library
    bitcoin-privbcast (added later in this series) links the BIP324 cipher
    without the node library. The cipher depends only on crypto, keys,
    randomness and chain parameters, which the common library already
    provides.
    e003fd2143
  4. refactor: move the transports out of net.h
    The transports and the messages they carry are needed by a tool that
    must not link the node library (bitcoin-privbcast, added later in this
    series). Move them, with NodeId and MAX_PROTOCOL_MESSAGE_LENGTH, to
    net_transport.{h,cpp} in the common library. net.h includes the new
    header and drops the includes that only the moved code used; files that
    name NodeId include the new header themselves.
    
    net.h includes the new header first, where bip324.h was: Qt's moc reads
    net.h through the GUI's headers and cannot parse libstdc++'s <concepts>
    unless an earlier header has defined _GLIBCXX_VISIBILITY.
    
    Move-only: no code changes.
    0582ee800b
  5. netbase: export Socks5ErrorString ec74e9c2f4
  6. build: move package policy to the common library e596fc3cc2
  7. --- part 2/4: bitcoin-privbcast ---
    Empty commit marking the start of part 2: the specification and the standalone tool. Nothing in the node changes.
    6a0027f99b
  8. DrahtBot commented at 2:47 PM on October 22, 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-->

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • src/test/privbcast_queue_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[0], 0, 0, coinbaseKey, script, 49 * COIN, false)
    • src/test/privbcast_queue_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[1], 0, 0, coinbaseKey, script, 49 * COIN, false)
    • src/test/privbcast_queue_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[1], 0, 0, coinbaseKey, script, 48 * COIN, false)
    • src/test/privbcast_queue_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[2], 0, 0, coinbaseKey, script, 49 * COIN, false)
    • src/test/privbcast_queue_tests.cpp: CreateValidMempoolTransaction(parent, 0, 0, coinbaseKey, script, 48 * COIN, false)
    • src/test/txpackage_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[0], 0, 0, coinbaseKey, parent_spk, coinbase_value - 10'000)
    • src/test/txpackage_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[0], 0, 0, coinbaseKey, parent_spk, coinbase_value - 100'000, false)
    • src/test/txpackage_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[1], 0, 0, coinbaseKey, parent_spk, coinbase_value - 10'000)
    • src/test/txpackage_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[2], 0, 0, coinbaseKey, parent_spk, coinbase_value - 10'000, false)
    • src/test/txpackage_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[3], 0, 0, coinbaseKey, parent_spk, coinbase_value - 10'000)
    • src/test/txpackage_tests.cpp: CreateValidMempoolTransaction(m_coinbase_txns[3], 0, 0, coinbaseKey, parent_spk, coinbase_value - 100'000, false)
    • src/test/txpackage_tests.cpp: CreateValidMempoolTransaction(tx, 0, 101, parent_key, child_spk, value, false)

    Possible places where comparison-specific test macros should replace generic comparisons:

    • test/functional/tool_privbcast_package.py: assert a["parent_hold_expired_ms"] is not None and a["parent_getdata_ms"] is None → Prefer assert_equal(a["parent_getdata_ms"], None) for the is None comparison.
    • test/functional/tool_privbcast_package.py: assert a["parent_getdata_ms"] is None and a["parent_hold_expired_ms"] is not None → Prefer assert_equal(a["parent_getdata_ms"], None) for the is None comparison.
    • test/functional/tool_privbcast_package.py: assert a["getdata_ms"] is None and a["tx_written_ms"] is None → Prefer assert_equal(..., None) for each comparison.

    <sup>2026-10-22 17:41:53</sup>

  9. 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
    interfaces of the tool and the node. Later commits cite its items by
    number.
    a083390b73
  10. privbcast: add the parameters and the schedule
    Start bitcoin_privbcast, the library that will run private broadcast
    jobs for bitcoin-privbcast and the 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.
    c1d3eb83e0
  11. 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.
    d638a035d0
  12. 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, and sends a numeric address as IPV4 or IPV6
    without resolving anything.
    
    Implements B2 and the Proxy interface section.
    19844ed5af
  13. 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 tests and the fuzz target inject a
    source that draws from their 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.
    655553d8dc
  14. 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.
    
    The tests run discovery against a scripted proxy behind fake sockets
    that connect as non-blocking sockets do. The fake socket and the
    proxy's side of the SOCKS5 exchange go into the test utilities, as the
    job's tests use them too.
    
    Implements B1, B2, C3, D1, R1, R2 and C7 for the queries.
    7da6cfd2cc
  15. 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. ExitStatus() gives the tool's exit status for a
    report.
    
    Implements A1, A3, A4, C1-C8, D1, D2, H1, H3 and the report interface.
    28926fa818
  16. test: extend the harness for bitcoin-privbcast
    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. The framework learns where
    bitcoin-privbcast is and how to skip a test without it.
    
    Prepares the functional test that the next commit adds with the binary.
    65da100adb
  17. privbcast: add bitcoin-privbcast
    The standalone tool: it reads one transaction on stdin, runs one job
    through the Tor proxy named by -tor and prints the report on stdout,
    with progress lines on stderr. It takes no configuration file and only
    a loopback proxy, refuses the regtest overrides on other chains and
    checks its arguments and input before any network activity. The
    discover command runs discovery alone.
    
    Build and packaging: the BUILD_PRIVBCAST option (on with BUILD_TESTS),
    the privbcast subcommand of the bitcoin wrapper, the man page, the
    Windows resource file, the manpage generator and Windows CI lists, and
    the test configuration that tells the functional framework the binary
    exists.
    
    The functional tests tool_privbcast.py, tool_privbcast_proxy.py and
    tool_privbcast_peers.py, with their harness in
    test_framework/privbcast.py, are the specification's black-box
    acceptance suite for the tool: scripted recipients and a scripted proxy
    check every wire-visible parameter, the schedule, the assignment, the
    exit statuses and cancellation.
    
    Implements B3, H2, U1 and the Interface section for bitcoin-privbcast.
    9f8105a33c
  18. doc: describe bitcoin-privbcast
    Add the user guide to private broadcast with its bitcoin-privbcast
    part: the Tor proxy the tool needs, how to send a transaction and read
    the exit status and the report, discover, and the limits of what a job
    protects.
    ca786cc7b9
  19. doc: release notes for bitcoin-privbcast 33fe47c1f7
  20. --- part 3/4: bitcoind -privatebroadcast as jobs ---
    Empty commit marking the start of part 3: the node runs each private broadcast as the tool's job, and the old relay and connections are removed.
    64f1f26454
  21. node: add the private broadcast job queue
    The node runs each private broadcast as a privbcast::Job, the tool's
    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 the tests 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: a fuzz target checks the
    rule on its own, and another the queue through its interface.
    
    bitcoin_node now links bitcoin_privbcast.
    
    Implements N3-N7, N9, N10, the node's D2 and D3, U3. Not yet wired:
    the next commit switches -privatebroadcast to it.
    71ea14ea32
  22. 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 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.
    
    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.
    8bd029b720
  23. 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.
    e851f4d978
  24. 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.
    c39973a4df
  25. 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.
    fded8bbf39
  26. doc: describe bitcoind -privatebroadcast
    Add the node part of the private broadcast guide: 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
    link between a job's connections and the node's peers at shutdown.
    c5ac672d8d
  27. doc: release notes for private broadcast jobs 26e3a21e1c
  28. --- part 4/4: package mode ---
    Empty commit marking the start of part 4: one parent and its child. The validation commits here serve only this part.
    a80a9d414a
  29. privbcast: serve a parent in package mode
    A child can pay for a parent too cheap to enter mempools alone when the
    recipient takes the two together. A job may now carry the parent: each
    attempt announces only the child, serves the parent to a peer that asks
    for it, and holds the PING for that request. A job without a parent
    runs and reports as before.
    
    Implements F1-F4 and the package amendments of E6-E8.
    d9f4c35cc3
  30. privbcast: accept a parent and child in the tool
    bitcoin-privbcast send also reads two transactions, an unconfirmed
    parent and its child in either order, checks that they are such a pair
    and runs the job in package mode. The acceptance suite gains its
    package sections in tool_privbcast_package.py.
    
    Implements the Extension's Interface for bitcoin-privbcast.
    be1433a1bd
  31. validation: sum a package's modified fees saturating
    Two prioritisetransaction deltas near INT64_MIN made the sum overflow.
    ee5719d0c6
  32. validation: test-accept a package as submission evaluates it
    ProcessNewPackage's test mode checks each transaction without package
    feerates or replacements, as testmempoolaccept does. With
    test_as_submission it runs submission's evaluation instead and stops
    short of the mempool, so a caller can learn whether a child pays for its
    parent before relaying them. A transaction that passed before another
    failed is reported by its own result, as submission reports it. Results
    do not list replaced transactions, and a parent that passes alone gives
    no answer for its child, which it does not stage.
    
    From #35286, without its RPC.
    76797040c2
  33. validation: test a package whose parent passes alone
    A test does not stage a transaction that passes alone, so the child
    that spends it was found missing its inputs. Test the new transactions
    together instead, each paying for itself. Their replacements get
    package RBF's checks of fees, feerate diagram and cluster limits, but
    not its rules that the package have no mempool ancestors and pay more
    per vbyte than the parent: submission would take the two in turn, each
    replacing what it may. A replacement of an ancestor of the package is
    refused instead, as it would take away the package's inputs. For a
    parent and child this accepts at least what submission accepts.
    d27d450877
  34. instagibbs force-pushed on Oct 22, 2026
  35. DrahtBot added the label CI failed on Oct 22, 2026
  36. DrahtBot commented at 3:18 PM on October 22, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task MSan: https://github.com/bitcoin/bitcoin/actions/runs/39798797867/job/119893347731</sub> <sub>LLM reason (✨ experimental): The build failed because Clang’s thread-safety analysis found calls to AcceptSubPackage without exclusive locks on cs_main and m_pool.cs in validation.cpp.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  37. fanquake added the label Private Broadcast on Oct 22, 2026
  38. rpc: queue submitpackage for private broadcast
    With -privatebroadcast, submitpackage now queues a private broadcast
    job instead of adding the transactions to the mempool. It takes one
    transaction, or a parent and its child, and queues one job, the
    child's, which carries the parent. A transaction whose txid is in the
    mempool counts as accepted; the rest is test-accepted, one transaction
    alone as by sendrawtransaction, two as a package, after the package
    checks on the whole submission, two as submitpackage evaluates them.
    maxfeerate follows each path's own convention. Unit tests drive the RPC through its table.
    
    The queue takes the parent with the transaction: a submission with a
    parent is covered only by a job with the same parent, one without by a
    job with any parent. getprivatebroadcastinfo lists the parent's txid
    and documents the report's package fields; abortprivatebroadcast
    matches the child's ids only. Both submission RPCs ask the queue
    whether an onion proxy is configured, which lets unit tests drive
    them without the process-wide proxy registry. The acceptance suite gains its package
    sections, run as p2p_private_broadcast.py --package.
    
    Implements the Extension's N5 and N11 amendments and its Interface for
    submitpackage.
    079099f079
  39. doc: specify package mode
    Add the specification's section "Extension: one parent, one child",
    which the preceding commits implement: a job that carries a child and
    its unconfirmed parent announces only the child and serves the parent
    on request (F1-F4), and the tool and the node take such a pair.
    30d5ea00b5
  40. doc: describe package mode
    Add the guide's part on a parent and its child: when it helps, which
    recipients accept such a package, what the tool and the node check and
    what peers can tell; and how submitpackage queues a job under
    -privatebroadcast.
    7f4e53befc
  41. doc: release notes for package mode eec78ccf7d
  42. instagibbs force-pushed on Oct 22, 2026
  43. DrahtBot removed the label CI failed on Oct 22, 2026
  44. instagibbs marked this as a draft on Oct 23, 2026
  45. instagibbs commented at 4:46 PM on October 23, 2026: member

    in draft, #36628 is the slimmed down branch to review


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