# Justification — Corrections Applied to the CC2 Mainnet Archive

This document explains **every change** made to Subscan's published CC2 mainnet archive
export, why it was made, and the evidence supporting it.

Nothing here was changed on a hunch. Corrections 1-3 restore rows that the chain
demonstrably produced and the export omitted. Correction 4 (section 9) fixes incorrect
fee values. All were verified against an independent replay of the full chain.

---

## 1. Where the data came from

**Source export:** Subscan archive CSVs for Creditcoin CC2 mainnet, downloaded
2026-09-15 from `gcs.subscan.io`. 60 files, 5.8 GB gzipped.

On download, every file was checked against the server's `content-length` and tested with
`gzip -t`. All 60 passed. The per-file record counts are recorded in `sources.csv`.

**Verification source:** a Creditcoin CC2 archive node. All 7,029,128 blocks were fetched
(block bodies, headers, and the `System::Events` storage item) and decoded from raw SCALE
using the runtime metadata for each spec version.

The two are fully independent: the corrections were derived from the export's own event
table, then checked against the chain replay, which had no knowledge of the export.

---

## 2. How the problems were found

`blocks.csv` carries `event_count` and `extrinsics_count` for every block. That gives a
self-contained checksum: sum those columns and compare against the actual row counts.

Each derived table was then reconciled against the events that produce it:

| derived table | should equal |
|---|---|
| `staking_rewards` | count of `staking.Rewarded` + `staking.Slashed` events |
| `transfers` (successful) | count of `balances.Transfer` events |
| `logs` | count of digest items per block header |

Three of these did not balance. The shortfalls were then located block by block.

---

## 3. The three row-restoration corrections

### 3.1 `staking_rewards` — 248,590 rows restored

**Evidence of the gap.** The export contains 11,509,240 rows with
`event_id = 'Rewarded'`. `events.csv` — the same export — contains 11,757,830
`staking.Rewarded` events. Comparing per block showed the difference is confined to
**42 blocks** between 2023-09-24 and 2025-01-05: 29 with no rows at all, 13 written only
partially.

The chain replay independently confirms 11,757,830 `staking.Rewarded` events.

**What was restored.** For each missing event, one row built from the event itself:

| field | source |
|---|---|
| `id` | the event's own `id` — verified identical on surviving rows |
| `account` | SS58 encoding of the event's `stash` parameter |
| `extrinsic_index`, `block_num`, `extrinsic_hash` | from the event row |
| `block_timestamp` | from `blocks.csv` |
| `amount` | the event's `amount` parameter |
| `event_type` / `module_id` / `event_id` / `era` | `0` / `staking` / `Rewarded` / `0` |

The SS58 codec was round-trip tested against existing addresses in the export before use.

**The one inferred field.** `reward_account` (the payee) is held in chain state, not in
the event, so it cannot be derived from the event alone. It was resolved from the dataset
itself:

- **248,070 rows** — the stash has exactly one payee everywhere else in the table; used that.
- **497 rows** — the stash changed payee at some point; used the payee from the temporally
  nearest surviving reward row for that stash.
- **23 rows** — the stash appears nowhere else in 11.5M rows; fell back to
  `reward_account = account`.

Those last 23 are the **only** values in this entire correction that are inferred rather
than derived. Payee equals stash for 99.57% of all rows historically, so they are very
likely correct, but they are stated here so the uncertainty is visible.

**Casing.** `module_id` was written lowercase `staking`. The export uses `Staking`
(capitalised) from block 4,295,682 onward; every restored block is below that range, so
lowercase matches the surrounding rows.

### 3.2 `transfers` — 4,762 rows restored

**Evidence of the gap.** The export contains 895,959 rows marked successful. `events.csv`
contains 900,721 `balances.Transfer` events. The difference falls entirely within blocks
**155,771 – 332,001** (2022-07-12 to 2022-11-18), across 3,426 blocks. Nothing after block
332,001 is affected.

The chain replay independently confirms 900,721 `balances.Transfer` events.

**What was restored.** Every one of the 4,762 events matched an extrinsic, every
`extrinsic_hash` agreed, and all 4,762 extrinsics succeeded. Fields:

| field | source |
|---|---|
| `id` | the event's own `id` |
| `extrinsic_index` | the extrinsic's numeric id from `extrinsics.csv` |
| `hash` | `extrinsic_hash` |
| `sender` / `receiver` | SS58 of the event's `from` / `to` parameters |
| `amount_v2` | raw amount from the event |
| `amount` | `amount_v2 / 1e18`, formatted; formatter checked against existing rows |
| `fee`, `nonce` | from the matching extrinsic |
| `success` / `module` / `asset_*` / `transfer_type` | `true` / `balances` / `CTC` / `0` |

Nothing inferred.

### 3.3 `logs` — 4 rows restored

**Evidence of the gap.** Blocks **170,453** and **332,006** have no rows in the log table.
Both carry the usual `PreRuntime` + `Seal` digest pair, confirmed by reading the block
headers from the node.

**What was restored.** The digest items were fetched from the chain via `chain_getHeader`
and SCALE-decoded: payload length, engine id (`pow_` = 1601662832), and payload bytes,
formatted identically to neighbouring rows.

**A mistake worth recording.** The first write of these four rows contained a 5-byte offset
error in the payload slice — the data field was shifted and truncated. It was caught by
comparing field lengths against neighbouring rows, then recomputed with an assertion that
the payload consumes the digest item exactly. The rows in this archive are the corrected
ones. This is noted because an archive should disclose its own near-misses, not just its
successes.

---

## 4. Provenance — finding every change

Every restored row carries **`source_id >= 61`**. No restored row reuses a Subscan file's
id.

| source_id | meaning |
|---|---|
| 1 – 60 | original Subscan archive files (see `sources.csv`) |
| 61 | `staking_rewards` rows rebuilt from `events.csv` |
| 62 | `transfers` rows rebuilt from `events.csv` |
| 63 | `logs` rows fetched from the chain |

```bash
# Subscan's export exactly as delivered
zcat staking_rewards.csv.gz | awk -F, 'NR==1 || $1 < 61'

# only our additions
zcat staking_rewards.csv.gz | awk -F, '$1 >= 61'
```

This means the archive is reversible: the original export can be recovered from it exactly,
without needing the original files.

---

## 5. What was NOT changed

Deliberately left alone, because changing them would mean editing original rows rather than
restoring missing ones. All are documented in `README.md`:

- Addresses written without the `0x` prefix in blocks 1,790,019 – 1,888,396 (955,021 rows)
- `staking_rewards.module_id` casing flip at block 4,295,682 (1,284,010 rows)
- `staking_rewards.era` always `0` (never populated by the export)
- `events.extrinsic_idx` written as `0` where no extrinsic applies (1,481,476 rows)
- 8 EVM addresses in `accounts.address`
- `blocks.csv` not being sorted by `block_num`

`blocks.csv`, `events.csv` and `accounts.csv` were **not modified at all** — they matched
the chain exactly. `extrinsics.csv` had no rows added or removed, but its `fee` /
`fee_used` columns were corrected — see section 9.

---

## 6. Verification after correction

Every table now matches the chain:

| dataset | archive | chain |
|---|---:|---:|
| blocks | 7,029,128 | 7,029,128 |
| events | 56,664,559 | 56,664,559 |
| extrinsics | 12,927,874 | 12,927,874 |
| logs | 14,061,564 | 14,061,564 |
| staking_rewards (Rewarded) | 11,757,830 | 11,757,830 |
| staking_rewards (Slashed) | 21,286 | 21,286 |
| transfers (successful) | 900,721 | 900,721 |

All 56,664,559 events were compared individually against the chain replay, in block order:
none missing, none extra, none out of sequence, with matching module, event name, phase and
extrinsic index throughout.

Two independent invariants also hold exactly:

```
timestamp.set extrinsics                  7,029,128  == block count (one per block)
ExtrinsicSuccess 11,269,927
ExtrinsicFailed  +  1,657,947
                 = 12,927,874  == total extrinsics
```

Per-block checks after correction:

```
blocks where staking.Rewarded events != staking_rewards rows :  0
blocks where balances.Transfer events != transfers rows      :  0
blocks with no log rows                                      :  0
```

All restored rows were re-read through a strict CSV parser: correct field counts, no
malformed rows, `logs.data` values are valid JSON.

---

## 7. A correction to our own earlier analysis

During the audit we briefly concluded that Subscan was missing 225 `system.NewAccount`
events. **That was wrong** — it was a bug in our own extraction, which required a `0x`
prefix and silently skipped rows written as bare hex. Checking a specific case (block
1,876,828) showed Subscan had the event, and the whole 14-event block matched our replay
index for index.

It is recorded here because the same reasoning error could otherwise be inherited by
anyone reading this archive's history: **Subscan's event table is complete.** The three
corrections above concern the derived tables only.

---

## 8. Reproducing this

1. Download the original export from `gcs.subscan.io` and verify with `gzip -t`.
2. Sum `event_count` / `extrinsics_count` over `blocks.csv`, compare against row counts.
3. Count `staking.Rewarded`, `balances.Transfer` and digest items per block in `events.csv`,
   compare against the derived tables.
4. To verify against the chain: fetch each block and its `System::Events` storage item from
   an archive node and decode from raw SCALE.

One detail that matters for step 4: a runtime upgrade takes effect at the **end** of its
block, so an upgrade block's contents must be decoded with the **previous** runtime's
metadata. Selecting metadata by the block's own reported `spec_version` fails outright at
blocks 273,865 and 715,217, and silently mis-decodes at the other five upgrade blocks.

---

*Prepared 2026-09-16. Chain: Creditcoin CC2 mainnet, genesis
`0xdd954cbf4000542ef1a15bca509cd89684330bee5e23766c527cdb0d7275e9c2`, blocks 1 – 7,029,128,
chain halted 2026-09-01.*

---

## 9. Correction 4 — fee / fee_used (added 2026-09-16)

This is the **first correction that modifies values Subscan published**, rather
than adding rows that were missing. It is documented in full here and in
`corrected.txt`, and it is completely reversible.

### What was wrong

The `fee` and `fee_used` columns were incorrect for signed extrinsics in blocks
**1 – 715,217** — the whole period running runtime specs 200, 201, 203 and 210.
Two distinct problems:

| | rows | |
|---|---:|---|
| fee inflated by exactly 2^32 | 3,584,036 | 4,294,967,296x too large |
| fee published as 0 despite a fee being paid | 147 | mostly `creditcoin.verify_transfer` |
| already correct, left untouched | 102 | inside the same block range |

Example — block 211, extrinsic `211-1` (`balances.transfer_all`):

```
as published : 54,318,221,910,555,623,885,373,440  = 54,318,221.91  CTC
actual       :             12,646,946,569,568,390  =  0.012646947   CTC
```

Nothing after block 715,217 is affected — 2,314,454 signed extrinsics from block
715,218 onward were already correct. The boundary is the spec 210 → 230 runtime
upgrade, not an arbitrary block. Block 715,217 is itself the upgrade block, still
executing under spec 210, and is affected.

### How the true value was established

Two independent sources agree with each other and disagree with the published
value:

1. The `balances.Withdraw` event emitted for the extrinsic — what was actually
   deducted from the payer's account.
2. The node's own `payment_queryInfo` RPC at the parent block hash, returning
   `partialFee`.

For block 211 both return `12,646,946,569,568,390`, identical to what we wrote.
So the runtime reports the correct fee; the published value is the outlier.

### What we did

For each signed extrinsic in blocks 1–715,217 we wrote the fee from that
extrinsic's `balances.Withdraw` event into both `fee` and `fee_used`.

**We did not divide by 2^32.** Every value is derived per row from the chain, and
the ratio against the published figure was recorded rather than assumed. That is
the only reason the 147 zero-fee rows were found — a blanket division would have
left them at zero and reported a clean 100% result.

```
rows examined                         3,584,285
rows changed                          3,584,183
rows already correct (untouched)            102
rows with no chain match                      0
```

### Reversibility

`fee_corrections.csv` lists **every** changed value:

```
block_num,extrinsic_index,call_module,call_module_function,fee_as_published,fee_corrected,ratio
211,211-1,balances,transfer_all,54318221910555623885373440,12646946569568390,4294967296
```

3,584,183 rows. Joining it back on `(block_num, extrinsic_index)` reconstructs
the export exactly as Subscan published it. Nothing is lost.

### Testnet

The cc2-testnet export does **not** have this defect. Testnet never ran specs
200/201/203/210. A sample of 63,768 signed extrinsics matched against their
`balances.Withdraw` events found 63,745 exact matches and 23 of the same
zero-fee rows. The testnet fees were left as published.
