Reference · Phase-1 Capstone

risk-graph-indexer — the whole system on one page

A map of all 37 lessons: the core data flow (build + consume), the four deeper strands (at_risk · discovery · self-checking · billing), the coordination primitives beneath it all, the cross-cutting invariants, where the code lives, and what to watch. The compressed essence — keep it; print it.

1 · What it does (one sentence)

It watches the blockchain in real time and turns on-chain events into a risk graph in Memgraph — nodes are addresses, typed edges are relationships — then computes who's at risk if something fails, and fires customer alerts when risk crosses a threshold. Self-seeds once from the chain, then maintains incrementally forever (no batch runs).

2 · End-to-end data flow

① block-ingest
RPC → RawBlock; round-robin, prefetch, 7-day cache, finality lag
L1 · L7
Redis Stream
blocks:{chain} — durable ordered log; consumer group; at-least-once; backpressure
L7
② indexer · decode
topic0 → registry → typed DecodedEvent (20 decoders)
L3
filter
monitored set (SISMEMBER) drops ~95%
L1
accumulate · coalesce · write
batch → UNWIND+MERGE → atomic commit w/ cursor
L4
③ enrichment-worker
poll pending bare nodes → classify (RPC/Etherscan/Blockscout) → structural edges
L5
discovery 🔁
related addrs → monitored set → graph self-expands (flywheel; admin/oracle discovery; bridger)
L5 · L2428
④ risk engine
exposure BFS + AT_RISK cells (failure-source → victim token); periodic full recompute → aggregate → caps → edges
L6 · L1923
graph-writer
ALL bulk writes funnel through ONE leased pod (no OCC); 2 lanes; DLQ+halt
L9

Genesis: the graph is born by self-seeding (factory walks around focus tokens), then the loop above runs forever. L10

…and the consumer side (what it's all for)

risk fields
node_risk_score/admin_risk_usd — max (USD) vs noisy-OR (0..1), HHI; stamped on nodes
L13
⑤ rule engine
customer rules: field+op+value over a scope; firing state machine debounces
L12
alert-processor
AlertStream → dedup (msg-id) → OpenSearch + notify (ext. service) → human
L15 · L18
read surface
query-api (scaffold) · NodeLookup — anchored, cached; reads pre-computed fields (CQRS-style: 1 writer, many readers)
L14
·
admin-panel
operator console — auth0 + RBAC + audit; Cypher-backed views (human control plane)
L17

3 · Follow one Transfer (the canonical trace)

  1. block-ingest fetches the block (with the Transfer log), caches RPC, XADDs to blocks:{chain}.
  2. indexer XREADGROUPs it; the transferDecoder (topic0 match) emits a typed TransferEvent.
  3. monitored-set check: receiver tracked? keep it (or promote if ≥ $1M focus-token transfer).
  4. accumulate the delta; coalesce with other transfers this batch into one net balance.
  5. flush: UNWIND…MERGE (token)-[:HOLDS]->(holder); new nodes get pending_enrichment=true.
  6. commit mutations + block cursor in one transaction (via the graph-writer Request) — atomic.
  7. later: enrichment classifies the bare node, writes structural edges, discovers related addrs.
  8. risk engine recomputes exposure / AT_RISK + risk fields (node_risk_score, admin_risk_usd) for the affected nodes. (L6·L13)
  9. a customer rule on a matching field crosses its threshold → firing state machine emits an AlertEvent. (L12)
  10. alert-processor consumes it, dedups by message-id, stores to OpenSearch, and delivers it to a human. (L15)

4 · The cross-cutting invariants (why it's correct)

Block-determinism: state at BlockCursor=N = pure function of blocks 0..N. ⇒ recovery is re-derivation, not repair. L8
Atomic commit: mutations + cursor land together or not at all ⇒ crashes roll back cleanly. L4
Idempotency: coalesced state + MERGE + monotonic updated_block guard ⇒ at-least-once redelivery is safe. L4·L7
One source of truth: Memgraph is canonical; Redis is a rebuildable projection (self-heal on startup). L8
Single-writer: all bulk writes serialize through one leased pod ⇒ no Memgraph OCC aborts. L9
Backpressure, end to end: slow down, don't drop, don't crash — ingest (L7), write side / watchdog (L8).
Effectively-once delivery: at-least-once transport + a stable idempotency key (MERGE / graphwrite IdemKey / alert doc-id = msg-id) ⇒ replays are no-ops. L4·L9·L15
Fail loud, never corrupt silently: stop + page rather than proceed wrong — watchdog pause (L8), DLQ + halt (L9), fatal index-mapping guard (L15). L15
Read/write asymmetry (CQRS): one careful serialized writer; many cheap anchored cached readers over pre-computed fields. L14
Prove it, three ways: the graph is continuously checked against the chain (chainref, audited + actuated), against its own rules (validation, monitored), and against the Python batch (parity). Derived data is never assumed correct. L29·L34·L23
Automated actuators stay caged: healers + the Linear promoter ship default-off / shadow-first, drip under a budget, never block the primary path, and prefer a loud duplicate to a silent miss. L30·L31·L33

5 · The 36-lesson index

#LessonThe one idea
L1Three-binary pipelineEvents in → graph edges out; 3 binaries + the monitored-set filter.
L2Graph data model:Entity nodes, graph_id partition, 19 edges in 8 categories; query rules.
L3Event decoder pathtopic0 → registry map → LogDecoder → typed event.
L4Write pathAccumulate → coalesce → UNWIND/MERGE → atomic commit w/ cursor.
L5Enrichment workerPoll → claim → classify → structural edges → discovery feedback loop.
L6Risk engineAT_RISK cells (failure-source → victim token) + exposure BFS, incremental.
L7Streaming backboneDurable log + consumer groups + at-least-once + backpressure.
L8Failure & recoveryDeterminism + truth-vs-cache ⇒ reorg/crash/flush/divergence all recover.
L9Single-writerOCC disaster → serialize via one leased graph-writer; explains recorder + 2 cursors.
L10BootstrapSelf-seed from the chain (factory walks); resumable locked task DAG.
L11ObservabilityMetrics/traces/logs; trace context hops through the stream; the signal map.
L12Rule engineCustomer rules over risk fields; scope resolution; firing state machine → alerts.
L13Risk mathSeverity weights; USD = max over attack channels; score = noisy-OR; HHI.
L14Read surfacequery-api scaffold; never full-scan; pre-computed fields; read/write (CQRS) asymmetry.
L15Alert processorAlertStream (L7 idiom) → dedup-by-msg-id → OpenSearch + notify; fail-loud ops.
L16Exposure-BFSWeight 1.0 decays ×edge-weight per hop; max over paths; minDelta prune; Bardoscia.
L17Admin panelOperator console: auth0 identity + RBAC guard + audit log; Cypher-backed views.
L18Delivery boundaryResilient HTTP adapter to an external notifier; know where the system ends.
Strand A · the at_risk engine, end to end
L19at_risk aggregationCells → number: value_pool collapse, max-in/sum-across, 3 caps. at_stake vs extractable.
L20Oracle attack pathOne target_role, two attacks: DeFi-borrow (other-asset) + NAV-redemption. Honest-zero.
L21Exit liquidity v2The drainable ceiling: 4 mechanisms, counter-token apportionment, the $2.85B LTV bug.
L22Multisig expansionAnchor → signer cells at at_stake/threshold; where the partial flag is born.
L23at_risk lifecycleTrigger (periodic full recompute) · HOLDS/vault paths · parity diff + 4 invariants.
Strand B · discovery — how the graph grows itself
L24Discovery flywheelBare node → RPC classify → RelatedAddrs → monitored set → repeat. Seeders vs refreshers.
L25Admin-role discovery17-probe battery (no admin standard); OZ ladder; severity stamped at source (feeds L13).
L26Oracle/NAV discoveryRegistry symbol-binding → value_defining_oracle + ORACLE_DEP; cross-source net.
L27Oracle bridgerTransitive ORACLE_DEP (pure Cypher): markets/vaults inherit their assets' oracles.
L28Project inferenceContract → protocol via label precedence cascade + canonical-slug parity. A good-enough heuristic.
Strand C · self-checking — does the graph stay correct?
L29Chainref harnessRe-read chain: C vs G → gap/excess/drift; streak → ticket/heal. Chain-truth vs parity.
L30Healer subsystemAuto-fix drift; 5 guards; heal-freely/prune-fearfully; shadow mode first.
L31Reconcile transportShared safe-apply: budget + 2s deadline + single-writer; secondary writes never stall primary.
L32Balance conservationΣHOLDS≈totalSupply; asymmetric band = coverage policy; big.Int sum, float ratio.
L33Linear promoterFile each finding once: create-then-mark, owned window, loud-dupe-over-silent-miss.
L34Validation suiteSelf-consistency monitor; fast/slow tiers; per-check isolation; monitor not control loop.
Strand D · billing + corners
L35Cost allocationAllocation, not measurement: blended-share of signals × sales-chosen pool; co_users split.
L36Balance-cache gatewayOne un-bypassable plausibility invariant contains sticky cache corruption.
L37Coordination primitivesLease + writer-epoch fencing (the "how" of L9); genesis self-heal; immutable-truth-heals-live.

Companion refs: Glossary (nodes/edges/terms + Cypher · incl. at_risk terms) · Add-a-Decoder card.

6 · Where the code lives (landmarks)

SubsystemKey files
Block producercmd/block-ingest/main.go, pkg/feed/rpc.go
Stream / queuepkg/queue/queue.go (XAdd/XReadGroup/XAck/XAutoClaim, backpressure)
Decoderspkg/decoder/topics.go (topic0s), decoder.go (registry + decoders)
Graph vocabularypkg/types/schema.go (NodeType, EdgeType, EdgeCategory)
Write pathpkg/indexer/batch_writer.go (accumulate/coalesce/flush), indexer.go
Enrichmentpkg/enrichment/{worker,classify,enricher}.go; docs/enrichment-pipeline.md
Risk enginepkg/risk/{at_risk_*,bfs,exposure}.go; PARITY_NOTE.md
Single-writerpkg/graphwrite/, pkg/indexer/tx_recorder.go; docs/single-writer.md
Failure/recoverycmd/indexer/cursor_watchdog.go, main.go (self-heal), pkg/genesis/balance_rebuild.go
Bootstrapinternal/freshstart/, pkg/bootstrap/, pkg/enrichment/v*_pool_seeder.go
Observabilitypkg/telemetry/, otel-collector-config.yaml, grafana/*.json
Rule enginepkg/rules/{engine,types,scope,firing}.go, docs/rule-fields.json (field catalog)
Risk mathpkg/risk/{admin_risk,node_risk_score,vault_concentration}.go, pkg/types/role_severity.go
Exposure BFSpkg/risk/bfs.go (computeNodeExposure), exposure.go (incremental)
Admin panelcmd/admin-panel/{auth.go, main.go}, auth0/xpapi_audit.go; docs in cmd/admin-panel/docs/
Read surfacecmd/query-api/main.go (scaffold), cmd/admin-panel/ (auth0), pkg/rules/node_lookup.go
Alert deliverypkg/alertprocessor/{processor,opensearchstore}.go, cmd/alert-processor/main.go, pkg/notification/
at_risk engine (L19–23)pkg/risk/at_risk_{cells,aggregate,extractable,exit_liquidity,value_pool,multisig,edges,scheduler,diff}.go
Discovery (L24–28)pkg/enrichment/{enricher,discovery,oracle_bridger,navlink_*,admin_roles,focus_token_admin_probes,project}.go
Self-checking (L29–34)pkg/quality/chainref/{verifier,runner,findings,linear_promoter,runner_healer,verify_*}.go, pkg/reconcile/, pkg/validation/
Billing (L35)pkg/pricing/allocate.go, cmd/cost-tool/; docs/pricing-model.md
Balance cache (L36)pkg/balancecache/balancecache.go (typed gateway + plausibility invariant)
Coordination (L37)pkg/statetracker/{lease,writer_epoch,transient,cursor}.go, pkg/genesis/balance_rebuild.go, pkg/reconcile/vaultheal/

7 · The signal map (concept → what to watch)

ConceptMetric
Backpressure (L7)block_ingest.backpressure.blocked
Writer backlog (L9)graphwrite_stream_backlog
Reconcile heal (L8)chainref_reconcile.heal.total
ConceptMetric
Risk cycle (L6)at_risk.cycle.duration
Enrich APIs (L5)blockscout.api.{calls,errors,duration}
Bootstrap (L10)bootstrap.task.attempts
Alerts (L15)OpenSearch risk-alerts index · Delivered flag
Quality findings (L29)OpenSearch quality-findings · streak; chainref_verifier.skip
Self-test (L34)validation.{errors,warnings,findings} per check_id

8 · The hard rules (from CLAUDE.md — keep these reflexive)

Scope every Cypher by graph_id; never full-scan (MATCH (n)); query both edge directions.
Read a node's kind via coalesce(n.type, n.subcategory, n.category). Memgraph has no APOC.
Single position > $1T = bug (overflow), not a number.
Bulk writes go through the graph-writer (a forbidigo lint guard enforces it); don't add raw ExecuteWrite.
Risk engine has strict parity-vs-Python — read the parity test before touching a cell path. Not a first-PR area.

🎓 Deep-understanding tour: L1–L37 — COMPLETE, every major package opened. The build/maintain pipeline (L1–L11), the consumer/product side (rules L12 · read surface L14 · alerts L15 · boundary L18 · admin-panel L17), and the four deeper strands: at_risk end to end (cells/aggregation L6·L19, oracle attack L20, exit liquidity L21, multisig L22, lifecycle L23, field math L13, exposure L16); discovery, how the graph grows itself (flywheel L24, admin/oracle discovery L25·L26, bridger L27, project inference L28); self-checking, how it stays correct (chainref L29 · healers/transport L30·L31 · a verifier L32 · promoter L33 · validation L34); and billing (cost allocation L35); under it all sit the coordination primitives — balance-cache gateway (L36) and the lease + writer-epoch fencing, genesis self-heal, and immutable-truth healers (L37). This map is the index to your collection — every box links to the lesson that explains it. Print it (⌘P → Save as PDF) and keep it beside the glossary. When phase 2 (contributing) begins, start from the Add-a-Decoder card + L3/L4.