diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..1d9c438 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,43 @@ +FROM mcr.microsoft.com/devcontainers/base:debian + +# Set non-interactive frontend for apt +ENV DEBIAN_FRONTEND=noninteractive + +# Switch to root for installing packages +USER root + +# Install additional dependencies +RUN apt update && apt install -y \ + curl \ + jq \ + python3 \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Node.js 20.x using NodeSource +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ + apt update && \ + apt install -y nodejs && \ + apt clean && \ + rm -rf /var/lib/apt/lists/* + +# Install Claude Code +RUN npm install -g @anthropic-ai/claude-code + +# Ensure all users have access to the tools +RUN chmod 755 /usr/local/bin/* && \ + # Create a directory for vscode user's binaries + mkdir -p /home/vscode/.local/bin && \ + chown -R vscode:vscode /home/vscode/.local/bin + +# Switch back to vscode user +USER vscode + +# Set environment variables +ENV PATH="/home/vscode/.local/bin:/root/.local/bin:$PATH" + +# Create .bashrc additions for PATH +RUN echo 'export PATH="/usr/local/bin:$HOME/.local/bin:$PATH"' >> $HOME/.bashrc + +# Set the default command +CMD ["sleep", "infinity"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..45d585f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,41 @@ +{ + "name": "GIP", + "dockerComposeFile": [ + "docker-compose.yml" + ], + "service": "dev-gip", + "features": { + "ghcr.io/devcontainers/features/git:1": { + "configureGitHubCLI": true, + "gitCredentialHelper": "cache" + }, + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/common-utils:2.5.3": {} + }, + "postCreateCommand": ".devcontainer/project-setup.sh", + "remoteUser": "vscode", + "workspaceFolder": "/work", + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.cwd": "/work" + }, + "extensions": [ + "tamasfe.even-better-toml", + "usernamehw.errorlens", + "yzhang.markdown-all-in-one", + "DavidAnson.vscode-markdownlint", + "shd101wyy.markdown-preview-enhanced", + "bierner.markdown-preview-github-styles", + "Gruntfuggly.todo-tree", + "donjayamanne.githistory", + "eamodio.gitlens", + "fill-labs.dependi", + "streetsidesoftware.code-spell-checker", + "Augment.vscode-augment", + "foundry-rs.foundry-vscode", + "BlueGlassBlock.better-json5" + ] + } + } +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..6ef4969 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,15 @@ +services: + dev-gip: + build: + context: . + dockerfile: Dockerfile + env_file: + - /opt/configs/graphprotocol/gip.env + working_dir: /work + user: "vscode" + volumes: + # Workspace + - ../:/work + + # Git repo root + - /git:/git diff --git a/.devcontainer/project-setup.sh b/.devcontainer/project-setup.sh new file mode 100755 index 0000000..508a3cc --- /dev/null +++ b/.devcontainer/project-setup.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# Project-specific setup script for graph +set -euo pipefail + +echo "Running project-specific setup for graph..." + +# Get the script directory and repository root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +echo "Script directory: $SCRIPT_DIR" +echo "Repository root: $REPO_ROOT" + +# Set up local user directories with proper permissions +echo "Setting up local user directories..." + +# Ensure all user directories exist and have proper ownership +sudo mkdir -p /home/vscode/.cache /home/vscode/.config /home/vscode/.local/share /home/vscode/.local/bin +sudo chown -R vscode:vscode /home/vscode/.cache /home/vscode/.config /home/vscode/.local +sudo chmod -R 755 /home/vscode/.cache /home/vscode/.config /home/vscode/.local + +echo "User directories set up with proper permissions" + +# Add CONTAINER_BIN_PATH to PATH if it's set +if [ -n "${CONTAINER_BIN_PATH:-}" ]; then + echo "CONTAINER_BIN_PATH is set to: $CONTAINER_BIN_PATH" + echo "Adding CONTAINER_BIN_PATH to PATH..." + + # Add to current PATH + export PATH="$CONTAINER_BIN_PATH:$PATH" + + # Add to .bashrc if not already there + if ! grep -q "export PATH=\"\$CONTAINER_BIN_PATH:\$PATH\"" "$HOME/.bashrc"; then + echo "Adding CONTAINER_BIN_PATH to .bashrc..." + echo ' +# Add CONTAINER_BIN_PATH to PATH if set +if [ -n "${CONTAINER_BIN_PATH:-}" ]; then + export PATH="$CONTAINER_BIN_PATH:$PATH" +fi' >> "$HOME/.bashrc" + fi + + echo "CONTAINER_BIN_PATH added to PATH" +else + echo "CONTAINER_BIN_PATH is not set, skipping PATH modification" +fi + +# Source shell customizations if available in PATH +if command -v shell-customizations &> /dev/null; then + SHELL_CUSTOMIZATIONS_PATH=$(command -v shell-customizations) + echo "Found shell customizations in PATH at: ${SHELL_CUSTOMIZATIONS_PATH}" + echo "Sourcing shell customizations..." + source "${SHELL_CUSTOMIZATIONS_PATH}" + + # Add to .bashrc if not already there + if ! grep -q "source.*shell-customizations" "$HOME/.bashrc"; then + echo "Adding shell customizations to .bashrc..." + echo "source ${SHELL_CUSTOMIZATIONS_PATH}" >> "$HOME/.bashrc" + fi +else + echo "Shell customizations not found in PATH, skipping..." +fi + +# Set up Git SSH signing +if [ -f "$SCRIPT_DIR/setup-git-signing.sh" ]; then + "$SCRIPT_DIR/setup-git-signing.sh" +else + echo "WARNING: setup-git-signing.sh not found, skipping Git SSH signing setup" +fi + +echo "Project-specific setup completed" diff --git a/.devcontainer/setup-git-signing.sh b/.devcontainer/setup-git-signing.sh new file mode 100755 index 0000000..78969dd --- /dev/null +++ b/.devcontainer/setup-git-signing.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Automatically configure Git to use SSH signing with forwarded SSH keys +set -euo pipefail + +echo "Setting up Git SSH signing..." + +# Check if SSH agent forwarding is working +if ! ssh-add -l &>/dev/null; then + echo "ERROR: No SSH keys found in agent. SSH agent forwarding is not set up correctly." + echo "SSH signing will not work without SSH agent forwarding." + exit 1 +fi + +# Get the first SSH key from the agent +SSH_KEY=$(ssh-add -L | head -n 1) +if [ -z "$SSH_KEY" ]; then + echo "ERROR: No SSH keys found in agent. SSH signing will not work." + exit 1 +fi + +# Extract the key type and key content +KEY_TYPE=$(echo "$SSH_KEY" | awk '{print $1}') +KEY_CONTENT=$(echo "$SSH_KEY" | awk '{print $2}') + +# Check if Git user settings are available +if [[ -z "${GIT_USER_NAME:-}" || -z "${GIT_USER_EMAIL:-}" ]]; then + echo "WARNING: Git user settings (GIT_USER_NAME and/or GIT_USER_EMAIL) are not set." + echo "Git commit signing will not be configured." + echo "If you need Git commit signing, add these variables to your environment file." + exit 0 +fi + +# Set Git user name from environment variable +echo "Setting Git user.name: $GIT_USER_NAME" +git config --global user.name "$GIT_USER_NAME" + +# Set Git user email from environment variable +echo "Setting Git user.email: $GIT_USER_EMAIL" +git config --global user.email "$GIT_USER_EMAIL" + +# Create the .ssh directory if it doesn't exist +mkdir -p ~/.ssh +chmod 700 ~/.ssh + +# Create or update the allowed signers file +echo "Updating allowed signers file..." +ALLOWED_SIGNERS_FILE=~/.ssh/allowed_signers +SIGNER_LINE="$GIT_USER_EMAIL $KEY_TYPE $KEY_CONTENT" + +# Create the file if it doesn't exist +if [ ! -f "$ALLOWED_SIGNERS_FILE" ]; then + echo "$SIGNER_LINE" > "$ALLOWED_SIGNERS_FILE" + echo "Created new allowed signers file." +else + # Check if the key is already in the file + if ! grep -q "$KEY_CONTENT" "$ALLOWED_SIGNERS_FILE"; then + # Append the key if it's not already there + echo "$SIGNER_LINE" >> "$ALLOWED_SIGNERS_FILE" + echo "Added new key to allowed signers file." + else + echo "Key already exists in allowed signers file." + fi +fi + +chmod 600 "$ALLOWED_SIGNERS_FILE" + +# Configure Git to use SSH signing +echo "Configuring Git to use SSH signing..." +git config --global gpg.format ssh +git config --global user.signingkey "key::$KEY_TYPE $KEY_CONTENT" +git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers +git config --global commit.gpgsign true + +echo "Git SSH signing setup complete!" +echo "Your commits will now be automatically signed using your SSH key." +echo "Make sure this key is added to GitHub as a signing key in your account settings." diff --git a/gips/0079.md b/gips/0079.md index b4f5728..09e9733 100644 --- a/gips/0079.md +++ b/gips/0079.md @@ -1,36 +1,270 @@ --- -GIP: "0079" -Title: Reserved -Authors: Rembrandt Kuipers +GIP: '0079' +Title: Indexer Service Quality Oracle +Authors: + - Rembrandt Kuipers + - Samuel Metcalfe Stage: Draft Discussions-To: TBD -Category: "Protocol Logic" -Depends-On: "GIP-0070" +Category: 'Protocol Logic' --- -This GIP number has been reserved. +## Abstract -This GIP is being drafted and all content is subject to change. +This GIP proposes the implementation of a Service Quality Oracle System that enforces minimum service quality requirements for indexers to receive indexing rewards. The system consists of an on-chain Oracle Contract that tracks indexer eligibility and off-chain Oracle Nodes that assess service quality and report eligible indexers to the Contract. This GIP also includes the necessary RewardsManager upgrades to integrate with the Oracle Contract. We anticipate that this mechanism will improve the overall reliability and performance of The Graph network and help the network better compete against other data service providers by aligning indexing incentives with service quality. -## Abstract +## Contents + + + +- [Abstract](#abstract) +- [Contents](#contents) +- [Motivation](#motivation) +- [High-Level Description](#high-level-description) + - [System Flow](#system-flow) +- [Detailed Specification](#detailed-specification) + - [Oracle Contract](#oracle-contract) + - [RewardsManager Integration](#rewardsmanager-integration) + - [Operational Flow](#operational-flow) + - [Oracle Node Responsibilities](#oracle-node-responsibilities) + - [Roles and Access Control](#roles-and-access-control) + - [Configuration Parameters](#configuration-parameters) +- [Off-chain Logic](#off-chain-logic) +- [Backward Compatibility](#backward-compatibility) +- [Risks](#risks) +- [Copyright Waiver](#copyright-waiver) ## Motivation -## Prior Art +The Graph Protocol currently distributes indexing rewards to all indexers regardless of their service quality or if actually serving queries. This creates a misalignment between protocol incentives and network utility, where indexers can claim rewards without providing meaningful service to data consumers. + +The Service Quality Oracle System addresses this by making eligibility for Indexing Rewards dependent on indexers meeting service quality requirements. This improves incentive alignment for indexers to maintain reliable infrastructure and serve end-user query traffic. ## High-Level Description +This GIP introduces a service quality enforcement system consisting of two main components: + +1. **Oracle Contract**: Tracks indexer eligibility based on service quality assessments from Oracle Nodes +2. **RewardsManager Integration**: Enforces eligibility checks before distributing indexing rewards + +### System Flow + +```mermaid +flowchart TD + Indexers[Indexers] -->|"① Serve queries"| Gateways[Gateways] + Gateways -->|"② Service quality data"| OracleNode[Oracle Node] + OracleNode -->|"③ Record eligible indexers"| Contract[Oracle Contract] + + Indexers -->|"④ Claim Indexing Rewards"| RewardsManager[Rewards Manager] + RewardsManager -->|"⑤ Check eligibility"| Contract + Contract -->|"⑥ Return eligibility status"| RewardsManager + RewardsManager -->|"⑦ Distribute rewards if eligible"| Indexers + + %% Reduce curvature on arrows 5 and 6 + linkStyle 4 curve:basis + linkStyle 5 curve:basis + + %% Styling + classDef component fill:#e1f5fe,stroke:#0277bd,stroke-width:2px + classDef governance fill:#fff3e0,stroke:#f57c00,stroke-width:2px + + class Indexers,Gateways,OracleNode,Contract,RewardsManager component + class Governance governance +``` + +**① Serve queries**: Indexers serve queries via Gateways, generating query performance data. + +**② Service quality data**: Gateways collect query performance metrics. + +**③ Record eligible indexers**: Oracle Nodes evaluate indexer performance against quality thresholds and record qualifying indexers as eligible in the Oracle Contract. + +**④ Claim Indexing Rewards**: Indexers attempt to claim indexing rewards (by closing allocations). + +**⑤ Check eligibility**: The Rewards Manager queries the Oracle Contract to verify whether the claiming indexer is currently eligible for rewards. + +**⑥ Return eligibility status**: The Contract returns the indexer's current eligibility status (eligible/not eligible) based on Oracle Node updates and expiration times. + +**⑦ Distribute rewards if eligible**: The Rewards Manager distributes rewards to eligible indexers and denies rewards to ineligible ones. + +This creates a system where indexers must maintain service quality standards for continued rewards eligibility. + ## Detailed Specification +### Oracle Contract + +The Oracle Contract provides the core functionality for tracking indexer service quality eligibility. It allows authorized Oracle Nodes to mark indexers as eligible to receive rewards, with eligibility expiring after a configurable period. + +1. **Role-based Access Control**: Only accounts with the `OPERATOR` role can grant the `ORACLE` role to trusted Oracle Nodes. +2. **Time-based Eligibility**: Indexer eligibility expires after a configurable period, requiring regular reverification. +3. **Global Quality Check Toggle**: Enables/disables service quality checking network-wide. +4. **Oracle Update Timeout**: Safety mechanism that treats all indexers as eligible if no Oracle Node updates occur within a configurable period. + +#### Interface + +```solidity +interface IServiceQualityOracle { + function isAllowed(address indexer) external view returns (bool); +} +``` + +#### Key Functions + +- **`allowIndexers(address[] calldata indexers)`**: Marks multiple indexers as eligible for rewards (ORACLE role required) +- **`isAllowed(address indexer)`**: Returns whether an indexer is currently eligible for rewards +- **`setAllowedPeriod(uint256 period)`**: Sets the eligibility duration (OPERATOR role required) +- **`setOracleUpdateTimeout(uint256 timeout)`**: Sets the Oracle Node update timeout (OPERATOR role required) +- **`setQualityCheckingActive(bool active)`**: Enables/disables quality checking globally (OPERATOR role required) + +### RewardsManager Integration + +The RewardsManager is upgraded to optionally enforce service quality before distributing rewards. This integration is backward compatible and only activates when an Oracle Contract is configured. + +1. **Optional Integration**: When an Oracle Contract is configured, the RewardsManager checks eligibility before reward distribution. +2. **Eligibility Verification**: Calls the Oracle Contract's `isAllowed()` function during reward claims. +3. **Reward Gating**: Only eligible indexers receive rewards; ineligible indexers are denied rewards. +4. **Backward Compatibility**: When no Oracle Contract is configured, operates exactly as before. + +#### Service Quality Enforcement + +```solidity +function takeRewards(address _allocationID) external returns (uint256) { + // ... existing logic ... + + if (address(serviceQualityOracle) != address(0)) { + address indexer = allocations[_allocationID].indexer; + if (!serviceQualityOracle.isAllowed(indexer)) { + emit RewardsDeniedDueToServiceQuality(indexer, _allocationID); + return 0; + } + } + + // ... continue with reward distribution ... +} +``` + +#### Configuration + +```solidity +function setServiceQualityOracle(address _oracle) external onlyRole(Roles.GOVERNOR) { + serviceQualityOracle = IServiceQualityOracle(_oracle); + emit ServiceQualityOracleSet(_oracle); +} +``` + +#### Events + +```solidity +event ServiceQualityOracleSet(address indexed oracle); +event RewardsDeniedDueToServiceQuality(address indexed indexer, address indexed allocationID); +``` + +#### Interface Requirements + +The RewardsManager expects the Oracle Contract to implement: + +```solidity +interface IServiceQualityOracle { + function isAllowed(address indexer) external view returns (bool); +} +``` + +### Operational Flow + +1. Oracle Nodes regularly assess indexer service quality through off-chain mechanisms. +2. Indexers meeting minimum service requirements are marked as eligible to receive indexing rewards in the Oracle Contract. +3. Eligibility for indexing rewards expires after a configurable period (initially 14 days), requiring reverification at regular intervals for continued eligibility. +4. When an indexer claims rewards, the RewardsManager checks with the Oracle Contract to verify eligibility. +5. Only eligible indexers receive rewards, creating a direct incentive to maintain service quality. + +### Oracle Node Responsibilities + +Oracle Nodes play a critical role in the Service Quality Oracle System: + +1. **Service Quality Assessment**: Oracle Nodes are responsible for assessing indexer service quality through off-chain mechanisms. +2. **Eligibility Marking**: Oracle Nodes mark eligible indexers in the Oracle Contract by calling the `allowIndexers` function. +3. **Transparency**: Oracle Nodes should provide transparency about their assessment methodology and results. + +Oracle Nodes are expected to regularly update indexer eligibility to ensure the system reflects current service quality. The frequency of updates should be less than the configured allowed period to ensure continuous eligibility for qualifying indexers. + +### Roles and Access Control + +**ServiceQualityOracle Contract:** + +- **GOVERNOR_ROLE**: Admin of all other roles; can grant/revoke OPERATOR_ROLE and PAUSE_ROLE +- **OPERATOR_ROLE**: Can grant/revoke the ORACLE role to trusted Oracle Nodes, and can modify configuration parameters (allowed period, Oracle Node update timeout, quality checking toggle) +- **ORACLE_ROLE**: Can mark indexers as eligible for rewards via `allowIndexers()` function +- **PAUSE_ROLE**: Can pause/unpause contract operations + +**RewardsManager Integration:** + +- **GOVERNOR_ROLE**: Can set the ServiceQualityOracle address via `setServiceQualityOracle()` +- **Existing roles**: All current RewardsManager roles and permissions remain unchanged +- **ServiceQualityOracle**: Provides read-only eligibility information when configured + +### Configuration Parameters + +The Oracle Contract includes several configurable parameters: + +1. **Allowed Period**: The period for which indexer eligibility lasts after being marked as eligible (default: 14 days). +2. **Oracle Update Timeout**: The period after which all indexers are considered eligible if no Oracle Node updates occur (default: 7 days). +3. **Quality Checking Active**: A flag to enable or disable quality checking network-wide (default: enabled). + +These parameters can be adjusted by operators to balance strictness and leniency based on network needs and maturity. + +## Off-chain Logic + +The Oracle Node logic for determining indexer service quality includes a conservative quality metrics assessment framework, designed to identify and exclude underperforming indexers from receiving indexing rewards, while providing a foundation for economic incentivization of further indexing service quality improvements as requirements are updated. We plan to refine requirements over the coming months/years to build upon this foundation such that indexers are encouraged to provide competitive subgraph indexing services that benefits network data consumers and upgrades the overall health and decentralization of the network. + +1. **Quality Metrics Assessment** + + At launch, Oracle Nodes will monitor a range of metrics designed to assess whether an indexer is providing sufficient indexing service quality to be eligible to claim indexing rewards from the protocol. The metrics sampled will evolve over time and indexers will need to ensure that their infrastructure is capable of meeting the latest requirements to be eligible for rewards. + + Our goal is to encourage sustained network participation from indexers and to discourage indexers from attempting to claim rewards without serving queries. We believe that serving queries is a critically important part of being an indexer and want to encourage indexers to compete for query volume. + +2. **Measurement Methodology** + + The initial implementation of Oracle Nodes builds upon pre-existing data pipelines (from our geographically distributed gateways into Google BigQuery) and queries that data directly. In this way, Oracle Nodes are able to utilize comprehensive query performance metrics that we already collect. + + The system processes data through a rolling 28-day evaluation window, analyzing internal query logs to identify qualifying indexers. This methodology focuses on identifying indexers that have consistent patterns of service delivery over a 28-day rolling window, rather than identifying indexers based on one-off peak performance. In this way we recognize that sustained and consistent uptime provides more value to data consumers and shows greater commitment from indexers than intermittent bursts of high performance. + + Oracle Node operations are automated with daily processing at consistent times of day, ensuring regular eligibility updates. The system incorporates comprehensive logging, health monitoring, and automatic retry mechanisms to maintain operational reliability while preserving complete audit trails for transparency and governance oversight. System failures are designed to be graceful, meaning that indexers are not left unable to claim rewards. + +3. **Eligibility Criteria** + + Please find the latest eligibility criteria here: [ELIGIBILITY_CRITERIA.md](https://github.com/graphprotocol/service-quality-oracle/blob/main/ELIGIBILITY_CRITERIA.md#eligibility-criteria) + + Initial thresholds are intentionally set conservatively to establish quality enforcement principles without disrupting existing network operations. This approach recognizes that indexers may need some time to optimize their infrastructure and acclimatize to new requirements, while ensuring that only severely underperforming or inactive indexers are excluded from rewards. The 5-day requirement set out in the document linked above represents only approximately 18% uptime over the 28-day evaluation period, establishing a deliberately low threshold that accommodates maintenance windows, temporary technical issues, and gradual participation ramp-up. + + Each threshold reflects practical operational requirements: The "200 OK" status requirement encourages indexers to ensure that their infrastructure is capable of serving successful queries; the 5-second response time accommodates infrastructure latency variations, while excluding severely degraded responses from counting towards eligibility; the 50,000 block synchronization requirement provides a generous buffer for sync issues, while encouraging indexers to sync to chain head; and the 500 GRT curation signal requirement puts up an economic barrier that indexers would have to overcome to game the system with self made subgraphs. + + As The Graph network adapts to quality-based rewards and indexer performance improves, thresholds can be gradually refined through progressive Oracle Node configuration updates, without requiring additional governance proposals. This evolutionary approach encourages quality improvements while providing indexers adequate time to enhance their service. We are highly motivated to ensure that indexers have sufficient time to adapt to new requirements before they roll out and will be proactive with our communications regarding any updates to the eligibility requirements. + +4. **Oracle Node Operation** + + Oracle Nodes are scheduled for daily execution. This daily cadence ensures timely reflection of service quality changes while providing indexers multiple opportunities to qualify for rewards within each evaluation period. Eligible indexer wallet addresses are submitted to the Oracle Contract. + + Oracle Nodes incorporate multiple failover mechanisms including automatic RPC provider switching, exponential backoff retry logic, and comprehensive health monitoring. All operations generate detailed logs for operational oversight, while historical eligibility data is preserved for a retention period for internal audit in dated directories. These measures ensure continuous Oracle Node operation. + ## Backward Compatibility -## Dependencies +The Service Quality Oracle System is designed to be backward compatible with the existing Graph Protocol: + +1. **Optional Integration**: The RewardsManager can operate with or without the Oracle Contract configured. +2. **Gradual Adoption**: Quality checking can be disabled initially to allow for testing and observation. +3. **Safety Mechanisms**: The Oracle Node update timeout ensures that rewards continue to flow if the Oracle Node system fails. + +## Risks -## Risks and Security Considerations +1. **Oracle Node Centralization**: Authorized Oracle Nodes have significant power over reward distribution. This is mitigated by: -## Validation + - Multiple Oracle Nodes can be authorized + - Oracle Node actions are transparent and on-chain + - Oracle update timeout ensures rewards continue if Oracle Nodes fail -## Rationale and Alternatives +2. **False Negatives**: Indexers might be incorrectly denied rewards. This is mitigated by: + - Regular verification opportunities + - Transparent eligibility criteria + - Oracle Node update timeout as a safety mechanism ## Copyright Waiver