Skip to content

feat(indexer): implement DIN Protocol subgraph — platform + task-level contracts#29

Open
robertocarlous wants to merge 18 commits into
InfiniteZeroFoundation:feat/din-indexerfrom
robertocarlous:feat/din-indexer
Open

feat(indexer): implement DIN Protocol subgraph — platform + task-level contracts#29
robertocarlous wants to merge 18 commits into
InfiniteZeroFoundation:feat/din-indexerfrom
robertocarlous:feat/din-indexer

Conversation

@robertocarlous

@robertocarlous robertocarlous commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the full DIN Protocol subgraph covering both platform-level and
task-level contracts, along with GraphQL integration into the CLI.


P4-IDX1 — Design

  • Event coverage audit (subgraph/docs/event-coverage-audit.md) — per-contract
    event table, gap analysis, and three proposed contract-side additions
  • GraphQL entity schema (subgraph/schema.graphql) — 17 entities covering model
    registration, manifest updates, validators, stake/slash/withdrawal, slasher
    registrations, DIN mint events, and token transfers
  • Subgraph manifest (subgraph/subgraph.yaml) — 4 data sources, 31 handlers,
    network din-local (chainId 1337)
  • Daemon event schema (subgraph/docs/daemon-event-schema.md) — subscription
    priority table for dind, coverage gaps, WebSocket recommendation for P4-7.1

P4-IDX2 — Implementation

  • AssemblyScript mapping handlers (subgraph/src/) — one file per contract:
    registry.ts (15 handlers), staking.ts, coordinator.ts, token.ts,
    shared utils.ts. Storage call bridge used for pending event additions
  • Local Graph node stack (subgraph/docker-compose.yml) — graph-node v0.34.1
    • IPFS Kubo + Postgres 16 against local anvil; quickstart in compose header
  • Example queries (subgraph/docs/example-queries.md) — 14 queries across
    validator registry, model registry, slash history, and slasher audit trail

P4-IDX3 — CLI Integration

  • Replaced both RPC enumeration loops in dincli/cli/dindao.py with GraphQL
    queries; silent fallback to RPC on connection error, HTTP error, or GraphQL
    error body
  • Unit tests (tests/test_list_pending_requests.py) — 6 tests, no live node
    required
  • Handoff notes (subgraph/docs/handoff.md) — DIN_SUBGRAPH_URL config,
    remaining RPC loop candidates in lms.py and aggregation.py
  • Doc-correction pass (Developer/issues/indexer.md) — resolved all 5 open
    questions, confirmed no stale references, added artefacts table

Task-level contracts (DINTaskCoordinator / DINTaskAuditor)

Implemented following the contract event audit and Umer's design confirmation.

  • Contract event audit (Documentation/technical/audits/task-contract-event-audit.md)
    — existing events, 23-site silent GI state machine, missing submission and
    finalization events, and six proposed additions with exact signatures and emit sites
  • 13 new schema entities covering the full GI lifecycle: GlobalIteration,
    aggregator/auditor registrations, T1/T2 batches, audit batches, local model
    submissions, scoring, eligibility voting/finalization, slash events, pass score
    governance, and GI state transition history
  • Dynamic data source templates in subgraph.yaml — one
    DINTaskCoordinator + DINTaskAuditor pair instantiated per model when
    ModelApproved fires on DINModelRegistry
  • registry.ts updated to call createWithContext() with shared context
    (modelId, paired contract addresses) so handlers on either side resolve the
    model back-reference and paired address without extra storage calls
  • taskCoordinator.ts — 4 handlers: aggregator registration, T1/T2 batch
    creation, aggregator slashing
  • taskAuditor.ts — 8 handlers: auditor registration, audit batch creation,
    scoring, eligibility voting/finalization, pass score governance, auditor
    slashing. Includes loadOrCreateLMS() stub so required relationship fields
    are never null before LocalModelSubmitted event lands

Pending — blocked on contract event additions

