Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions store/postgres/src/deployment_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,20 @@ impl DeploymentStore {
// Unfortunately the machinery needed to do this is not yet in place.
// The best we can do right now is just to make sure that the block number
// is high enough.
let state = deployment::state(&mut conn, site.as_ref()).await?;
if block.number < state.earliest_block_number {
// Pruning removed the causality-region digests this block's POI is
// computed from. Querying anyway returns a well-formed but wrong POI
// that nobody can tell apart from a correct one, which is worse than
// failing: an allocation closing against a pruned epoch block would
// submit it and could be disputed.
return Err(StoreError::Unknown(anyhow!(
"block {} has been pruned for deployment {}; the earliest available block is {}",
block.number,
site.deployment,
state.earliest_block_number
)));
}
if latest_block_ptr.number < block.number {
// If a subgraph has failed deterministically then any blocks past head
// should return the same POI
Expand Down
1 change: 1 addition & 0 deletions store/test-store/tests/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod postgres {
pub mod aggregation;
pub mod chain_head;
pub mod graft;
pub mod pruned_poi;
pub mod relational;
pub mod relational_bytes;
pub mod store;
Expand Down
75 changes: 75 additions & 0 deletions store/test-store/tests/postgres/pruned_poi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// A proof of indexing for a block that pruning has already removed cannot be computed:
// the causality-region digests it is hashed from are gone. Returning a well-formed but
// wrong POI is worse than failing, because nothing downstream can tell the difference.

use graph::prelude::{DeploymentHash, SubgraphStore as _};
use test_store::*;

const SUBGRAPH_GQL: &str = "
type User @entity {
id: ID!,
name: String
}
";

/// The block pointer the POS tests use: a deterministic hash per number.
fn ptr(n: i32) -> graph::prelude::BlockPtr {
let hash = graph::prelude::alloy::primitives::B256::from(
graph::prelude::alloy::primitives::U256::from(n as u64),
);
graph::prelude::BlockPtr::new(hash.into(), n.into())
}

/// Put the subgraph at `head` and move its history window forward to `earliest`,
/// which is what the pruner leaves behind once it has passed the blocks below it.
async fn set_head_and_prune(deployment: &DeploymentHash, head: i32, earliest: i32) {
let mut conn = PRIMARY_POOL.get().await.unwrap();
let hash = deployment.as_str().to_string();
let hash_bytes = format!("\\x{}", "11".repeat(32));
let stmt = format!(
"UPDATE subgraphs.deployment SET earliest_block_number = {earliest} WHERE subgraph = '{hash}';\
UPDATE subgraphs.head SET block_number = {head}, block_hash = '{hash_bytes}' WHERE id = \
(SELECT id FROM subgraphs.deployment WHERE subgraph = '{hash}');"
);
diesel_async::SimpleAsyncConnection::batch_execute(&mut conn, &stmt)
.await
.unwrap();
}

#[test]
fn proof_of_indexing_refuses_a_pruned_block() {
run_test_sequentially(|store| async move {
let id = DeploymentHash::new("prunedPoi").unwrap();
create_test_subgraph(&id, SUBGRAPH_GQL).await;

// The subgraph is at block 200; everything below 100 has been pruned away.
set_head_and_prune(&id, 200, 100).await;

let sg_store = store.subgraph_store();
let indexer = None;

// A retained block is unaffected.
let retained = sg_store
.get_proof_of_indexing(&id, &indexer, ptr(120))
.await;
assert!(
retained.is_ok(),
"a retained block must still produce a POI: {retained:?}"
);

// A pruned block must be an error, not a silently wrong hash.
let pruned = sg_store
.get_proof_of_indexing(&id, &indexer, ptr(50))
.await;
match pruned {
Err(e) => {
let msg = format!("{e}");
assert!(
msg.contains("pruned") || msg.contains("earliest"),
"the error should say the block was pruned, got: {msg}"
);
}
Ok(v) => panic!("a pruned block returned {v:?} instead of an error"),
}
})
}