Skip to content

[Bug] Grafting from a Pruned Source at a Block Below earliest_block_number Silently Produces Incomplete State #6607

@0x19dG87

Description

@0x19dG87

Summary

When a subgraph that has been continuously pruned is used as a graft base, and the graft.block specified in the manifest falls below the source's earliest_block_number, graph-node accepts the graft without any warning or error. The resulting subgraph silently inherits an inconsistent state: data_sources$ rows survive pruning (they are never deleted), but the entity data they reference has been removed. The grafted subgraph then fails with unexpected null in handler on every event that touches an entity whose row no longer exists in the pruned source.


Background

Pruning removes historical entity rows older than history_blocks blocks from a deployment's store tables. However, data_sources$ rows — which record which dynamic contract addresses the subgraph is watching — are never pruned. This is intentional: deleting them would cause the subgraph to stop listening to those contracts.

This creates a structural gap after sustained pruning: data_sources$ may contain entries for contracts whose corresponding entity rows have been deleted. As long as the subgraph continues indexing forward from its pruned state this is harmless, since the live entities exist for recent blocks. But if a new subgraph is grafted from this source at a block below earliest_block_number, the grafted copy inherits the orphaned data_sources$ references with no entity rows to back them.

Graft validation does not check whether the requested graft block is at or above the source's earliest_block_number. The graft is accepted silently.

By contrast, graphman copy was updated in PR #6384 (Feb 2026) to reject copies where the offset block falls below the source's earliest_block_number. The manifest graft path has no equivalent guard.


Reproduction Scenario

Prerequisites

  • A subgraph using a factory-contract / dynamic data sources pattern (e.g. Uniswap V3 style: a factory deploys pool contracts, each pool becomes a dynamic data source)
  • The source subgraph has been running for an extended period with continuous pruning configured (history_blocks: N)

Steps

  1. Deploy and sync the source subgraph with pruning enabled. After extended operation, earliest_block_number advances to a block significantly above the subgraph's genesis. All factory-created entity rows (pools, tokens) from early in the subgraph's history are deleted from the store tables; their data_sources$ rows remain.

  2. Create a grafted subgraph at a block below earliest_block_number:

    graft:
      base: <source-deployment-hash>
      block: <block-number-below-source-earliest_block_number>

    graph-node accepts the manifest. No error or warning is emitted at deploy time or at startup.

  3. The grafted subgraph begins indexing from the graft block. It immediately encounters events on factory-created contracts whose entity rows were pruned from the source. Every such event produces a deterministic unexpected null in handler error, because the expected entity (e.g. a Pool) does not exist.

  4. Errors accumulate silently as non-fatal. The subgraph remains unhealthy indefinitely, producing incorrect or missing query results for all affected entities with no automated recovery path.


Observed Behaviour

Reference case

  • Chain: Scroll
  • Source subgraph (grafted from): QmR6VP1qoF8nxhtMaGEg9VMmkaTDFqyeM8nJWkakP6nHes
  • Grafted subgraph (affected): QmNqxqVfBETuMhV91BfquULGBLFvPZwr3ADVFTMgGZcqNf
  • Graft block: 14,146,768
  • Source earliest_block_number: 33,660,708
  • The graft block sits ~19.5 million blocks below the source's prune boundary

Error log (representative entries)

ERRO Subgraph error 1/1,
  code: SubgraphSyncingFailure,
  error: transaction b459efa6523aead5fe69fc50945b2c10137f7555f3dba0b6a57d725e9e951c30:
    error while executing at wasm backtrace:
      0: 0x8aaf - <unknown>!src/mappings/core/handleSwap:
    Mapping aborted at src/mappings/core.ts, line 263, column 16,
    with message: unexpected null in handler `handleSwap`
  block_hash: 0x106299e06bbce9ce2b214cdaf46373c8f212f1b4f2d8893733923b4fc1957c01,
  block_number: 14252708, handler: handleSwap, deterministic: true,
  sgd: 1296, subgraph_id: QmNqxqVfBETuMhV91BfquULGBLFvPZwr3ADVFTMgGZcqNf,
  component: SubgraphInstanceManager
ERRO Subgraph error 1/1,
  code: SubgraphSyncingFailure,
  error: transaction d6b53305b97f91b336281727ac4aabc85dc66ad3a002087f81b093ed26d84f94:
    Mapping aborted at src/mappings/core.ts, line 263, column 16,
    with message: unexpected null in handler `handleSwap`
  block_number: 14252759, deterministic: true,
  sgd: 1296, subgraph_id: QmNqxqVfBETuMhV91BfquULGBLFvPZwr3ADVFTMgGZcqNf
ERRO Subgraph error 1/1,
  code: SubgraphSyncingFailure,
  error: transaction bda5e0f0830f1daec60ec6c2c82598ab1403ae97788391edbc46e99a7d5f04e2:
    Mapping aborted at src/mappings/core.ts, line 263, column 16,
    with message: unexpected null in handler `handleSwap`
  block_number: 14253040, deterministic: true,
  sgd: 1296, subgraph_id: QmNqxqVfBETuMhV91BfquULGBLFvPZwr3ADVFTMgGZcqNf

Impact

  • Silent data corruption. No warning, no validation failure, no startup log. There is no signal to the operator that the graft will produce missing entities.
  • Permanent unhealthy state. Every event on a pruned entity generates a deterministic non-fatal error. These errors cannot be cleared without reconstructing the missing rows from the underlying chain.
  • Incorrect query results. Queries over entities whose rows were not copied return null or incomplete data.

Similar Issue

#6581

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions