Skip to content

gonkavip/unclaimed

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gonka Settle-Dropped Rewards Recovery

This directory contains a reference computation for refunding rewards that the chain's x/inference module assigned to participants but dropped at the SetSettleAmount step — a known on-chain bug where SettleAccounts calls continue on participants whose GetBitcoinSettleAmounts returned settleError (typically ErrNegativeCoinBalance). When this happens the participant has a positive rewarded_coins in EpochPerformanceSummary, but no SettleAmount record is ever written, so their MsgClaimRewards returns "No rewards for this address" and the coins are eventually swept into the gov module account by TransferOldSettleAmountsToGovernance.

The output CSV maps each affected participant to the exact amount of ngonka they should receive, with per-epoch breakdown. The numbers are deterministic and verifiable against the on-chain state of any gonka archive node.

TL;DR

  • 19 miners are victims of the settle-drop bug across the chain's history.
  • Total amount: 1 075.336 GNK of rewards that were computed and owed but never written to a SettleAmount record.
  • The proposal refunds each affected miner exactly what the chain computed for them, per epoch.
  • Detection uses two standard chain endpoints only — no off-chain data, no third-party services, no log scraping.

Why Only This Bug (Not All Unclaimed Rewards)

Many participants have rewarded_coins > 0 and claimed = false in their EpochPerformanceSummary. Most of those are NOT bugs. The chain has a one-epoch claim window: after epoch N's payouts are settled, the participant must submit MsgClaimRewards during epoch N+1 with a valid seed signature, sufficient successful validations, and the correct epoch_index. If they miss that window — for any operational reason (node downtime, lost keys, wrong seed, bad validations) — the rewards expire and are swept into gov by design.

This proposal explicitly does not refund those cases. The participant had a chance to claim and didn't. That is correct protocol behaviour, not a bug.

What this proposal does refund is the case where the participant had no chance to claim because the chain itself skipped writing their claim ticket. That is unambiguously on-chain malfunction, and the only party that can correct it is governance.

The Settle Bug — Code Path

SettleAccounts in inference-chain/x/inference/keeper/accountsettle.go runs as an EndBlocker at every epoch boundary. The relevant sequence:

  1. GetBitcoinSettleAmounts (in bitcoin_rewards.go) computes per- participant SettleAmount and settleError. If the participant has CoinBalance < 0 and the computed RewardCoins are smaller than the debt, the code sets RewardCoins = 0 and returns settleError = types.ErrNegativeCoinBalance.
  2. SettleAccounts writes EpochPerformanceSummary for every participant unconditionally, copying WorkCoins and RewardCoins.
  3. A second loop iterates the same amounts and calls SetSettleAmountWithGovernanceTransfer only for those with amount.Error == nil. Participants with settleError hit continue and never get a SettleAmount record.

Net result: the chain's books say the participant earned X ngonka, but no claim ticket exists. The participant's wallet sees the rewarded amount in epoch_performance_summary and submits MsgClaimRewards, which validateRequest rejects because GetSettleAmount returns not found.

On-Chain Detection — Two Endpoints Are Enough

A participant is a refund candidate if and only if both are true at the moment epoch N+1 begins (i.e. at block height epoch_meta[N+1].effective_block_height):

  1. GET /productscience/inference/inference/epoch_performance_summary/{N}/{addr} returns rewarded_coins > 0.

  2. The full pagination of GET /productscience/inference/inference/settle_amount (with header x-cosmos-block-height: <eff_{N+1}>) does NOT contain an entry with participant == addr and epoch_index == N.

Both endpoints are part of the standard inference module proto schema. Any archive full node serves them. No third-party indexer, no tx_search, no event-log parsing, no proto-decoding of authz-wrapped messages.

This same pair of conditions correctly identifies victims regardless of how the participant runs their claim flow (direct from cold key, or via authz-granted warm key, or any other variation). Tracker / explorer parsers are not required.

Algorithm

The computation is a 4-step pipeline implemented in unclaimed.py. Inputs come exclusively from a gonka archive full node via standard Cosmos REST endpoints.

