mint: automatic reward trigger with reorg-safe epoch boundaries #41
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/reward-trigger"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Until now someone had to tell the mint by hand when a new mining epoch starts. With this change the mint watches the Bitcoin chain itself: when a block reward lands on the mint's address, that block is the boundary, the old epoch closes and a new one opens. Epoch state lives inside the mint's own database, written in the same transaction as the quote payments it goes with. The five regtest scenarios from
docs/EPOCH_DESIGN.mdall pass on a live dev stack against this exact code.How it works
The watcher. A background loop wakes every second (ten in prod). It asks the node for the current height, checks that the blocks it already saw are still on the chain, reads any new blocks, and looks at each coinbase for a payment to the mint's address. That is the only kind of payment it reacts to.
Provisional epochs. A fresh block can still be orphaned, so a new epoch starts in a waiting state. Shares mined meanwhile still earn quotes, but the quotes stay unpaid. Once the boundary block has enough confirmations (
confirmation_depth: 3 in dev, 6 in prod) the epoch becomes final and all its quotes are paid at once. Nobody gets spendable ecash backed by a block that could still disappear.Reorgs. If the boundary block is replaced by another block at the same height that also pays the mint, the epoch is kept and points at the new block. If it is replaced by a block that does not pay the mint, the epoch is dissolved: its unpaid quotes move back into the previous epoch and are paid there, since the work was real even though the block was not. Before that happens the mint checks that the dissolved epoch's keyset never issued anything; if it has, the watcher stops and logs a critical error rather than guess.
Where the state lives. In cdk's key-value table inside
mint.sqlite, under thehashpoolnamespace: one value for the epoch records, one for the watcher's position. Finalizing an epoch pays its quotes and marks it final in a single transaction, so a crash leaves the database either fully before or fully after. There is no side file. (A leftoverepochs.jsonfrom a pre-merge build is refused on a fresh database and warned about otherwise;just clean cashuis the clean slate.)One hole closed. cdk serves an HTTP route that lets anyone create an ehash quote for any amount. Since finalizing pays every unpaid quote in the epoch, that would be free money, so that one route is now refused. Only ehash is affected: ehash quotes are created solely by the pool over SV2. Quote creation for bitcoin units (
bolt11,bolt12) and every other route, including the quote lookup and batch check the proxy depends on, stay open, so an operator can serve sats beside ehash.The manual lever.
rotate-epochstill works but refuses while an epoch is provisional, so it cannot stomp on a boundary the watcher is tracking.Known limits
Risk
R3: persistent state in the mint database and money paths, running unattended. Rollback is a revert plus restart; the two stored values are additive and old code never read them. What a reviewer can still catch: a write in
finalize,dissolveoropen_epochthat escapes the lock or the transaction, or any path that reachesderegister_payment_processor(closing an epoch must retire, never deregister, or its paid quotes become unmintable).How it was checked
epochs.jsonguard, and the route classifier (blocked ehash forms, allowed everything else).currentpublish race; all fixed. Two findings were wrong and are recorded as such in the commit history and invnprc/notes/hashpool/epoch-reward-trigger-validation.md, which has the full run logs.Closes vnprc/hashpool#18. Closes vnprc/hashpool#36 (superseded: the store moved into the database). Refs vnprc/hashpool#13.