diff --git a/Cargo.lock b/Cargo.lock
index cf45e2f48e4..cd4739d3538 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1644,6 +1644,7 @@ dependencies = [
"graph-store-postgres",
"hex",
"http 0.1.21",
+ "itertools",
"jsonrpc-core",
"lazy_static",
"mockall 0.9.1",
diff --git a/chain/ethereum/Cargo.toml b/chain/ethereum/Cargo.toml
index 9fac3b107d0..ab912f1d864 100644
--- a/chain/ethereum/Cargo.toml
+++ b/chain/ethereum/Cargo.toml
@@ -28,6 +28,7 @@ ethabi = { git = "https://github.com/graphprotocol/ethabi.git", branch = "master
# We have a couple custom patches to web3.
web3 = { git = "https://github.com/graphprotocol/rust-web3", branch = "master" }
+itertools = "0.10.0"
graph-runtime-wasm = { path = "../../runtime/wasm" }
graph-runtime-derive = { path = "../../runtime/derive" }
diff --git a/chain/ethereum/src/adapter.rs b/chain/ethereum/src/adapter.rs
index f7a933d4a51..650b3852577 100644
--- a/chain/ethereum/src/adapter.rs
+++ b/chain/ethereum/src/adapter.rs
@@ -22,6 +22,7 @@ use crate::capabilities::NodeCapabilities;
use crate::{data_source::DataSource, Chain};
pub type EventSignature = H256;
+pub type FunctionSelector = [u8; 4];
#[derive(Clone, Debug)]
pub struct EthereumContractCall {
@@ -268,7 +269,8 @@ impl EthereumLogFilter {
pub(crate) struct EthereumCallFilter {
// Each call filter has a map of filters keyed by address, each containing a tuple with
// start_block and the set of function signatures
- pub contract_addresses_function_signatures: HashMap
)>,
+ pub contract_addresses_function_signatures:
+ HashMap)>,
}
impl EthereumCallFilter {
@@ -353,12 +355,12 @@ impl EthereumCallFilter {
}
}
-impl FromIterator<(BlockNumber, Address, [u8; 4])> for EthereumCallFilter {
+impl FromIterator<(BlockNumber, Address, FunctionSelector)> for EthereumCallFilter {
fn from_iter(iter: I) -> Self
where
- I: IntoIterator- ,
+ I: IntoIterator
- ,
{
- let mut lookup: HashMap)> = HashMap::new();
+ let mut lookup: HashMap)> = HashMap::new();
iter.into_iter()
.for_each(|(start_block, address, function_signature)| {
if !lookup.contains_key(&address) {
@@ -385,7 +387,7 @@ impl From for EthereumCallFilter {
.contract_addresses
.into_iter()
.map(|(start_block_opt, address)| (address, (start_block_opt, HashSet::default())))
- .collect::)>>(),
+ .collect::)>>(),
}
}
}
diff --git a/chain/ethereum/src/chain.rs b/chain/ethereum/src/chain.rs
index 10d4d6f9170..c265f19a2d9 100644
--- a/chain/ethereum/src/chain.rs
+++ b/chain/ethereum/src/chain.rs
@@ -1,10 +1,8 @@
-use std::collections::HashSet;
-use std::iter::FromIterator;
-use std::sync::Arc;
-
use anyhow::{Context, Error};
use graph::data::subgraph::UnifiedMappingApiVersion;
-use graph::prelude::{EthereumCallCache, LightEthereumBlock, LightEthereumBlockExt};
+use graph::prelude::{
+ EthereumCallCache, LightEthereumBlock, LightEthereumBlockExt, StopwatchMetrics,
+};
use graph::{
blockchain::{
block_stream::{
@@ -23,6 +21,9 @@ use graph::{
SubgraphStore,
},
};
+use std::collections::HashSet;
+use std::iter::FromIterator;
+use std::sync::Arc;
use crate::data_source::DataSourceTemplate;
use crate::data_source::UnresolvedDataSourceTemplate;
@@ -140,6 +141,7 @@ impl Blockchain for Chain {
loc: &DeploymentLocator,
capabilities: &Self::NodeCapabilities,
unified_api_version: UnifiedMappingApiVersion,
+ stopwatch_metrics: StopwatchMetrics,
) -> Result, Error> {
let eth_adapter = self.eth_adapters.cheapest_with(capabilities)?.clone();
let logger = self
@@ -152,8 +154,9 @@ impl Blockchain for Chain {
logger,
ethrpc_metrics,
eth_adapter,
+ stopwatch_metrics,
chain_store: self.chain_store.cheap_clone(),
- _unified_api_version: unified_api_version,
+ unified_api_version,
};
Ok(Arc::new(adapter))
}
@@ -180,7 +183,12 @@ impl Blockchain for Chain {
let requirements = filter.node_capabilities();
let triggers_adapter = self
- .triggers_adapter(&deployment, &requirements, unified_api_version.clone())
+ .triggers_adapter(
+ &deployment,
+ &requirements,
+ unified_api_version.clone(),
+ metrics.stopwatch.clone(),
+ )
.expect(&format!(
"no adapter for network {} with capabilities {}",
self.name, requirements
@@ -306,9 +314,10 @@ pub struct DummyDataSourceTemplate;
pub struct TriggersAdapter {
logger: Logger,
ethrpc_metrics: Arc,
+ stopwatch_metrics: StopwatchMetrics,
chain_store: Arc,
eth_adapter: Arc,
- _unified_api_version: UnifiedMappingApiVersion,
+ unified_api_version: UnifiedMappingApiVersion,
}
#[async_trait]
@@ -324,9 +333,11 @@ impl TriggersAdapterTrait for TriggersAdapter {
self.logger.clone(),
self.chain_store.clone(),
self.ethrpc_metrics.clone(),
+ self.stopwatch_metrics.clone(),
from,
to,
filter,
+ self.unified_api_version.clone(),
)
.await
}
@@ -354,9 +365,11 @@ impl TriggersAdapterTrait for TriggersAdapter {
logger.clone(),
self.chain_store.clone(),
self.ethrpc_metrics.clone(),
+ self.stopwatch_metrics.clone(),
block_number,
block_number,
filter,
+ self.unified_api_version.clone(),
)
.await?;
assert!(blocks.len() == 1);
@@ -368,7 +381,7 @@ impl TriggersAdapterTrait for TriggersAdapter {
&filter.log,
&full_block.ethereum_block,
));
- triggers.append(&mut parse_call_triggers(&filter.call, &full_block));
+ triggers.append(&mut parse_call_triggers(&filter.call, &full_block)?);
triggers.append(&mut parse_block_triggers(filter.block.clone(), &full_block));
Ok(BlockWithTriggers::new(block, triggers))
}
diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs
index f7a94a39099..a2e72fd627c 100644
--- a/chain/ethereum/src/ethereum_adapter.rs
+++ b/chain/ethereum/src/ethereum_adapter.rs
@@ -1,25 +1,22 @@
+use ethabi::ParamType;
use ethabi::Token;
use futures::future;
use futures::prelude::*;
-use lazy_static::lazy_static;
-use std::collections::{HashMap, HashSet};
-use std::convert::TryFrom;
-use std::iter::FromIterator;
-use std::sync::Arc;
-use std::time::Instant;
-
-use ethabi::ParamType;
+use graph::components::transaction_receipt::LightTransactionReceipt;
+use graph::data::subgraph::UnifiedMappingApiVersion;
+use graph::prelude::StopwatchMetrics;
use graph::{
blockchain::{block_stream::BlockWithTriggers, BlockPtr, IngestorError},
prelude::{
- anyhow, async_trait, debug, error, ethabi,
+ anyhow::{self, anyhow, bail},
+ async_trait, debug, error, ethabi,
futures03::{self, compat::Future01CompatExt, FutureExt, StreamExt, TryStreamExt},
- hex, retry, stream, tiny_keccak, trace, warn,
+ hex, info, retry, stream, tiny_keccak, trace, warn,
web3::{
self,
types::{
Address, Block, BlockId, BlockNumber as Web3BlockNumber, Bytes, CallRequest,
- FilterBuilder, Log, H256,
+ Filter, FilterBuilder, Log, Transaction, TransactionReceipt, H256,
},
},
BlockNumber, ChainStore, CheapClone, DynTryFuture, Error, EthereumCallCache, Logger,
@@ -30,9 +27,15 @@ use graph::{
components::ethereum::*,
prelude::web3::types::{Trace, TraceFilter, TraceFilterBuilder, H160},
};
+use itertools::Itertools;
+use lazy_static::lazy_static;
+use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
+use std::convert::TryFrom;
+use std::iter::FromIterator;
+use std::sync::Arc;
+use std::time::Instant;
use web3::api::Web3;
use web3::transports::batch::Batch;
-use web3::types::Filter;
use crate::chain::BlockFinality;
use crate::{
@@ -1378,9 +1381,11 @@ pub(crate) async fn blocks_with_triggers(
logger: Logger,
chain_store: Arc,
subgraph_metrics: Arc,
+ stopwatch_metrics: StopwatchMetrics,
from: BlockNumber,
to: BlockNumber,
filter: &TriggerFilter,
+ unified_api_version: UnifiedMappingApiVersion,
) -> Result>, Error> {
// Each trigger filter needs to be queried for the same block range
// and the blocks yielded need to be deduped. If any error occurs
@@ -1484,8 +1489,8 @@ pub(crate) async fn blocks_with_triggers(
block_hashes.insert(to_hash);
triggers_by_block.entry(to).or_insert(Vec::new());
- let mut blocks = adapter
- .load_blocks(logger1, chain_store, block_hashes)
+ let blocks = adapter
+ .load_blocks(logger1, chain_store.clone(), block_hashes)
.and_then(
move |block| match triggers_by_block.remove(&(block.number() as BlockNumber)) {
Some(triggers) => Ok(BlockWithTriggers::new(
@@ -1502,6 +1507,23 @@ pub(crate) async fn blocks_with_triggers(
.compat()
.await?;
+ // Filter out call triggers that come from unsuccessful transactions
+
+ let mut blocks = if unified_api_version
+ .equal_or_greater_than(&graph::data::subgraph::API_VERSION_0_0_5)
+ {
+ let section =
+ stopwatch_metrics.start_section("filter_call_triggers_from_unsuccessful_transactions");
+ let futures = blocks.into_iter().map(|block| {
+ filter_call_triggers_from_unsuccessful_transactions(block, ð, &chain_store, &logger)
+ });
+ let blocks = futures03::future::try_join_all(futures).await?;
+ section.end();
+ blocks
+ } else {
+ blocks
+ };
+
blocks.sort_by_key(|block| block.ptr().number);
// Sanity check that the returned blocks are in the correct range.
@@ -1587,14 +1609,21 @@ pub(crate) fn parse_log_triggers(
pub(crate) fn parse_call_triggers(
call_filter: &EthereumCallFilter,
block: &EthereumBlockWithCalls,
-) -> Vec {
+) -> anyhow::Result> {
match &block.calls {
Some(calls) => calls
.iter()
.filter(move |call| call_filter.matches(call))
- .map(move |call| EthereumTrigger::Call(Arc::new(call.clone())))
+ .map(
+ move |call| match block.transaction_for_call_succeeded(call) {
+ Ok(true) => Ok(Some(EthereumTrigger::Call(Arc::new(call.clone())))),
+ Ok(false) => Ok(None),
+ Err(e) => Err(e),
+ },
+ )
+ .filter_map_ok(|some_trigger| some_trigger)
.collect(),
- None => vec![],
+ None => Ok(vec![]),
}
}
@@ -1627,3 +1656,156 @@ pub(crate) fn parse_block_triggers(
}
triggers
}
+
+async fn fetch_receipt_from_ethereum_client(
+ eth: &EthereumAdapter,
+ transaction_hash: &H256,
+) -> anyhow::Result {
+ match eth
+ .web3
+ .eth()
+ .transaction_receipt(*transaction_hash)
+ .compat()
+ .await
+ {
+ Ok(Some(receipt)) => Ok(receipt),
+ Ok(None) => bail!("Could not find transaction receipt"),
+ Err(error) => bail!("Failed to fetch transaction receipt: {}", error),
+ }
+}
+
+async fn filter_call_triggers_from_unsuccessful_transactions(
+ mut block: BlockWithTriggers,
+ eth: &EthereumAdapter,
+ chain_store: &Arc,
+ logger: &Logger,
+) -> anyhow::Result> {
+ // Return early if there is no trigger data
+ if block.trigger_data.is_empty() {
+ return Ok(block);
+ }
+
+ let initial_number_of_triggers = block.trigger_data.len();
+
+ // Get the transaction hash from each call trigger
+ let transaction_hashes: BTreeSet = block
+ .trigger_data
+ .iter()
+ .filter_map(|trigger| match trigger {
+ EthereumTrigger::Call(call_trigger) => Some(call_trigger.transaction_hash),
+ _ => None,
+ })
+ .collect::