Skip to content

Latest commit

History

History
245 lines (177 loc) 路 7.53 KB

File metadata and controls

245 lines (177 loc) 路 7.53 KB

Parseon Terminology

This document defines the preferred vocabulary for Parseon code, API design, documentation, and future architecture work.

Naming should stay boring and precise. Clever names are cute until they become migration pain, API confusion, and a future archaeology project with grep.

Preferred vocabulary

Term Meaning Notes
Chain One EVM network indexed by Parseon. Ethereum, Base, Arbitrum, Optimism, etc.
Monitor User-defined indexing rule for a contract function. Prefer this over Watcher.
Target What a monitor matches. Usually chain_id + contract address + function selector.
Filter Optional condition applied after a call is matched and decoded. Prefer this over Predicate, Where, or Condition.
Cursor Per-monitor indexing progress. Last indexed or finalized block for a monitor.
BlockSource Adapter that fetches blocks, transactions, and receipts. Prefer this over generic Provider in core abstractions.
Storage Adapter that persists chains, monitors, progress, and decoded results. PostgreSQL first, MongoDB later.
Cache Adapter that stores fetched blocks or receipts temporarily. Memory first, Redis later.
Worker Runtime task that indexes one chain. One worker per chain in the multi-chain architecture.
Scheduler Component that decides which block ranges to fetch next. Owns batching and concurrency decisions.
DecodedCall A matched transaction call with decoded ABI parameters. Core/domain term.
DecodedEvent A matched EVM log with decoded event parameters and log identity. Core/domain term.
ResultRecord Storage-level persisted decoded call. Internal persistence term.
MonitorResult API representation of a persisted decoded call. User-facing API term.
Adapter Compile-time integration around the core. Prefer this over Plugin for now.
Sink Optional output destination for decoded data. Kafka, webhook, ClickHouse, files, etc.
Reorg Chain rewrite that invalidates already indexed blocks. Must be handled by core logic.
Finality Guarantee level of indexed data. Exposed through API/status metadata.

Monitor, not Watcher

Use Monitor for the user-defined rule:

Monitor = Target + block range + optional Filter + Cursor

A monitor has an immutable definition and mutable operational state. Its chain, target, block range, and filter are fixed at creation. enabled controls pause/resume, while the worker owns cursor and completion progress.

Prefer:

POST /monitors
GET /monitors/{id}
PATCH /monitors/{id} { "enabled": false }
GET /monitors/{id}/results
POST /monitors/{id}/reindex  # future dedicated operation

Avoid using Watcher for public API, database entities, or new core models. Watcher sounds like a running process and collides with Worker.

Monitor behavior lives in core::monitor; runtime execution belongs to core::worker.

Target

A monitor target defines what onchain call should be matched.

The human-readable ABI signature is accepted only when creating a monitor and is not persisted. A function selector is the fixed four-byte dispatch value derived from that signature, while an event topic0 is its fixed 32-byte signature hash.

Typical target fields:

chain_id
address
selector

Recommended Rust-style shape:

pub struct MonitorTarget {
    pub chain_id: ChainId,
    pub address: Address,
    pub selector: Selector,
}

Filter

A filter is an optional condition applied after matching and decoding.

Use Filter for user-facing and internal naming. Avoid Predicate unless referring to implementation internals of an expression evaluator.

Example JSON filter DSL:

{
  "and": [
    { "field": "tx.from", "op": "eq", "value": "0x1111111111111111111111111111111111111111" },
    { "field": "params.value", "op": "gte", "value": "1000000000000000000" }
  ]
}

The first filter language is a bounded, versioned JSON AST compiled against the monitor ABI before indexing. It supports scalar equality, integer ordering, and short-circuit boolean composition over decoded parameters and the metadata already fetched for successful calls or events.

Keep the JSON frontend separate from the typed expression representation. A later textual language can add richer paths, arithmetic, composite values, table state, and SQL compilation without moving parsing or database concerns into worker evaluation.

BlockSource, not Provider

Use BlockSource for core abstractions that read chain data.

Implementations may include:

JsonRpcSource
ErpcSource
EtherscanSource
FallbackSource

Reasoning:

  • Provider already has specific meaning in EVM/RPC libraries.
  • Source works for JSON-RPC, eRPC, Etherscan, archive nodes, and fallback chains.
  • Etherscan is not equivalent to JSON-RPC and should usually be used for backfill, fallback, ABI discovery, or metadata enrichment, not primary live indexing.

Storage and Sink

Use Storage for primary state and queryable decoded results.

Examples:

PostgresStorage
MongoStorage

Use Sink for optional output destinations that receive decoded data but do not own Parseon state.

Examples:

KafkaSink
WebhookSink
ClickHouseSink
FileSink

Do not call Redis storage in core terminology. Redis is a cache unless it becomes an explicitly supported source of truth, which it should not by default.

Cache

Use Cache for temporary fetched data.

Examples:

MemoryBlockCache
RedisBlockCache
NoopBlockCache

Cache keys must be chain-aware:

chain_id + block_number
chain_id + block_hash
chain_id + tx_hash

DecodedCall, DecodedEvent, ResultRecord, MonitorResult

Use different names for different layers:

Layer Term
Core DecodedCall
Core DecodedEvent
Storage ResultRecord
API MonitorResult

Use DecodedCall for calldata and DecodedEvent for EVM logs; do not collapse these distinct result kinds into one domain term.

Finality status

Use finality_status for user-facing result/block status.

Preferred values for early versions:

provisional
finalized
reorged

Internal lifecycle states may also include:

indexed
failed

Default API result queries should prefer finalized data where the chain and source can provide that guarantee.

Adapter, not Plugin

Use Adapter for now.

Parseon should start with compile-time adapters backed by stable Rust traits:

Storage adapter
BlockSource adapter
Cache adapter
Sink adapter

Avoid runtime-loaded plugins in early versions. Dynamic loading in Rust adds ABI stability, versioning, async trait, panic-safety, and deployment complexity. That is a lot of machinery just to rename a dependency problem.

External plugin processes over gRPC or another protocol can be considered later if real user needs appear.

Naming summary

Prefer:

Monitor, not Watcher
Filter, not Predicate
Target, not Subscription
BlockSource, not Provider
Storage, not DB plugin
Cache, not Cache plugin
Worker, not Watcher
DecodedCall or DecodedEvent, not generic Event
MonitorResult, not Transaction
Cursor, not Offset
Adapter, not Plugin
Sink, not Export plugin

A good Parseon sentence should read like this:

Parseon runs chain workers. Each worker reads finalized calls and logs from a block source, matches them against monitor targets, decodes calldata into decoded calls and logs into decoded events, persists monitor results through storage, and uses cache adapters to reduce repeated reads.