diff --git a/graph/src/components/store/traits.rs b/graph/src/components/store/traits.rs index 6690a29c517..0249e999da6 100644 --- a/graph/src/components/store/traits.rs +++ b/graph/src/components/store/traits.rs @@ -219,6 +219,12 @@ pub trait SubgraphStore: Send + Sync + 'static { /// being set up async fn least_block_ptr(&self, id: &DeploymentHash) -> Result, StoreError>; + /// Return the earliest block for which the deployment with the given + /// `id` still has entity data. Blocks below this are either pre-`startBlock` + /// or have been removed by pruning, and cannot serve as a graft or copy + /// source. + async fn earliest_block_number(&self, id: &DeploymentHash) -> Result; + async fn is_healthy(&self, id: &DeploymentHash) -> Result; /// Find all deployment locators for the subgraph with the given hash. diff --git a/graph/src/data/subgraph/mod.rs b/graph/src/data/subgraph/mod.rs index 5717f7ad273..0c20405cdf1 100644 --- a/graph/src/data/subgraph/mod.rs +++ b/graph/src/data/subgraph/mod.rs @@ -600,7 +600,24 @@ impl Graft { self.block, ptr.number - 1 ))), - (Some(_), _) => Ok(()), + (Some(_), _) => { + // The graft block must be at or above the base's earliest + // available block. Below that the base store no longer has + // the entity versions the copy step would need: either the + // base started above the graft block, or pruning has + // removed the history. + let earliest_block = store + .earliest_block_number(&self.base) + .await + .map_err(|e| GraftBaseInvalid(e.to_string()))?; + if self.block < earliest_block { + return Err(GraftBaseInvalid(format!( + "failed to graft onto `{}` at block {} since its earliest available block is {}", + self.base, self.block, earliest_block + ))); + } + Ok(()) + } } } } diff --git a/store/postgres/src/subgraph_store.rs b/store/postgres/src/subgraph_store.rs index 8c268562aa7..b4435cae0cb 100644 --- a/store/postgres/src/subgraph_store.rs +++ b/store/postgres/src/subgraph_store.rs @@ -1888,6 +1888,12 @@ impl SubgraphStoreTrait for SubgraphStore { store.block_ptr(site.cheap_clone()).await } + async fn earliest_block_number(&self, id: &DeploymentHash) -> Result { + let (store, site) = self.store(id).await?; + let state = store.deployment_state(site.cheap_clone()).await?; + Ok(state.earliest_block_number) + } + async fn is_healthy(&self, id: &DeploymentHash) -> Result { let (store, site) = self.store(id).await?; let health = store.health(&site).await?;