fix(utxo): register epoch mining-reward boxes as account-mirror provenance (danaher #2819, reward path) - #8394
Conversation
|
| Metric | Value |
|---|---|
| Trust Score | 36/100 |
| Certificate ID | BCOS-8022541e |
| Tier | L2 (not met) |
What does this mean?
The BCOS (Beacon Certified Open Source) engine scans for:
- SPDX license header compliance
- Known CVE vulnerabilities (OSV database)
- Static analysis findings (Semgrep)
- SBOM completeness
- Dependency freshness
- Test infrastructure evidence
- Review attestation tier
BCOS v2 Engine - Free & Open Source (MIT) - Elyan Labs
|
Heads-up before merging: the reward boxes this registration picks up don't equal the balance credits they shadow. The mint truncates the same Repro against main (real |
Two ways finalize_epoch's UTXO_DUAL_WRITE mint diverged from the account model it is supposed to mirror (#2819, favoritegrandson-tech): 1. Precision split: the account credit truncated the share to 6 decimals (amount_i64) while the mint truncated the same Decimal to 8 decimals, so each reward box was up to 99 nRTC larger than the credit and /utxo/integrity reported the models disagreeing after every epoch with fractional shares. The mint is now derived from the truncated credit: amount_nrtc = amount_i64 * (UTXO_UNIT // ACCOUNT_UNIT). A module-level assert pins that ratio as an exact integer. 2. Ghost mint: a miner enrolled without a balances row gets no account credit (no-phantom invariant) but was still minted a UTXO box. The mint is now gated on the account row actually being credited. This is a prerequisite for #8394 (registering reward boxes as mirrors must not create mirrors larger than the balance they mirror). Tests (end-to-end finalize_epoch, dual-write on): the reward box equals the credit x100 for co-prime weights, a ghost gets nothing, and the credited miner still mirrors exactly while a ghost dilutes the weight. Both fail on main (45 nRTC drift; 0.75 RTC ghost mint) and pass here. tests/ + epoch/UTXO/dual-write suites: 0 regressions (4192 -> 4194). UTXO_DUAL_WRITE is off and prod has 0 UTXO rows, so there is no history to reconcile. Reported-by: favoritegrandson-tech Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Scott <scottbphone12@gmail.com>
…nance (danaher #2819, reward path) Same cross-model double-spend class as the /utxo/transfer receiver residual, in the epoch settlement path. Under UTXO_DUAL_WRITE=1, finalize_epoch credits each miner's ACCOUNT balance (primary ledger) AND mints a UTXO reward box for them — but never registered those boxes in account_mirror_boxes. So each reward was spendable via BOTH models (UTXO box + account balance) = double spend; a total-only integrity check stays models_agree=True. Latent today (prod runs UTXO_DUAL_WRITE=0). Fix: - account_mirror_boxes is now part of the canonical UTXO SCHEMA_SQL, so init_tables() creates it for every dual-write writer. This avoids a CREATE TABLE inside finalize_epoch's open settlement transaction, which Python's sqlite3 would implicit-commit (splitting the atomic epoch settle). (Comment in SCHEMA_SQL is kept free of the ';' character — _execute_schema splits on it.) - finalize_epoch registers every reward box of each dual-write batch into account_mirror_boxes (located by the batch's creation_height; apply_transaction enforces one mining_reward per height, so all boxes at that height are exactly the batch's outputs). Pure INSERTs, same connection/transaction as the settle. The mirror-input exclusion then blocks re-spending these boxes via the UTXO path, forcing the reward to move through the account path (single spend). Tests (node/test_utxo_dualwrite_reward_provenance.py): init_tables creates the table; a registered reward box is excluded from UTXO spendable candidates (double-spend closed) while an unregistered one is not (control); finalize_epoch wires the registration inside the dual-write block (source/AST, matching test_epoch_utxo_dual_write_guard's convention). All fail on origin/main, pass here. UTXO + settlement suites: 165 passed, no new failures (2 pre-existing genesis-migration failures are identical on origin/main). Companion to PR #8388 (transfer receiver residual). Keep UTXO_DUAL_WRITE=0 on prod until both merge. danaher-j #2819 bounty remains held pending destination. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbyXP4eiiRYEa8GsQtQPhR
The reward-mirror registration selected every box at batch_height. The
one-mining_reward-per-height guard only limits MINT transactions: an
ordinary /utxo/transfer box can share that height (both use slot numbers),
and would be tagged as an account mirror, locking the user's own funds
(every spend -> 409 ACCOUNT_MIRROR_BOX_NOT_SPENDABLE). Join on the
mining_reward tx so only this batch's mint outputs are registered.
Materialize the rows (bounded by UTXO_MAX_OUTPUTS) because the INSERTs
reuse the same cursor.
End-to-end test (real finalize_epoch, dual-write on, a user transfer box
at the batch height): the user box is not tagged, each reward box is
registered once, and each mirror equals its account credit exactly
(needs the precision fix underneath). Fails on the previous height-only
query ('user-box' tagged) and on main (nothing registered).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Scott <scottbphone12@gmail.com>
124ff24 to
3f73034
Compare
|
Updated (rebased + tightened).
Regression check against the #8481 base (CI |
…8481) Two ways finalize_epoch's UTXO_DUAL_WRITE mint diverged from the account model it is supposed to mirror (#2819, favoritegrandson-tech): 1. Precision split: the account credit truncated the share to 6 decimals (amount_i64) while the mint truncated the same Decimal to 8 decimals, so each reward box was up to 99 nRTC larger than the credit and /utxo/integrity reported the models disagreeing after every epoch with fractional shares. The mint is now derived from the truncated credit: amount_nrtc = amount_i64 * (UTXO_UNIT // ACCOUNT_UNIT). A module-level assert pins that ratio as an exact integer. 2. Ghost mint: a miner enrolled without a balances row gets no account credit (no-phantom invariant) but was still minted a UTXO box. The mint is now gated on the account row actually being credited. This is a prerequisite for #8394 (registering reward boxes as mirrors must not create mirrors larger than the balance they mirror). Tests (end-to-end finalize_epoch, dual-write on): the reward box equals the credit x100 for co-prime weights, a ghost gets nothing, and the credited miner still mirrors exactly while a ghost dilutes the weight. Both fail on main (45 nRTC drift; 0.75 RTC ghost mint) and pass here. tests/ + epoch/UTXO/dual-write suites: 0 regressions (4192 -> 4194). UTXO_DUAL_WRITE is off and prod has 0 UTXO rows, so there is no history to reconcile. Reported-by: favoritegrandson-tech Signed-off-by: Scott <scottbphone12@gmail.com> Co-authored-by: Scott <scottbphone12@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
RTC RewardThis merged PR earned 5 RTC — sent to |
…venance (#8388) * fix(utxo): dual-write reward boxes exactly mirror the account credit Two ways finalize_epoch's UTXO_DUAL_WRITE mint diverged from the account model it is supposed to mirror (#2819, favoritegrandson-tech): 1. Precision split: the account credit truncated the share to 6 decimals (amount_i64) while the mint truncated the same Decimal to 8 decimals, so each reward box was up to 99 nRTC larger than the credit and /utxo/integrity reported the models disagreeing after every epoch with fractional shares. The mint is now derived from the truncated credit: amount_nrtc = amount_i64 * (UTXO_UNIT // ACCOUNT_UNIT). A module-level assert pins that ratio as an exact integer. 2. Ghost mint: a miner enrolled without a balances row gets no account credit (no-phantom invariant) but was still minted a UTXO box. The mint is now gated on the account row actually being credited. This is a prerequisite for #8394 (registering reward boxes as mirrors must not create mirrors larger than the balance they mirror). Tests (end-to-end finalize_epoch, dual-write on): the reward box equals the credit x100 for co-prime weights, a ghost gets nothing, and the credited miner still mirrors exactly while a ghost dilutes the weight. Both fail on main (45 nRTC drift; 0.75 RTC ghost mint) and pass here. tests/ + epoch/UTXO/dual-write suites: 0 regressions (4192 -> 4194). UTXO_DUAL_WRITE is off and prod has 0 UTXO rows, so there is no history to reconcile. Reported-by: favoritegrandson-tech Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Scott <scottbphone12@gmail.com> * fix(utxo): register epoch mining-reward boxes as account-mirror provenance (danaher #2819, reward path) Same cross-model double-spend class as the /utxo/transfer receiver residual, in the epoch settlement path. Under UTXO_DUAL_WRITE=1, finalize_epoch credits each miner's ACCOUNT balance (primary ledger) AND mints a UTXO reward box for them — but never registered those boxes in account_mirror_boxes. So each reward was spendable via BOTH models (UTXO box + account balance) = double spend; a total-only integrity check stays models_agree=True. Latent today (prod runs UTXO_DUAL_WRITE=0). Fix: - account_mirror_boxes is now part of the canonical UTXO SCHEMA_SQL, so init_tables() creates it for every dual-write writer. This avoids a CREATE TABLE inside finalize_epoch's open settlement transaction, which Python's sqlite3 would implicit-commit (splitting the atomic epoch settle). (Comment in SCHEMA_SQL is kept free of the ';' character — _execute_schema splits on it.) - finalize_epoch registers every reward box of each dual-write batch into account_mirror_boxes (located by the batch's creation_height; apply_transaction enforces one mining_reward per height, so all boxes at that height are exactly the batch's outputs). Pure INSERTs, same connection/transaction as the settle. The mirror-input exclusion then blocks re-spending these boxes via the UTXO path, forcing the reward to move through the account path (single spend). Tests (node/test_utxo_dualwrite_reward_provenance.py): init_tables creates the table; a registered reward box is excluded from UTXO spendable candidates (double-spend closed) while an unregistered one is not (control); finalize_epoch wires the registration inside the dual-write block (source/AST, matching test_epoch_utxo_dual_write_guard's convention). All fail on origin/main, pass here. UTXO + settlement suites: 165 passed, no new failures (2 pre-existing genesis-migration failures are identical on origin/main). Companion to PR #8388 (transfer receiver residual). Keep UTXO_DUAL_WRITE=0 on prod until both merge. danaher-j #2819 bounty remains held pending destination. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbyXP4eiiRYEa8GsQtQPhR * fix(utxo): register only this epoch's mint outputs as reward mirrors The reward-mirror registration selected every box at batch_height. The one-mining_reward-per-height guard only limits MINT transactions: an ordinary /utxo/transfer box can share that height (both use slot numbers), and would be tagged as an account mirror, locking the user's own funds (every spend -> 409 ACCOUNT_MIRROR_BOX_NOT_SPENDABLE). Join on the mining_reward tx so only this batch's mint outputs are registered. Materialize the rows (bounded by UTXO_MAX_OUTPUTS) because the INSERTs reuse the same cursor. End-to-end test (real finalize_epoch, dual-write on, a user transfer box at the batch height): the user box is not tagged, each reward box is registered once, and each mirror equals its account credit exactly (needs the precision fix underneath). Fails on the previous height-only query ('user-box' tagged) and on main (nothing registered). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Scott <scottbphone12@gmail.com> * fix(utxo): register dual-write transfer outputs as account-mirror provenance (danaher #2819 receiver residual) Under UTXO_DUAL_WRITE=1, `balances` (account) is the primary ledger and UTXO boxes are its shadow; `account_mirror_boxes` records which boxes back account value, with the consensus invariant `mirror <= balance`. The mirror-input exclusion already blocks spending a mirror box via the UTXO path (input side, fixed earlier). The RESIDUAL: a /utxo/transfer credits the receiver's account balance AND creates a spendable UTXO output box for the receiver, but never registered that output as mirror provenance — so the same value was spendable via BOTH models (UTXO output box + account credit) = double spend. A total-only integrity check stays models_agree=True throughout (danaher-j private report, #2819 residual; latent today: UTXO_DUAL_WRITE is off on prod). Fix: - utxo_db.apply_transaction now exposes the authoritative tx_id on the caller's tx dict (tx['tx_id']), so the endpoint can locate the boxes it created. - The dual-write branch of /utxo/transfer registers every output box of the transfer (receiver at index 0, change at index 1) into account_mirror_boxes, so the unconditional mirror-input exclusion blocks re-spending them via UTXO; the value must move via the account path (which consumes the mirror on settle). - Added a fail-closed per-wallet `mirror <= balance` assertion (compared in nRTC: mirror value_nrtc vs balance amount_i64 * (UNIT//ACCOUNT_UNIT)); a violation rolls the transfer back rather than committing money that exists twice. Tests: node/test_utxo_dualwrite_receiver_provenance.py (3 tests) — receiver output is mirror-registered, receiver box is excluded from UTXO spendable candidates (double-spend closed), invariant holds. All 3 fail on origin/main and pass with this change. Existing UTXO suites: 154 passed, no new failures (17 pre-existing POC/isolation failures are identical on origin/main). RELATED (not fixed here, flagged for follow-up): the epoch mining-reward dual-write path (rustchain_v2_integrated...:~5698) likewise creates UTXO reward boxes without account_mirror_boxes provenance — same class, same latency behind UTXO_DUAL_WRITE=0. Keep UTXO_DUAL_WRITE=0 on prod until both are resolved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbyXP4eiiRYEa8GsQtQPhR * test(utxo): update receiver-provenance tests for canonical recipients; annotate bounded fetch - The tests used 'bob' as the recipient. /utxo/transfer format-checks to_address since #8396/#8465 (lower-case RTC + 40 hex), so the transfer returned 400 before reaching the code under test. Use a canonical address. - test_dual_write_exact_balance_goes_to_zero seeded 100 RTC of UTXO against a 10 RTC account. With receiver/change outputs registered as account mirrors, the 90 RTC change exceeds the (now 0) account balance and the MIRROR_EXCEEDS_BALANCE guard correctly fails closed. Seed the consistent dual-write state (UTXO == account) the invariant assumes. - Annotate the per-transfer output fetch for the fetchall guard (bounded by one transfer's outputs). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Scott <scottbphone12@gmail.com> --------- Signed-off-by: Scott <scottbphone12@gmail.com> Co-authored-by: Scott <scottbphone12@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ses (#8485) finalize_epoch() mints UTXO reward boxes under UTXO_DUAL_WRITE, but production does not settle through it: cron -> POST /rewards/settle -> settle_epoch (= settle_epoch_rip200), which had no UTXO code at all. Rehearsed on a snapshot of the live DB with dual-write on: accounts were credited 1,500,000 uRTC and ZERO boxes were minted, i.e. the UTXO model falls a whole epoch pot (1.5 RTC) behind every epoch. Safe direction (mirror <= balance still holds) but /utxo/integrity would report the models disagreeing, growing daily. Mint from epoch_rewards, which BOTH settlement branches write, and hook both: the standard path and the anti-double-mining path, which returns early and is the branch production actually takes (rehearsal: ADM). Values come from the credited uRTC x100, never re-truncated (#8481), and each box is registered as an account mirror, joined on the mining_reward tx so a user's /utxo/transfer box at the same height is never tagged (#8394). Failure raises -> the caller rolls back -> the epoch stays unsettled, rather than committing a half-mirrored settlement. utxo_db.apply_transaction: set (and restore) row_factory on a caller connection. settle_epoch_rip200 opens a plain sqlite3.connect, and the reads inside apply_transaction use row['col'] -> TypeError. Found by the new tests, not in review. Rehearsal after the fix: ADM path minted 8 boxes = exactly the 150,000,000 nRTC credited, all mirrored, 0 mirror>balance, 0 unmirrored. 0 regressions (4216 -> 4219); fetchall guard 11/11. Signed-off-by: Scott <scottbphone12@gmail.com> Co-authored-by: Scott <scottbphone12@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Companion to #8388. Closes the same cross-model double-spend class in the epoch settlement path (found while fixing the
/utxo/transferreceiver residual).The bug
Under
UTXO_DUAL_WRITE=1,finalize_epochcredits each miner's account balance (the primary ledger) and mints a UTXO reward box for them — but never registered those boxes inaccount_mirror_boxes. So each mining reward was spendable via both models (UTXO box + account balance) = double spend. A total-only integrity check staysmodels_agree=True. Latent today (prod runsUTXO_DUAL_WRITE=0).Fix
account_mirror_boxesis now canonical UTXO schema (SCHEMA_SQL), soinit_tables()creates it for every dual-write writer. This deliberately avoids aCREATE TABLEinsidefinalize_epoch's open settlement transaction — Python'ssqlite3issues an implicit COMMIT before DDL, which would split the atomic epoch settle. (The schema comment is kept free of the;character, since_execute_schemasplitsSCHEMA_SQLon it — a real gotcha caught in testing.)finalize_epochregisters every reward box of each dual-write batch intoaccount_mirror_boxes, located by the batch'screation_height(apply_transaction enforces onemining_rewardper height, so all boxes at that height are exactly the batch's outputs). Pure INSERTs on the same connection/transaction.The mirror-input exclusion then blocks re-spending these boxes via the UTXO path, forcing the reward through the account path (single spend).
Tests (
node/test_utxo_dualwrite_reward_provenance.py)init_tablescreatesaccount_mirror_boxes;finalize_epochwires the registration inside the dual-write block (source/AST, matchingtest_epoch_utxo_dual_write_guard's convention).All fail on
origin/main, pass here. UTXO + settlement suites: 165 passed, no new failures (2 pre-existing genesis-migration failures are identical onorigin/main).Notes
UTXO_DUAL_WRITE=0on prod until both land.🤖 Generated with Claude Code
https://claude.ai/code/session_01KbyXP4eiiRYEa8GsQtQPhR