[DO NOT MERGE] Erlay: bandwidth-efficient transaction relay protocol (Full implementation) #35591

pull sr-gi wants to merge 38 commits into bitcoin:master from sr-gi:2026-05-erlay-recon-only-full-impl changing 30 files +4426 −402
  1. sr-gi commented at 12:40 PM on June 23, 2026: member

    Erlay Project Tracking: #30249 Conceptual Discussion: #34542


    This is a full implementation of Erlay. Its purpose is to check the integrity and correctness of the implementation against changes/additions that may originate from the review process and/or rebases on top of newer functionality.

    This is not to be merged. Functionality will be spread across multiple smaller PRs to ease the review process.


    Approach

    This approach uses Erlay as a fallback mechanism for transaction propagation. Instead of mixing fanout and reconciliation into a single connection type, the current approach leaves the existing connections as they are, and adds additional low-bandwidth connections to be used in case the node is being eclipsed. This connections should have minimal cost under normal circumstances, and only undergo real traffic in case other 8 full-outbound connections are being captured.

    outbound-full-reconciliation connections

    For now, we are adding 4 additional reconciliation-only connections to the node while we test it's impact on real node running the approach. Further analysis may be needed to pick a meaningful value for this. The number of inbound connections should also be scaled based on how many connections we are adding.

  2. DrahtBot commented at 12:40 PM on June 23, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/35591.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36167 ([RFC] Enable -Wunused by fanquake)
    • #35561 (net: move some CNodeState fields to Peer by Crypt-iQ)
    • #35522 (refactor: Extract per-message helpers from SendMessages() (move-only) by pablomartin4btc)
    • #34743 (p2p: don't disconnect manual peers for block stalling by willcl-ark)
    • #32554 (bench: replace embedded raw block with configurable block generator by l0rinc)
    • #28690 (build: Introduce internal kernel library by sedited)
    • #27052 (test: rpc: add last block announcement time to getpeerinfo result by LarryRuane)

    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.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • Giving it a 6x marging to prevent flakiness -> Giving it a 6x margin to prevent flakiness [misspelled word]
    • peer1 only has one one tx in the set, which matches out sketch som no diff -> peer1 only has one tx in the set, which matches our sketch so no diff [multiple misspellings]
    • The node should have snapsotted the set. -> The node should have snapshotted the set. [misspelled word]
    • after the snapshop is created -> after the snapshot is created [misspelled word]
    • Removing after the snapshop should have no effect on the extension. -> Removing after the snapshot should have no effect on the extension. [misspelled word]
    • Allows to infer capacity of a reconciliation sketch based on it's char[] representation, -> Allows inference of the capacity of a reconciliation sketch based on its char[] representation, [“it's” should be “its”; wording is also ungrammatical]
    • Re set the peer's phase -> Reset the peer's phase [misspelled word]

    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):

    • self.generate_txs(self.wallet, 0, 0, 2) in test/functional/p2p_txrecon_initiator.py
    • self.test_reconciliation_initiator_no_extension(3, 3, 20) in test/functional/p2p_txrecon_initiator.py
    • self.generate_txs(self.wallet, 0, 10, 0) in test/functional/p2p_txrecon_responder.py
    • self.generate_txs(self.wallet, 0, 1, 0) in test/functional/p2p_txrecon_responder.py
    • self.generate_txs(self.wallet, 0, 5, 0) in test/functional/p2p_txrecon_responder.py
    • self.test_reconciliation_responder_flow_no_extension(3, 3, 20) in test/functional/p2p_txrecon_responder.py

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

    • [test/functional/p2p_txrecon_disabled.py] assert self.nodes[0].process.poll() is None, f"node crashed handling {msgname}" -> use assert_equal(self.nodes[0].process.poll(), None)

    <sup>2026-09-04 19:28:07</sup>

  3. sr-gi force-pushed on Jun 23, 2026
  4. sr-gi commented at 1:13 PM on June 23, 2026: member

    Rebased on master.

    Opening this so it can be tested against CI and to make sure nothing obvious is missing. Next step will be testing with real nodes (most likely in Warnet).

    Things to consider/add:

    • This is currently using sendtxrcncl as a way, for outbound nodes, to signal they want to establish a full-reconciliation connection, plus adding a limit on the number of full-reconcilition inbounds that will be accepted by a node. This is not the originally intended way of using sendtxrcncl. It may be worth considering an approach based on BIP-434.
    • The extension phase was designed so peers with an ongoing reconciliation that had underpredicted the sketch capacity could still reconcile without having to directly fallback to fanout. Given reconciliation is now used as fallback, and the extra bandwidth may not be as problematic, it may be worth considering overshooting the initial sketch capacity and getting rid of the extension phase.
    • Fuzz tests are missing
  5. DrahtBot added the label CI failed on Jun 23, 2026
  6. sr-gi force-pushed on Jun 23, 2026
  7. sr-gi force-pushed on Jun 23, 2026
  8. DrahtBot removed the label CI failed on Jun 23, 2026
  9. sr-gi force-pushed on Jun 23, 2026
  10. sr-gi commented at 11:04 PM on June 23, 2026: member

    Fixed some typos and addressed some of the linter suggestions

    6554707...e63db1a

  11. sr-gi force-pushed on Jun 24, 2026
  12. DrahtBot added the label CI failed on Jun 24, 2026
  13. sr-gi force-pushed on Jun 24, 2026
  14. sr-gi commented at 2:41 PM on June 24, 2026: member

    Fixed several bugs and added a new test for receiving reconciliation messages when reconciliation is not enabled.

    e63db1a...35bcd04

  15. sr-gi force-pushed on Jun 24, 2026
  16. sr-gi force-pushed on Jun 24, 2026
  17. DrahtBot removed the label CI failed on Jun 24, 2026
  18. sr-gi commented at 8:57 PM on June 24, 2026: member

    Addressed some issues pointed out by corecheck.

    35bcd04...d08f127

  19. brunoerg commented at 4:23 PM on June 29, 2026: contributor

    **Testing report (LLM/experimental) based on the results of an incremental mutation testing run for this PR - full result is avaliable at: https://bitcoincore.space**


    The overall result is mixed. The PR already has useful coverage for the happy-path reconciliation flow, basic protocol violations, and some queue/timer behavior. However, the survivors show that the tests still leave several important behaviors only weakly specified, especially where reconciliation falls back, extends, or interacts with INV relay bookkeeping.

    What the current tests do well

    The existing tests cover the basic handshake and some normal flows reasonably well:

    That baseline is good enough to kill most straightforward mutations. The survivors are mostly about what happens around the edges of the protocol, not the central flow.

    Main gaps exposed by the surviving mutants

    1. Extension handling is under-tested

    This is the clearest gap. Several survivors change extension behavior without being detected:

    • not sending REQSKETCHEXT after an undecodable sketch in net_processing
    • not snapshotting state before the extension round
    • accepting/rejecting malformed extension sizes incorrectly
    • flipping success/failure outcomes of extension decoding
    • using the wrong transaction set after extension failure
    • not clearing peer state after extension completion

    This lines up with the current functional tests: both initiator and responder tests explicitly stop short of extension coverage and even leave TODOs for it in test/functional/p2p_txrecon_initiator.py and test/functional/p2p_txrecon_responder.py.

    What should be improved:

    • add end-to-end functional tests that force an extension round and assert the exact message sequence: REQTXRCNCL -> SKETCH -> REQSKETCHEXT -> SKETCH -> RECONCILDIFF
    • assert both extension success and extension failure behavior
    • verify that extension failure falls back to announcing the snapshotted set, not the live set
    • verify that state is cleared after extension completion and that a second reconciliation starts cleanly

    2. Boundary conditions are not pinned down tightly enough

    Several survivors change strict inequalities to inclusive ones and still pass:

    • remote_sketch_capacity > MAX_SKETCH_CAPACITY changed to >=
    • extended_capacity > MAX_SKETCH_CAPACITY * 2 changed to >=
    • peer_q > Q_PRECISION changed to >=

    This means the tests exercise invalid-above-limit cases, but not the exact boundary values that should remain valid. There is already a good example of this style for rounded q formatting in src/test/txreconciliation_tests.cpp, but the same precision is missing for protocol acceptance thresholds.

    What should be improved:

    • add exact-boundary tests for MAX_SKETCH_CAPACITY, 2 * MAX_SKETCH_CAPACITY, and Q_PRECISION
    • check both sides of each threshold: limit must pass, limit + 1 must fail

    3. INV relay side effects are only partially asserted

    Many survivors in PeerManagerImpl::AnnounceTxs() and the send path in SendMessages() remove or alter important side effects without breaking tests:

    • skipping heap construction/pop order
    • changing continue to break when one candidate should not be sent
    • not inserting into m_tx_inventory_known_filter
    • not erasing from m_tx_inventory_to_send
    • not flushing INV batches at MAX_INV_SZ
    • not clearing the batch after sending

    The current tests mostly check that transactions eventually arrive. They do not strongly check how they are batched, filtered, or suppressed from future re-announcement. That leaves a lot of bookkeeping mutations alive.

    What should be improved:

    • add tests with a mix of sendable and unsendable transactions and assert that later eligible transactions are still announced
    • assert that transactions already known to the peer are not re-announced in the next round
    • add a case above MAX_INV_SZ and verify the number and sizes of INV messages
    • verify that reconciliation and fanout paths do not duplicate announcements after internal state should have been cleared

    4. Some state-management behaviors are observable in principle, but not asserted

    Survivors also show weak checking around reconciliation state transitions:

    • not clearing m_short_id_mapping
    • not clearing peer state after handling a result
    • always taking the “removed” branch in TryRemovingFromSet
    • not recording m_announced_while_reconciling
    • returning success from queue-selection paths that should be false

    These are not all equally severe, but together they indicate that the tests often validate the final external effect of a single round without checking the follow-up round that would expose stale state.

    What should be improved:

    • add two-step tests that perform one reconciliation round and then immediately start another to detect stale snapshots, stale mappings, or stale queue state
    • specifically verify the “received while reconciling” behavior across the extension path, not just the non-extension path

    Bottom line

    The current tests are good at proving that the basic txreconciliation flow works. They are not yet strong enough to fully specify the extension path, the exact protocol boundaries, or the internal bookkeeping that prevents duplicate, truncated, or stale announcements.

    If I had to prioritize follow-up work, I would do it in this order:

    1. Add full initiator/responder extension-path functional tests.
    2. Add exact boundary tests for sketch capacity and q.
    3. Add stronger assertions around INV batching, filtering, and duplicate suppression.
    4. Add second-round/state-cleanup tests to catch stale reconciliation state.

    That would likely eliminate most of the meaningful survivors from this run and materially improve confidence in the PR’s tests.

  20. DrahtBot added the label Needs rebase on Jul 24, 2026
  21. sr-gi force-pushed on Aug 13, 2026
  22. DrahtBot removed the label Needs rebase on Aug 13, 2026
  23. sr-gi commented at 8:05 PM on August 13, 2026: member

    Rebased to include the latest changes in master, including the changes introduced in https://github.com/bitcoin/bitcoin/pull/34628.

    Reworked the transaction announcement logic so most of it can be re-used by the trickle code path and the new post-reconciliation code path.

    Also started to killing @brunoerg reported mutants.

    d08f127...41de5f9

  24. DrahtBot added the label Needs rebase on Aug 14, 2026
  25. sr-gi force-pushed on Aug 17, 2026
  26. sr-gi force-pushed on Aug 17, 2026
  27. sr-gi commented at 4:29 PM on August 17, 2026: member

    Finished covering the most relevant mutants. Left out the ones regarding extensions, as I'm still debating whether those will be part of the PR.

    41de5f9...816d2cd

  28. sr-gi force-pushed on Aug 17, 2026
  29. sr-gi commented at 5:04 PM on August 17, 2026: member

    Rebased master to cover #35852, and applied the inline const(expr) convention introduced by the PR to cover new definitions.

    816d2cd...d34d736

  30. DrahtBot removed the label Needs rebase on Aug 17, 2026
  31. sr-gi force-pushed on Aug 18, 2026
  32. sr-gi force-pushed on Aug 18, 2026
  33. DrahtBot added the label CI failed on Aug 18, 2026
  34. DrahtBot removed the label CI failed on Aug 18, 2026
  35. sr-gi force-pushed on Aug 19, 2026
  36. sr-gi commented at 3:36 PM on August 19, 2026: member

    Fixed several uncovered edge cases.

    d34d736...b0186aa

  37. sr-gi force-pushed on Aug 19, 2026
  38. sr-gi commented at 7:23 PM on August 19, 2026: member

    Added benches for how long it takes to build and decode sketches based on their capacity. This may affect the max sketch capacity that we may accept to work with.

    ns/element element/s err% total benchmark
    9,992.50 100,075.03 0.6% 0.33 ReconcileSketchConstructReconSetMax
    536,306.23 1,864.61 0.1% 96.69 ReconcileSketchDecodeExtensionMax
    281,615.18 3,550.94 0.1% 25.38 ReconcileSketchDecodeMaxCapacity
    39,963.18 25,023.03 1.1% 1.30 ReconcileSketchDecodeReconSetMax
    2,379.92 420,182.37 3.5% 0.01 ReconcileSketchDecodeTypical

    This results in the following:

    benchmark capacity per-decode
    ReconcileSketchDecodeTypical 54 0.13 ms
    ReconcileSketchDecodeReconSetMax 3001 120 ms
    ReconcileSketchDecodeMaxCapacity 8192 2.3 s
    ReconcileSketchDecodeExtensionMax 16384 8.8 s
    • The decode for a typical/realistic round (7 tx/s, 30s, q=0.25) is in the order of tens of microseconds.
    • A round between two Core nodes (limiting their set size to MAX_RECONSET_SIZE) takes ~120ms
    • A peer that advertises sketches as big as they get (MAX_SKETCH_CAPACITY) can make us take ~2.3s to decode
      • This is amplified to 8.8s in the extension case, as we allow up to double the max capacity

    This applies only to outbound peers, as those are the ones that makes us decode. This bears the question, should we limit the maximum sketch size that we accept far bellow MAX_SKETCH_CAPACITY? In the case of Core nodes, sets cannot have more than MAX_RECONSET_SIZE elements. So the maximum sketch sizes are 3001 for the initial sketch, 6002 for an extension.

    b0186aa...62bf7d1

  39. DrahtBot added the label CI failed on Aug 19, 2026
  40. DrahtBot commented at 7:24 PM on August 19, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task iwyu: https://github.com/bitcoin/bitcoin/actions/runs/32289116534/job/96185584884</sub> <sub>LLM reason (✨ experimental): CI failed because IWYU detected and rejected incorrect/missing #include ordering/content (generated “Failure generated from IWYU” for src/bench/txreconciliation.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>

  41. sr-gi force-pushed on Aug 19, 2026
  42. DrahtBot removed the label CI failed on Aug 19, 2026
  43. sr-gi commented at 10:22 AM on August 20, 2026: member

    This should be ready to review at this point, even though some details still need deciding. Happy to split the PR into smaller chunks if it makes it easier.

    Open questions (that may arise from reviewing parts of the code):

    • Should we negotiate reconciliation using BIP-434?
    • Should the new connection type be considered a full-outbound? If so, the eviction logic needs to be patched to include these.
    • Are extensions worth if we will be using reconciliation as a fallback, or should we just slightly overestimate the sketch capacity and default to fanout on failure?
    • Should we cap the maximum capacity sketch we accept closer to MAX_RECONSET_SIZE than to MAX_SKETCH_CAPACITY?
  44. DrahtBot added the label Needs rebase on Aug 24, 2026
  45. sr-gi force-pushed on Aug 24, 2026
  46. DrahtBot removed the label Needs rebase on Aug 24, 2026
  47. sr-gi commented at 12:34 PM on August 24, 2026: member

    Rebased e366778...534a890

  48. in src/init.cpp:2194 in 534a89069d outdated
    2187 | @@ -2188,6 +2188,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
    2188 |      CConnman::Options connOptions;
    2189 |      connOptions.m_local_services = g_local_services;
    2190 |      connOptions.m_max_automatic_connections = num_p2p_max_connections;
    2191 | +    // We only offer reconciliation when we relay transactions, so under -blocksonly these
    2192 | +    // connections could never negotiate it and would waste their outbound slots.
    2193 | +    if (args.GetBoolArg("-txreconciliation", DEFAULT_TXRECONCILIATION_ENABLE) && !peerman_opts.ignore_incoming_txs) {
    2194 | +        connOptions.m_max_outbound_full_recon = MAX_OUTBOUND_FULL_RECON_CONNECTIONS;
    


    brunoerg commented at 5:58 PM on August 31, 2026:

    Could reconcilitation peers exceed m_max_outbound_full_recon during v2-to-v1 fallback? Thinking about a possible race.


    sr-gi commented at 10:29 AM on September 1, 2026:

    I took a look at this, and I think we can overshoot it by 1 if there is a race between a v2-to-v1 downgrade and the opening of a new full recon connection. Since we do not check for extra reconciliation peers in EvictExtraOutboundPeers, we can end up with an extra connection.

    I will note this, I think it can be handled alongside deciding whether we want to count reconciliations connections as full outbounds (we will need eviction logic in that case too).

  49. in src/node/txreconciliation_impl.cpp:286 in 534a89069d outdated
     281 | +        if (extended_sketch_size % BYTES_PER_SKETCH_CAPACITY != 0) return ReconciliationError::PROTOCOL_VIOLATION;
     282 | +
     283 | +        // We allow the peer to send an extension for any capacity, not just original capacity * 2,
     284 | +        // but it should be within the limits. The limits are MAX_SKETCH_CAPACITY * 2, so that
     285 | +        // they can extend even the largest (originally) sketch.
     286 | +        // TODO: benchmarks show decode is quadratic in capacity (~12s at MAX_SKETCH_CAPACITY * 2),
    


    brunoerg commented at 6:07 PM on August 31, 2026:

    I got 31.2 seconds on my machine (Ryzen 9 7900). Wondering if a malicious peer could use it to cause a DoS since it would block all msgs processings?


    sr-gi commented at 9:11 AM on September 1, 2026:

    Yes, I think it can. I left a comment on this earlier on the PR.

    Given this is quadratic, I think limiting the size of the sketch (and potentially getting rid of extensions) will make it less of an issue.

    #35591 (comment)

  50. in src/net_processing.cpp:5484 in 534a89069d
    5479 | +    }
    5480 | +
    5481 | +    // Among transactions requested by short ID here, we should send only those transactions
    5482 | +    // sketched (stored in local set snapshot), because otherwise we would leak privacy (mempool content).
    5483 | +    if (msg_type == NetMsgType::RECONCILDIFF) {
    5484 | +        bool recon_result;
    


    brunoerg commented at 6:12 PM on August 31, 2026:

    nit: I think that, according to the BIP330, we must accept 0 or 1 here. This way it would treat any nonzero byte as true.


    sr-gi commented at 9:20 AM on September 1, 2026:

    You're right, will change

  51. sr-gi force-pushed on Sep 1, 2026
  52. sr-gi commented at 10:44 AM on September 1, 2026: member

    Addresses @brunoerg comments.

    I changed the way we parse recon_result to mimic the check we already have in place for BIP152. Also added a test for it.

    Regarding the race, I left a comment so we can address it when addressing general recon connection eviction.

    I'd appreciate your input on whether OUTBOUND_FULL_RECONCILIATION should be considered full outbound, and on whether extensions are worth keeping.

    534a890...25e545c

  53. DrahtBot added the label CI failed on Sep 1, 2026
  54. refactor: redesigns txreconciliation file split and namespace
    Splits the txreconciliation logic in three files instead of two, allowing the
    TxreconciliationState to be properly tested, instead of being internal to
    txreconciliation.cpp.
    
    Also includes everything in the node namespace, instead of being part
    of an anonymous one.
    312095c26b
  55. refactor: remove legacy comments
    These comments became irrelevant in one of the previous code changes.
    They simply don't make sense anymore.
    9deab543e0
  56. refactor: Defines generic error to be used in several reconciliation methods 0d3d5c9b24
  57. refactor: add full stop in existing txreconciliation LogDebug lines 8e9fec2a27
  58. p2p: Allows inbound reconciliation connections up to a limit
    Set the current limit to 32.
    e703cdf680
  59. sr-gi force-pushed on Sep 1, 2026
  60. sr-gi commented at 1:57 PM on September 1, 2026: member

    Rebased to fix CI, plus slightly decreased MAX_INBOUND_RECONCILIATION_PEERS for now, as some inbounds were being evicted in p2p_sendtxrcncl.py after #36078.

    MAX_INBOUND_RECONCILIATION_PEERS is currently a magic number, so we can set it properly (and increase the number of inbounds accordingly) once we decide on how to treat recon connections eviction.

    d08f127...0eddbbc

  61. DrahtBot removed the label CI failed on Sep 1, 2026
  62. net, gui, test: adds new connection type (OUTBOUND_FULL_RECONCILIATION)
    Adds a new connection type that will be used for reconciliation only.
    
    Defines the default max number of this type of connections to 4.
    3792744ae7
  63. p2p: Count reconciliation connections as full outbound connections
    Reconciliation connections relay everything a full-relay connection does
    (tx, block, addr). They only differ in how transactions are exchanged. Treat
    them as full outbound connections. This includes:
    
    - seeding: they count towards SEED_OUTBOUND_CONNECTION_THRESHOLD, so we stop
      querying seeds once we have enough peers, no matter their type.
    - network diversity: they count towards m_network_conn_counts, so a network
      covered by a reconciliation peer is not picked as a preferred network, and
      a reconciliation peer that is our only connection to a network is protected
      from eviction.
    - chain sync: they can be protected from the bad/lagging chain logic, which
      they are already subject to via IsOutboundOrBlockRelayConn.
    
    Reconciliation peers get their own, smaller protection budget so that they
    cannot use up the protection our full-relay peers rely on.
    4d2cb97a86
  64. refactor: Extract EvictWorstOutboundPeer out of EvictExtraOutboundPeers
    Make the eviction logic into a method based on the connection type so we
    can reuse it for OUTBOUND_FULL_RECON too.
    ab8528650b
  65. sr-gi force-pushed on Sep 4, 2026
  66. p2p: Evict extra reconciliation peers
    Evict extra reconciliation peers using the same logic as we do for
    full relay peers.
    0ce6c11e2a
  67. p2p: send SENDTXRCNCL messages only over OUTBOUND_FULL_RECONCILIATION connection 07823648d9
  68. p2p: Disconnect outbound reconciliation peers that do not negotiate
    Reconciliation slots are reserved to peers that negotiate reconciliation.
    
    We treat this similarly to a block-relay-only peer that sends us transactions.
    1a42ba6945
  69. p2p: Functions to add/remove wtxids to tx reconciliation sets
    They will be used later on.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    c4d9ef36fc
  70. p2p: Add transactions to reconciliation sets
    Transactions are added to the reconciliation sets of reconciling peers, and processed normally for fanout peers.
    9d60dee5ed
  71. p2p: Add helper to compute reconciliation tx short ids and a cache of short ids to wtxids 2ac2615781
  72. p2p: Deal with shortid collisions for reconciliation sets
    If a transaction to be added to a peer's recon set has a shot id collisions (a previously
    added wtxid maps to the same short id), both transaction should be fanout, given
    our peer may have added the opposite transaction to our recon set, and these two
    transaction won't be reconciled.
    07659f3221
  73. p2p: Add peers to reconciliation queue on negotiation
    When we're finalizing negotiation, we should add the peers
    for which we will initiate reconciliations to the queue.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    1e89a027ae
  74. p2p: Track reconciliation requests schedule
    We initiate reconciliation by looking at the queue periodically
    with equal intervals between peers to achieve efficiency.
    
    This will be later used to see whether it's time to initiate.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    2962667892
  75. p2p: Initiate reconciliation round
    When the time comes for the peer, we send a
    reconciliation request with the parameters which
    will help the peer to construct a (hopefully) sufficient
    reconciliation sketch for us. We will then use that
    sketch to find missing transactions.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    6f0ffb61ae
  76. test: Functional test for reqtxrcncl
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    bbfa86d2b6
  77. p2p: Handle reconciliation request
    Store the parameters the peer sent us inside th reconciliation request.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    9409b1807c
  78. p2p: Add helper to compute sketches for tx reconciliation
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    c1b900ef62
  79. p2p: Respond to a reconciliation request
    When the time comes, we should send a sketch of our
    local reconciliation set to the reconciliation initiator.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    7e4c2b5edd
  80. p2p: Add a function to identify local/remote missing txs
    When the sketches from both sides are combined successfully,
    the diff is produced. Then this diff can (together with the local txs)
    be used to identified which transactions are missing locally and remotely.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    ff227373d0
  81. refactor: Extract AnnounceTxs out of SendMessages
    Move the transaction announcement loop from SendMessages into its own
    method, so it can be reused to announce transactions after a
    reconciliation round. No behaviour change.
    07d6c6d1fc
  82. p2p: Add a function to announce transactions after reconciliation
    Transactions the peer is found to be missing during a reconciliation round
    need to be announced right away, rather than waiting for the next trickle
    interval and being added back to the reconciliation set.
    
    Add AnnounceReconciliationTxs on top of AnnounceTxs.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    db3d3e10ec
  83. p2p: Handle reconciliation sketch and successful decoding
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    5a73d050f1
  84. p2p: Request extension if decoding failed
    If after decoding a reconciliation sketch it turned out
    to be insufficient to find set difference, request extension.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    fad7f9c261
  85. p2p: Be ready to receive sketch extension
    Store the initial sketches so that we are able to process
    extension sketch while avoiding transmitting the same data.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    6bc290a609
  86. p2p: Prepare for sketch extension request
    To be ready to respond to a sketch extension request
    from our peer, we should store a snapshot of our state
    and capacity of the initial sketch, so that we compute
    extension of the same size and over the exact same
    transactions.
    
    Transactions arriving during this reconciliation will
    be instead stored in the regular set.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    b9ef0cce59
  87. p2p: Keep track of announcements during txrcncl extension
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    9f7f1d102b
  88. p2p: Handle reconciliation extension request
    If peer failed to reconcile based on our initial response sketch,
    they will ask us for a sketch extension. Store this request to respond later.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    fd95370940
  89. p2p: Respond to sketch extension request
    Sending an extension may allow the peer to reconcile
    transactions, because now the full sketch has twice
    as much capacity.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    50dc0109a7
  90. p2p: Handle sketch extension
    If a peer sent us an extension sketch, we should
    reconstruct a full sketch from it with the snapshot
    we stored initially, and attempt to decode the difference.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    ddfd680a0e
  91. p2p: Add a finalize incoming reconciliation function
    This currently unused function is supposed to be used once
    a reconciliation round is done. It cleans the state corresponding
    to the passed reconciliation.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    880bdd7430
  92. p2p: Handle reconciliation finalization message
    Once a peer tells us reconciliation is done, we should behave as follows:
    - if it was successful, just respond them with the transactions they asked
      by short ID.
    - if it was a full failure, respond with all local transactions from the reconciliation
      set snapshot
    - if it was a partial failure (only low or high part was failed after a bisection),
      respond with all transactions which were asked for by short id,
      and announce local txs which belong to the failed chunk.
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    cf883f512b
  93. p2p, test: Add tx reconciliation functional tests
    We may still need to add more tests, specially around extensions (if we keep them)
    
    Co-authored-by: Gleb Naumenko <naumenko.gs@gmail.com>
    3554b3db62
  94. p2p: Remove transactions from reconciliation sets when removed from the mempool
    A transaction that has left our mempool can no longer be served, but nothing
    removed it from the reconciliation sets it had been added to. It would still be
    sketched, requested by the peer, and then dropped, wasting sketch capacity and a
    round trip after every block.
    
    Prune on both removal signals: removeUnchecked deliberately skips
    TransactionRemovedFromMempool for MemPoolRemovalReason::BLOCK, so mined
    transactions, which are the bulk of the case, only arrive via
    MempoolTransactionsRemovedForBlock.
    
    Snapshots of an in-flight round are left untouched so that a sketch extension
    still describes the same elements as the sketch already sent. Flagging the wtxid
    instead keeps it out of the announcement.
    0752851617
  95. bench: Adds txreconciliation benches
    Raises the question of whether the current sketch capacity limits are too high
    37912ec178
  96. sr-gi force-pushed on Sep 4, 2026
  97. DrahtBot added the label CI failed on Sep 4, 2026
  98. DrahtBot commented at 7:28 PM on September 4, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task ASan + LSan + UBSan + integer: https://github.com/bitcoin/bitcoin/actions/runs/33909308334/job/101141694892</sub> <sub>LLM reason (✨ experimental): CI failed because the fuzz build didn’t compile: connman.cpp calls ConnmanTestMsg::GetExtraFullOutboundCount() which no longer exists (“no member named”).</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>

  99. sr-gi commented at 7:42 PM on September 4, 2026: member

    @brunoerg, I've gone ahead and added reconciliation connections as full outbound connections, and dealt with connection eviction in the same way we did for full outbounds.

    I'm happy to re-work this is reviewers think this should no be seen as full outbounds, but I thought it may be best to just go ahead with an approach instead of waiting for discussion. These is split in two commits, ab8528650be36714554934946679d69247dbb719 to refactor the current logic into a method that can be reused by reconciliation connections, and a 0ce6c11e2a1c2f583a5e0b4eaed5f019c62c0dba that implements that for them.

    With this, I should have also covered your concern about v2->v1 downgrades, as outbound reconciliations now have eviction logic.

    On top of that, I've changed the logic for when a peer does not accept our reconciliation connection. Before, we will keep that node connected, acting as a regular outbound but taking a reconciliation slot. This is dangerous as it can lead to 12 full outbound connections, which may make us create even more redundant announcements. Now, those connections are dropped, and we look for another peer.

    This can create some issues during the deployment phase, if not enough slots are available for all the new connections. I think we can mitigate this by deploying the feature in two phases. Phase 1 deploys the code, but only makes nodes accept incoming reconciliation connections, not start them, that is gated over a flag that is disabled by default (or even just disabled in general). Once enough deployment is reached, we can flip that feature and allow nodes to proactively initiate those. I have currently not gated this, as it makes it easier for testing, but I think it is worth mentioning.

    0eddbbc...37912ec

  100. DrahtBot removed the label CI failed on Sep 4, 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-09-09 07:56 UTC