Skip to content

Commit 0095a56

Browse files
committed
Subgraph Compositions: Validations
1 parent be90031 commit 0095a56

File tree

12 files changed

+180
-154
lines changed

12 files changed

+180
-154
lines changed

graph/src/data/subgraph/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ pub struct BaseSubgraphManifest<C, S, D, T> {
577577
#[derive(Debug, Deserialize)]
578578
#[serde(rename_all = "camelCase")]
579579
pub struct IndexerHints {
580-
prune: Option<Prune>,
580+
pub prune: Option<Prune>,
581581
}
582582

583583
impl IndexerHints {

graph/src/data_source/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ impl<C: Blockchain> UnresolvedDataSource<C> {
339339
.await
340340
.map(DataSource::Onchain),
341341
Self::Subgraph(unresolved) => unresolved
342-
.resolve(resolver, logger, manifest_idx)
342+
.resolve::<C>(resolver, logger, manifest_idx)
343343
.await
344344
.map(DataSource::Subgraph),
345345
Self::Offchain(_unresolved) => {

graph/src/data_source/subgraph.rs

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ use crate::{
22
blockchain::{block_stream::EntitySourceOperation, Block, Blockchain},
33
components::{link_resolver::LinkResolver, store::BlockNumber},
44
data::{
5-
subgraph::{calls_host_fn, SPEC_VERSION_1_3_0},
5+
subgraph::{
6+
calls_host_fn, SubgraphManifest, UnresolvedSubgraphManifest, LATEST_VERSION,
7+
SPEC_VERSION_1_3_0,
8+
},
69
value::Word,
710
},
811
data_source::{self, common::DeclaredCall},
912
ensure,
1013
prelude::{CheapClone, DataSourceContext, DeploymentHash, Link},
14+
schema::TypeKind,
1115
};
1216
use anyhow::{anyhow, Context, Error, Result};
1317
use futures03::{stream::FuturesOrdered, TryStreamExt};
@@ -211,8 +215,62 @@ pub struct UnresolvedMapping {
211215
}
212216

213217
impl UnresolvedDataSource {
218+
fn validate_mapping_entities<C: Blockchain>(
219+
mapping_entities: &[String],
220+
source_manifest: &SubgraphManifest<C>,
221+
) -> Result<(), Error> {
222+
for entity in mapping_entities {
223+
let type_kind = source_manifest.schema.kind_of_declared_type(&entity);
224+
225+
match type_kind {
226+
Some(TypeKind::Interface) => {
227+
return Err(anyhow!(
228+
"Entity {} is an interface and cannot be used as a mapping entity",
229+
entity
230+
));
231+
}
232+
Some(TypeKind::Aggregation) => {
233+
return Err(anyhow!(
234+
"Entity {} is an aggregation and cannot be used as a mapping entity",
235+
entity
236+
));
237+
}
238+
None => {
239+
return Err(anyhow!("Entity {} not found in source manifest", entity));
240+
}
241+
Some(TypeKind::Object) => {}
242+
}
243+
}
244+
Ok(())
245+
}
246+
247+
async fn resolve_source_manifest<C: Blockchain>(
248+
&self,
249+
resolver: &Arc<dyn LinkResolver>,
250+
logger: &Logger,
251+
) -> Result<Arc<SubgraphManifest<C>>, Error> {
252+
let source_raw = resolver
253+
.cat(logger, &self.source.address.to_ipfs_link())
254+
.await
255+
.context("Failed to resolve source subgraph manifest")?;
256+
257+
let source_raw: serde_yaml::Mapping = serde_yaml::from_slice(&source_raw)
258+
.context("Failed to parse source subgraph manifest as YAML")?;
259+
260+
let deployment_hash = self.source.address.clone();
261+
262+
let source_manifest = UnresolvedSubgraphManifest::<C>::parse(deployment_hash, source_raw)
263+
.context("Failed to parse source subgraph manifest")?;
264+
265+
source_manifest
266+
.resolve(resolver, logger, LATEST_VERSION.clone())
267+
.await
268+
.context("Failed to resolve source subgraph manifest")
269+
.map(Arc::new)
270+
}
271+
214272
#[allow(dead_code)]
215-
pub(super) async fn resolve(
273+
pub(super) async fn resolve<C: Blockchain>(
216274
self,
217275
resolver: &Arc<dyn LinkResolver>,
218276
logger: &Logger,
@@ -224,7 +282,38 @@ impl UnresolvedDataSource {
224282
"source" => format_args!("{:?}", &self.source),
225283
);
226284

227-
let kind = self.kind;
285+
let kind = self.kind.clone();
286+
let source_manifest = self.resolve_source_manifest::<C>(resolver, logger).await?;
287+
let source_spec_version = &source_manifest.spec_version;
288+
289+
if source_spec_version < &SPEC_VERSION_1_3_0 {
290+
return Err(anyhow!(
291+
"Source subgraph manifest spec version {} is not supported, minimum supported version is {}",
292+
source_spec_version,
293+
SPEC_VERSION_1_3_0
294+
));
295+
}
296+
297+
let pruning_enabled = match source_manifest.indexer_hints.as_ref() {
298+
None => false,
299+
Some(hints) => hints.prune.is_some(),
300+
};
301+
302+
if pruning_enabled {
303+
return Err(anyhow!(
304+
"Pruning is enabled for source subgraph, which is not supported"
305+
));
306+
}
307+
308+
let mapping_entities: Vec<String> = self
309+
.mapping
310+
.handlers
311+
.iter()
312+
.map(|handler| handler.entity.clone())
313+
.collect();
314+
315+
Self::validate_mapping_entities(&mapping_entities, &source_manifest)?;
316+
228317
let source = Source {
229318
address: self.source.address,
230319
start_block: self.source.start_block,

store/test-store/tests/chain/ethereum/manifest.rs

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,32 @@ const GQL_SCHEMA: &str = r#"
3737
type TestEntity @entity { id: ID! }
3838
"#;
3939
const GQL_SCHEMA_FULLTEXT: &str = include_str!("full-text.graphql");
40+
const SOURCE_SUBGRAPH_MANIFEST: &str = "
41+
dataSources: []
42+
schema:
43+
file:
44+
/: /ipfs/QmSourceSchema
45+
specVersion: 1.3.0
46+
";
47+
48+
const SOURCE_SUBGRAPH_SCHEMA: &str = "
49+
type TestEntity @entity { id: ID! }
50+
type User @entity { id: ID! }
51+
type Profile @entity { id: ID! }
52+
53+
type TokenData @entity(timeseries: true) {
54+
id: Int8!
55+
timestamp: Timestamp!
56+
amount: BigDecimal!
57+
}
58+
59+
type TokenStats @aggregation(intervals: [\"hour\", \"day\"], source: \"TokenData\") {
60+
id: Int8!
61+
timestamp: Timestamp!
62+
totalAmount: BigDecimal! @aggregate(fn: \"sum\", arg: \"amount\")
63+
}
64+
";
65+
4066
const MAPPING_WITH_IPFS_FUNC_WASM: &[u8] = include_bytes!("ipfs-on-ethereum-contracts.wasm");
4167
const ABI: &str = "[{\"type\":\"function\", \"inputs\": [{\"name\": \"i\",\"type\": \"uint256\"}],\"name\":\"get\",\"outputs\": [{\"type\": \"address\",\"name\": \"o\"}]}]";
4268
const FILE: &str = "{}";
@@ -83,23 +109,33 @@ impl LinkResolverTrait for TextResolver {
83109
}
84110
}
85111

86-
async fn resolve_manifest(
112+
async fn try_resolve_manifest(
87113
text: &str,
88114
max_spec_version: Version,
89-
) -> SubgraphManifest<graph_chain_ethereum::Chain> {
115+
) -> Result<SubgraphManifest<graph_chain_ethereum::Chain>, anyhow::Error> {
90116
let mut resolver = TextResolver::default();
91117
let id = DeploymentHash::new("Qmmanifest").unwrap();
92118

93119
resolver.add(id.as_str(), &text);
94120
resolver.add("/ipfs/Qmschema", &GQL_SCHEMA);
95121
resolver.add("/ipfs/Qmabi", &ABI);
96122
resolver.add("/ipfs/Qmmapping", &MAPPING_WITH_IPFS_FUNC_WASM);
123+
resolver.add("/ipfs/QmSource", &SOURCE_SUBGRAPH_MANIFEST);
124+
resolver.add("/ipfs/QmSource2", &SOURCE_SUBGRAPH_MANIFEST);
125+
resolver.add("/ipfs/QmSourceSchema", &SOURCE_SUBGRAPH_SCHEMA);
97126
resolver.add(FILE_CID, &FILE);
98127

99128
let resolver: Arc<dyn LinkResolverTrait> = Arc::new(resolver);
100129

101-
let raw = serde_yaml::from_str(text).unwrap();
102-
SubgraphManifest::resolve_from_raw(id, raw, &resolver, &LOGGER, max_spec_version)
130+
let raw = serde_yaml::from_str(text)?;
131+
Ok(SubgraphManifest::resolve_from_raw(id, raw, &resolver, &LOGGER, max_spec_version).await?)
132+
}
133+
134+
async fn resolve_manifest(
135+
text: &str,
136+
max_spec_version: Version,
137+
) -> SubgraphManifest<graph_chain_ethereum::Chain> {
138+
try_resolve_manifest(text, max_spec_version)
103139
.await
104140
.expect("Parsing simple manifest works")
105141
}
@@ -184,7 +220,7 @@ dataSources:
184220
- Gravatar
185221
network: mainnet
186222
source:
187-
address: 'QmSWWT2yrTFDZSL8tRyoHEVrcEKAUsY2hj2TMQDfdDZU8h'
223+
address: 'QmSource'
188224
startBlock: 9562480
189225
mapping:
190226
apiVersion: 0.0.6
@@ -195,7 +231,7 @@ dataSources:
195231
/: /ipfs/Qmmapping
196232
handlers:
197233
- handler: handleEntity
198-
entity: User
234+
entity: TestEntity
199235
specVersion: 1.3.0
200236
";
201237

@@ -214,6 +250,42 @@ specVersion: 1.3.0
214250
}
215251
}
216252

253+
#[tokio::test]
254+
async fn subgraph_ds_manifest_aggregations_should_fail() {
255+
let yaml = "
256+
schema:
257+
file:
258+
/: /ipfs/Qmschema
259+
dataSources:
260+
- name: SubgraphSource
261+
kind: subgraph
262+
entities:
263+
- Gravatar
264+
network: mainnet
265+
source:
266+
address: 'QmSource'
267+
startBlock: 9562480
268+
mapping:
269+
apiVersion: 0.0.6
270+
language: wasm/assemblyscript
271+
entities:
272+
- TestEntity
273+
file:
274+
/: /ipfs/Qmmapping
275+
handlers:
276+
- handler: handleEntity
277+
entity: TokenStats # This is an aggregation and should fail
278+
specVersion: 1.3.0
279+
";
280+
281+
let result = try_resolve_manifest(yaml, SPEC_VERSION_1_3_0).await;
282+
assert!(result.is_err());
283+
let err = result.unwrap_err();
284+
assert!(err
285+
.to_string()
286+
.contains("Entity TokenStats is an aggregation and cannot be used as a mapping entity"));
287+
}
288+
217289
#[tokio::test]
218290
async fn graft_manifest() {
219291
const YAML: &str = "
@@ -1506,7 +1578,7 @@ dataSources:
15061578
- Gravatar
15071579
network: mainnet
15081580
source:
1509-
address: 'QmSWWT2yrTFDZSL8tRyoHEVrcEKAUsY2hj2TMQDfdDZU8h'
1581+
address: 'QmSource'
15101582
startBlock: 9562480
15111583
mapping:
15121584
apiVersion: 0.0.6
@@ -1537,6 +1609,8 @@ dataSources:
15371609
resolver.add("/ipfs/Qmabi", &ABI);
15381610
resolver.add("/ipfs/Qmschema", &GQL_SCHEMA);
15391611
resolver.add("/ipfs/Qmmapping", &MAPPING_WITH_IPFS_FUNC_WASM);
1612+
resolver.add("/ipfs/QmSource", &SOURCE_SUBGRAPH_MANIFEST);
1613+
resolver.add("/ipfs/QmSourceSchema", &SOURCE_SUBGRAPH_SCHEMA);
15401614

15411615
let resolver: Arc<dyn LinkResolverTrait> = Arc::new(resolver);
15421616

tests/integration-tests/source-subgraph/subgraph.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
specVersion: 0.0.8
1+
specVersion: 1.3.0
22
schema:
33
file: ./schema.graphql
44
dataSources:

tests/integration-tests/subgraph-data-sources/subgraph.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ dataSources:
66
name: Contract
77
network: test
88
source:
9-
address: 'Qmaqf8cRxfxbduZppSHKG9DMuX5JZPMoGuwGb2DQuo48sq'
9+
address: 'QmaKaj4gCYo4TmGq27tgqwrsBLwNncHGvR6Q9e6wDBYo8M'
1010
startBlock: 0
1111
mapping:
1212
apiVersion: 0.0.7

tests/runner-tests/subgraph-data-sources/abis/Contract.abi

Lines changed: 0 additions & 15 deletions
This file was deleted.

tests/runner-tests/subgraph-data-sources/package.json

Lines changed: 0 additions & 13 deletions
This file was deleted.

tests/runner-tests/subgraph-data-sources/schema.graphql

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/runner-tests/subgraph-data-sources/src/mapping.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)