Reference · Phase-1 Capstone

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

A map of all 50 lessons: the core data flow (build + consume), the four deeper strands (at_risk · discovery · self-checking · billing), the coordination primitives beneath it all, the post-tour deeper dives (risk math · recorder · Cypher · Go · decoder · firing), the four cross-cutting syntheses + the end-to-end exploit-trace capstone, the 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 50-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.
Deeper dives · below lesson altitude (post-tour, into specific algorithms & hot-path code)
L38node_risk_score internalsNoisy-OR of 4 factors — 3 are 0.5 stubs today; governance = min-over-admins (vs admin_risk's max).
L39HHI / vault concentrationHHI = Σ shares² (squares punish bigness); effective strategies = 1/HHI; em-dash-not-zero.
L40Exposure-BFS edge weightsEdge weight = transmission coefficient: LTV / allocation-share / 1.0 / 0.0. Closes L16.
L41graphwrite recorderA Tx that buffers; coalesce-by-template → UNWIND; lanes; DLQ-over-XACK-forget. The "how" of L4/L9/L31.
L42Reading the CypherSkills: six idioms (anchored scope · UNWIND-by-id · bounded both-dir walk · collect · MERGE/anti-join · coalesce-kind).
L43Go concurrency patternsSkills: ctx-cancellable loop · fan-out + WaitGroup · isolate w/ timeout+recover · Mutex; context underneath.
L44Protocol decoderBytes → typed event: indexed-topic / fixed-data / dynamic head-tail; bound-before-allocate guard. The "how" of L3.
L45Rules firing state machineCLEAR/ACTIVE/COOLDOWN; one cooldown = suppression + hysteresis; events have no ACTIVE. The "how" of L12.
Cross-cutting syntheses & capstone · the unifying disciplines, then all of it applied
L46Combinator familyOne principle behind MAX/SUM/MIN/noisy-OR: collapse overlap, combine independence; worst-for-danger, best-for-protection.
L47Float-determinism & parityExact-in (KeyedSum/big.Int), tolerant-out (×1.001/epsilon); non-determinism is the enemy; lint + parity enforce it.
L48Idempotency everywhereAt-least-once ⇒ every write a no-op on replay: upsert / monotonic guard / anti-join / whole-set recompute.
L49RPC-budget economicsOne scarce resource shapes the architecture: filter/cache/batch/sample/slice/pace/degrade. The graph is a cache of the chain.
L50Trace an exploit end-to-endCapstone: one AddedOwner log → ~20 subsystems → a human, with all four lenses live. Applies all 49.

Companion refs: Glossary (nodes/edges/terms + Cypher · incl. at_risk & risk-engine 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 journey: L1–L50 — COMPLETE. Every package opened, every mechanism opened below it, four cross-cutting disciplines named, and the whole machine applied to one real attack (L50). 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). Then the deeper dives drop below lesson altitude: the risk math down to its parameters (node_risk_score L38 · HHI L39 · BFS edge weights L40), the most-referenced hot-path mechanism (the graphwrite recorder L41), the byte-level decode (L44) and the alerting state machine (L45), plus two skills lessons closing the stated stack gaps — reading the Cypher (L42) and the Go concurrency patterns (L43). Finally, four cross-cutting syntheses name the disciplines that unify it all — combinators (L46), float-determinism & parity (L47), idempotency (L48), RPC economics (L49) — and a capstone (L50) follows one real attack end-to-end through ~20 subsystems with all four lenses live. 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.