Step 1 — Resolve epoch membership and boundaries

For every epoch in [FROM_EPOCH..LAST_EPOCH+1], fetch /inference/inference/epoch_group_data/{n}:

  • validation_weights[*].member_address is the participant set for epoch n.
  • effective_block_height is the start block of epoch n. We use effective_block_height of epoch n+1 as the snapshot height for epoch n (the block at which SettleAccounts for epoch n has just finished).

Step 2 — Fetch per-participant rewards

For each (epoch, addr) pair, fetch /inference/inference/epoch_performance_summary/{epoch}/{addr} and record rewarded_coins and claimed.

Step 3 — Fetch SettleAmount snapshot at each epoch boundary

For each epoch n, fetch the full paginated list /inference/inference/settle_amount?pagination.limit=500 with the header x-cosmos-block-height: eff_{n+1}. Record every (epoch_index, participant) pair present at that height.

Step 4 — Apply the bug signature

Keep only pairs (epoch=N, addr=X) where:

rewarded_coins[N][X] > 0   AND   (N, X) NOT IN settle_present

For each kept pair, write the ngonka amount into the per-address row at the column corresponding to N. Sum across all kept pairs into total_ngonka and total_gnk.

All arithmetic is exact integer ngonka.

Output

A single CSV (unclaimed.csv by default):

address, 1, 2, 3, ..., N, total_ngonka, total_gnk
gonka1...,0,0,...,150749450599,...,0,150749450599,150.749450599
gonka1...,0,0,...,156696892692,...,0,156696892692,156.696892692
...

Each row is one affected miner. The epoch columns hold the per-epoch amounts. The two right-most columns are per-address totals. Sum of the total_ngonka column equals the total proposal payout.

Why an Archive Node Is Required

Both detection steps query historical state:

  • epoch_performance_summary for past epochs.
  • settle_amount at a past block height (via x-cosmos-block-height).

Most public RPC nodes prune historical state after a few weeks, so older epochs will return 404. To compute this list back to the start of the chain, you need an archive node — one that retains all historical state since block 1.

A run against a pruned node will silently skip the older epochs and miss most of the affected miners.

Statistics

  • Affected miners: 19
  • Total unclaimed (settle-drop bug only): 1 075.336 GNK
  • Mechanism: SettleAccounts settleError → SetSettleAmount skipped → MsgClaimRewards returns "No rewards for this address".

Reproducing the Computation

pip install -r requirements.txt
python3 unclaimed.py <ARCHIVE_NODE_IP> --out unclaimed.csv

The script accepts either a hostname/IP (default port 8000) or a full URL. A SQLite cache is created in cache_unclaimed_<NODE>/ so subsequent runs are incremental — re-running takes seconds rather than the ~30 minutes a full cold run takes.

To bound the range, use --from-epoch and --to-epoch:

python3 unclaimed.py <ARCHIVE_NODE_IP> --from-epoch 1 --to-epoch 273 --out unclaimed.csv

Files

  • unclaimed.py — end-to-end script (archive node required).
  • requirements.txt — single dependency (aiohttp).
  • cache_unclaimed_<NODE>/cache.db — SQLite cache. Safe to delete; it will be rebuilt on the next run.
  • unclaimed.csv — output (generated). One row per affected miner with per-epoch breakdown and totals.

Verification Checklist for Reviewers

  • The script connects to an archive node and successfully returns epoch_performance_summary for the oldest epoch in scope.
  • Pick any row in the CSV. For one of its non-zero epoch columns N, query epoch_performance_summary/{N}/{addr}rewarded_coins must match the column value and claimed = false.
  • For the same (N, addr), query settle_amount/{addr} with header x-cosmos-block-height: <eff of epoch N+1> and confirm the response is 404 not found (i.e. SettleAmount was never written).
  • As a positive control: pick any address with claimed = true for epoch N. The same settle_amount query at the same block must return a record (and that address must NOT appear in the CSV).
  • Sum of the total_ngonka column equals the script's reported total.
  • No address has total_ngonka == 0 (rows are only written for addresses with at least one settle-dropped pair).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages