Skip to content

Commit 4195ae0

Browse files
sameh-faroukclaude
andcommitted
docs: update all project documentation
- README.md: Node v16→v20, src/types described as auto-generated, added Makefile targets and docs/typeChanges.md to project layout, restored architecture diagram - docs/readme.md: added links to typeChanges.md and release_process.md - docs/development.md: added sections for type generation workflow, GraphQL schema modification, and DB reset; restored log screenshots - docs/production.md: documented all env vars for indexer and processor, added architecture overview, improved resync section - docs/release_process.md: documented make version-bump, fixed chart paths (indexer/chart.yaml→indexer/chart/Chart.yaml), added CI auto-publish note - indexer/readme.md: documented START_HEIGHT, reformatted container list as table - indexer/development.md: fixed title (types.json→typesBundle.json), clarified typesBundle is frozen with link to typeChanges.md - indexer/chart/README.md: fixed typo (missing space in helm command) - processor-chart/README.md: fixed wrong path (cd tfchain/graphql/processor-chart→cd processor-chart) - scripts/readme.md: fixed typo (Sripts→Scripts), fixed wrong script name (restart-db.sh→reset-db.sh), added missing scripts (seed-versions.sh, merge-versions.js, init-db.sh) Refs: #119, #120 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 89ea8cc commit 4195ae0

11 files changed

Lines changed: 520 additions & 103 deletions

File tree

README.md

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,45 @@
1-
# Tfchain graphql
1+
# TFChain GraphQL
22

3-
[Subsquid](https://docs.subsquid.io) is used to index and provide a graphql interface on top of tfchain.
3+
[Subsquid](https://docs.subsquid.io) is used to index and provide a GraphQL interface on top of TFChain.
44

55
## Concept
66

77
The substrate events are processed in a multi-step pipeline:
88

9-
Tfchain => Squid Indexer => Indexer GraphQL gateway => Squid Processor => Database => Query Node GraphQL endpoint
9+
TFChain => Squid Indexer => Indexer GraphQL gateway => Squid Processor => Database => Query Node GraphQL endpoint
1010

1111
![Bird eye overview](https://gblobscdn.gitbook.com/assets%2F-MdI-MAyz-csivC8mmdb%2Fsync%2Fe587479ff22ad79886861487b2734b6556302d10.png?alt=media)
1212

1313
## Prerequisites
1414

15-
* Node v16x
15+
* Node v20+
1616
* Docker
17-
* Docker-compose
17+
* Docker Compose
1818

1919
## Running
2020

21-
see [docs](./docs/readme.md)
21+
See [docs](./docs/readme.md)
2222

2323
## Project layout
2424

25-
- `indexer` - docker-compose setup for the indexer
26-
- `db` - Processor db migration files
27-
- `scripts` - Scripts for generating initial state and development scripts
28-
- `src` - Source
29-
- `mappings` - Mapper functions for the indexer data
30-
- `model` - Generated models from the `schema.graphql` file
31-
- `types` - Type files that require manual edit if the schema changes / or chain types change
32-
- `processor.ts` - Processor entrypoint
33-
- `typegen` - Where the declaration files are generated from (used for development)
34-
- `tfchainVersions.jsonl` - Generated tfchain runtime versions and their data
35-
- `typegen.json` - Typegen config
36-
- `typesBundle.json` - Typegen bundle config
37-
- `schema.graphql` - The graphql schema file, changes to this file will results in changes to the models (`src/models`)
25+
- `indexer/` - Docker Compose setup for the indexer (archive)
26+
- `db/` - Processor database migration files
27+
- `scripts/` - Utility scripts (see [scripts/readme.md](./scripts/readme.md))
28+
- `src/` - Processor source code
29+
- `mappings/` - Event handler functions that map chain events to database entities
30+
- `model/` - Generated TypeORM models from `schema.graphql`
31+
- `types/` - Auto-generated type definitions (do not edit manually — run `make typegen`)
32+
- `processor.ts` - Processor entrypoint: event subscription and dispatch
33+
- `typegen/` - Type generation infrastructure
34+
- `tfchainVersions.jsonl` - Append-only log of runtime metadata from all TFChain networks
35+
- `typegen.json` - Typegen config: which events to generate types for
36+
- `typesBundle.json` - Frozen pre-V14 type mappings (do not edit for new runtime versions)
37+
- `docs/` - Documentation
38+
- [typeChanges.md](./docs/typeChanges.md) - How to handle type changes on chain (adding new runtime versions, resync guidance)
39+
- [development.md](./docs/development.md) - Local development setup
40+
- [production.md](./docs/production.md) - Production deployment
41+
- [release_process.md](./docs/release_process.md) - Release workflow
42+
- `schema.graphql` - GraphQL schema — changes here regenerate `src/model/` via `yarn codegen`
43+
- `Makefile` - Common tasks: `typegen`, `typegen-add`, `typegen-seed`, `version-bump`
44+
- `processor-chart/` - Helm chart for processor + query node deployment
45+
- `indexer/chart/` - Helm chart for indexer stack deployment

docs/advanced-development.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Advanced Development Guide
2+
3+
This document covers the internal architecture of the tfchain_graphql indexer/processor stack. For the day-to-day workflow (adding new runtime versions, resyncing), see [typeChanges.md](./typeChanges.md).
4+
5+
## 1. Pre-V14 Metadata and the typesBundle
6+
7+
Substrate runtime metadata comes in different versions. TFChain has two eras:
8+
9+
- **V12 (pre-V14)**: metadata is NOT self-describing for custom types. The `typesBundle.json` file provides type definitions that tell the decoder how to interpret SCALE-encoded data.
10+
- **V14+**: metadata is self-describing. All type information is embedded in the metadata itself. The typesBundle is not used.
11+
12+
The boundary differs per network:
13+
14+
| Network | Pre-V14 specs | First V14 spec |
15+
|---------|--------------|----------------|
16+
| Devnet | 49-67 | 101 |
17+
| QAnet | 61-67 | 104 |
18+
| Testnet | 9-70 | 113 |
19+
| Mainnet | 31-70 | 113 |
20+
21+
The typesBundle uses `minmax` ranges to define which type definitions apply at which spec versions. For example, `[50, None]` means "from spec 50 onwards until overridden by a later entry." When a type changes (e.g., a new field is added), a new `minmax` entry is added with the updated definition.
22+
23+
**Event hashes** for pre-V14 specs are computed from the typesBundle type definitions combined with the metadata. This is important: the same Rust struct can produce different event hashes depending on how the typesBundle defines it (field names, field order).
24+
25+
## 2. The Indexer-Processor Contract
26+
27+
The data flow from chain to GraphQL API is:
28+
29+
```
30+
Chain (SCALE-encoded events)
31+
-> Indexer (substrate-ingest) decodes SCALE using typesBundle -> stores decoded JSON in CockroachDB
32+
-> Gateway serves stored JSON
33+
-> Processor's decodeEvent() reads args[fieldName] from the JSON
34+
-> Processor stores entities in PostgreSQL
35+
-> Query node serves GraphQL API from PostgreSQL
36+
```
37+
38+
The processor does NOT decode raw SCALE bytes. It reads pre-decoded JSON from the gateway. The `decodeEvent` method in `@subsquid/substrate-processor` iterates over the event's field definitions and reads `args[fieldName]` from the stored JSON object.
39+
40+
This means **field names must match** between:
41+
- The typesBundle that the indexer used to decode and store the JSON
42+
- The type definitions the processor expects (derived from the current typesBundle + metadata)
43+
44+
If the typesBundle is updated but the indexer is not resynced, the stored JSON has field names from the old typesBundle while the processor expects names from the new one. This mismatch causes assertion failures during decoding.
45+
46+
**Rule: always resync the indexer after changing the typesBundle.** Alternatively, add workaround patches in the mapping handlers to fix the field names before decoding (see topic 4).
47+
48+
## 3. Cross-Network Metadata Differences
49+
50+
A given spec version number typically represents the same runtime binary across all networks. This was verified by comparing metadata hex from the firesquid indexers on all 4 networks:
51+
52+
```graphql
53+
# Query on each network's firesquid
54+
{ metadata(where: {specVersion_eq: 49}) { hex } }
55+
```
56+
57+
All shared pre-V14 specs have identical metadata across all networks that deployed them.
58+
59+
**Exceptions**: specs 125 and 134 have different WASM on devnet vs other networks. Devnet received release candidate builds that were later revised before deployment to qa/test/main. These are V14 specs, so the typesBundle is not involved. Verified that all tracked event hashes are identical despite the metadata differences (the changes are in non-event types).
60+
61+
The JSONL merge script (`scripts/merge-versions.js`) deduplicates by specVersion, keeping the first entry encountered. Since devnet is seeded first, devnet's metadata wins for conflicts.
62+
63+
## 4. The `dedicatedFarm:` Colon Bug
64+
65+
The typesBundle historically had a typo: `"dedicatedFarm:"` (trailing colon) in the Farm struct definition at `[63, None]`.
66+
67+
- **Introduced**: commit `478ee70`
68+
- **Fixed**: commit `980dd11`
69+
- **Grid deployment updated**: commit `119b5dc` (June 2024)
70+
- **Indexers resynced**: never
71+
72+
Because the indexers were never resynced, all network indexer snapshots contain decoded JSON with `"dedicatedFarm:"` (with colon) as the field key for pre-V14 Farm events. The current typesBundle (without colon) produces a different event hash, and the processor expects `"dedicatedFarm"` (no colon) as the field name.
73+
74+
When the processor reads `args["dedicatedFarm"]`, it gets `undefined` because the stored key is `"dedicatedFarm:"`. The SCALE JSON codec then asserts `typeof undefined == "boolean"` and crashes.
75+
76+
**Workaround** (in the `isV63` branches of `farmStored` and `farmUpdated`):
77+
78+
```typescript
79+
(item.event.args as any).dedicatedFarm = false
80+
```
81+
82+
This adds the expected field name to the args object before decoding. The value `false` is safe because `farmStored` hardcodes `dedicatedFarm = false` anyway.
83+
84+
**Proper fix**: resync all indexers with the corrected typesBundle so the stored JSON has correct field names. After resync, the workaround can be removed.
85+
86+
**Note**: the mainnet `grid_deployment` repo still has the old typesBundle with this colon bug. Devnet, qanet, and testnet have the corrected version.
87+
88+
## 5. How Typegen Assigns Version Labels
89+
90+
Typegen reads the JSONL file in order (sorted by specVersion). For each tracked event, it:
91+
92+
1. Computes the event hash at each specVersion by decoding the metadata (using the typesBundle for pre-V14)
93+
2. Compares the hash to the previous entry's hash
94+
3. If the hash changed, generates a new `isVxx`/`asVxx` accessor named after the specVersion
95+
4. If the hash is the same as the previous entry, skips it (consecutive hash dedup)
96+
97+
This means:
98+
- The JSONL order determines which specVersion gets the "canonical" label for each hash
99+
- The same hash can produce accessors at multiple specs if it appears, changes, then reverts (e.g., Twin hash oscillates at specs 125-127 due to devnet metadata differences)
100+
- Typegen **cannot** handle two JSONL entries with the same specVersion (duplicate TypeScript method names would cause compilation errors)
101+
102+
The `isVxx` runtime check is `getEventHash(eventName) === 'hash'`. It checks the current block's runtime hash, not the spec version. So `isV9` can match blocks at any spec version as long as the event hash is the same.
103+
104+
## 6. Network Deployment History
105+
106+
| Network | Genesis spec | Genesis block | Pre-V14 range | Notes |
107+
|---------|-------------|---------------|---------------|-------|
108+
| Testnet | 9 | 0 | 9-70 | Oldest continuous chain. Has all historical specs. |
109+
| Mainnet | 31 | 0 | 31-70 | Started later than testnet. |
110+
| QAnet | 61 | 0 | 61-67 | Reset. Started from spec 61. |
111+
| Devnet | 49 | 0 | 49-67 | Reset multiple times. Current chain starts at spec 49. Has RC specs 63-67 that are devnet-only. |
112+
113+
**Spec reuse after resets**: devnet was reset multiple times. Spec numbers 1-48 existed on the old devnet but are gone. The current devnet starts at spec 49. Git history may show commits with the same spec number from different eras. The deployed version is always the commit that bumps `spec_version` in `substrate-node/runtime/src/lib.rs`.
114+
115+
**Git commit pattern**: developers add features in commits while the runtime still has the old spec version, then a separate commit bumps the spec. The bump commit is what gets built and deployed. When tracing types at a spec version, look at the commit that set `spec_version: XX`, not earlier commits that may show intermediate states.
116+
117+
**Verifying deployed runtime**: compare metadata hex from firesquid indexers across networks. If the hex matches, the same WASM was deployed. If it differs, the networks have different code at that spec (typically devnet RC vs production release).
118+
119+
## 7. Debugging Event Decode Failures
120+
121+
### Symptoms
122+
123+
- `AssertionError: typeof value == "boolean"` (or "string", "number")
124+
- `AssertionError: The expression evaluated to a falsy value` in codec-json.js
125+
- Processor crash loop at a specific block
126+
127+
### Step 1: Identify the spec version
128+
129+
Enable `SQD_DEBUG=sqd:processor:mapping` on the processor (avoid `sqd:processor:*` which floods logs with serialization errors from the node-fetch URLSearchParams bug). Look for the specId in debug output or check which block the processor is stuck on:
130+
131+
```bash
132+
docker logs processor-container 2>&1 | grep "last processed block"
133+
```
134+
135+
Then query the indexer explorer for the spec at that block:
136+
```graphql
137+
{ blocks(where: {height_eq: XXXXX}) { spec { specVersion } } }
138+
```
139+
140+
### Step 2: Determine pre-V14 or V14
141+
142+
- Devnet: spec < 101 is pre-V14
143+
- Testnet/Mainnet: spec < 113 is pre-V14
144+
- QAnet: spec < 104 is pre-V14
145+
146+
For V14 failures, the issue is in the auto-generated types. For pre-V14, the typesBundle is involved.
147+
148+
### Step 3: For pre-V14 failures
149+
150+
1. Check the typesBundle `minmax` range covering this spec
151+
2. Verify the type definition matches the Rust source at that spec
152+
3. **Check what the indexer stored**: query the firesquid gateway for the event:
153+
```graphql
154+
{ events(where: {name_eq: "TfgridModule.FarmStored", block: {height_eq: XXXXX}}) { args } }
155+
```
156+
4. Compare the stored field names with what the processor expects
157+
5. If field names don't match, the indexer was built with a different typesBundle
158+
159+
### Step 4: Compare metadata across networks
160+
161+
Query each network's firesquid:
162+
163+
```graphql
164+
{ metadata(where: {specVersion_eq: XX}) { hex } }
165+
```
166+
167+
Compare the hex values directly. Same hex = same WASM. Different hex = different code at the same spec (typically devnet RC).
168+
169+
### Step 5: Compare event hashes
170+
171+
Save metadata to temporary JSONL files and run typegen on each to see what event hashes they produce:
172+
173+
```bash
174+
# Create single-entry JSONL from indexer metadata
175+
# Run typegen with the tracked events
176+
# Compare the getEventHash lines in the output
177+
```
178+
179+
### Step 6: Check production code
180+
181+
If production works but your branch doesn't:
182+
183+
```bash
184+
diff <(git show origin/production-branch:src/types/events.ts | grep getEventHash | sort) \
185+
<(grep getEventHash src/types/events.ts | sort)
186+
```
187+
188+
Look for removed hash branches or missing workaround patches in the mapping handlers.
189+
190+
### Common pitfalls
191+
192+
- **Mixed-version DB contamination**: running two different processor versions against the same PostgreSQL database creates mixed entity ID formats. Always do a full DB reset when switching versions.
193+
- **CockroachDB snapshot extraction**: never use `--strip-components` when extracting indexer snapshots. The tar archive has SST files at root level.
194+
- **Startup race**: on first start, the processor may fail with "relation does not exist" if migrations haven't completed. Docker restart policy recovers this automatically.
195+
- **Shared Docker network DNS**: when both compose stacks share a network, all service names must be unique across both stacks. The indexer uses `cockroachdb` (not `db`) to avoid collision with the processor's PostgreSQL `db` service.

0 commit comments

Comments
 (0)