Skip to content

core: pluggable address decoder hook + standalone-coin authorize work-resend (BCH G3b served-coinbase) - #585

Merged
frstrtr merged 2 commits into
masterfrom
bch/g3b-core-cashaddr-resend
Jul 2, 2026
Merged

core: pluggable address decoder hook + standalone-coin authorize work-resend (BCH G3b served-coinbase)#585
frstrtr merged 2 commits into
masterfrom
bch/g3b-core-cashaddr-resend

Conversation

@frstrtr

@frstrtr frstrtr commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Summary

Two src/core seams behind the BCH G3b served-coinbase result. Both are required so a miner CashAddr username resolves to a real payout output instead of a value-0 OP_RETURN coinbase (which BCHN rejects as bad-txns / BIP30). Bundled because both are needed for the same served-coinbase outcome; each seam has its own justification below.

This is an exceptional core/shared change (touches src/core/address_utils + src/core/stratum_server.cpp, the shared stratum path for every coin). NOT eligible for self-merge or auto-merge — surfaced to integrator for operator tap. Not requesting merge until the gates below are green.

Seam (a) — coin-registered address decoders (src/core/address_utils.{hpp,cpp})

address_to_script() now consults coin-registered fallback decoders after its built-in bech32/base58 formats fail. Core stays format-agnostic and holds no coin-specific address knowledge: a decoder is an opaque functor string -> scriptPubKey, returning {} to mean "not my format, try the next one". Thread-safe registry, additive, and inert until a coin calls register_address_decoder().

BCH supplies its CashAddr decoder in src/impl/bch/coin/cashaddr.hpp (header-only, included here). Pre/post evidence on the BCH harness: address_to_script("bchtest:q...") returned {} before the decoder was registered and the correct P2PKH scriptPubKey after — proving the fallback is clean and built-ins are untouched.

Seam (b) — authorize work-resend gate (src/core/stratum_server.cpp)

The post-authorize work-resend was gated on !merged_addresses_.empty(). Standalone parent coins (BCH/BTC — no merged chains) never populate merged_addresses_, so they kept mining the pre-authorize job, whose empty username_ produced an empty payout_script -> degenerate coinbase. The gate now fires when the now-known username_ or merged addrs can change the coinbase.

Single-fire (all coins): the change is a single guard around a single send_notify_work(true) — no second send added. The resend fires exactly once in every case.

Corrected new-firing scope (the prior "standalone-parent only" claim was incomplete — there are TWO new-firing classes):

  1. Standalone parent (BCH/BTC, no merged chains) — username_ set, merged_addresses_ empty.
  2. P2WSH/P2TR-primary LTC/DOGE (Cases 7-9) — a 32-byte witness program cannot reduce to a 20-byte hash160, so merged_addresses_ is not auto-derived even though username_ is set. Under the OLD merged-only gate these miners got no post-authorize resend and kept mining the pre-authorize degenerate value-0 coinbase. The widened gate now resends once (force_clean) so their coinbase carries the real payout — address_to_script() returns 00 20 <32B> (P2WSH) / 51 20 <32B> (P2TR). This is a latent-bug fix for taproot/segwit-script LTC/DOGE miners: a genuine, correct LTC/DOGE behavior change, not a no-op.

For a P2PKH/P2WPKH/P2SH merged primary, merged_addresses_ is populated on authorize, so the new disjunction is true for the same reason the old condition was — identical coinbase, behavior unchanged. ltc-doge-production-steward verified single-fire / no double-notify against the live serve path (handle_authorize, single send_notify_work(true), same coinbase build). Deterministic matrix coverage including P2WSH/P2TR LTC primaries lands in test/test_address_resolution.cpp Suite 8 (ResendGate.*, 7/7): asserts exactly-one-fire, merged-empty on the non-convertible path, the real 34-byte witness payout, and that the old merged-only gate would have stayed silent (regression witness).

Gates (pending — do not merge until green)

  • Full rollup CLEAN (not UNSTABLE): Linux / ASan-UBSan / Windows + all 4 coin smokes (ltc/doge/dgb/dash) + bch
  • Single-fire resend matrix incl. P2TR/P2WSH LTC + standalone-parent (test/test_address_resolution.cpp Suite 8 ResendGate.*, 7/7 PASS) + ltc-doge live serve-path sign-off (no double-notify, identical coinbase build)

Outstanding (tracked, NOT in this PR)

  • BCH stratum serve-path decoder registration (rides BCH M5 pool-serve landing; the work_source.cpp call-site is not on master)
  • Live won-block-lands-on-BCHN leg — bitaxe rig allocation (routed to vm-fleet)

frstrtr and others added 2 commits June 27, 2026 16:36
…dalone-coin payout

Two core seams behind the BCH G3b served-coinbase result; both required so a
miner CashAddr username resolves to a real payout output instead of a
value-0 OP_RETURN coinbase (which BCHN rejects as bad-txns / BIP30).

Seam (a) -- coin-registered address decoders (src/core/address_utils):
  address_to_script() consults coin-registered fallback decoders AFTER its
  built-in bech32/base58 formats fail. Core stays format-agnostic and holds
  NO coin-specific address knowledge: a decoder is an opaque functor
  string -> scriptPubKey, {} meaning "not my format, try the next". Additive
  and inert until a coin calls register_address_decoder(). BCH supplies its
  CashAddr decoder (src/impl/bch/coin/cashaddr.hpp, header-only); the stratum
  serve-path call-site lands with the BCH M5 pool-serve work.

Seam (b) -- authorize work-resend gate (src/core/stratum_server.cpp):
  the post-authorize work-resend was gated on !merged_addresses_.empty().
  Standalone parent coins (BCH/BTC: no merged chains) never populate
  merged_addresses_, so they kept mining the pre-authorize job whose empty
  username_ produced an empty payout_script -> degenerate coinbase. Gate now
  fires when the now-known username_ OR merged addrs can change the coinbase.
  Single guard, single send_notify_work(true): for merged coins (LTC/DOGE)
  merged_addresses_ is populated on authorize so the disjunction holds for the
  same reason as before -> fires exactly once, identical coinbase. The only
  new-firing case is the standalone-parent path, which cannot affect merged
  coins.

Outstanding (tracked, not in this PR): BCH stratum serve-path decoder
registration (M5 pool-serve), and the live won-block-lands-on-BCHN leg
(bitaxe rig allocation).
…e-fire matrix

The post-authorize work-resend gate (PR #585 seam b) widened from
merged-only to (username non-empty OR merged non-empty). The prior
rationale understated the new-firing scope as standalone-parent only.

A P2WSH/P2TR primary carries a 32-byte witness program that cannot
reduce to a 20-byte hash160, so merged_addresses_ is never auto-derived
even on a merged coin (LTC/DOGE) whose username_ IS set. Under the old
merged-only gate these miners got no resend and kept mining the
pre-authorize degenerate value-0 coinbase; the widened gate now resends
once (force_clean) so the coinbase carries the real witness-script
payout. This is a latent-bug fix for taproot/segwit-script LTC/DOGE
miners, not a no-op.

- stratum_server.cpp: corrected gate comment to record BOTH new-firing
  classes (standalone-parent AND P2WSH/P2TR-primary LTC/DOGE).
- test/test_address_resolution.cpp: Suite 8 ResendGate single-fire
  matrix mirroring handle_authorize across the full username set incl.
  P2WSH/P2TR LTC primaries; asserts exactly-one-fire, merged-empty for
  the non-convertible path, real 34-byte witness payout, and that the
  old merged-only gate would have stayed silent (regression witness).
@frstrtr
frstrtr merged commit 2cedd38 into master Jul 2, 2026
32 of 42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant