diff --git a/graph/src/data/subgraph/mod.rs b/graph/src/data/subgraph/mod.rs index 0d1b0b5d05d..d14b2c89b29 100644 --- a/graph/src/data/subgraph/mod.rs +++ b/graph/src/data/subgraph/mod.rs @@ -672,6 +672,73 @@ pub type SubgraphManifest = pub struct UnvalidatedSubgraphManifest(SubgraphManifest); impl UnvalidatedSubgraphManifest { + fn validate_subgraph_datasources( + data_sources: &[DataSource], + spec_version: &Version, + ) -> Vec { + let mut errors = Vec::new(); + + // Check spec version support for subgraph datasources + if *spec_version < SPEC_VERSION_1_3_0 { + if data_sources + .iter() + .any(|ds| matches!(ds, DataSource::Subgraph(_))) + { + errors.push(SubgraphManifestValidationError::DataSourceValidation( + "subgraph".to_string(), + anyhow!( + "Subgraph datasources are not supported prior to spec version {}", + SPEC_VERSION_1_3_0 + ), + )); + return errors; + } + } + + let subgraph_ds_count = data_sources + .iter() + .filter(|ds| matches!(ds, DataSource::Subgraph(_))) + .count(); + + if subgraph_ds_count > 5 { + errors.push(SubgraphManifestValidationError::DataSourceValidation( + "subgraph".to_string(), + anyhow!("Cannot have more than 5 subgraph datasources"), + )); + } + + let has_subgraph_ds = subgraph_ds_count > 0; + let has_onchain_ds = data_sources + .iter() + .any(|d| matches!(d, DataSource::Onchain(_))); + + if has_subgraph_ds && has_onchain_ds { + errors.push(SubgraphManifestValidationError::DataSourceValidation( + "subgraph".to_string(), + anyhow!("Subgraph datasources cannot be used alongside onchain datasources"), + )); + } + + // Check for duplicate source subgraphs + let mut seen_sources = std::collections::HashSet::new(); + for ds in data_sources.iter() { + if let DataSource::Subgraph(ds) = ds { + let source_id = ds.source.address(); + if !seen_sources.insert(source_id.clone()) { + errors.push(SubgraphManifestValidationError::DataSourceValidation( + "subgraph".to_string(), + anyhow!( + "Multiple subgraph datasources cannot use the same source subgraph {}", + source_id + ), + )); + } + } + } + + errors + } + /// Entry point for resolving a subgraph definition. /// Right now the only supported links are of the form: /// `/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k` @@ -742,6 +809,12 @@ impl UnvalidatedSubgraphManifest { } } + // Validate subgraph datasource constraints + errors.extend(Self::validate_subgraph_datasources( + &self.0.data_sources, + &self.0.spec_version, + )); + match errors.is_empty() { true => Ok(self.0), false => Err(errors), @@ -1005,6 +1078,17 @@ impl UnresolvedSubgraphManifest { ); } + // Validate subgraph datasource constraints + if let Some(error) = UnvalidatedSubgraphManifest::::validate_subgraph_datasources( + &data_sources, + &spec_version, + ) + .into_iter() + .next() + { + return Err(anyhow::Error::from(error).into()); + } + // Check the min_spec_version of each data source against the spec version of the subgraph let min_spec_version_mismatch = data_sources .iter() diff --git a/graph/src/data_source/subgraph.rs b/graph/src/data_source/subgraph.rs index e5214f9a804..9e120a4c82c 100644 --- a/graph/src/data_source/subgraph.rs +++ b/graph/src/data_source/subgraph.rs @@ -287,6 +287,14 @@ impl UnresolvedDataSource { let source_manifest = self.resolve_source_manifest::(resolver, logger).await?; let source_spec_version = &source_manifest.spec_version; + if source_manifest + .data_sources + .iter() + .any(|ds| matches!(ds, crate::data_source::DataSource::Subgraph(_))) + { + return Err(anyhow!("Nested subgraph data sources are not supported.")); + } + if source_spec_version < &SPEC_VERSION_1_3_0 { return Err(anyhow!( "Source subgraph manifest spec version {} is not supported, minimum supported version is {}", diff --git a/store/test-store/tests/chain/ethereum/manifest.rs b/store/test-store/tests/chain/ethereum/manifest.rs index 4619ddf53e1..9d094ae5817 100644 --- a/store/test-store/tests/chain/ethereum/manifest.rs +++ b/store/test-store/tests/chain/ethereum/manifest.rs @@ -20,7 +20,8 @@ use graph::env::ENV_VARS; use graph::prelude::web3::types::H256; use graph::prelude::{ anyhow, async_trait, serde_yaml, tokio, BigDecimal, BigInt, DeploymentHash, Link, Logger, - SubgraphManifest, SubgraphManifestValidationError, SubgraphStore, UnvalidatedSubgraphManifest, + SubgraphManifest, SubgraphManifestResolveError, SubgraphManifestValidationError, SubgraphStore, + UnvalidatedSubgraphManifest, }; use graph::{ blockchain::NodeCapabilities as _, @@ -317,7 +318,7 @@ dataSources: - Profile network: mainnet source: - address: 'QmSource' + address: 'QmSource2' startBlock: 9562500 mapping: apiVersion: 0.0.6 @@ -1706,3 +1707,153 @@ dataSources: assert_eq!(3, decls.len()); }); } + +#[tokio::test] +async fn mixed_subgraph_and_onchain_ds_manifest_should_fail() { + let yaml = " +schema: + file: + /: /ipfs/Qmschema +dataSources: + - name: SubgraphSource + kind: subgraph + entities: + - User + network: mainnet + source: + address: 'QmSource' + startBlock: 9562480 + mapping: + apiVersion: 0.0.6 + language: wasm/assemblyscript + entities: + - TestEntity + file: + /: /ipfs/Qmmapping + handlers: + - handler: handleEntity + entity: User + - kind: ethereum/contract + name: Gravity + network: mainnet + source: + address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC' + abi: Gravity + startBlock: 1 + mapping: + kind: ethereum/events + apiVersion: 0.0.6 + language: wasm/assemblyscript + entities: + - Gravatar + abis: + - name: Gravity + file: + /: /ipfs/Qmabi + file: + /: /ipfs/Qmmapping + handlers: + - event: NewGravatar(uint256,address,string,string) + handler: handleNewGravatar +specVersion: 1.3.0 +"; + + let result = try_resolve_manifest(yaml, SPEC_VERSION_1_3_0).await; + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(err + .to_string() + .contains("Subgraph datasources cannot be used alongside onchain datasources")); +} + +#[test] +fn nested_subgraph_ds_manifest_should_fail() { + let yaml = r#" +schema: + file: + /: /ipfs/Qmschema +dataSources: +- name: SubgraphSource + kind: subgraph + entities: + - User + network: mainnet + source: + address: 'QmNestedSource' + startBlock: 9562480 + mapping: + apiVersion: 0.0.6 + language: wasm/assemblyscript + entities: + - TestEntity + file: + /: /ipfs/Qmmapping + handlers: + - handler: handleEntity + entity: User +specVersion: 1.3.0 +"#; + + // First modify SOURCE_SUBGRAPH_MANIFEST to include a subgraph datasource + const NESTED_SOURCE_MANIFEST: &str = r#" +schema: + file: + /: /ipfs/QmSourceSchema +dataSources: +- kind: subgraph + name: NestedSource + network: mainnet + entities: + - User + source: + address: 'QmSource' + startBlock: 1 + mapping: + apiVersion: 0.0.6 + language: wasm/assemblyscript + entities: + - User + file: + /: /ipfs/Qmmapping + handlers: + - handler: handleNested + entity: User +specVersion: 1.3.0 +"#; + + let mut resolver = TextResolver::default(); + let id = DeploymentHash::new("Qmmanifest").unwrap(); + + resolver.add(id.as_str(), &yaml); + resolver.add("/ipfs/Qmabi", &ABI); + resolver.add("/ipfs/Qmschema", &GQL_SCHEMA); + resolver.add("/ipfs/Qmmapping", &MAPPING_WITH_IPFS_FUNC_WASM); + resolver.add("/ipfs/QmNestedSource", &NESTED_SOURCE_MANIFEST); + resolver.add("/ipfs/QmSource", &SOURCE_SUBGRAPH_MANIFEST); + resolver.add("/ipfs/QmSourceSchema", &SOURCE_SUBGRAPH_SCHEMA); + + let resolver: Arc = Arc::new(resolver); + + let raw = serde_yaml::from_str(yaml).unwrap(); + test_store::run_test_sequentially(|_| async move { + let result: Result, _> = + UnvalidatedSubgraphManifest::resolve( + id, + raw, + &resolver, + &LOGGER, + SPEC_VERSION_1_3_0.clone(), + ) + .await; + + match result { + Ok(_) => panic!("Expected resolution to fail"), + Err(e) => { + assert!(matches!(e, SubgraphManifestResolveError::ResolveError(_))); + let error_msg = e.to_string(); + println!("{}", error_msg); + assert!(error_msg.contains("Nested subgraph data sources are not supported.")); + } + } + }) +}