fix(sophia): reject negative enroll weights that amplify epoch payouts (#14583)#7871
Merged
Scottcjn merged 1 commit intoJul 3, 2026
Conversation
…s (#14583) The /epoch/enroll endpoint parsed `temporal`/`rtc` weight factors with `_finite_float`, which accepts any finite value including negatives. The resulting `total_weight = temporal * rtc * hw` was stored verbatim, so a caller with a valid ticket could enroll a negative weight. At settlement, `finalize_epoch` computes `sum_w = sum(weights)`; a negative weight shrinks sum_w and inflates every other miner's pro-rata share (`w / sum_w`), enabling reward theft / denial-of-weight. This was the only enrollment path in the node that did not exclude non-positive weights — rip0202_enrollment and rustchain_block_producer already do (`weight <= 0` / "negative weights canonicalise to 0 units"). Fix (defense in depth): - /epoch/enroll rejects negative `temporal`/`rtc` and any `total_weight <= 0` with `invalid_weights`, before consuming the ticket. - enroll_epoch() backstops direct callers: non-finite / non-positive weights are never persisted. - finalize_epoch() excludes non-positive / non-finite weights from sum_w and payouts, so a poisoned legacy row cannot distort settlement. Adds tests covering the endpoint rejection (ticket preserved), the enroll_epoch backstop, and settlement exclusion of a poisoned negative row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
|
Welcome to RustChain! Thanks for your first pull request. Before we review, please make sure:
Bounty tiers: Micro (1-10 RTC) | Standard (20-50) | Major (75-100) | Critical (100-150) A maintainer will review your PR soon. Thanks for contributing! |
Contributor
RTC RewardThis merged PR earned 5 RTC — sent to |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #14583 (Critical: Reward Amplification via Negative Weight in SOPHIA).
Bug
/epoch/enrollparses thetemporal/rtcweight factors with_finite_float, which accepts any finite value, including negatives. The producttotal_weight = temporal * rtc * hwis stored verbatim byenroll_epoch, with no non-negativity check. A caller holding a valid ticket can therefore enroll a negative weight for anyminer_pubkey.At settlement,
finalize_epochcomputessum_w = sum(weights)and pays each minertotal_reward * (w / sum_w). A negative weight shrinkssum_w, inflating every other (attacker-controlled) miner's pro-rata share — reward theft / denial-of-weight.This was the only enrollment path in the node that did not exclude non-positive weights.
node/rip0202_enrollment.py("negative weights canonicalise to 0 units (excluded)") andnode/rustchain_block_producer.py(if enroll_weight <= 0) already guard against it.Fix (defense in depth)
/epoch/enroll: reject negativetemporal/rtcand anytotal_weight <= 0withinvalid_weights, before consuming the ticket (so a rejected request never burns a ticket).enroll_epoch(): backstop — non-finite / non-positive weights are never persisted, protecting any internal caller.finalize_epoch(): exclude non-positive / non-finite weights fromsum_wand payouts, so a poisoned legacy row (enrolled before this guard) cannot distort settlement.Tests
Added to
node/tests/test_sophia_elya_service.py:temporal: -5.0withinvalid_weightsand preserves the ticket;enroll_epochdrops negative and zero weights, keeps the positive one;finalize_epochexcludes a poisoned-9.0row sosum_w == 1.0and the whole reward goes to the honest miner.pytest node/tests/test_sophia_elya_service.py→ 12 passed; adjacent epoch/settlement suites (reward-overflow, finalize-ledger, settlement-atomic, money-units, anti-double-mining-enroll, weight-fixedpoint, integrated-balance) → 39 passed./claim #14583