Skip to content
Merged
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
84 changes: 84 additions & 0 deletions graph/src/data/subgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,73 @@ pub type SubgraphManifest<C> =
pub struct UnvalidatedSubgraphManifest<C: Blockchain>(SubgraphManifest<C>);

impl<C: Blockchain> UnvalidatedSubgraphManifest<C> {
fn validate_subgraph_datasources(
data_sources: &[DataSource<C>],
spec_version: &Version,
) -> Vec<SubgraphManifestValidationError> {
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`
Expand Down Expand Up @@ -742,6 +809,12 @@ impl<C: Blockchain> UnvalidatedSubgraphManifest<C> {
}
}

// 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),
Expand Down Expand Up @@ -1005,6 +1078,17 @@ impl<C: Blockchain> UnresolvedSubgraphManifest<C> {
);
}

// Validate subgraph datasource constraints
if let Some(error) = UnvalidatedSubgraphManifest::<C>::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()
Expand Down
8 changes: 8 additions & 0 deletions graph/src/data_source/subgraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,14 @@ impl UnresolvedDataSource {
let source_manifest = self.resolve_source_manifest::<C>(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 {}",
Expand Down
155 changes: 153 additions & 2 deletions store/test-store/tests/chain/ethereum/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _,
Expand Down Expand Up @@ -317,7 +318,7 @@ dataSources:
- Profile
network: mainnet
source:
address: 'QmSource'
address: 'QmSource2'
startBlock: 9562500
mapping:
apiVersion: 0.0.6
Expand Down Expand Up @@ -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<dyn LinkResolverTrait> = Arc::new(resolver);

let raw = serde_yaml::from_str(yaml).unwrap();
test_store::run_test_sequentially(|_| async move {
let result: Result<UnvalidatedSubgraphManifest<Chain>, _> =
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."));
}
}
})
}
Loading