Five handlers are commented out in subgraph.yaml pending the additions
proposed in the audit (§4). All schema fields and handler stubs are in place.

Event Contract Unblocks
GIStateChanged(uint GI, uint8 newState) DINTaskCoordinator Full GI state history
LocalModelSubmitted(uint GI, uint modelIndex, address client, bytes32 cid) DINTaskAuditor Client submission tracking
T1AggregationSubmitted / T2AggregationSubmitted DINTaskCoordinator Aggregation submission indexing
T1BatchFinalized / T2Finalized DINTaskCoordinator Winning CID and global model CID

Uncommenting the entries in subgraph.yaml and regenerating the ABI is all
that is needed once these events land in the contracts.

Audits all events emitted by DINModelRegistry, DinValidatorStake,
DinCoordinator, and DinToken against indexer reconstruction requirements.

Findings:
- ModelRegistrationRequested: missing isOpenSource and fee — cannot filter
  pending requests by type or surface fee amounts without a storage call
- ManifestUpdateRequested: missing requester address — cannot attribute
  pending updates to their submitter
- DinCoordinator.withdraw(): no event emitted — ETH treasury outflows are
  invisible to the indexer
- SlasherContractAdded/Removed: identical signatures on DinCoordinator and
  DinValidatorStake — resolved at schema level via sourceContract field,
  no contract change needed
- ValidatorJailed path is dead code (jailedUntil is never set); flagged
  for the P3-4.x slashing spec to ensure a jailing event lands when that
  mechanism is added

Proposed additions (confirmed with Umer, 2026-07-09 — land as one PR after
audit review):
  1. ModelRegistrationRequested: + bool isOpenSource, uint256 fee
  2. ManifestUpdateRequested: + address indexed requester
  3. DinCoordinator: new ETHTreasuryWithdrawn(address indexed to, uint256 amount)

Includes daemon event schema table (P4-7.1 feed) mapping platform events to
dind subscription requirements and coverage gaps.
…ph (P4-IDX1)

Defines all queryable entities for the DIN platform subgraph covering
DINModelRegistry, DinValidatorStake, DinCoordinator, and DinToken.

Entities:
  Registry:  ModelRegistrationRequest, Model, ManifestUpdateRequest,
             ModelKillSwitchEvent, FeeSnapshot, RegistryFeeWithdrawal,
             DAOAdminTransfer
  Staking:   Validator (aggregate), StakeEvent, SlashEvent, UnstakeRequest,
             WithdrawalClaim, SlasherRegistration
  Coordinator: DINMintEvent, TreasuryWithdrawal, ExchangeRateSnapshot
  Token:     TokenMintEvent, TokenTransferEvent

Design notes:
- SlasherRegistration carries sourceContract to keep DinCoordinator and
  DinValidatorStake event streams distinguishable (identical event signatures)
- ModelRegistrationRequest.isOpenSource / .feePaid and ManifestUpdateRequest
  .requester are marked pending the §6.1/6.2 event additions; mapping handlers
  will use storage calls as a bridge until those events land
- TreasuryWithdrawal entity is defined but will not be populated until the
  ETHTreasuryWithdrawn event addition (audit §6.3) lands
- Task-level contracts (DINTaskCoordinator/DINTaskAuditor) are out of scope;
  deferred to P5+ dynamic data sources
Defines the Graph Protocol subgraph manifest covering all four platform
contracts on the local development chain (chainId 1337, network: din-local).

Data sources:
  - DINModelRegistry  → src/registry.ts    (15 event handlers)
  - DinValidatorStake → src/staking.ts     (8 event handlers)
  - DinCoordinator    → src/coordinator.ts (5 event handlers + 1 commented-out
                                            pending ETHTreasuryWithdrawn addition)
  - DinToken          → src/token.ts       (2 event handlers)

ABIs copied from dincli/abis/ into subgraph/abis/ — single source of truth
until a build step syncs them automatically.

