feat(blockchain): accrue Reward Points to non-outlier validator submissions - #64
Merged
Merged
Conversation
…ssions Sixth slice of #29/ADR-011 §5, completing the incentives bullet after the registry (#43), gated origins (#46), aggregation (#47), committee assignment (#48) and disputes (#49). When a round closes, every submission that survived outlier trimming earns PointsPerAcceptedSubmission Reward Points; the trimmed high and low earn nothing for that round. Honest reporting is therefore the paying strategy, and a validator that is consistently an outlier -- whether colluding or merely malfunctioning -- is paid nothing without needing a separate detection mechanism. Trimming had to become attributable to do this: the old trimmed_mean sorted bare scores and lost track of who submitted what. It is now trimmed(), returning the surviving submissions alongside their mean, sorted by (score_bps, validator) rather than score alone -- with tied scores that makes the choice of which entry gets trimmed total-ordered and therefore identical on every node, which a score-only sort did not guarantee. pallet-rewards gains accrue_points(), a non-extrinsic entry point, so it stays the only writer of RewardBalances and keeps its own overflow checking regardless of caller. Validators claim through the existing signed claim_reward path -- balances are keyed by account, so providers and validators share it without a second store. The new ValidatorRewards trait keeps the two pallets decoupled, wired by the runtime's ValidatorRewardsBridge, matching the existing ReputationUpdater/ProviderInspector pattern. RoundClosed now reports a 'rewarded' count, so an observer can see how many submissions actually counted without replaying the trim. Known gap, documented at the accrual site rather than left implicit: crediting is one-way -- an upheld dispute does not claw back points already accrued for that round. Clawback belongs with slashing economics, which ADR-011 explicitly leaves out of scope. Adds 3 tests (36 in this pallet, 84 workspace-wide): only survivors of trimming are paid and both outliers get zero; RoundClosed's rewarded count matches the payouts; all-tied scores trim deterministically by validator id, paying exactly the middle three. Verified (blockchain/): cargo fmt --all -- --check; SKIP_WASM_BUILD=1 cargo clippy --workspace --exclude openinfra-node --all-targets -- -D warnings; SKIP_WASM_BUILD=1 cargo test --workspace --exclude openinfra-node. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Summary
Sixth slice of #29 / ADR-011 §5, completing the incentives bullet after the registry (#43), gated origins (#46), aggregation (#47), committee assignment (#48) and disputes (#49).
When a round closes, every submission that survived outlier trimming earns
PointsPerAcceptedSubmissionReward Points; the trimmed high and low earn nothing for that round. Honest reporting is therefore the paying strategy, and a validator that is consistently an outlier — whether colluding or merely malfunctioning — is paid nothing, without needing a separate detection mechanism.Trimming had to become attributable
The old
trimmed_meansorted bare scores and lost track of who submitted what, so it couldn't say who to pay. It's nowtrimmed(), returning the surviving submissions alongside their mean.It sorts by
(score_bps, validator)rather than score alone — with tied scores, that makes the choice of which entry gets trimmed total-ordered and therefore identical on every node, which a score-only sort did not guarantee. That's a consensus-correctness point, not a style one.Decoupling
pallet-rewardsgainsaccrue_points(), a non-extrinsic entry point, so it stays the only writer ofRewardBalancesand keeps its own overflow checking regardless of caller. Validators claim through the existing signedclaim_rewardpath — balances are keyed by account, so providers and validators share it without a second store. The newValidatorRewardstrait keeps the two pallets decoupled, wired by the runtime'sValidatorRewardsBridge, matching the existingReputationUpdater/ProviderInspectorpattern.RoundClosednow reports arewardedcount, so an observer can see how many submissions actually counted without replaying the trim.Known gap (documented at the accrual site, not left implicit)
Crediting is one-way: an upheld dispute does not claw back points already accrued for that round. Clawback belongs with slashing economics, which ADR-011 explicitly leaves out of scope.
Tests (3 new, 36 in this pallet, 84 workspace-wide)
Only survivors of trimming are paid and both outliers get zero ·
RoundClosed'srewardedcount matches the actual payouts · all-tied scores trim deterministically by validator id, paying exactly the middle three.Verification
What remains in #29
Nothing blockchain-side except slashing and VRF committee entropy, both explicitly deferred by ADR-011. The open work is the dashboard validator views and the independent validator executable that drives
SolveChallenge— the latter is a new component boundary (own mTLS identity, chain signer, deployment), so it's flagged for a human decision rather than assumed.🤖 Generated with Claude Code