bch: forward txs on the send path via bch::new_tx_hashes accessor (#905) - #913
Merged
Conversation
send_shares() probed `requires { obj->m_new_transaction_hashes; }` directly on
the share object to decide which referenced txs to push to a peer. That flat
spelling is FALSE for every BCH share variant -- v17/v33 nest the list inside
m_tx_info, v34/v35/v36 (SegWit-less) carry no list at all -- so the needed_txs
forwarding block was unreachable and BCH forwarded no tx bytes. A relayed
v17/v33 share referencing a tx the peer lacked was the canonical "referenced
unknown transaction" disconnect. The receive side (protocol_actual/legacy,
node.hpp) already spells m_tx_info.m_new_transaction_hashes correctly, which is
why only the send side was dead.
Route the probe through a single accessor, mirroring ltc::new_tx_hashes (#873):
new src/impl/bch/share_tx_refs.hpp returns the nested list for v17/v33 and
nullptr for v34+ (nothing to forward) -- a new variant is either handled in one
of the two known spellings or reported as "no list", never silently skipped on a
member-name mismatch.
Guard: share_tx_refs_kat_test pins the topology (flat spelling absent on every
variant, nested present exactly on v17/v33, accessor null-branch for v34+) and
the runtime delivery (v17/v33 list forwarded by reference, v34+ forward nothing).
Registered in the BCH test CMake list and both COIN_BCH build.yml --target
lists (main + AsAN legs). Share wire-format unchanged; p2pool-merged-v36
surface: none.
frstrtr
added a commit
that referenced
this pull request
Jul 27, 2026
…coin new_tx_hashes SSOT The old flat m_new_transaction_hashes probe was FALSE for every share variant (v17/v33 nest the refs in m_tx_info; v34+ carry none), so the F3 gate collected empty refs and treated every share as vacuously backable -- it never withheld an incomplete share on any version. Route each coin through its SSOT accessor from #913/#914 (btc/bch::new_tx_hashes, dgb::append_share_tx_refs) so the gate sees the real refs. Adds BtcBroadcastGate.RealShareTxInfoRefsWithheld driving a REAL v17/v33 share (not a top-level mock); mutation-verify confirms it goes red when the probe is reverted to the dead flat form.
frstrtr
added a commit
that referenced
this pull request
Jul 29, 2026
… was sent (#880) * btc/dgb/bch: gate share broadcast on tx completeness + mark only what was sent Two reward-critical share-loss fixes, ported to BTC, DGB and BCH from the already-correct DASH implementation (src/impl/dash/node.cpp). F3 -- tx-completeness broadcast gate. send_shares() collected the referenced new-tx bytes with `if (it != m_known_txs.end()) full_txs.emplace_back(...)` and no else branch: a tx we do not hold was silently omitted and the share was written to the peer anyway. Canonical p2pool then drops the connection on "referenced unknown transaction" (p2p.py), which isolates us from the sharechain and orphans our shares. Shares pulled via sharereply arrive without their tx bytes, so this is reachable in normal operation. send_shares now filters the outgoing batch through partition_backable() (gate on the BYTES in m_known_txs, not on the peer's possibly-stale have_tx advert) and the now-unreachable omission path increments a counter that logs a warning. F2 -- mark only what was sent. broadcast_share() inserted each walked hash into m_shared_share_hashes INSIDE the chain-walk loop, before send_shares ran. send_shares returned void and has abandon paths (tracker try_to_lock miss, empty batch, and now the F3 gate), and zero connected peers abandons the batch too. A marked-but-unsent share is lost permanently: the next walk breaks on the first marked hash, so nothing ever re-pushes it -- silent PPLNS-credit loss with no retry path. send_shares now returns the hashes it actually wrote and broadcast_share marks only those, after the peer loop, via broadcast_and_mark(). m_last_broadcast_to likewise records what was written rather than what was offered, so a withheld share is never blamed for a peer disconnect (which would mark it rejected and block all future re-broadcast). Consensus surface: NONE. Both changes decide only WHETHER a share goes on the wire. Share bytes, minting, PPLNS weighting and payout are untouched, and a withheld share stays in the chain and is re-offered once its txs arrive. Primitives follow the per-coin header pattern established for BTC in #868: src/impl/{btc,dgb,bch}/known_txs_retention.hpp. The LTC lane is concurrently hoisting all_txs_backable into src/core/; these four per-coin headers (including dash) should be collapsed onto that hoist once both land. Tests ride existing allowlisted CI targets -- no new add_executable, no build.yml change: btc -> btc_share_test (broadcast_gate_test.cpp) dgb -> dgb_share_test (broadcast_gate_test.cpp, known_txs_retention_test.cpp) bch -> bch_embedded_block_broadcast_test (broadcast_gate_test.cpp; the bch test tree has no GTest harness, so the TU exposes a checks function the host main() calls) Mutation-verified red: deleting the gate reddens 2 btc / 4 bch assertions; restoring mark-before-send reddens 4 btc / 5 bch assertions. * dgb: rescope to the reachable readvertise path; label broadcast_share pre-wiring A caller-side reachability trace shows DGB is not symmetric with btc/bch: main_dgb.cpp binds no create/mint-share fn, so dgb::NodeImpl::broadcast_share and notify_local_share have zero callers repo-wide -- DGB never mints a local share today (#884). DGB's live path into send_shares is readvertise_best_share() (ROOT-2 re-advert on best-change plus a 10s one-shot timer), which re-pushes PEER-RECEIVED shares and deliberately bypasses the de-dup set. Verified independently: set_mint_share_fn / set_create_share_fn / broadcast_share / notify_local_share have zero occurrences in main_dgb.cpp; the only set_mint_share_fn binding in the tree is main_dash.cpp:1854 plus dgb's own work_source_test. readvertise_best_share is called from node.cpp:1767 and 1774. BTC (main_btc.cpp:1336 after lk.unlock() at :1323) and BCH (pool_entrypoint.hpp:564 after lk.unlock() at :562) are both live and unchanged. Changes: - dgb: extract the readvertise recording rule into readvertise_and_record() and wire readvertise_best_share() through it. This is DGB's live reward-critical marking discipline: m_last_broadcast_to is what a peer disconnect within 10s converts into m_rejected_share_hashes, and the readvertise walk `continue`s past rejected hashes permanently. Recording the OFFERED batch would let a share the F3 gate merely withheld be blamed for someone else's drop and excluded from every future re-advert -- a propagation loss for the whole sharechain, since DGB re-advertises peer-received shares. - dgb: rescope the KATs onto that live path. The gate cases (partition_backable) and the new DgbReadvertiseRecording cases drive code production executes; the broadcast_and_mark cases are renamed DgbBroadcastMarkingPrewired_NotReachedToday so no reader mistakes them for live protection. Removes the broadcast_share de-dup-walk KAT, which modelled an unreachable function -- the shape of the LTC #873 miss. - dgb: label NodeImpl::broadcast_share in source as pre-wiring for #884. The F2 fix stays so the seam is correct when the mint path is bound and so the three coins remain symmetric, but it protects nothing at runtime today. - btc/bch: document the ROOT-2 interaction at readvertise_best(). Marking is now a strict subset of what the pre-fix code marked, so the walk breaks later or in the same place, never earlier: the re-advert re-pushes a superset of what it used to. Monotonically better, strictly better for head shares minted while no peer was connected. It does not fully close ROOT-2 here -- shares already accepted by another peer stay marked and the walk still breaks, as before. That needs the ltc/dgb-style de-dup-bypass readvertise; pre-existing, out of scope. No behaviour change in this commit for btc/bch. Mutation-verified red on the DGB live path: recording the offered batch instead of what was written reddens WithheldShareIsNeverBlamedForAPeerDrop; dropping the empty-send skip reddens PeerThatReceivedNothingIsNotRecorded; deleting the gate reddens 2 DgbBroadcastGate cases. * btc/bch/dgb: route F3 send-path tx-completeness gate through the per-coin new_tx_hashes SSOT The old flat m_new_transaction_hashes probe was FALSE for every share variant (v17/v33 nest the refs in m_tx_info; v34+ carry none), so the F3 gate collected empty refs and treated every share as vacuously backable -- it never withheld an incomplete share on any version. Route each coin through its SSOT accessor from #913/#914 (btc/bch::new_tx_hashes, dgb::append_share_tx_refs) so the gate sees the real refs. Adds BtcBroadcastGate.RealShareTxInfoRefsWithheld driving a REAL v17/v33 share (not a top-level mock); mutation-verify confirms it goes red when the probe is reverted to the dead flat form. * btc: mirror ltc broadcast lock-discipline trace; pin send-path reachability Instrument btc::NodeImpl::broadcast_share with the deferred/acquired/ reached-send counters and IsNull/contains guards that ltc already carries, and add broadcast_lock_discipline_test.cpp driving a REAL btc::NodeImpl against a populated sharechain. Proves the F3 send path (tx-completeness gate + send_shares + mark-after-send) is reachable, not dead code behind an always-held lock: reached_send is 0 when the caller holds the tracker mutex EXCLUSIVELY on the calling thread and 1 when it does not -- the shape main_btc create_share_fn guarantees by dropping the exclusive lock (lk.unlock) before calling broadcast_share. Rides the allowlisted btc_share_test target (no new add_executable, #769). --------- Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
send_shares()(src/impl/bch/node.cpp) decided which peer-referenced txs to forward by probingrequires { obj->m_new_transaction_hashes; }directly on the share object. That flat spelling isfalsefor every BCH share variant:bch::Sharem_tx_info.m_new_transaction_hashes(nested)bch::NewSharem_tx_info.m_new_transaction_hashes(nested)bch::SegwitMiningSharem_tx_info)bch::PaddingBugfixSharebch::MergedMiningShareSo the entire
needed_txs/remember_txforwarding block was unreachable — BCH forwarded no tx bytes, and a relayed v17/v33 share referencing a tx the peer lacked was exactly the canonical "referenced unknown transaction" disconnect (#905). The receive side (protocol_actual.cpp, protocol_legacy.cpp, node.hpp) already spellsm_tx_info.m_new_transaction_hashescorrectly, which is why only the send side was dead.Fix
New
src/impl/bch/share_tx_refs.hpp—bch::new_tx_hashes(obj)returns the nested list for v17/v33 andnullptrfor v34+ (nothing to forward). Direct port ofltc::new_tx_hashes(#873, ced0d0b); BCH variant topology is identical.send_shares()now routes through it. A new variant is either handled in one of the two known spellings or reported as "no list" — never silently skipped on a member-name mismatch again.seam-2 (post-broadcast lock discipline) is a STRUCTURAL NO-OP for BCH
LTC #873 carried a second seam:
send_shares()was made to return the hashes it actually wrote, andbroadcast_sharewas reworked so the tracker lock is released before the mining-notify / broadcast fan-out (its ingest pathremember_txinserts intom_known_txsunder a different discipline, so reading that map mid-broadcast under the held lock was the hazard #873 closed).BCH does not need that change, and it is not ported here — established by inspection of the BCH broadcast path, not by analogy:
lk.unlock()on the tracker lock before the broadcast +mining_submitfan-out (pool_entrypoint.hpp:561); the broadcast runs on the io thread with no share-tracker lock held. The held-lock-across-broadcast ordering that ltc/doge: gate share broadcast on tx completeness, mark only what was sent #873 had to fix never existed on BCH.send_shares()isvoidand needs no return value: the LTC return-hashes plumbing existed only to let the reworkedbroadcast_sharemark what was actually sent under the new lock ordering. With no ordering to change, there is nothing to report back.i.e. the invariant #873's seam-2 established for LTC is one BCH already holds structurally. This PR is the seam-1 port only; there is no BCH seam-2 to land. (DGB shares the same seam-1 gap on its own lane and must check its own seam-2 separately; LTC is done via #873.)
Compatibility
Share wire-format unchanged — this is relay plumbing over the existing nested list. p2pool-merged-v36 surface: none.
Test
bch_share_tx_refs_kat_test(new) pins:Registered in
src/impl/bch/test/CMakeLists.txt(BCH_ABLA_TESTS) and both COIN_BCHbuild.yml--targetlists (main + AsAN legs). Local:bch_suite builds clean,c2pool-bchlinks, KAT (ctest #326) PASS.Merge held for integrator (push-approval card).