Contract addresses are placeholders (0x000...001–004); replace with actual
deployed addresses after running the platform contract deployment script.

SlasherContractAdded/Removed duplicate-signature handling: DinValidatorStake
handlers produce the canonical slasher-set records; DinCoordinator handlers
produce audit-trail records with sourceContract set to the coordinator address.
Defines which platform-contract events the dind daemon must subscribe to,
the reaction each event triggers in the daemon job queue, and all current
coverage gaps. Handoff document from P4-IDX1 design to Santiago's P4-7.1
on-chain event listening engine.

Contents:
  §1 Subscription priority — immediate (blacklist/kill-switch), high-priority
     (slash/approval/manifest), and informational buckets
  §2 Full event payload reference for all daemon-consumed events, including
     PENDING field annotations for the two confirmed additions (audit §6.1/6.2)
  §3 Coverage gaps:
     - Task-level GI events (T1AggregationStarted etc.) not in platform
       contracts — listed as P5+ task-level indexing requirement with the
       exact event schema needed for daemon filtering
     - ValidatorJailed dead code path — required event shape documented for
       when P3-4.x slashing spec adds the jailing mechanism
     - ETHTreasuryWithdrawn missing from DinCoordinator.withdraw()
  §4 Subscription mechanics recommendation for Santiago (WebSocket
     eth_subscribe, lastProcessedBlock per-contract, replay on restart)
  §5 Integration order — dincli → SDK extraction → dind, per Umer 2026-07-09
…ontracts

Five source files covering all 30 event handlers declared in subgraph.yaml.

src/utils.ts
  - eventId(): stable txHash-logIndex entity IDs for event-based records
  - loadOrCreateValidator(): initialises Validator aggregate on first encounter
  - syncValidatorStatus(): mirrors DinValidatorStake._syncValidatorStatus()
    to keep the Validator.status field in sync after every mutation

src/registry.ts  (DINModelRegistry — 15 handlers)
  - Registration request lifecycle: Requested → Approved / Rejected
  - Bridge for missing event fields: modelRequests(id) storage call populates
    isOpenSource, feePaid, manifestCID, taskCoordinator, taskAuditor until the
    audit §6.1/6.2 event additions land
  - Manifest update lifecycle: Requested → Updated / Rejected; requester
    derived from Model.owner as bridge until §6.2 addition lands
  - Kill switch: ModelDisabled / ModelEnabled update Model.disabled and emit
    ModelKillSwitchEvent
  - Fee snapshots: individual setters read full fee schedule from storage so
    every FeeSnapshot carries all four values; FeesUpdated reads from event
    params directly (atomic — no storage call needed)
  - FeesWithdrawn → RegistryFeeWithdrawal
  - DAOAdminUpdated → DAOAdminTransfer

src/staking.ts  (DinValidatorStake — 8 handlers)
  - ValidatorStaked / Slashed / UnstakeRequested / WithdrawalClaimed update the
    Validator aggregate and create the corresponding history entities
  - Slash handler mirrors contract's active-stake-first, then pending-withdrawal
    deduction logic for accurate aggregate tracking
  - Blacklisted / Unblacklisted flip Validator.blacklisted and re-sync status
  - SlasherContractAdded/Removed: canonical slasher-set records with
    sourceContract = DinValidatorStake address

src/coordinator.ts  (DinCoordinator — 5 handlers)
  - EthDepositAndDINminted → DINMintEvent
  - DinPerEthUpdated → ExchangeRateSnapshot
  - ValidatorStakeContractUpdated: intentional no-op (one-time deploy wiring)
  - SlasherContractAdded/Removed: audit-trail SlasherRegistration records with
    sourceContract = DinCoordinator address (distinct from staking.ts records)
  - ETHTreasuryWithdrawn handler stubbed and commented pending audit §6.3

