rindexer Documentation

repository·master·Indexed 20 days ago

https://github.com/joshstevens19/rindexer

A high-speed, open-source EVM indexing toolset written in Rust. rindexer allows developers to index blockchain events using a no-code YAML configuration or a developer framework for building custom indexing pipelines. It includes a CLI (rindexer_cli v0.41.0) for project management and code generation, and provides a GraphQL API for querying indexed data. It supports multi-network indexing, factory pattern discovery, and storage backends including PostgreSQL, ClickHouse, and CSV.

Tokens
116.2K
Snippets
385
Records
512
Agent score
71%

What's inside rindexer

  1. Overview of rindexer

    master

    rindexer is a high-speed, open-source indexing toolset written in Rust, designed for compatibility with any EVM (Ethereum Virtual Machine) chain. It provides two primary ways to use it:

    1. No-code indexing: Use a simple YAML configuration file to index chain events without writing additional code.
    2. Developer framework: Use the provided Rust foundations to build custom, highly extendable indexing pipelines, allowing you to focus on business logic while rindexer handles the heavy lifting.

    Out of the box, rindexer provides a GraphQL API to query your indexed data instantly.

  2. Understand the core value of rindexer

    master

    rindexer is a Rust-based indexing solution for EVM chains designed to solve the complexity and performance bottlenecks of traditional indexing infrastructure. It provides two primary modes of operation:

    1. No-code Indexing: For basic data reporting and simple event indexing, you can use straightforward YAML-based configurations. This allows you to start indexing without writing any code.
    2. Custom Framework: For advanced requirements, rindexer provides a framework that abstracts the low-level complexities of fetching chain data. This allows developers to focus on implementing project-specific business logic rather than the mechanics of chain interaction.

    By leveraging Rust, rindexer offers high-throughput data management with minimal latency and memory safety, making it suitable for real-time data analysis.

  3. How rindexer handles log block-timestamps

    master

    Standard JSON-RPC specs often do not expose block timestamps in log results, forcing indexers to perform an additional RPC lookup per log. rindexer uses several strategies to mitigate this bottleneck:

    1. RPC Support: Leveraging node implementations (like GETH and RETH) that include timestamps directly in logs.
    2. Delta Run-Length Encoding: For chains with roughly fixed block times, rindexer uses highly compressed binary files (kB to MB scale) to store "runs" of the delta between timestamps. This optimizes backfill operations.
    3. Fixed Timestamps Chains: For the simplest networks, rindexer treats the entire chain as a single "run," allowing timestamp calculation for any block up to a validated consistency point.
    4. Sampled & Batched Lookups: If no precomputed mapping exists, rindexer performs optimized, concurrent, and batched RPC calls.
      • Loose Time-Ordering (Sampling): Users can configure a sample rate to fetch timestamps at spaced intervals (e.g., every 50 blocks) and interpolate between them. This significantly reduces network latency and RPC processing at the cost of slight timestamp inaccuracy.
  4. Understand Reth ExEx chain state notifications

    master

    When running in Reth mode, rindexer processes three types of ExExNotification events to maintain consistency with the chain state:

    1. Committed: Emitted when new blocks are added to the canonical chain.
    2. Reorged: Emitted during reorganizations. It provides the range of blocks to revert (revert_from_block to revert_to_block) and the details of the new canonical blocks (new_from_block to new_to_block and new_tip_hash).
    3. Reverted: Emitted when blocks are rolled back (chain rollback).
    // Committed
    Committed {
        from_block: 19000000,
        to_block: 19000100,
        tip_hash: 0x123...
    }
    
    // Reorged
    Reorged {
        revert_from_block: 19000098,
        revert_to_block: 19000100,
        new_from_block: 19000098,
        new_to_block: 19000101,
        new_tip_hash: 0x456...
    }
    
    // Reverted
    Reverted {
        from_block: 19000099,
        to_block: 19000100
    }
  5. How Event Handlers work in rindexer

    master

    An event handler is a closure passed to an event's .handler() method. It receives two primary arguments:

    1. results: A collection of decoded event logs and their associated transaction information.
    2. context: A thread-safe struct providing access to services like the database and CSV appender.

    Because of Rust's memory management requirements, the handler closure must use the async move syntax. The handler must return a Result<(), String>. If the handler returns an Err, rindexer will retry the event with exponential backoff. A successful return is Ok(()).

    TransferEvent::handler(
        |results, context| async move {
            // Your custom logic here
            Ok(())
        },
        no_extensions(),
    ).await
  6. Configure Kafka streams in rindexer

    master

    rindexer allows you to stream data to Kafka by adding a kafka property under the contracts or native_transfers section of your YAML configuration. The kafka property accepts an array of topics, allowing you to split streams across different Kafka topics.

    Supported configurations include both SSL and non-SSL queues.

    contracts:
    - name: MyContract
      # ... other contract details
      streams:
        kafka:
          brokers:
            - ${KAFKA_BROKER_URL}
          topics:
            - topic: my-topic
              networks:
                - ethereum
              events:
                - event_name: Transfer
  7. Filter Kafka streams using event conditions

    master

    You can apply filters to event data before it is streamed to Kafka using the conditions key within an event configuration. This is particularly useful for filtering on non-indexed fields in the Solidity event.

    Supported operators:

    • >: Greater than (numbers only)
    • <: Less than (numbers only)
    • =: Equals
    • >=: Greater than or equal to (numbers only)
    • <=: Less than or equal to (numbers only)
    • ||: Logical OR
    • &&: Logical AND

    Use the input names defined in your ABI to reference fields. For tuples, use object notation (e.g., tupleName.fieldName).

    Note: For performance, it is advised to filter indexed fields in the contract details section of rindexer.yaml rather than using conditions, as indexed fields can be filtered at the request level.

    events:
      - event_name: Transfer
        conditions:
          - "value": ">=2000000000000000000 && value <=4000000000000000000"
          - "from": "0x0338ce5020c447f7e668dc2ef778025ce3982662 || 0x0338ce5020c447f7e668dc2ef778025ce398266u"
          - "quoteParams.profileId": "=1"
  8. Configure Cron Triggers for scheduled operations

    master

    You can trigger table operations on a schedule using cron in addition to event-driven events. This is useful for periodic data fetching (polling on-chain state), price feeds, snapshots, or heartbeat data.

    Tables can define events, cron, or both. When using cron, you must use either interval (for simple time durations) or schedule (for standard cron expressions), but not both.

    Available Variables in Cron Operations: Because cron runs on a schedule rather than in response to an event, event-specific fields like $from, $to, or $value are NOT available. You can only use:

    • $call(...): View function calls
    • $contract: The contract address from contract details
    • $rindexer_block_number: The latest block number at execution time
    • $rindexer_timestamp: The current timestamp
    • Literals: String or number values (e.g., "eth-usd", 100)
    tables:
      - name: eth_price
        columns:
          - name: id
            type: string
          - name: price
            type: int256
        cron:
          - interval: 5s
            operations:
              - type: upsert
                where:
                  id: "eth-usd"
                set:
                  - column: price
                    action: set
                    value: $call($contract, "latestAnswer()")
  9. Configure Postgres Docker setup

    master

    When using Postgres as a storage backend, you can choose how the database is provisioned:

    • Yes (Docker): rindexer will use Docker to automatically spin up a PostgreSQL database. This is recommended for local development.
    • No (Manual): rindexer will not use Docker. You must provide your own PostgreSQL connection details via environment variables in your .env file.
  10. Create custom derived state with tables

    master

    Custom tables are the primary way to perform no-code indexing. Instead of just logging raw events, you can maintain derived state (like token balances or ownership) by defining how events update specific columns.

    When using tables, you do not need to specify include_events; rindexer automatically subscribes to the events referenced in your table definitions.

    Example: Tracking ERC20 balances To track balances, you use upsert operations on a balances table, using $to and $from to identify the accounts and $value to adjust the amount.

    contracts:
      - name: USDC
        details:
          - network: ethereum
            address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
            start_block: 18600000
        abi: ./abis/ERC20.json
        tables:
          - name: balances
            columns:
              - name: holder
              - name: balance
                default: "0"
            events:
              - event: Transfer
                operations:
                  - type: upsert
                    where:
                      holder: $to
                    if: "$to != 0x0000000000000000000000000000000000000000"
                    set:
                      - column: balance
                        action: add
                        value: $value
                  - type: upsert
                    where:
                      holder: $from
                    if: "$from != 0x0000000000000000000000000000000000000000"
                    set:
                      - column: balance
                        action: subtract
                        value: $value
  11. Configure Custom Tables vs Raw Event Logging

    master

    You can choose between using custom tables for structured data or include_events for raw event logging. They operate independently:

    ConfigurationResult
    tables onlyOnly custom tables are created and populated. No raw event tables.
    include_events onlyOnly raw event tables are created (traditional logging).
    BothBoth custom tables AND raw event tables are created.

    Recommendation: For most use cases, use tables without include_events to save storage space.

  12. How service health checks are performed

    master

    Rindexer performs specific checks to determine the status of its core components:

    Database Health Check

    Verifies PostgreSQL connectivity by executing a SELECT 1 query.

    • healthy: PostgreSQL is enabled and the query succeeds.
    • unhealthy: PostgreSQL is enabled but connection or query fails.
    • not_configured: PostgreSQL is enabled but no database client is available.
    • disabled: PostgreSQL is not enabled in the configuration.

    Indexing Health Check

    Monitors the indexer process state via the global IS_RUNNING flag.

    • healthy: The indexer is currently running.
    • stopped: The indexer is not running.

    Sync Health Check

    Verifies data synchronization based on the storage backend:

    • PostgreSQL: Queries information_schema.tables for user-created event tables. Status is no_data if no event tables exist (common in new deployments).
    • CSV: Checks if the CSV directory exists and contains .csv files. Status is no_data if the directory is missing or empty.