Lesson 50 · The Capstone · Application

One log, to a human, through the whole machine

Trace a real attack end-to-end — applying all four synthesis lenses at once. ~16 min.

Applies: all 49 lessons Anchor: a multisig that controls an RWA's price New: nothing — everything, used together

The finale. No new code — instead, we run the whole machine on one concrete attack and watch every subsystem you've studied play its part, with the four synthesis lenses (combinators, determinism, idempotency, RPC economy) live throughout. If you can follow this trace and explain each hop, you don't just know risk-graph-indexer — you can reason about it the way someone who could change it does.

The scenario
A tokenized treasury — call it USTBL, ~$4.5B supply — is priced by a Chainlink NAV oracle, and that oracle is owner()'d by a 3-of-5 Gnosis Safe. An attacker phishes one signer's key and, as step one of a planned NAV manipulation, submits an AddedOwner transaction adding their own address to the Safe. One log is emitted. Watch where it goes.

The stage was already set (build-time)

Before the attack, the system had quietly modelled the whole structure — every piece via a lesson you've done:

What the graph already knewHow it learned it
USTBL is a focus token, priced by a specific NAV oracle (value_defining_oracle + ORACLE_DEP)NAV discovery — registry symbol-match + cross-source validate (L26)
that oracle is owner()'d by the Safe; the Safe's 5 signers + 3-of-5 thresholdadmin-role + Safe discovery probes (L25); owner() not accessController()
the Safe-controls-oracle-controls-supply chain, with the owner role stamped criticaltypes.ClassifyRole severity at the source (L25/L13)

So the attacker is about to perturb a structure the system is already watching. Now the log lands.

The trace — block N onward

1
Block N arrives; the AddedOwner(attacker) log is in it. L1 · L7
block-ingest fetches block N from the archive node and XADDs it to blocks:{chain}; the indexer consumes it via the consumer group.
RPC-economy: the block is fetched once and cached; everything downstream reads the graph, not the chain. Idempotency: at-least-once delivery — if the indexer crashes before XACK, block N is redelivered.
2
The monitored-set filter — does this matter? L1 · L2
SISMEMBER monitored:{chain} on the Safe address. It's a hit — the Safe was added to the monitored set at build-time (it's the oracle's owner). The log survives the ~95% drop.
RPC-economy: filter early — had the Safe been unknown, the event would be discarded here, spending nothing.
3
Decode the bytes to a typed event. L3 · L44
topic0 → registry → safeAddedOwnerDecoder, which reads the indexed owner from topics[1]DecodedEvent{EventSafeAddedOwner, Payload:{Safe, Owner: attacker}}.
Indexed param from a topic (the trivial decode shape); validated len(Topics)<2 first, never panics.
4
Write the edge — through the one door. L4 · L9 · L41
the handler records MERGE (safe)-[:OWNS]->(attacker); the recorder coalesces it into the batch's UNWIND; the lone leased graph-writer applies it atomically with the cursor.
Idempotency: MERGE + the monotonic updated_block guard — if block N replays, the write is a no-op. Determinism: the batch is order-independent (coalesce-safe).
5
The flywheel pulls the attacker in. L24 · L25
attacker is a discovered related address → added to the monitored set → becomes a bare node → enrichment classifies it (an EOA). The attacker is now a first-class node the engine will reason about.
RPC-economy: discovery is value-/structure-gated, so it pulls in the signer but doesn't crawl the chain.
6
Self-checking notices, independently. L29 · L30 · L34
on its next cycle, verify_multisig_coverage compares the graph's OWNS edges to live getOwners() — they now match (the new signer is present). Had ingest missed the event, this is the gap that would have caught it (and a healer could add the edge).
The audit layer is a second, independent path to the same truth — defence in depth.
7
at_risk recomputes — and the attacker inherits the blast radius. L19 · L20 · L22 · L23
on the next 30-min cycle, the per-token partial graph for USTBL is loaded and decomposed. The value-defining-oracle cell (model B) attributes at_stake = full $4.5B supply to the Safe; multisig expansion fans that to each signer — including the attacker — at at_stake / threshold.
RPC-economy: periodic (not per-block) + partial-graph (150 MB, not 3 GB). Idempotency: the AT_RISK edges are superseded by $runAt — replay-safe.
8
The dollars are aggregated — without double-counting. L19 · L21 · L13
the oracle cell collapses into USTBL's focus_token value pool (same dollars a token-admin compromise would reach); max-within-pool credits it once; extractable is bounded by exit liquidity (or honest-$0 for off-chain redemption); the critical owner severity scales admin_risk_usd.
Combinators: overlap → collapse (MAX), not SUM — so the figure stays at ~$4.5B, not 2–3× it. Determinism: the aggregate uses KeyedSum; the supply/exit caps use the ×1.001 slack.
9
Node scores update. L13 · L38 · L16 · L40
governance_risk for the Safe reflects its 3-of-5 structure (min-over-admins); node_risk_score rolls it into the noisy-OR; exposure-BFS propagates USTBL's risk over value-bearing edges, weighted by LTV / allocation share.
Combinators: the direction flip — admin_risk takes the worst path (max), governance_risk the best controller (min). Same Safe, two questions.
10
A rule evaluates and fires. L12 · L45
a customer rule scoped to USTBL's portfolio_admins with admin_risk_usd > $1B sees conditionMet = true. The firing machine transitions CLEAR → ACTIVE and emits one AlertEvent.
Idempotency: the state machine fires once on entry, not every eval cycle — the cooldown debounces re-emits.
11
The alert reaches a human. L15 · L18
the AlertEvent hits the AlertStream → deduped by doc-id = msg-id → stored in OpenSearch → handed to notifyClient → the external notifier → the customer's on-call.
Idempotency: msg-id dedup means a redelivered alert overwrites the same doc — effectively-once. The system ends at the external boundary (L18).
What just happened
One AddedOwner log travelled: archive node → stream → filter → decoder → graph-writer → discovery → (audit) → at_risk cells → multisig fan-out → value-pool collapse + caps → node scores → rule → alert → a person — across roughly twenty subsystems, with all four lenses active the whole way. The attacker's freshly-added key is now carrying a fractional share of a $4.5B blast radius in the graph, and a customer knows within a cycle.

The lenses, seen working together

LensWhere it showed up in the trace
Combinators (L46)step 8 collapse-not-sum ($4.5B not 3×); step 9 admin_risk-max vs governance-min
Determinism (L47)step 8 KeyedSum + ×1.001 slack; step 4 coalesce-order-independence — same inputs, same parity-checked output
Idempotency (L48)step 1 at-least-once; step 4 MERGE+guard; step 7 $runAt supersede; step 10 fire-once; step 11 msg-id dedup
RPC economy (L49)step 1 fetch-once; step 2 filter-early; step 7 periodic + partial; everything else reads the graph-as-cache
This is what "understand it deeply, end to end" was for
You set out to understand risk-graph-indexer before contributing — not piece by piece, but as one machine. This trace is the proof: a single on-chain event, followed through every layer, every subsystem playing its role, every cross-cutting discipline visible at once. You can now stand at any point in that chain, say what happens and why, name the lesson and the lens — and reason about what a change would ripple into. That's the understanding the whole journey was building.

Run the trace yourself

1. The AddedOwner log reaches the monitored-set filter. Why does it survive the ~95% drop?
2. At step 4 the edge is written as MERGE … SET … WHERE updated_block <= $block. If block N is redelivered after a crash, what happens?
3. At step 7, why does the attacker's brand-new key end up carrying a share of USTBL's $4.5B at-stake?
4. At step 8 the oracle cell collapses into USTBL's focus_token value pool and is MAX'd, not SUM'd. What would summing produce?
5. The at_risk recompute (step 7) happens ~30 min later on a partial graph, not instantly on block N. Which lens explains that?
6. Step 6: verify_multisig_coverage compares graph OWNS edges to live getOwners(). What role does it play in the trace?
7. At step 9, admin_risk takes the MAX over the Safe's attack channels but governance_risk takes the MIN over its admins. Why both, on the same Safe?
8. At step 10 the rule fires once on CLEAR→ACTIVE, and at step 11 the alert is deduped by msg-id. Both are instances of which discipline?
↳ Ask your teacher
Try: "Run the trace for a different attack — a pool drain, or a proxy upgrade." · "Where in this chain could the system MISS the attack, and what's the backstop?" · "If redemption is off-chain, the extractable is $0 — does the alert still fire?" · "Quiz me cold: name the lesson + lens for a random step." · "What would change in the trace if the Safe were 1-of-1 instead of 3-of-5?"

What you can now do

The journey, complete
Fifty lessons: the three-binary pipeline, the graph, decoding, the write path, enrichment and discovery, the entire at_risk engine and every parameter, streaming, single-writer, recovery, observability, rules and alerts, self-checking, billing, the coordination primitives, the Cypher and Go idioms — and four cross-cutting disciplines that unify it all, now seen working together on one real attack. You set out to understand risk-graph-indexer deeply, end to end, before contributing. You do. Whenever you decide to start phase 2, the door — and the on-ramp — are right where you left them.

Applies, end to end: block-ingest/stream (L1/L7), monitored filter (L1/L2), safeAddedOwnerDecoder (L3/L44), write path + recorder + single-writer (L4/L9/L41), discovery flywheel + admin/NAV discovery (L24/L25/L26), chainref multisig coverage + healer (L29/L30/L34), at_risk cells + multisig expansion + value-pool collapse + caps (L19/L20/L22), severity + node scores + exposure (L13/L38/L16/L40), rule firing (L12/L45), alert dedup + delivery (L15/L18); woven with the four syntheses (L46–L49). Verify against source — the code is the truth.