src/token.ts  (DinToken — 2 handlers)
  - TokensMinted → TokenMintEvent
  - Transfer → TokenTransferEvent (covers mints from 0x0 and normal transfers)
Runs graph-node, IPFS (kubo), and Postgres against the local anvil chain
(chainId 1153 1337, network: din-local) for subgraph development and testing.

Services:
  graph-node  graphprotocol/graph-node:v0.34.1
    - ports 8000 (HTTP), 8001 (WS), 8020 (deploy), 8030 (status), 8040 (metrics)
    - ethereum env var maps din-local → host.docker.internal:8545
    - GRAPH_ETHEREUM_TARGET_TRIGGERS_PER_BLOCK_RANGE=1 for on-demand anvil blocks
  ipfs        ipfs/kubo:v0.27.0
    - port 5001 (API); used by graph-node and graph deploy
  postgres    postgres:16-alpine
    - POSTGRES_INITDB_ARGS sets UTF8/C locale required by graph-node TOAST

Both ipfs and postgres have healthchecks; graph-node depends_on postgres
with condition: service_healthy so it waits for the DB to be ready before
attempting to connect.

.env.example documents the ETHEREUM_RPC and Postgres credential overrides.
Linux users must replace host.docker.internal with 172.17.0.1.

Startup sequence is documented in the docker-compose.yml header comment.
Replace the two for-idx-in-range enumeration loops in
list-pending-requests with GraphQL queries to the local subgraph,
with a silent fallback to the original RPC loops when the
graph-node endpoint is unavailable.

Add unit tests covering the GraphQL happy path and all three
fallback triggers (connection error, GraphQL error body, HTTP 5xx).
Documents the integration point, GraphQL queries used, fallback
behaviour, DIN_SUBGRAPH_URL config, and follow-up RPC loop
candidates in lms.py and aggregation.py.
@umeradl umeradl added this to the Devnet 3.0 milestone Jul 12, 2026
@umeradl

umeradl commented Jul 13, 2026

Copy link
Copy Markdown
Member

@robertocarlous One correction and a note on what to work on next.

Correction — there is no Phase 5. The "deferred to P5+" framing in this PR description and in the earlier issue discussion was a mistake on my part: Developer/ROADMAP.md has no P5 — the roadmap currently runs through P3 (cryptoeconomic layer) and P4 (daemon + indexer). Please disregard the P5+ references.

Next step: until your next weekly task is assigned, continue working on the subgraph — extend it to the task-level contracts (DINTaskCoordinator / DINTaskAuditor), i.e. the dynamic data sources work previously labelled out of scope. Since task-level events follow the federated-learning lifecycle (Global Iterations, LMS, auditor scoring, two-tier aggregation, slashing), read Documentation/public/workflows/model-workflow.md first to build that context before designing the task-level entities. The lms.py and aggregation.py RPC-loop call sites listed in your handoff notes are the natural integration targets for this follow-up.

@umeradl umeradl mentioned this pull request Jul 13, 2026
13 tasks
… DINTaskAuditor templates

taskCoordinator.ts — four handlers covering aggregator registration,
T1/T2 batch creation, and aggregator slashing. GlobalIteration is
lazily instantiated on the first event per GI.

taskAuditor.ts — eight handlers covering auditor registration, audit
batch creation, scoring, eligibility voting/finalization, pass score
governance, and auditor slashing. Introduces loadOrCreateLMS() which
pre-creates a stub LocalModelSubmission (zero client/modelCID) on first
reference so the required relationship field on AuditScoreSubmission,
EligibilityVote, and EligibilityResult is never null at query time;
stubs are upgraded in-place once the LocalModelSubmitted event lands.
@robertocarlous robertocarlous changed the title Feat/din indexer feat(indexer): implement DIN Protocol subgraph — platform + task-level contracts Jul 14, 2026
@robertocarlous

Copy link
Copy Markdown
Collaborator Author

@umermjd11 Pls can you take a look at this, thank you

@umeradl umeradl added the Draft label Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants