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
83 changes: 83 additions & 0 deletions chain/ethereum/src/data_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,10 @@ impl blockchain::DataSourceTemplate<Chain> for DataSourceTemplate {
fn kind(&self) -> &str {
&self.kind
}

fn has_address_scoped_block_handler(&self) -> bool {
self.mapping.has_block_handler_with_call_filter()
}
}

#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)]
Expand Down Expand Up @@ -1597,3 +1601,82 @@ fn string_to_b256(s: &str) -> B256 {
pub struct TemplateSource {
pub abi: String,
}

#[cfg(test)]
mod address_scoped_block_handler_tests {
use super::*;
use graph::blockchain::DataSourceTemplate as _;

fn template_with(block_handlers: Vec<MappingBlockHandler>) -> DataSourceTemplate {
let mut mapping = Mapping {
kind: "ethereum/events".to_string(),
api_version: semver::Version::new(0, 0, 7),
language: "wasm/assemblyscript".to_string(),
entities: vec![],
abis: vec![],
block_handlers,
call_handlers: vec![],
event_handlers: vec![],
runtime: std::sync::Arc::new(vec![]),
link: Link {
link: "test".to_string(),
},
};
mapping.block_handlers.shrink_to_fit();
DataSourceTemplate {
kind: "ethereum/contract".to_string(),
network: Some("test".to_string()),
name: "T".to_string(),
manifest_idx: 0,
source: TemplateSource {
abi: "abi".to_string(),
},
mapping,
}
}

fn handler(filter: Option<BlockHandlerFilter>) -> MappingBlockHandler {
MappingBlockHandler {
handler: "h".to_string(),
filter,
}
}

#[test]
fn call_filtered_block_handler_is_address_scoped() {
let template = template_with(vec![handler(Some(BlockHandlerFilter::Call))]);
assert!(template.has_address_scoped_block_handler());
}

#[test]
fn block_handlers_without_a_call_filter_are_not_address_scoped() {
for filter in [
None,
Some(BlockHandlerFilter::Once),
Some(BlockHandlerFilter::Polling {
every: std::num::NonZeroU32::new(1).unwrap(),
}),
] {
let template = template_with(vec![handler(filter.clone())]);
assert!(
!template.has_address_scoped_block_handler(),
"a {filter:?} block handler does not depend on the created address"
);
}
}

#[test]
fn a_template_without_block_handlers_is_not_address_scoped() {
assert!(!template_with(vec![]).has_address_scoped_block_handler());
}

#[test]
fn one_call_filtered_handler_is_enough_among_other_handlers() {
let template = template_with(vec![
handler(None),
handler(Some(BlockHandlerFilter::Call)),
handler(Some(BlockHandlerFilter::Once)),
]);
assert!(template.has_address_scoped_block_handler());
}
}
31 changes: 28 additions & 3 deletions core/src/subgraph/runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use graph::blockchain::block_stream::{
BlockStream, BlockStreamEvent, BlockWithTriggers, FirehoseCursor,
};
use graph::blockchain::{
Block, BlockTime, Blockchain, DataSource as _, SubgraphFilter, Trigger, TriggerFilter as _,
TriggerFilterWrapper,
Block, BlockTime, Blockchain, DataSource as _, DataSourceTemplate as _, SubgraphFilter,
Trigger, TriggerFilter as _, TriggerFilterWrapper,
};
use graph::components::store::{
EmptyStore, GetScope, ReadStore, SeqGenerator, StoredDynamicDataSource,
Expand Down Expand Up @@ -147,7 +147,32 @@ where
}

fn is_static_filters_enabled(&self) -> bool {
self.inputs.static_filters || self.ctx.hosts_len() > ENV_VARS.static_filters_threshold
let requested =
self.inputs.static_filters || self.ctx.hosts_len() > ENV_VARS.static_filters_threshold;
if !requested {
return false;
}

// Static filters deliberately drop the addresses of created data
// sources, so a template handler that fires only on calls to the
// address of the data source it creates has nothing to match on and
// would never run. Such subgraphs have to keep the full filter, which
// means restarting the block stream whenever a data source is created.
let incompatible = self.ctx.templates().iter().any(|template| {
template
.as_onchain()
.is_some_and(|t| t.has_address_scoped_block_handler())
});
if incompatible {
info!(
self.logger,
"subgraph has a template handler that only fires on calls to the created \
data source's address; keeping the full trigger filter instead of static filters"
);
return false;
}

true
}

fn build_filter(&self) -> TriggerFilterWrapper<C> {
Expand Down
12 changes: 12 additions & 0 deletions graph/src/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,18 @@ pub trait DataSourceTemplate<C: Blockchain>: Send + Sync + Debug {
fn name(&self) -> &str;
fn manifest_idx(&self) -> u32;
fn kind(&self) -> &str;
/// Whether a data source created from this template carries a handler that
/// only fires on calls to that specific data source's address, and which
/// therefore cannot be expressed by a filter built from the static data
/// sources alone.
///
/// Static filters drop the addresses of created data sources on purpose, so
/// a chain that can have such a handler must report it here and let the
/// runner fall back to the full filter. Defaults to `false` for chains
/// whose trigger filters have no equivalent construct.
fn has_address_scoped_block_handler(&self) -> bool {
false
}
fn info(&self) -> DataSourceTemplateInfo {
DataSourceTemplateInfo {
api_version: self.api_version(),
Expand Down