Reward trigger: mint-side chain watcher with reorg-safe epoch boundaries #18

Open
opened 2026-08-12 18:07:56 +01:00 by vnprc-agent · 0 comments
Collaborator

Build the trigger: the mint watches the chain, and a block reward landing on its receive script closes the current epoch and opens the next — provisionally at detection, final at D confirmations, correct under reorgs. Design: docs/EPOCH_DESIGN.md (reward detection and reorg sections).

Primary goals:

  • Chain watcher — a thin scanner over the node's RPC (reuse roles/roles-utils/rpc): on each new block, match coinbase outputs against the mint's configured receive script (script comparison, not address strings). Persist the last processed block and the last ~D hashes; on restart, re-scan forward — rewards seen during downtime open and close epochs in sequence, empty epochs allowed; dedupe reward events by height.
  • Reward event abstraction — the watcher emits a reward-received event (height, amount, pool identity, source) — on-chain the pool identity is the pubkey configured alongside the receive script, and coinbase is the one source now — an upstream pool paying over lightning slots in later.
  • Boundary state machine — detection calls #16's open_epoch (provisional; new quotes unpaid). At H+D with the boundary still canonical: mark final, bulk-pay the epoch's quotes, retire the previous epoch's registration. Reorg before finality with no replacement payment: dissolve — re-stamp the never-paid quotes to the previous epoch and pay them, splice the record, assert the dissolved keyset issued nothing (nonzero is a critical invariant violation). Same-height re-mine that pays us: keep the epoch, swap the recorded block hash, restart the confirmation clock. Never reopen a final epoch.
  • Config — receive script, confirmation depth D (low on regtest, 6+ production), poll interval. Residual risk is explicit: a reorg deeper than D after tokens exist is accepted, as with any pool's confirmation policy.

Current state and specifics

The mint has zero bitcoin connectivity today; jd-server's MiniRpcClient and its regtest RPC config are the reuse points. Not cdk-bdk: its detection is quote-bound, it drops the funding block height epochs are named by, and it has no coinbase awareness (custody can move to a CDK wallet in the settlement milestone; the trigger needs only a script to watch). Not pool-announced: proxy-built blocks bypass the pool, the pool gets no acceptance ack, and a lost message loses the event, while a payment on-chain waits for a re-scan.

Witness

The five regtest scenarios from the design doc: deterministic trigger (node+mint only, generatetoaddress 1 <mint address> → exactly one rotation, quotes flip paid at D); full-stack (pool coinbase pointed at the mint via #14, brief miner burst → the pool's own winning block rotates the epoch, epoch heights monotonic); catch-up (rewards mined while the mint is down); dissolve (invalidate the boundary block, extend the other branch → quotes re-stamped and paid, record spliced); same-height re-mine (epoch reused, new block hash, clock restarted). Reward assertions compute the subsidy from height (regtest halves every 150 blocks).

Scope

In: watcher, event abstraction, boundary state machine, config, the five scenarios. Out: pool→mint announce-then-verify, keyset id in the coinbase, settlement-side maturity handling, lightning source.

Part of #13. Gated on #16 and #17.

Build the trigger: the mint watches the chain, and a block reward landing on its receive script closes the current epoch and opens the next — provisionally at detection, final at D confirmations, correct under reorgs. Design: `docs/EPOCH_DESIGN.md` (reward detection and reorg sections). Primary goals: - **Chain watcher** — a thin scanner over the node's RPC (reuse `roles/roles-utils/rpc`): on each new block, match coinbase outputs against the mint's configured receive script (script comparison, not address strings). Persist the last processed block and the last ~D hashes; on restart, re-scan forward — rewards seen during downtime open and close epochs in sequence, empty epochs allowed; dedupe reward events by height. - **Reward event abstraction** — the watcher emits a reward-received event (height, amount, pool identity, source) — on-chain the pool identity is the pubkey configured alongside the receive script, and coinbase is the one source now — an upstream pool paying over lightning slots in later. - **Boundary state machine** — detection calls #16's `open_epoch` (provisional; new quotes unpaid). At H+D with the boundary still canonical: mark final, bulk-pay the epoch's quotes, retire the previous epoch's registration. Reorg before finality with no replacement payment: dissolve — re-stamp the never-paid quotes to the previous epoch and pay them, splice the record, assert the dissolved keyset issued nothing (nonzero is a critical invariant violation). Same-height re-mine that pays us: keep the epoch, swap the recorded block hash, restart the confirmation clock. Never reopen a final epoch. - **Config** — receive script, confirmation depth D (low on regtest, 6+ production), poll interval. Residual risk is explicit: a reorg deeper than D after tokens exist is accepted, as with any pool's confirmation policy. ### Current state and specifics The mint has zero bitcoin connectivity today; jd-server's `MiniRpcClient` and its regtest RPC config are the reuse points. Not cdk-bdk: its detection is quote-bound, it drops the funding block height epochs are named by, and it has no coinbase awareness (custody can move to a CDK wallet in the settlement milestone; the trigger needs only a script to watch). Not pool-announced: proxy-built blocks bypass the pool, the pool gets no acceptance ack, and a lost message loses the event, while a payment on-chain waits for a re-scan. ### Witness The five regtest scenarios from the design doc: deterministic trigger (node+mint only, `generatetoaddress 1 <mint address>` → exactly one rotation, quotes flip paid at D); full-stack (pool coinbase pointed at the mint via #14, brief miner burst → the pool's own winning block rotates the epoch, epoch heights monotonic); catch-up (rewards mined while the mint is down); dissolve (invalidate the boundary block, extend the other branch → quotes re-stamped and paid, record spliced); same-height re-mine (epoch reused, new block hash, clock restarted). Reward assertions compute the subsidy from height (regtest halves every 150 blocks). ### Scope In: watcher, event abstraction, boundary state machine, config, the five scenarios. Out: pool→mint announce-then-verify, keyset id in the coinbase, settlement-side maturity handling, lightning source. Part of #13. Gated on #16 and #17.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
vnprc/hashpool#18
No description provided.