Skip to content

gonkavip/reconstruction

Repository files navigation

Gonka epoch reward reconstruction

Two standalone scripts that rebuild the subsidy (Bitcoin-style) reward distribution for a Gonka epoch by reading historical chain state from a chain-api endpoint (an archive node). They require no chain binary, no .env, and no third-party packages — just Python 3 and network access to a node.

Script What it answers
forecast_epoch_reward.py What did / would the chain pay this epoch? (fast approximation)
forecast_epoch_reward_nocap.py What would the chain have paid if the ComputeGroupCap did not exist, and how does that compare to the actual on-chain payout? (exact settlement math)

Both write a per-participant CSV and print a summary.


Requirements

Both scripts use only the Python 3 standard library — there is nothing to pip install. You need:

  • Python >= 3.10
  • network access to a Gonka chain-api endpoint (an archive node for old epochs)

A requirements.txt is included for completeness (it lists no packages):

pip install -r requirements.txt   # no-op; here only to make the dependency story explicit

Quick start

Run from inside this scripts/ directory:

# actual / approximate distribution
python3 forecast_epoch_reward.py node1.gonka.ai 267

# counterfactual "no group cap" distribution, with a column comparing to
# what the chain really paid
python3 forecast_epoch_reward_nocap.py node1.gonka.ai 267 --compare

node1.gonka.ai is the node host; the chain-api URL is built automatically as http://<node>:8000/chain-api. You can also pass a full http://host:port or a .../chain-api URL. An archive node is required for old epochs whose state has been pruned on regular nodes.

Output CSV defaults to ./epoch_<N>_reward.csv (capped) or ./epoch_<N>_reward_nocap.csv (no-cap). Override with --out PATH.


Why two scripts — the difference in approach

The two scripts answer different questions and use different math. This matters: they disagree by ~1% on the actual distribution, and that gap is exactly the part that determines a fair restitution number.

forecast_epoch_reward.py — simplified ratio model

A fast reconstruction of the reward path:

reward_i = subsidy_pool × weight_i × ratio_i / Σ weight
ratio_i  = active_i ? min(confirmation_weight_i / scaled_weight_to_confirm_i, 1) : 0
active_i = ratio_i ≥ 0.5 × 0.909   (0.4545)

It uses the capped weight already stored in EpochGroupData and an activity ratio. It is a good approximation but drifts ~1% from what the chain actually paid, because it does not faithfully model the effective-weight rescale or the burned (un-redistributed) share.

forecast_epoch_reward_nocap.py — exact settlement math, cap removed

A faithful port of the on-chain settlement path in x/inference/keeper/bitcoin_rewards.go (lines 597–675), with the ComputeGroupCap (delegation_weight_calculator.go:330–350) lifted.

effectiveWeight_i = confirmation_weight_i
    if weight_i < rawTotal_i:  effectiveWeight_i = CW_i × weight_i / rawTotal_i   # rescale into consensus scale
    effectiveWeight_i = min(effectiveWeight_i, fullWeight_i)
    if INACTIVE/INVALID:       effectiveWeight_i = 0                              # earns nothing...

reward_i = subsidy_pool × effectiveWeight_i / Σ fullWeight_j                      # ...but still counts in the denominator

Three things the simplified model glossed over, and which this script gets exactly right (validated to 0.0000% against actual rewarded_coins for e267 when run in capped mode):

  1. Effective-weight rescale. When a member's consensus weight was reduced below their raw PoC contribution (rawTotal), the chain rescales confirmation_weight by weight / rawTotal and clamps it to weight. This is the precise numerator the reward formula uses.

  2. Burned / unconfirmed weight is kept in the denominator. Inactive, invalid, and partially-unconfirmed participants do not get their share redistributed to everyone else — it is burned to governance. The denominator is Σ fullWeight over all members, so removing a member's reward does not inflate everyone else's. Ignoring this is the source of the ~1% drift.

  3. Status is derived, not looked up. A member is INACTIVE/INVALID exactly when their CPoC ratio confirmation_weight / rawTotal < 0.4545 (0.5 × pocDeviationCoeff). This reproduces the chain's ACTIVE/INACTIVE/INVALID split with zero mismatches on e267, so the script needs no per-participant status query.

Removing the cap

ComputeGroupCap only ever shrinks a non-initial model group, by applying a single scaleFactor = cap / rawTotal to every member's contribution. The no-cap script sets that factor to 1.0, so each capped member's consensus weight becomes its full uncapped contribution:

weight_i(no-cap) = fullWeight_i(no-cap) = rawTotal_i = Σ_models floor(coeff × Σ poc_weight)

Everything else (CW, the rescale branch, status zeroing, the burned-share denominator) is left identical to the chain — so the result isolates the cap's effect and nothing else.

Why this gives a very different restitution number

A naive "uncap the weights" calculation (e.g. dividing each Kimi member's confirmation weight by the old, capped network total) double-counts: it raises the numerators without raising the denominator, so the "fair shares" sum to well over 100% of the pool. The exact model recomputes the denominator too. As a result, the corrected per-epoch compensation comes out several times smaller than the naive figure (for e267: ~56k GNK of positive shortfall vs ~246k from the naive method), because lifting Kimi's cap also dilutes every other group's share of the same fixed pool.


Reading the no-cap CSV

Columns (--compare adds the last two):

Column Meaning
status ACTIVE or INACTIVE_THRESHOLD (derived from the CPoC ratio)
weight_chain consensus weight the chain actually stored (post-cap)
full_weight_nocap consensus weight with the cap removed (= raw_total for capped members)
raw_total uncapped PoC contribution Σ floor(coeff × poc_weight)
confirmation_weight CPoC-confirmed work (the reward numerator base)
effective_weight final numerator after rescale/clamp/status
share_pct effective_weight / Σ full_weight_nocap
reward_nocap_gnk reward under the no-cap counterfactual
actual_rewarded_gnk what the chain really paid (--compare only)
diff_gnk reward_nocap − actual; positive = underpaid (owed), negative = overpaid

The summary line reports total_distributed_gnk, burned_to_governance_gnk, and (with --compare) two restitution totals:

  • Σ extra owed (no-cap − actual) — net difference across all members (can be negative: lifting the cap shifts pool share between groups).
  • Σ extra owed (positive only) — sum of positive diff only, i.e. paying the underpaid without clawing back from the overpaid. This is the figure to use for a restitution that only ever pays out.

CLI reference

forecast_epoch_reward_nocap.py <node> <epoch> [options]

  <node>            node host (node1.gonka.ai), or full http://host:port[/chain-api] URL
  <epoch>           epoch number, e.g. 267

  --compare         also fetch actual rewarded_coins and append diff columns
                    (one extra request per participant — slower)
  --height-end H    pin the snapshot block height (skips auto-discovery;
                    use the epoch's last block for a settled snapshot)
  --initial-model M founding model id exempt from the cap
                    (default: first sub_group_models entry; cap removal is a
                    no-op for it, so this rarely needs setting)
  --out PATH        output CSV path
  --quiet           suppress progress logs

forecast_epoch_reward.py takes the same <node> <epoch> plus --height-end, --out, --quiet.

Notes / troubleshooting

  • 503 Service Temporarily Unavailable — the node proxy is briefly overloaded. The no-cap script retries transient 5xx up to 4× with backoff; if it still fails, re-run. Pinning --height-end reduces the number of probe requests during height discovery.
  • --compare is slower — it issues one epoch_performance_summary request per participant (~50 per epoch). Drop it if you only need the no-cap distribution.
  • Use an archive node for pruned historical epochs; a regular node only serves recent state.
  • Numbers are GNK (1 GNK = 10⁹ ngonka). The chain stores ngonka; the scripts print/convert to GNK.

How the chain math maps to the code

Step Chain source
subsidy pool initial × exp(decay × (epoch−genesis)) bitcoin_reward_params
group cap scaleFactor = cap / rawTotal delegation_weight_calculator.go:330–350
rawTotal = Σ floor(coeff × poc_weight) delegation_pipeline.go:149, bitcoin_rewards.go:181 (CoefficientAdjustedWeight)
effective-weight rescale + clamp bitcoin_rewards.go:617–630
status zeroing, full-weight denominator (burn) bitcoin_rewards.go:597–675
activity threshold 0.5 × pocDeviationCoeff pocDeviationCoeff = 0.909

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages