Skip to content

bch(m5): share + pool p2p + embedded-daemon entrypoint (M5 merge — 12 commits, M3/M4 absorbed) - #133

Merged
frstrtr merged 20 commits into
masterfrom
bch/m5-share-layer
Jun 18, 2026
Merged

bch(m5): share + pool p2p + embedded-daemon entrypoint (M5 merge — 12 commits, M3/M4 absorbed)#133
frstrtr merged 20 commits into
masterfrom
bch/m5-share-layer

Conversation

@frstrtr

@frstrtr frstrtr commented Jun 17, 2026

Copy link
Copy Markdown
Owner

bch(m5): share + pool p2p + embedded-daemon entrypoint — purpose-built merge PR

Repurposed from the prior "held pile" framing. This PR is scoped to exactly the M5 code that should land on master. M3 and M4 are already merged, so nothing from those milestones lands here.

Verified bookkeeping

Exactly what lands (origin/master..6afa896d, 12 commits, all src/impl/bch/ + shared src/core/coin/merkle.hpp)

6afa896d wire CoinNode seam into EmbeddedDaemon — embedded-primary + RPC fallback
a77ccb98 EmbeddedDaemon entrypoint assembly — own+wire coin::Node/EmbeddedCoinNode/AblaRuntime
e464cffd AblaRuntime — close embedded-daemon ABLA size loop
19c2b746 pin stratum conformance vs p2poolBCH @6603b79 (final sweep leg)
be5cbb2e disable SegWit in share format to conform with p2poolBCH
774426ee merkle-root accept gate before full_block emit; share merkle.hpp
e36871ef pin block-arrival emit conformance vs p2poolBCH @6603b79
fc592b80 emit full_block on blocktxn completion path
8965c9aa M5 sD — AblaBlockFeed wires full_block event to ABLA size tracker
fa243fad M5 sC — live full-block size feed drives ABLA template budget
c15f3926 pool share node + monitor — node.hpp + pool_monitor.hpp (M5 slice 1B)
8b0e7d8d share/pool p2p protocol scaffold — peer + messages (M5 slice 1)

Properties

  • Head 6afa896d, base master, mergeStateStatus CLEAN / MERGEABLE.
  • CI fully green (all 17 checks pass, incl. all-coin smoke + ASan/UBSan).
  • Conformant vs frstrtr/p2poolBCH @6603b79 (share serialization, stratum, emit-completeness, PPLNS unchanged).
  • Zero p2pool-merged-v36 surface — BCH is a standalone SHA256d parent; additive only.
  • All commits GPG-signed; no third-party attribution.

frstrtr added 11 commits June 17, 2026 18:54
Lay the BCH share-layer p2p protocol skeleton against the M3 core::coin
seam, mirroring the btc share layer. Source-only; no impl_bch CMake
registration (bch stays skip-green, off ci-steward critical path).

peer.hpp: per-connection peer state (versions, services, remote/remembered
txs). messages.hpp: p2pool wire protocol (version/ping/addr*/shares/
sharereq/sharereply/bestblock/*_tx) — kept byte-compatible with
p2pool-merged-v36 (p2pool/p2p.py).

BCH divergence: no SegWit/witness, so message_remember_tx serializes
coin::MutableTransaction plainly (no TX_WITH_WITNESS wrapper); confirmed
against bitcoincash.py (no SEGWIT_ACTIVATION_VERSION) and coin/transaction.hpp.
…slice 1B)

Mirrors the btc pool layer for the BCH SHA256d standalone-parent chain:

- node.hpp: pool::BaseNode<Config,ShareChain,Peer> share node — share_getter
  ReplyMatcher, known_txs, tracker/storage wiring, verified-hash LevelDB
  persistence via m_on_share_verified. SHA256d share-PoW verify (no scrypt);
  NO merged-mining / AuxPoW path (BCH is a standalone parent).
- pool_monitor.hpp: hashrate + difficulty monitor; coin-independent, drives
  off get_pool_attempts_per_second.
- share_tracker.hpp: add verified sub-chain (known_verified mirror) + node
  callback hooks (m_on_share_verified / m_on_block_found / m_on_share_difficulty;
  no merged-block callback — BCH has no aux leg) + port
  get_pool_attempts_per_second (p2pool-faithful work/target math).

Source-only; no impl_bch CMake registration (bch stays skip-green). All three
units -fsyntax-only clean under -std=c++20.
Re-home the ABLA size feed to the full-block/daemon layer so the template
builder builds to the live CHIP-2023-01 block-size limit, not just the
32 MB floor. ABLA is advanced by each block s actual serialized size, which
the headers-only SPV header_chain does not carry (M4 s3 pin); the running
State therefore cannot derive from the header chain and must be fed real
per-block sizes as full blocks arrive at the daemon layer.

- abla_tracker.hpp (new): AblaTracker folds live contiguous block sizes
  through abla::State::NextBlockState, keeps the running tip State, and
  hands a tip State* to build_template. Detects size-feed gaps and marks
  itself stale so the builder falls back to the floor until reanchor().
  floor_anchored() gives a safe cold start at the 32 MB activation State.
- template_builder.hpp: EmbeddedCoinNode gains an optional AblaTracker;
  getwork() passes the per-tip ABLA State to build_template when the feed
  is current at the tip, else nullptr -> the existing 32 MB floor budget.

Hard floor invariant: the dynamic budget is always >= the 32 MB floor
(ABLA only raises; absent/stale feed falls back outright). Validated by
roundtrip: cold start 32 MB, 5000 full blocks grow to 39.48 MB (matches
M4 s3 replay), mixed load never < floor, gap -> floor, testnet fixed 32 MB.

LOCAL build budget only: zero p2pool-merged-v36 surface (no PoW/share/
coinbase/PPLNS touch). Source-only, scope confined to src/impl/bch/coin/,
no impl_bch CMake registration (bch stays skip-green).
Closes the gap left by M5 slice C: AblaTracker::record_block_size existed
but nothing in-tree called it. AblaBlockFeed subscribes to
interfaces::Node::full_block and folds each best-chain block.s actual
serialized size into the tracker, resolving height from the HeaderChain
best-chain index (header sync precedes full-block download per BIP130).

Contiguous-tip-only by construction: blocks not on the indexed best chain
are skipped, and the tracker.s own gap-detect drops the budget to the
32 MB floor rather than fold an untrusted height. Reorg/skip can only ever
lower the budget to the safe floor, never raise it on bad data
(never-undercut invariant). Reanchor is a separate path.

p2pool-merged-v36 surface: NONE — local build-time block-size budget only;
never touches PoW hash, share format, coinbase commitment, or PPLNS.
Per-coin isolation: src/impl/bch/coin/ only. Header-only, build-inert
(no impl_bch CMake registration; bch stays skip-green).

-fsyntax-only OK (g++ -std=c++20).
The compact-block + getblocktxn round-trip reconstructed and delivered the
full block but never fired interfaces::Node::full_block, unlike the direct
block handler and the complete-path cmpctblock handler. Blocks arriving via
that path therefore never reached AblaBlockFeed, producing a height gap that
dropped the template size budget to the 32 MB safe floor. Emit the
reconstructed block on full_block so the live ABLA size feed covers all three
delivery paths (block / cmpctblock-complete / cmpctblock+blocktxn).
Record the emit-completeness conformance verdict in the p2p_node header.
The frstrtr/p2poolBCH baseline (@6603b79) delivers block arrivals via exactly
two events: handle_inv -> new_block.happened(hash) and handle_block ->
get_block.got_response(hash, block) -- a single full-block path, no broadcast
event, no compact-block handling. Our direct block handler mirrors that
ReplyMatcher surface; the two compact-block paths (cmpctblock-complete and
cmpctblock+blocktxn) are additive embedded-daemon coverage with no Python
analog, funneling into the same full_block sink. full_block is internal ABLA/
mempool plumbing -- zero p2pool-merged-v36 surface. Conformance documentation
only; no behavioral change.
….hpp

Validate every received/reconstructed block against its header merkle
commitment before publishing it on full_block. All three delivery paths
(direct block msg, compact-block-complete, compact+getblocktxn round-trip)
now funnel through emit_full_block(), which recomputes the SHA256d tx
merkle (BCH==BTC rule; CTOR only fixes order, already carried) and drops
any header/txs pair that does not cohere -- so a bogus serialized size can
never be folded into the ABLA feed. Mirrors BCHN CheckMerkleRoot at accept.

Extract merkle_hash_pair/compute_merkle_root into coin/merkle.hpp as the
single source shared by the GBT template builder (producer) and the
full-block accept path (consumer); template_builder.hpp now includes it.

Source-only / build-inert (no impl_bch CMake reg). p2pool-merged-v36
surface: NONE -- local parent-chain accept gate, no share/PoW/coinbase
commitment/PPLNS touch.
BCH rejected SegWit at the 2017 fork and the p2poolBCH oracle never emits a
segwit_data field in any share version: data.py:63-66 derives is_segwit_activated
from getattr(net, SEGWIT_ACTIVATION_VERSION, 0) and bitcoincash[_testnet].py
define no SEGWIT_ACTIVATION_VERSION, so it is False for all versions.

c2pool-bch had SEGWIT_ACTIVATION_VERSION = 17 (a stray BTC/LTC port), which
forced segwit_data into the share_info serialization and into the ref_hash for
every v17..v35 share -- a guaranteed byte-level divergence from p2poolBCH and
thus a sharechain fork against existing BCH history.

Set the sentinel to 0 (disabled) and gate is_segwit_activated() on it, mirroring
the oracle guard. Route the 6 raw ver >= SEGWIT_ACTIVATION_VERSION comparisons in
share_check.hpp through is_segwit_activated(ver) so the disabled sentinel is
honored consistently (serialization, ref_hash, gentx output count, merkle root).
Non-segwit fallback paths already exist at every site. Conformance fix; v17..v35
now byte-match the oracle. Legacy-version conformance only; v36 segwit handling
tracked separately. Zero p2pool-v36 surface beyond restoring it.
WorkData struct + getwork() GBT m_data emission feed the coin-agnostic
core/stratum_server notify path 1:1 with p2poolBCH stratum.py/data.py.
BCH-specific inputs (version=4 floor, 8-char-hex bits, no-segwit coinb2)
confirmed non-impacting on the notify wire-shape. Conformance-fixture
only; zero compile surface, zero p2pool-v36 surface. M5 sweep COMPLETE.
Owner that constructs the AblaTracker + AblaBlockFeed and wires them to a
live interfaces::Node and EmbeddedCoinNode. The four pieces existed in
isolation (tracker folds sizes, feed subscribes to full_block, embedded
node consumes the budget, full_block fires post merkle-accept-gate) but
nothing tied them together. After wire():

  full_block -> AblaBlockFeed -> AblaTracker -> EmbeddedCoinNode::getwork
             -> TemplateBuilder dynamic ABLA budget (else 32 MB floor).

Cold-start floor anchor (never undercuts the 32 MB floor; live sizes only
raise it); BCHN-pinned-anchor ctor + reanchor() passthrough for gap/reorg.
Tracker declared before feed so lifetimes are correct. Single-coin,
src/impl/bch/coin/ only; zero p2pool-merged-v36 surface. Source-only,
header-only, no impl_bch CMake registration (bch stays skip-green).
…eddedCoinNode/AblaRuntime

One owner per embedded daemon instance: constructs HeaderChain, Mempool,
EmbeddedCoinNode, coin::Node<Config> and AblaRuntime in lifetime order, runs
the node, and closes the ABLA size loop via AblaRuntime::wire(). Embedded
getwork() is the primary work source; the external BCHN-RPC fallback path
(coin::Node::init_rpc/m_rpc) is retained alongside per the per-coin
external_fallback invariant. Cold-start floor-anchored ABLA; the VM300
BCHN-pin reanchor is exposed as apply_bchn_anchor() and left for the
operator-gated anchor-capture step. Header-only, build-inert, src/impl/bch
only, zero p2pool-merged-v36 surface.
@frstrtr
frstrtr force-pushed the bch/m5-share-layer branch from 5adcbfa to a77ccb9 Compare June 17, 2026 18:56
…RPC fallback

EmbeddedDaemon owned EmbeddedCoinNode (work source) and Node (P2P +
external BCHN-RPC) but nothing constructed the core::coin::ICoinNode the
pool/web_server consumes. Build the CoinNode in run() once Node::rpc() is
live: embedded work source = primary, external BCHN-RPC = retained
fallback (v36 external_fallback invariant). Add Node::rpc() accessor.
Header-only, build-inert (bch skip-green); zero p2pool-v36 surface;
src/impl/bch/ only.
@frstrtr frstrtr changed the title bch(m5): share layer — held pile (13 slices, durability push, NOT for merge) bch(m5): share + pool p2p + embedded-daemon entrypoint (M5 merge — 12 commits, M3/M4 absorbed) Jun 17, 2026
frstrtr added 8 commits June 17, 2026 21:10
…300 anchor

Add BchnAnchorRecord: the static, read-only VM300 bchn-bch capture @955700
(height/hash/chainwork/time + ABLA control state, recorded once; the live VM
is never read here). EmbeddedDaemon::dry_run_bchn_anchor() logs exactly what
apply_bchn_anchor() would pin and confirms the recorded control state is still
at the 32 MB floor, so cold-start origin is the recorded anchor (not genesis)
while the AblaRuntime replay stays pinned to the safe floor. Real reanchor
stays operator-gated; surfaces a non-floor budget if a future capture shows one.

Embedded-internal, src/impl/bch/ only, zero p2pool-v36 / bitcoin_family surface.
…k invariants

Validates the three ICoinNode invariants web_server depends on against
coin_node.cpp: (1) no work source -> empty WorkView, no throw; (2) embedded
present -> WorkView sourced from embedded (is_embedded, !has_rpc); (3) no RPC
sink -> submit_block_hex returns false via guard, not a throw. The external
NodeRPC fallback leg is integration-only-by-design (not faked, to keep BCH
NodeRPC a 1:1 btc mirror). Source-only / build-inert: -fsyntax-only verified,
impl_bch stays unregistered (skip-green).
…s 32 MB floor

Pins the safety claim EmbeddedDaemon cold-start rests on: the dynamic build
budget can only EQUAL-OR-EXCEED the 32 MB activation/floor limit. Asserts
against AblaTracker: (1) floor_anchored cold-start budget == floor exactly;
(2) contiguous record advances cursor and budget stays >= floor (ABLA only
raises); (3) a height gap marks the cursor stale -> state_for_tip()==nullptr,
budget falls back to the floor, and further records are ignored until
reanchor(); (4) reanchor() restores a current State and re-enables folding.
Mainnet (growing) and testnet (fixed 32 MB no-op) both covered.

Source-only / build-inert: impl_bch stays unregistered (bch=skip-green).
Verified compile+run out of tree (ALL PASS), header-only against coin/abla*.hpp
— no node/RPC/boost. p2pool-merged-v36 surface: NONE.
Faithful header-only port of BCHN cashaddr.cpp + cashaddrenc.cpp into
bch::coin::cashaddr: base32 + BCH-code 40-bit PolyMod checksum, version-byte
{type,size} packing, ConvertBits 8<->5, and the bitcoincash/bchtest/bchreg
prefixes. Self-contained at the {type,hash}<->string seam (CashAddrContent) --
no chainparams/pubkey/script graph, so it is build-inert and unit-testable
out of tree.

BCH divergences carried: CashTokens token-aware address types (z/r,
CHIP-2022-02) and P2SH32 32-byte hashes (CHIP, May 2023) via the version
size bits, alongside legacy 20-byte P2PKH/P2SH.

cashaddr_test.cpp: 34 checks, vectors lifted verbatim from BCHN
cashaddrenc_tests.cpp + cashaddr_tests.cpp (the conformance oracle) --
anchored encode strings (P2PKH/P2SH + token variants), 20B/32B roundtrip,
rejection paths (corrupt checksum / wrong prefix / mixed case / bad length),
and checksum-only valid_strings. Compiles + runs ALL PASS standalone.

Operator/payout-address codec only -- share, sharechain, coinbase-commitment
and PPLNS layers serialize scripts, not address strings. p2pool-merged-v36
surface: NONE. impl_bch stays unregistered (bch = skip-green).
… pin raises

Pins the two claims EmbeddedDaemon::apply_bchn_anchor()/dry_run_bchn_anchor()
(48a4534) rest on: (A) the static VM300 capture @955700 is at the 32 MB floor,
so pinning it is a no-op vs the cold-start floor — is_floor() agrees with the
rebuilt abla::State on mainnet+testnet and the recorded limit field; (B) a
future ABOVE-floor capture, when reanchored, RAISES the build budget to its
live limit and never undercuts the floor. Standalone (abla*.hpp +
bchn_anchor_record.hpp; no HeaderChain/Node/RPC). Build-inert, impl_bch
unregistered. p2pool-merged-v36 surface: NONE.
… + off-chain-skip + dup-idempotent)

Pins the two load-bearing decisions abla_floor_invariant_test does not
exercise (it drives AblaTracker directly, bypassing the feed):
  (a) height resolved from HeaderChain index, never guessed
  (b) off-chain block (unindexed hash) dropped with NO record_block_size
      -> cursor never advanced on a height we cannot trust
Drives on_full_block against a real in-memory HeaderChain seeded so one
crafted block resolves to a known height: contiguous fold raises budget
(>= 32 MB floor), off-chain block = zero state change, duplicate = clean
idempotent ignore. Source-only (impl_bch unregistered; bch=skip-green).
compile+link+run ALL PASS. p2pool-merged-v36 surface: NONE.
…happy-path + ABLA at-tip/stale floor + real-node ICoinNode seam

Closes the getwork() coverage gap (seam test drove a fake; this exercises the
REAL EmbeddedCoinNode end-to-end against in-memory HeaderChain + empty Mempool).
Source-only / build-inert (impl_bch unregistered, bch=skip-green). p2pool-v36
surface: NONE — emits the coin-agnostic WorkData the sweep already pinned.
@frstrtr
frstrtr marked this pull request as ready for review June 18, 2026 08:08
@frstrtr
frstrtr merged commit ee7d405 into master Jun 18, 2026
17 checks passed
frstrtr added a commit that referenced this pull request Jun 18, 2026
…ed-body lane

Non-destructive merge of origin/master into bch/m5-embedded-body-wip to keep
the embedded daemon body lane on current master. No force-push, shared history
preserved. External-RPC fallback retained.
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