Skip to content

audit: add LEP-6 shadow scoring for storage truth#119

Closed
j-rafique wants to merge 3 commits intoLEP-6-report-ingestionfrom
LEP-6-shadow-scoring
Closed

audit: add LEP-6 shadow scoring for storage truth#119
j-rafique wants to merge 3 commits intoLEP-6-report-ingestionfrom
LEP-6-shadow-scoring

Conversation

@j-rafique
Copy link
Copy Markdown
Contributor

@j-rafique j-rafique commented Apr 20, 2026

Summary

Implements LEP-6 PR3 (shadow scoring) on LEP-6-shadow-scoring (PR #119) by adding storage-truth score execution for ingested storage_proof_results and persisting shadow-only scoring state.

This PR updates state + scoring + observability only. It does not enable enforcement penalties.

Base / Scope

  • Base branch: LEP-6-report-ingestion
  • Head branch: LEP-6-shadow-scoring
  • Mode behavior: scoring executes only when storage-truth mode is SHADOW/SOFT/FULL, with PR3 intent focused on shadow bookkeeping and score evolution.

What Changed

1) Storage-truth score pipeline execution

  • MsgSubmitEpochReport now applies storage-truth scoring after report persistence.
  • Added keeper scoring engine for per-result deltas:
    • Node suspicion (N)
    • Reporter reliability (R)
    • Ticket deterioration (D)
  • Added lazy decay application on read/update paths for all three dimensions.

2) Reporter trust band + bookkeeping state

  • Added trust-band enum and persisted trust-band state for reporters.
  • Added contradiction counters and bookkeeping fields needed for PR3 behavior:
    • Reporter contradiction tracking
    • Ticket failure-window tracking
    • Ticket contradiction tracking
    • Last observed target/reporter/result/epoch metadata

3) PR3 scoring behavior coverage

  • Trust-band-aware scaling for node/ticket deltas.
  • Repeated failure escalation across distinct epochs.
  • Contradiction detection across epochs for same ticket/target.
  • Contradiction penalties for both:
    • current reporter
    • previously contradicting reporter (when applicable)

4) Event observability

  • Added storage_truth_score_updated event and attributes for:
    • epoch/reporter/target/ticket/result
    • updated N/R/D values
    • reporter trust band
    • repeated failure count
    • contradiction flags and contradicted reporter

5) PR review hardening fixes included

  • Removed redundant repeated GetParams(...).WithDefaults() store reads in inner scoring helpers by threading already-fetched params.
  • Made scaling overflow-safe for extreme score values (scaleInt64TowardZero) and added edge-case coverage.

Tests

Added/updated focused tests for:

  • Result-class scoring outcomes
  • Decay application
  • Trust threshold effects
  • Repeated failure escalation
  • Contradiction detection/penalties
  • Event emission attributes
  • Query visibility for expanded score state
  • Internal math/helper edge cases (including overflow scenarios)

Non-Goals (explicitly deferred)

  • No penalty/enforcement activation matrix in this PR.
  • No heal/recheck transaction finalization workflow in this PR.
  • No slashing behavior changes.

Risk / Compatibility

  • Backward-compatible with existing ingestion/report flow.
  • Changes are additive to LEP-6 shadow state paths and do not introduce hard enforcement actions in this milestone.

@roomote-v0
Copy link
Copy Markdown

roomote-v0 Bot commented Apr 20, 2026

Rooviewer Clock   See task

Both previously flagged issues have been addressed in this revision. Params are now threaded through as parameters (no redundant store reads), and scaleInt64TowardZero uses 128-bit arithmetic via math/bits to handle extreme values safely. No new issues found.

  • storage_truth_scoring.go: Redundant GetParams store reads inside applyReporterReliabilityDelta and applyTicketDeteriorationDelta -- params are already fetched in the caller and should be threaded through
  • storage_truth_scoring.go: scaleInt64TowardZero can silently overflow on value * numerator for extreme score values, inconsistent with the saturated arithmetic used elsewhere in the scoring pipeline
Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

Comment thread x/audit/v1/keeper/storage_truth_scoring.go Outdated
Comment thread x/audit/v1/keeper/storage_truth_scoring.go Outdated
@j-rafique j-rafique force-pushed the LEP-6-shadow-scoring branch from 51ea7b0 to c86ef0e Compare April 22, 2026 08:22
@j-rafique j-rafique self-assigned this Apr 22, 2026
@mateeullahmalik
Copy link
Copy Markdown
Contributor

Production-gate review by Zee — 12 findings

Methodology: full file-by-file read of every non-generated changed file in this PR's diff (pr-119 vs its base branch), cross-checked against:

  • LEP-6 spec (Notion source of truth)
  • invariant-first-coding skill (write-path enumeration, sibling symmetry, single source of truth, post-fix re-audit)
  • Cosmos SDK consensus discipline (no float, no map iteration, bounded EndBlock, genesis round-trip, errorsmod wrapping)

Status legend: each finding's status is computed at the PR #122 stack-tip (consensus-gap-fixes commit a51c439), so 'FIXED' means a downstream PR in the stack already addresses it; 'OPEN' means it is still present at the tip and must be fixed before merge / Phase-2 activation. Severity rubric in the charter (~/work/lep6-review/ctx/charter.md): CRITICAL = consensus halt / state corruption / non-determinism in ABCI; HIGH = spec mismatch with economic impact, missing genesis round-trip, replay enabler; MEDIUM = invariant asymmetry without immediate exploit, unbounded loop with practical bound, missing param validation.

Severity breakdown: HIGH=7, MEDIUM=5


119-F1 — Decay was linear instead of geometric (spec §14/§15/§16)

119-F2 — Node-suspicion deltas don't match spec §14 (no symbol/index split, wrong magnitudes)

119-F3 — Ticket-deterioration deltas don't match spec §16

119-F4 — Reporter-reliability deltas semantically inverted vs spec §15

119-F5 — Contradiction penalty applied to BOTH the contradicting and contradicted reporter

119-F6 — Trust multiplier 0.25 (LOW_TRUST=0.5, INELIGIBLE=0.25) violates spec §15.4 floor 0.5

119-F9 — Pattern-escalation predicate counts same-ticket repeats instead of distinct-ticket per node, magnitudes 2/4/6 vs spec 10/15/12

119-F10 — updateRecentFailureEpochCount window uses StorageTruthProbationEpochs (default 3) instead of spec's 14-epoch failure-pattern window

119-F11 — validateInt64 allows zero/negative decay; decayTowardZero short-circuits on ≤0 — gov can disable decay forever

  • Severity: MEDIUM
  • File: x/audit/v1/types/params.go (413-415, 419-420), x/audit/v1/keeper/storage_truth_scoring.go (508-511)
  • Status at PR feat(audit): finalize LEP-6 consensus gap fixes #122 tip: OPEN at PR feat(audit): finalize LEP-6 consensus gap fixes #122validateInt64 still doesn't impose lower bound on the three decay params. Add validateStorageTruthDecay requiring > 0 and a sane upper cap; invoke from Params.Validate().
  • What: A single bad governance proposal setting decay=0 disables score decay forever; decay=huge instantly zeroes every score.

119-F12 — InitGenesis on score-states: no LastUpdatedEpoch <= currentEpoch clamp

119-F7 — Same-epoch contradictions silently dropped (< instead of <=)

119-F8 — TicketDeteriorationState lacks ValidateBasic on genesis import

  • Severity: MEDIUM
  • File: x/audit/v1/keeper/genesis.go
  • Lines: ~58-68
  • Status at PR feat(audit): finalize LEP-6 consensus gap fixes #122 tip: OPEN at PR feat(audit): finalize LEP-6 consensus gap fixes #122 head; the same gap exists for NodeSuspicionState and ReporterReliabilityState.
  • What: Genesis InitGenesis trusts imported (LastFailureEpoch, RecentFailureEpochCount, LastUpdatedEpoch) without invariant checks. Malformed genesis (e.g. LastUpdatedEpoch > currentEpoch) silently accepted; lazy epochDelta will treat as 'no decay forever'.

This review is posted as a COMMENT (not REQUEST_CHANGES) so it does not block merge mechanically — but the CRITICAL and HIGH items must be triaged before activation. I'm available to walk through any of these in detail.

— Zee

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements LEP-6 PR3 “shadow scoring” for storage-truth by executing scoring on ingested storage_proof_results, persisting additional scoring/bookkeeping state, and emitting a dedicated score-update event.

Changes:

  • Add storage-truth scoring pipeline on MsgSubmitEpochReport with node/reporter/ticket score deltas, trust-band scaling, decay-on-update, and contradiction/repeated-failure bookkeeping.
  • Extend protobuf state with ReporterTrustBand and additional persisted fields for reporter and ticket scoring/bookkeeping.
  • Add/expand tests to cover scoring outcomes, decay, trust thresholds, escalation/contradictions, query visibility, and event attributes.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
x/audit/v1/types/events.go Introduces storage-truth score update event type and attribute keys.
x/audit/v1/types/audit.pb.go Regenerated protobuf output to include trust band + added state fields.
x/audit/v1/keeper/storage_truth_scoring.go Implements storage-truth scoring engine, bookkeeping, decay helpers, and event emission.
x/audit/v1/keeper/msg_submit_epoch_report.go Hooks scoring execution into epoch report submission flow.
proto/lumera/audit/v1/audit.proto Adds ReporterTrustBand enum and new persisted bookkeeping fields.
app/proto_bridge.go Registers the new enum for proto bridge usage.
x/audit/v1/keeper/msg_submit_epoch_report_storage_truth_scores_test.go Adds end-to-end tests for scoring, decay, scaling, events, escalation, contradictions.
x/audit/v1/keeper/query_storage_truth_test.go Expands query tests to validate new fields and scored-ingestion visibility.
x/audit/v1/keeper/storage_truth_state_test.go Updates state round-trip tests for newly persisted fields.
x/audit/v1/keeper/storage_truth_scoring_internal_test.go Adds focused unit tests for internal math/scoring helpers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +251 to +256
if isStorageTruthFailureClass(result.ResultClass) && epochID != state.LastFailureEpoch {
nextState.LastFailureEpoch = epochID
nextState.RecentFailureEpochCount = updateRecentFailureEpochCount(state, epochID, params)
} else if !found {
nextState.RecentFailureEpochCount = 0
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In applyTicketDeteriorationDelta, a brand-new ticket failure at epoch 0 won’t set LastFailureEpoch/RecentFailureEpochCount because the failure update is gated by epochID != state.LastFailureEpoch (and state.LastFailureEpoch is 0 for a new/zero-value state). This causes the first failure in epoch 0 to be recorded with RecentFailureEpochCount=0, and the next epoch’s failure will be treated as count=1 (no escalation) rather than count=2.

Consider changing the condition to also treat "not found" as needing initialization (e.g., update failure fields when !found || epochID != state.LastFailureEpoch), and add a test covering the first-ever failure when epochID == 0 to ensure RecentFailureEpochCount becomes 1.

Copilot uses AI. Check for mistakes.
@j-rafique j-rafique force-pushed the LEP-6-report-ingestion branch from cb16594 to af4c9fc Compare April 27, 2026 13:04
@j-rafique j-rafique closed this Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants