diff --git a/chain/arweave/src/chain.rs b/chain/arweave/src/chain.rs index 2a49859e48c..77431c30fbd 100644 --- a/chain/arweave/src/chain.rs +++ b/chain/arweave/src/chain.rs @@ -6,6 +6,7 @@ use graph::blockchain::{ EmptyNodeCapabilities, NoopDecoderHook, NoopRuntimeAdapter, }; use graph::cheap_clone::CheapClone; +use graph::components::adapter::ChainId; use graph::components::store::DeploymentCursorTracker; use graph::data::subgraph::UnifiedMappingApiVersion; use graph::env::EnvVars; @@ -41,7 +42,7 @@ use graph::blockchain::block_stream::{ pub struct Chain { logger_factory: LoggerFactory, - name: String, + name: ChainId, client: Arc>, chain_store: Arc, metrics_registry: Arc, @@ -157,7 +158,8 @@ impl Blockchain for Chain { number: BlockNumber, ) -> Result { self.client - .firehose_endpoint()? + .firehose_endpoint() + .await? .block_ptr_for_number::(logger, number) .await .map_err(Into::into) @@ -171,7 +173,7 @@ impl Blockchain for Chain { self.client.clone() } - fn block_ingestor(&self) -> anyhow::Result> { + async fn block_ingestor(&self) -> anyhow::Result> { let ingestor = FirehoseBlockIngestor::::new( self.chain_store.cheap_clone(), self.chain_client(), diff --git a/chain/cosmos/src/chain.rs b/chain/cosmos/src/chain.rs index 8b151015861..e8ce70ef99e 100644 --- a/chain/cosmos/src/chain.rs +++ b/chain/cosmos/src/chain.rs @@ -1,5 +1,6 @@ use graph::blockchain::firehose_block_ingestor::FirehoseBlockIngestor; use graph::blockchain::{BlockIngestor, NoopDecoderHook}; +use graph::components::adapter::ChainId; use graph::env::EnvVars; use graph::prelude::MetricsRegistry; use graph::substreams::Clock; @@ -36,7 +37,7 @@ use crate::{codec, TriggerFilter}; pub struct Chain { logger_factory: LoggerFactory, - name: String, + name: ChainId, client: Arc>, chain_store: Arc, metrics_registry: Arc, @@ -150,7 +151,7 @@ impl Blockchain for Chain { logger: &Logger, number: BlockNumber, ) -> Result { - let firehose_endpoint = self.client.firehose_endpoint()?; + let firehose_endpoint = self.client.firehose_endpoint().await?; firehose_endpoint .block_ptr_for_number::(logger, number) @@ -166,7 +167,7 @@ impl Blockchain for Chain { self.client.clone() } - fn block_ingestor(&self) -> anyhow::Result> { + async fn block_ingestor(&self) -> anyhow::Result> { let ingestor = FirehoseBlockIngestor::::new( self.chain_store.cheap_clone(), self.chain_client(), diff --git a/chain/ethereum/src/chain.rs b/chain/ethereum/src/chain.rs index 990995894b0..43ecf8924dc 100644 --- a/chain/ethereum/src/chain.rs +++ b/chain/ethereum/src/chain.rs @@ -5,6 +5,7 @@ use graph::blockchain::firehose_block_ingestor::{FirehoseBlockIngestor, Transfor use graph::blockchain::{ BlockIngestor, BlockTime, BlockchainKind, ChainIdentifier, TriggersAdapterSelector, }; +use graph::components::adapter::ChainId; use graph::components::store::DeploymentCursorTracker; use graph::data::subgraph::UnifiedMappingApiVersion; use graph::firehose::{FirehoseEndpoint, ForkStep}; @@ -146,7 +147,7 @@ impl BlockStreamBuilder for EthereumStreamBuilder { let chain_store = chain.chain_store(); let chain_head_update_stream = chain .chain_head_update_listener - .subscribe(chain.name.clone(), logger.clone()); + .subscribe(chain.name.to_string(), logger.clone()); // Special case: Detect Celo and set the threshold to 0, so that eth_getLogs is always used. // This is ok because Celo blocks are always final. And we _need_ to do this because @@ -156,6 +157,7 @@ impl BlockStreamBuilder for EthereumStreamBuilder { ChainClient::Rpc(adapter) => { adapter .cheapest() + .await .ok_or(anyhow!("unable to get eth adapter for chan_id call"))? .chain_id() .await? @@ -199,7 +201,7 @@ impl BlockRefetcher for EthereumBlockRefetcher { logger: &Logger, cursor: FirehoseCursor, ) -> Result { - let endpoint = chain.chain_client().firehose_endpoint()?; + let endpoint = chain.chain_client().firehose_endpoint().await?; let block = endpoint.get_block::(cursor, logger).await?; let ethereum_block: EthereumBlockWithCalls = (&block).try_into()?; Ok(BlockFinality::NonFinal(ethereum_block)) @@ -286,7 +288,7 @@ impl RuntimeAdapterBuilder for EthereumRuntimeAdapterBuilder { pub struct Chain { logger_factory: LoggerFactory, - name: String, + pub name: ChainId, node_id: NodeId, chain_identifier: Arc, registry: Arc, @@ -314,7 +316,7 @@ impl Chain { /// Creates a new Ethereum [`Chain`]. pub fn new( logger_factory: LoggerFactory, - name: String, + name: ChainId, node_id: NodeId, registry: Arc, chain_store: Arc, @@ -360,12 +362,12 @@ impl Chain { // TODO: This is only used to build the block stream which could prolly // be moved to the chain itself and return a block stream future that the // caller can spawn. - pub fn cheapest_adapter(&self) -> Arc { + pub async fn cheapest_adapter(&self) -> Arc { let adapters = match self.client.as_ref() { ChainClient::Firehose(_) => panic!("no adapter with firehose"), ChainClient::Rpc(adapter) => adapter, }; - adapters.cheapest().unwrap() + adapters.cheapest().await.unwrap() } } @@ -454,13 +456,15 @@ impl Blockchain for Chain { ) -> Result { match self.client.as_ref() { ChainClient::Firehose(endpoints) => endpoints - .endpoint()? + .endpoint() + .await? .block_ptr_for_number::(logger, number) .await .map_err(IngestorError::Unknown), ChainClient::Rpc(adapters) => { let adapter = adapters .cheapest() + .await .with_context(|| format!("no adapter for chain {}", self.name))? .clone(); @@ -507,7 +511,7 @@ impl Blockchain for Chain { self.client.clone() } - fn block_ingestor(&self) -> anyhow::Result> { + async fn block_ingestor(&self) -> anyhow::Result> { let ingestor: Box = match self.chain_client().as_ref() { ChainClient::Firehose(_) => { let ingestor = FirehoseBlockIngestor::::new( @@ -524,6 +528,7 @@ impl Blockchain for Chain { ChainClient::Rpc(rpc) => { let eth_adapter = rpc .cheapest() + .await .ok_or_else(|| anyhow!("unable to get adapter for ethereum block ingestor"))?; let logger = self .logger_factory @@ -675,7 +680,10 @@ impl TriggersAdapterTrait for TriggersAdapter { filter: &TriggerFilter, ) -> Result>, Error> { blocks_with_triggers( - self.chain_client.rpc()?.cheapest_with(&self.capabilities)?, + self.chain_client + .rpc()? + .cheapest_with(&self.capabilities) + .await?, self.logger.clone(), self.chain_store.clone(), self.ethrpc_metrics.clone(), @@ -705,7 +713,11 @@ impl TriggersAdapterTrait for TriggersAdapter { match &block { BlockFinality::Final(_) => { - let adapter = self.chain_client.rpc()?.cheapest_with(&self.capabilities)?; + let adapter = self + .chain_client + .rpc()? + .cheapest_with(&self.capabilities) + .await?; let block_number = block.number() as BlockNumber; let blocks = blocks_with_triggers( adapter, @@ -738,6 +750,7 @@ impl TriggersAdapterTrait for TriggersAdapter { self.chain_client .rpc()? .cheapest() + .await .ok_or(anyhow!("unable to get adapter for is_on_main_chain"))? .is_on_main_chain(&self.logger, ptr.clone()) .await @@ -774,7 +787,8 @@ impl TriggersAdapterTrait for TriggersAdapter { }), ChainClient::Rpc(adapters) => { let blocks = adapters - .cheapest_with(&self.capabilities)? + .cheapest_with(&self.capabilities) + .await? .load_blocks( self.logger.cheap_clone(), self.chain_store.cheap_clone(), diff --git a/chain/ethereum/src/data_source.rs b/chain/ethereum/src/data_source.rs index d5453bee92f..12ef2c0b703 100644 --- a/chain/ethereum/src/data_source.rs +++ b/chain/ethereum/src/data_source.rs @@ -1041,10 +1041,13 @@ impl DecoderHook { .map(|call| call.as_eth_call(block_ptr.clone(), self.eth_call_gas)) .unzip(); - let eth_adapter = self.eth_adapters.call_or_cheapest(Some(&NodeCapabilities { - archive: true, - traces: false, - }))?; + let eth_adapter = self + .eth_adapters + .call_or_cheapest(Some(&NodeCapabilities { + archive: true, + traces: false, + })) + .await?; let call_refs = calls.iter().collect::>(); let results = eth_adapter diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 6b1ce416ba2..631f45eddb8 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1868,7 +1868,8 @@ pub(crate) async fn get_calls( } else { client .rpc()? - .cheapest_with(capabilities)? + .cheapest_with(capabilities) + .await? .calls_in_block( &logger, subgraph_metrics.clone(), diff --git a/chain/ethereum/src/ingestor.rs b/chain/ethereum/src/ingestor.rs index 1c1603b59fa..14108580346 100644 --- a/chain/ethereum/src/ingestor.rs +++ b/chain/ethereum/src/ingestor.rs @@ -1,4 +1,5 @@ use crate::{chain::BlockFinality, EthereumAdapter, EthereumAdapterTrait, ENV_VARS}; +use graph::components::adapter::ChainId; use graph::futures03::compat::Future01CompatExt; use graph::{ blockchain::{BlockHash, BlockIngestor, BlockPtr, IngestorError}, @@ -16,7 +17,7 @@ pub struct PollingBlockIngestor { eth_adapter: Arc, chain_store: Arc, polling_interval: Duration, - network_name: String, + network_name: ChainId, } impl PollingBlockIngestor { @@ -26,7 +27,7 @@ impl PollingBlockIngestor { eth_adapter: Arc, chain_store: Arc, polling_interval: Duration, - network_name: String, + network_name: ChainId, ) -> Result { Ok(PollingBlockIngestor { logger, @@ -225,7 +226,7 @@ impl BlockIngestor for PollingBlockIngestor { } } - fn network_name(&self) -> String { + fn network_name(&self) -> ChainId { self.network_name.clone() } } diff --git a/chain/ethereum/src/lib.rs b/chain/ethereum/src/lib.rs index 934830ecde5..b83415146ac 100644 --- a/chain/ethereum/src/lib.rs +++ b/chain/ethereum/src/lib.rs @@ -32,7 +32,6 @@ pub use crate::adapter::{ ProviderEthRpcMetrics, SubgraphEthRpcMetrics, TriggerFilter, }; pub use crate::chain::Chain; -pub use crate::network::EthereumNetworks; pub use graph::blockchain::BlockIngestor; #[cfg(test)] diff --git a/chain/ethereum/src/network.rs b/chain/ethereum/src/network.rs index 11b06eddb5a..b63e7a3cc85 100644 --- a/chain/ethereum/src/network.rs +++ b/chain/ethereum/src/network.rs @@ -1,15 +1,14 @@ use anyhow::{anyhow, bail}; -use graph::cheap_clone::CheapClone; +use graph::blockchain::ChainIdentifier; +use graph::components::adapter::{ChainId, NetIdentifiable, ProviderManager, ProviderName}; use graph::endpoint::EndpointMetrics; use graph::firehose::{AvailableCapacity, SubgraphLimit}; use graph::prelude::rand::seq::IteratorRandom; use graph::prelude::rand::{self, Rng}; -use std::cmp::Ordering; -use std::collections::HashMap; use std::sync::Arc; pub use graph::impl_slog_value; -use graph::prelude::Error; +use graph::prelude::{async_trait, Error}; use crate::adapter::EthereumAdapter as _; use crate::capabilities::NodeCapabilities; @@ -29,7 +28,31 @@ pub struct EthereumNetworkAdapter { limit: SubgraphLimit, } +#[async_trait] +impl NetIdentifiable for EthereumNetworkAdapter { + async fn net_identifiers(&self) -> Result { + self.net_identifiers().await + } + fn provider_name(&self) -> ProviderName { + self.adapter.provider().into() + } +} + impl EthereumNetworkAdapter { + pub fn new( + endpoint_metrics: Arc, + capabilities: NodeCapabilities, + adapter: Arc, + limit: SubgraphLimit, + ) -> Self { + Self { + endpoint_metrics, + capabilities, + adapter, + limit, + } + } + fn is_call_only(&self) -> bool { self.adapter.is_call_only() } @@ -48,7 +71,8 @@ impl EthereumNetworkAdapter { #[derive(Debug, Clone)] pub struct EthereumNetworkAdapters { - pub adapters: Vec, + chain_id: ChainId, + manager: ProviderManager, call_only_adapters: Vec, // Percentage of request that should be used to retest errored adapters. retest_percent: f64, @@ -56,49 +80,87 @@ pub struct EthereumNetworkAdapters { impl Default for EthereumNetworkAdapters { fn default() -> Self { - Self::new(None) + Self { + chain_id: "".into(), + manager: ProviderManager::default(), + call_only_adapters: vec![], + retest_percent: DEFAULT_ADAPTER_ERROR_RETEST_PERCENT, + } } } impl EthereumNetworkAdapters { - pub fn new(retest_percent: Option) -> Self { + #[cfg(debug_assertions)] + pub fn for_testing( + adapters: Vec, + call_only: Vec, + ) -> Self { + use graph::slog::{o, Discard, Logger}; + + use graph::components::adapter::MockIdentValidator; + let chain_id: ChainId = "testing".into(); + + Self::new( + chain_id.clone(), + ProviderManager::new( + Logger::root(Discard, o!()), + vec![(chain_id, adapters)].into_iter(), + Arc::new(MockIdentValidator), + ), + call_only, + None, + ) + } + + pub fn new( + chain_id: ChainId, + manager: ProviderManager, + call_only_adapters: Vec, + retest_percent: Option, + ) -> Self { + #[cfg(debug_assertions)] + call_only_adapters.iter().for_each(|a| { + a.is_call_only(); + }); + Self { - adapters: vec![], - call_only_adapters: vec![], + chain_id, + manager, + call_only_adapters, retest_percent: retest_percent.unwrap_or(DEFAULT_ADAPTER_ERROR_RETEST_PERCENT), } } - pub fn push_adapter(&mut self, adapter: EthereumNetworkAdapter) { - if adapter.is_call_only() { - self.call_only_adapters.push(adapter); - } else { - self.adapters.push(adapter); - } - } - pub fn all_cheapest_with( + pub async fn all_cheapest_with( &self, required_capabilities: &NodeCapabilities, ) -> impl Iterator + '_ { let cheapest_sufficient_capability = self - .adapters - .iter() + .manager + .get_all(&self.chain_id) + .await + .unwrap_or_default() + .into_iter() .find(|adapter| &adapter.capabilities >= required_capabilities) .map(|adapter| &adapter.capabilities); - self.adapters - .iter() + self.manager + .get_all(&self.chain_id) + .await + .unwrap_or_default() + .into_iter() .filter(move |adapter| Some(&adapter.capabilities) == cheapest_sufficient_capability) .filter(|adapter| adapter.get_capacity() > AvailableCapacity::Unavailable) } - pub fn cheapest_with( + pub async fn cheapest_with( &self, required_capabilities: &NodeCapabilities, ) -> Result, Error> { let retest_rng: f64 = (&mut rand::thread_rng()).gen(); let cheapest = self .all_cheapest_with(required_capabilities) + .await .choose_multiple(&mut rand::thread_rng(), 3); let cheapest = cheapest.iter(); @@ -123,20 +185,23 @@ impl EthereumNetworkAdapters { )) } - pub fn cheapest(&self) -> Option> { + pub async fn cheapest(&self) -> Option> { // EthereumAdapters are sorted by their NodeCapabilities when the EthereumNetworks // struct is instantiated so they do not need to be sorted here - self.adapters + self.manager + .get_all(&self.chain_id) + .await + .unwrap_or_default() .first() .map(|ethereum_network_adapter| ethereum_network_adapter.adapter.clone()) } - pub fn remove(&mut self, provider: &str) { - self.adapters - .retain(|adapter| adapter.adapter.provider() != provider); - } + // pub fn remove(&mut self, provider: &str) { + // self.manager.get_all_sync(&self.chain_id).unwrap_or_default() + // .retain(|adapter| adapter.adapter.provider() != provider); + // } - pub fn call_or_cheapest( + pub async fn call_or_cheapest( &self, capabilities: Option<&NodeCapabilities>, ) -> anyhow::Result> { @@ -145,11 +210,14 @@ impl EthereumNetworkAdapters { // so we will ignore this error and return whatever comes out of `cheapest_with` match self.call_only_adapter() { Ok(Some(adapter)) => Ok(adapter), - _ => self.cheapest_with(capabilities.unwrap_or(&NodeCapabilities { - // Archive is required for call_only - archive: true, - traces: false, - })), + _ => { + self.cheapest_with(capabilities.unwrap_or(&NodeCapabilities { + // Archive is required for call_only + archive: true, + traces: false, + })) + .await + } } } @@ -179,96 +247,101 @@ impl EthereumNetworkAdapters { } } -#[derive(Clone)] -pub struct EthereumNetworks { - pub metrics: Arc, - pub networks: HashMap, -} - -impl EthereumNetworks { - pub fn new(metrics: Arc) -> EthereumNetworks { - EthereumNetworks { - networks: HashMap::new(), - metrics, - } - } - - pub fn insert_empty(&mut self, name: String) { - self.networks.entry(name).or_default(); - } - - pub fn insert( - &mut self, - name: String, - capabilities: NodeCapabilities, - adapter: Arc, - limit: SubgraphLimit, - ) { - let network_adapters = self.networks.entry(name).or_default(); - - network_adapters.push_adapter(EthereumNetworkAdapter { - capabilities, - adapter, - limit, - endpoint_metrics: self.metrics.cheap_clone(), - }); - } - - pub fn remove(&mut self, name: &str, provider: &str) { - if let Some(adapters) = self.networks.get_mut(name) { - adapters.remove(provider); - } - } - - pub fn extend(&mut self, other_networks: EthereumNetworks) { - self.networks.extend(other_networks.networks); - } - - pub fn flatten(&self) -> Vec<(String, NodeCapabilities, Arc)> { - self.networks - .iter() - .flat_map(|(network_name, network_adapters)| { - network_adapters - .adapters - .iter() - .map(move |network_adapter| { - ( - network_name.clone(), - network_adapter.capabilities, - network_adapter.adapter.clone(), - ) - }) - }) - .collect() - } - - pub fn sort(&mut self) { - for adapters in self.networks.values_mut() { - adapters.adapters.sort_by(|a, b| { - a.capabilities - .partial_cmp(&b.capabilities) - // We can't define a total ordering over node capabilities, - // so incomparable items are considered equal and end up - // near each other. - .unwrap_or(Ordering::Equal) - }) - } - } - - pub fn adapter_with_capabilities( - &self, - network_name: String, - requirements: &NodeCapabilities, - ) -> Result, Error> { - self.networks - .get(&network_name) - .ok_or(anyhow!("network not supported: {}", &network_name)) - .and_then(|adapters| adapters.cheapest_with(requirements)) - } -} +// #[derive(Debug, Clone)] +// pub struct EthereumNetworks { +// pub logger: Logger, +// pub metrics: Arc, +// pub networks: HashMap, +// } + +// impl EthereumNetworks { +// pub fn new(logger: Logger, metrics: Arc) -> EthereumNetworks { +// EthereumNetworks { +// networks: HashMap::new(), +// metrics, +// logger, +// } +// } + +// pub fn insert_empty(&mut self, name: String) { +// self.networks.entry(name).or_default(); +// } + +// pub fn insert( +// &mut self, +// name: String, +// capabilities: NodeCapabilities, +// adapter: Arc, +// limit: SubgraphLimit, +// ) { +// let network_adapters = self.networks.entry(name).or_default(); + +// network_adapters.push_adapter(EthereumNetworkAdapter { +// capabilities, +// adapter, +// limit, +// endpoint_metrics: self.metrics.cheap_clone(), +// }); +// } + +// pub fn remove(&mut self, name: &str, provider: &str) { +// if let Some(adapters) = self.networks.get_mut(name) { +// adapters.remove(provider); +// } +// } + +// pub fn extend(&mut self, other_networks: EthereumNetworks) { +// self.networks.extend(other_networks.networks); +// } + +// pub fn flatten(&self) -> Vec<(String, NodeCapabilities, Arc)> { +// self.networks +// .iter() +// .flat_map(|(network_name, network_adapters)| { +// network_adapters +// .adapters +// .iter() +// .map(move |network_adapter| { +// ( +// network_name.clone(), +// network_adapter.capabilities, +// network_adapter.adapter.clone(), +// ) +// }) +// }) +// .collect() +// } + +// pub fn sort(&mut self) { +// for adapters in self.networks.values_mut() { +// adapters.adapters.sort_by(|a, b| { +// a.capabilities +// .partial_cmp(&b.capabilities) +// // We can't define a total ordering over node capabilities, +// // so incomparable items are considered equal and end up +// // near each other. +// .unwrap_or(Ordering::Equal) +// }) +// } +// } + +// pub fn adapter_with_capabilities( +// &self, +// network_name: String, +// requirements: &NodeCapabilities, +// ) -> Result, Error> { +// self.networks +// .get(&network_name) +// .ok_or(anyhow!("network not supported: {}", &network_name)) +// .and_then(|adapters| adapters.cheapest_with(requirements)) +// } +// } #[cfg(test)] mod tests { + use graph::cheap_clone::CheapClone; + use graph::components::adapter::{MockIdentValidator, ProviderManager}; + use graph::data::value::Word; use graph::http::HeaderMap; use graph::{ endpoint::{EndpointMetrics, Provider}, @@ -281,9 +354,7 @@ mod tests { use std::sync::Arc; use uuid::Uuid; - use crate::{ - EthereumAdapter, EthereumAdapterTrait, EthereumNetworks, ProviderEthRpcMetrics, Transport, - }; + use crate::{EthereumAdapter, EthereumAdapterTrait, ProviderEthRpcMetrics, Transport}; use super::{EthereumNetworkAdapter, EthereumNetworkAdapters, NodeCapabilities}; @@ -345,7 +416,6 @@ mod tests { #[tokio::test] async fn adapter_selector_selects_eth_call() { let metrics = Arc::new(EndpointMetrics::mock()); - let chain = "mainnet".to_string(); let logger = graph::log::logger(true); let mock_registry = Arc::new(MetricsRegistry::mock()); let transport = Transport::new_rpc( @@ -380,28 +450,26 @@ mod tests { .await, ); - let mut adapters = { - let mut ethereum_networks = EthereumNetworks::new(metrics); - ethereum_networks.insert( - chain.clone(), + let mut adapters: EthereumNetworkAdapters = EthereumNetworkAdapters::for_testing( + vec![EthereumNetworkAdapter::new( + metrics.cheap_clone(), NodeCapabilities { archive: true, traces: false, }, - eth_call_adapter.clone(), + eth_adapter.clone(), SubgraphLimit::Limit(3), - ); - ethereum_networks.insert( - chain.clone(), + )], + vec![EthereumNetworkAdapter::new( + metrics.cheap_clone(), NodeCapabilities { archive: true, traces: false, }, - eth_adapter.clone(), + eth_call_adapter.clone(), SubgraphLimit::Limit(3), - ); - ethereum_networks.networks.get(&chain).unwrap().clone() - }; + )], + ); // one reference above and one inside adapters struct assert_eq!(Arc::strong_count(ð_call_adapter), 2); assert_eq!(Arc::strong_count(ð_adapter), 2); @@ -413,6 +481,7 @@ mod tests { archive: false, traces: true, }) + .await .is_err()); // Check cheapest is not call only @@ -421,16 +490,21 @@ mod tests { archive: true, traces: false, }) + .await .unwrap(); assert_eq!(adapter.is_call_only(), false); } // Check limits { - let adapter = adapters.call_or_cheapest(None).unwrap(); + let adapter = adapters.call_or_cheapest(None).await.unwrap(); assert!(adapter.is_call_only()); assert_eq!( - adapters.call_or_cheapest(None).unwrap().is_call_only(), + adapters + .call_or_cheapest(None) + .await + .unwrap() + .is_call_only(), false ); } @@ -443,6 +517,7 @@ mod tests { archive: true, traces: false, })) + .await .unwrap(); assert_eq!(adapter.is_call_only(), false); } @@ -451,7 +526,6 @@ mod tests { #[tokio::test] async fn adapter_selector_unlimited() { let metrics = Arc::new(EndpointMetrics::mock()); - let chain = "mainnet".to_string(); let logger = graph::log::logger(true); let mock_registry = Arc::new(MetricsRegistry::mock()); let transport = Transport::new_rpc( @@ -486,35 +560,33 @@ mod tests { .await, ); - let adapters = { - let mut ethereum_networks = EthereumNetworks::new(metrics); - ethereum_networks.insert( - chain.clone(), + let adapters: EthereumNetworkAdapters = EthereumNetworkAdapters::for_testing( + vec![EthereumNetworkAdapter::new( + metrics.cheap_clone(), NodeCapabilities { archive: true, traces: false, }, - eth_call_adapter.clone(), + eth_adapter.clone(), SubgraphLimit::Unlimited, - ); - ethereum_networks.insert( - chain.clone(), + )], + vec![EthereumNetworkAdapter::new( + metrics.cheap_clone(), NodeCapabilities { archive: true, traces: false, }, - eth_adapter.clone(), + eth_call_adapter.clone(), SubgraphLimit::Limit(3), - ); - ethereum_networks.networks.get(&chain).unwrap().clone() - }; + )], + ); // one reference above and one inside adapters struct assert_eq!(Arc::strong_count(ð_call_adapter), 2); assert_eq!(Arc::strong_count(ð_adapter), 2); let keep: Vec> = vec![0; 10] .iter() - .map(|_| adapters.call_or_cheapest(None).unwrap()) + .map(|_| graph::block_on(adapters.call_or_cheapest(None)).unwrap()) .collect(); assert_eq!(keep.iter().any(|a| !a.is_call_only()), false); } @@ -522,7 +594,6 @@ mod tests { #[tokio::test] async fn adapter_selector_disable_call_only_fallback() { let metrics = Arc::new(EndpointMetrics::mock()); - let chain = "mainnet".to_string(); let logger = graph::log::logger(true); let mock_registry = Arc::new(MetricsRegistry::mock()); let transport = Transport::new_rpc( @@ -557,33 +628,35 @@ mod tests { .await, ); - let adapters = { - let mut ethereum_networks = EthereumNetworks::new(metrics); - ethereum_networks.insert( - chain.clone(), + let adapters: EthereumNetworkAdapters = EthereumNetworkAdapters::for_testing( + vec![EthereumNetworkAdapter::new( + metrics.cheap_clone(), NodeCapabilities { archive: true, traces: false, }, eth_call_adapter.clone(), SubgraphLimit::Disabled, - ); - ethereum_networks.insert( - chain.clone(), + )], + vec![EthereumNetworkAdapter::new( + metrics.cheap_clone(), NodeCapabilities { archive: true, traces: false, }, eth_adapter.clone(), SubgraphLimit::Limit(3), - ); - ethereum_networks.networks.get(&chain).unwrap().clone() - }; + )], + ); // one reference above and one inside adapters struct assert_eq!(Arc::strong_count(ð_call_adapter), 2); assert_eq!(Arc::strong_count(ð_adapter), 2); assert_eq!( - adapters.call_or_cheapest(None).unwrap().is_call_only(), + adapters + .call_or_cheapest(None) + .await + .unwrap() + .is_call_only(), false ); } @@ -591,7 +664,6 @@ mod tests { #[tokio::test] async fn adapter_selector_no_call_only_fallback() { let metrics = Arc::new(EndpointMetrics::mock()); - let chain = "mainnet".to_string(); let logger = graph::log::logger(true); let mock_registry = Arc::new(MetricsRegistry::mock()); let transport = Transport::new_rpc( @@ -614,23 +686,26 @@ mod tests { .await, ); - let adapters = { - let mut ethereum_networks = EthereumNetworks::new(metrics); - ethereum_networks.insert( - chain.clone(), + let adapters: EthereumNetworkAdapters = EthereumNetworkAdapters::for_testing( + vec![EthereumNetworkAdapter::new( + metrics.cheap_clone(), NodeCapabilities { archive: true, traces: false, }, eth_adapter.clone(), SubgraphLimit::Limit(3), - ); - ethereum_networks.networks.get(&chain).unwrap().clone() - }; + )], + vec![], + ); // one reference above and one inside adapters struct assert_eq!(Arc::strong_count(ð_adapter), 2); assert_eq!( - adapters.call_or_cheapest(None).unwrap().is_call_only(), + adapters + .call_or_cheapest(None) + .await + .unwrap() + .is_call_only(), false ); } @@ -654,6 +729,7 @@ mod tests { )); let logger = graph::log::logger(true); let provider_metrics = Arc::new(ProviderEthRpcMetrics::new(mock_registry.clone())); + let chain_id: Word = "chain_id".into(); let adapters = vec![ fake_adapter( @@ -678,8 +754,9 @@ mod tests { // Set errors metrics.report_for_test(&Provider::from(error_provider.clone()), false); - let mut no_retest_adapters = EthereumNetworkAdapters::new(Some(0f64)); - let mut always_retest_adapters = EthereumNetworkAdapters::new(Some(1f64)); + let mut no_retest_adapters = vec![]; + let mut always_retest_adapters = vec![]; + adapters.iter().cloned().for_each(|adapter| { let limit = if adapter.provider() == unavailable_provider { SubgraphLimit::Disabled @@ -687,7 +764,7 @@ mod tests { SubgraphLimit::Unlimited }; - no_retest_adapters.adapters.push(EthereumNetworkAdapter { + no_retest_adapters.push(EthereumNetworkAdapter { endpoint_metrics: metrics.clone(), capabilities: NodeCapabilities { archive: true, @@ -696,18 +773,37 @@ mod tests { adapter: adapter.clone(), limit: limit.clone(), }); - always_retest_adapters - .adapters - .push(EthereumNetworkAdapter { - endpoint_metrics: metrics.clone(), - capabilities: NodeCapabilities { - archive: true, - traces: false, - }, - adapter, - limit, - }); + always_retest_adapters.push(EthereumNetworkAdapter { + endpoint_metrics: metrics.clone(), + capabilities: NodeCapabilities { + archive: true, + traces: false, + }, + adapter, + limit, + }); }); + let manager = ProviderManager::::new( + logger, + vec![( + chain_id.clone(), + no_retest_adapters + .iter() + .cloned() + .chain(always_retest_adapters.iter().cloned()) + .collect(), + )] + .into_iter(), + Arc::new(MockIdentValidator), + ); + let no_retest_adapters = EthereumNetworkAdapters::new( + chain_id.clone(), + manager.cheap_clone(), + vec![], + Some(0f64), + ); + let always_retest_adapters = + EthereumNetworkAdapters::new(chain_id, manager.cheap_clone(), vec![], Some(1f64)); assert_eq!( no_retest_adapters @@ -715,6 +811,7 @@ mod tests { archive: true, traces: false, }) + .await .unwrap() .provider(), no_error_provider @@ -725,6 +822,7 @@ mod tests { archive: true, traces: false, }) + .await .unwrap() .provider(), error_provider @@ -748,14 +846,15 @@ mod tests { ], mock_registry.clone(), )); + let chain_id: Word = "chain_id".into(); let logger = graph::log::logger(true); let provider_metrics = Arc::new(ProviderEthRpcMetrics::new(mock_registry.clone())); // Set errors metrics.report_for_test(&Provider::from(error_provider.clone()), false); - let mut no_retest_adapters = EthereumNetworkAdapters::new(Some(0f64)); - no_retest_adapters.adapters.push(EthereumNetworkAdapter { + let mut no_retest_adapters = vec![]; + no_retest_adapters.push(EthereumNetworkAdapter { endpoint_metrics: metrics.clone(), capabilities: NodeCapabilities { archive: true, @@ -765,49 +864,71 @@ mod tests { .await, limit: SubgraphLimit::Unlimited, }); + + let mut always_retest_adapters = vec![]; + always_retest_adapters.push(EthereumNetworkAdapter { + endpoint_metrics: metrics.clone(), + capabilities: NodeCapabilities { + archive: true, + traces: false, + }, + adapter: fake_adapter( + &logger, + &no_error_provider, + &provider_metrics, + &metrics, + false, + ) + .await, + limit: SubgraphLimit::Unlimited, + }); + let manager = ProviderManager::::new( + logger.clone(), + vec![( + chain_id.clone(), + no_retest_adapters + .iter() + .cloned() + .chain(always_retest_adapters.iter().cloned()) + .collect(), + )] + .into_iter(), + Arc::new(MockIdentValidator), + ); + + let always_retest_adapters = EthereumNetworkAdapters::new( + chain_id.clone(), + manager.cheap_clone(), + vec![], + Some(1f64), + ); assert_eq!( - no_retest_adapters + always_retest_adapters .cheapest_with(&NodeCapabilities { archive: true, traces: false, }) + .await .unwrap() .provider(), - error_provider + no_error_provider ); - - let mut always_retest_adapters = EthereumNetworkAdapters::new(Some(1f64)); - always_retest_adapters - .adapters - .push(EthereumNetworkAdapter { - endpoint_metrics: metrics.clone(), - capabilities: NodeCapabilities { - archive: true, - traces: false, - }, - adapter: fake_adapter( - &logger, - &no_error_provider, - &provider_metrics, - &metrics, - false, - ) - .await, - limit: SubgraphLimit::Unlimited, - }); + let no_retest_adapters = + EthereumNetworkAdapters::new(chain_id.clone(), manager, vec![], Some(0f64)); assert_eq!( - always_retest_adapters + no_retest_adapters .cheapest_with(&NodeCapabilities { archive: true, traces: false, }) + .await .unwrap() .provider(), - no_error_provider + error_provider ); - let mut no_available_adapter = EthereumNetworkAdapters::default(); - no_available_adapter.adapters.push(EthereumNetworkAdapter { + let mut no_available_adapter = vec![]; + no_available_adapter.push(EthereumNetworkAdapter { endpoint_metrics: metrics.clone(), capabilities: NodeCapabilities { archive: true, @@ -823,10 +944,22 @@ mod tests { .await, limit: SubgraphLimit::Disabled, }); - let res = no_available_adapter.cheapest_with(&NodeCapabilities { - archive: true, - traces: false, - }); + let manager = ProviderManager::new( + logger, + vec![( + chain_id.clone(), + no_available_adapter.iter().cloned().collect(), + )] + .into_iter(), + Arc::new(MockIdentValidator), + ); + let no_available_adapter = EthereumNetworkAdapters::new(chain_id, manager, vec![], None); + let res = no_available_adapter + .cheapest_with(&NodeCapabilities { + archive: true, + traces: false, + }) + .await; assert!(res.is_err(), "{:?}", res); } diff --git a/chain/ethereum/src/runtime/runtime_adapter.rs b/chain/ethereum/src/runtime/runtime_adapter.rs index 87cfd6b11b1..3a2b4d49b32 100644 --- a/chain/ethereum/src/runtime/runtime_adapter.rs +++ b/chain/ethereum/src/runtime/runtime_adapter.rs @@ -91,10 +91,11 @@ impl blockchain::RuntimeAdapter for RuntimeAdapter { name: "ethereum.call", func: Arc::new(move |ctx, wasm_ptr| { // Ethereum calls should prioritise call-only adapters if one is available. - let eth_adapter = eth_adapters.call_or_cheapest(Some(&NodeCapabilities { - archive, - traces: false, - }))?; + let eth_adapter = + graph::block_on(eth_adapters.call_or_cheapest(Some(&NodeCapabilities { + archive, + traces: false, + })))?; ethereum_call( ð_adapter, call_cache.cheap_clone(), @@ -111,10 +112,10 @@ impl blockchain::RuntimeAdapter for RuntimeAdapter { let ethereum_get_balance = HostFn { name: "ethereum.getBalance", func: Arc::new(move |ctx, wasm_ptr| { - let eth_adapter = eth_adapters.cheapest_with(&NodeCapabilities { + let eth_adapter = graph::block_on(eth_adapters.cheapest_with(&NodeCapabilities { archive, traces: false, - })?; + }))?; eth_get_balance(ð_adapter, ctx, wasm_ptr).map(|ptr| ptr.wasm_ptr()) }), }; @@ -123,10 +124,10 @@ impl blockchain::RuntimeAdapter for RuntimeAdapter { let ethereum_get_code = HostFn { name: "ethereum.hasCode", func: Arc::new(move |ctx, wasm_ptr| { - let eth_adapter = eth_adapters.cheapest_with(&NodeCapabilities { + let eth_adapter = graph::block_on(eth_adapters.cheapest_with(&NodeCapabilities { archive, traces: false, - })?; + }))?; eth_has_code(ð_adapter, ctx, wasm_ptr).map(|ptr| ptr.wasm_ptr()) }), }; diff --git a/chain/near/src/chain.rs b/chain/near/src/chain.rs index 1eba594d968..b84d88341dc 100644 --- a/chain/near/src/chain.rs +++ b/chain/near/src/chain.rs @@ -7,6 +7,7 @@ use graph::blockchain::{ NoopRuntimeAdapter, }; use graph::cheap_clone::CheapClone; +use graph::components::adapter::ChainId; use graph::components::store::DeploymentCursorTracker; use graph::data::subgraph::UnifiedMappingApiVersion; use graph::env::EnvVars; @@ -160,7 +161,7 @@ impl BlockStreamBuilder for NearStreamBuilder { pub struct Chain { logger_factory: LoggerFactory, - name: String, + name: ChainId, client: Arc>, chain_store: Arc, metrics_registry: Arc, @@ -279,7 +280,7 @@ impl Blockchain for Chain { logger: &Logger, number: BlockNumber, ) -> Result { - let firehose_endpoint = self.client.firehose_endpoint()?; + let firehose_endpoint = self.client.firehose_endpoint().await?; firehose_endpoint .block_ptr_for_number::(logger, number) @@ -295,7 +296,7 @@ impl Blockchain for Chain { self.client.clone() } - fn block_ingestor(&self) -> anyhow::Result> { + async fn block_ingestor(&self) -> anyhow::Result> { let ingestor = FirehoseBlockIngestor::::new( self.chain_store.cheap_clone(), self.chain_client(), diff --git a/chain/starknet/src/chain.rs b/chain/starknet/src/chain.rs index f28dfa94c1f..b75286282fc 100644 --- a/chain/starknet/src/chain.rs +++ b/chain/starknet/src/chain.rs @@ -14,7 +14,10 @@ use graph::{ RuntimeAdapter as RuntimeAdapterTrait, }, cheap_clone::CheapClone, - components::store::{DeploymentCursorTracker, DeploymentLocator}, + components::{ + adapter::ChainId, + store::{DeploymentCursorTracker, DeploymentLocator}, + }, data::subgraph::UnifiedMappingApiVersion, env::EnvVars, firehose::{self, FirehoseEndpoint, ForkStep}, @@ -40,7 +43,7 @@ use crate::{ pub struct Chain { logger_factory: LoggerFactory, - name: String, + name: ChainId, client: Arc>, chain_store: Arc, metrics_registry: Arc, @@ -148,7 +151,7 @@ impl Blockchain for Chain { logger: &Logger, number: BlockNumber, ) -> Result { - let firehose_endpoint = self.client.firehose_endpoint()?; + let firehose_endpoint = self.client.firehose_endpoint().await?; firehose_endpoint .block_ptr_for_number::(logger, number) @@ -164,7 +167,7 @@ impl Blockchain for Chain { self.client.clone() } - fn block_ingestor(&self) -> Result> { + async fn block_ingestor(&self) -> Result> { let ingestor = FirehoseBlockIngestor::::new( self.chain_store.cheap_clone(), self.chain_client(), diff --git a/chain/substreams/examples/substreams.rs b/chain/substreams/examples/substreams.rs index a0abfba6082..eac9397b893 100644 --- a/chain/substreams/examples/substreams.rs +++ b/chain/substreams/examples/substreams.rs @@ -59,9 +59,9 @@ async fn main() -> Result<(), Error> { Arc::new(endpoint_metrics), )); - let client = Arc::new(ChainClient::new_firehose(FirehoseEndpoints::from(vec![ - firehose, - ]))); + let client = Arc::new(ChainClient::new_firehose(FirehoseEndpoints::for_testing( + vec![firehose], + ))); let mut stream: SubstreamsBlockStream = SubstreamsBlockStream::new( diff --git a/chain/substreams/src/block_ingestor.rs b/chain/substreams/src/block_ingestor.rs index eba52516fc8..36c0edded5c 100644 --- a/chain/substreams/src/block_ingestor.rs +++ b/chain/substreams/src/block_ingestor.rs @@ -6,6 +6,7 @@ use graph::blockchain::block_stream::{BlockStreamError, FirehoseCursor}; use graph::blockchain::{ client::ChainClient, substreams_block_stream::SubstreamsBlockStream, BlockIngestor, }; +use graph::components::adapter::ChainId; use graph::prelude::MetricsRegistry; use graph::slog::trace; use graph::substreams::Package; @@ -27,7 +28,7 @@ pub struct SubstreamsBlockIngestor { chain_store: Arc, client: Arc>, logger: Logger, - chain_name: String, + chain_name: ChainId, metrics: Arc, } @@ -36,7 +37,7 @@ impl SubstreamsBlockIngestor { chain_store: Arc, client: Arc>, logger: Logger, - chain_name: String, + chain_name: ChainId, metrics: Arc, ) -> SubstreamsBlockIngestor { SubstreamsBlockIngestor { @@ -192,7 +193,7 @@ impl BlockIngestor for SubstreamsBlockIngestor { } } - fn network_name(&self) -> String { + fn network_name(&self) -> ChainId { self.chain_name.clone() } } diff --git a/chain/substreams/src/chain.rs b/chain/substreams/src/chain.rs index a871d813e08..ab8491ebd27 100644 --- a/chain/substreams/src/chain.rs +++ b/chain/substreams/src/chain.rs @@ -189,12 +189,12 @@ impl Blockchain for Chain { self.client.clone() } - fn block_ingestor(&self) -> anyhow::Result> { + async fn block_ingestor(&self) -> anyhow::Result> { Ok(Box::new(SubstreamsBlockIngestor::new( self.chain_store.cheap_clone(), self.client.cheap_clone(), self.logger_factory.component_logger("", None), - "substreams".to_string(), + "substreams".into(), self.metrics_registry.cheap_clone(), ))) } diff --git a/core/src/subgraph/instance_manager.rs b/core/src/subgraph/instance_manager.rs index 223b855d132..4bbfd1db2d9 100644 --- a/core/src/subgraph/instance_manager.rs +++ b/core/src/subgraph/instance_manager.rs @@ -11,6 +11,7 @@ use graph::blockchain::{Blockchain, BlockchainKind, DataSource, NodeCapabilities use graph::components::metrics::gas::GasMetrics; use graph::components::subgraph::ProofOfIndexingVersion; use graph::data::subgraph::{UnresolvedSubgraphManifest, SPEC_VERSION_0_0_6}; +use graph::data::value::Word; use graph::data_source::causality_region::CausalityRegionSeq; use graph::env::EnvVars; use graph::prelude::{SubgraphInstanceManager as SubgraphInstanceManagerTrait, *}; @@ -307,7 +308,7 @@ impl SubgraphInstanceManager { .collect::>(); let required_capabilities = C::NodeCapabilities::from_data_sources(&onchain_data_sources); - let network = manifest.network_name(); + let network: Word = manifest.network_name().into(); let chain = self .chains @@ -426,7 +427,7 @@ impl SubgraphInstanceManager { unified_api_version, static_filters: self.static_filters, poi_version, - network, + network: network.to_string(), instrument, }; diff --git a/core/src/subgraph/registrar.rs b/core/src/subgraph/registrar.rs index 715f06d71b4..fe80d118457 100644 --- a/core/src/subgraph/registrar.rs +++ b/core/src/subgraph/registrar.rs @@ -9,6 +9,7 @@ use graph::components::store::{DeploymentId, DeploymentLocator, SubscriptionMana use graph::components::subgraph::Settings; use graph::data::subgraph::schema::DeploymentCreate; use graph::data::subgraph::Graft; +use graph::data::value::Word; use graph::futures01; use graph::futures01::future; use graph::futures01::stream; @@ -643,7 +644,7 @@ async fn create_subgraph_version( .await .map_err(SubgraphRegistrarError::ManifestValidationError)?; - let network_name = manifest.network_name(); + let network_name: Word = manifest.network_name().into(); let chain = chains .get::(network_name.clone()) @@ -726,7 +727,7 @@ async fn create_subgraph_version( &manifest.schema, deployment, node_id, - network_name, + network_name.into(), version_switching_mode, ) .map_err(SubgraphRegistrarError::SubgraphDeploymentError) diff --git a/graph/src/blockchain/builder.rs b/graph/src/blockchain/builder.rs index 3ea1464a2a3..ee801bdee07 100644 --- a/graph/src/blockchain/builder.rs +++ b/graph/src/blockchain/builder.rs @@ -1,6 +1,6 @@ use super::Blockchain; use crate::{ - components::store::ChainStore, env::EnvVars, firehose::FirehoseEndpoints, + components::store::ChainStore, data::value::Word, env::EnvVars, firehose::FirehoseEndpoints, prelude::LoggerFactory, prelude::MetricsRegistry, }; use std::sync::Arc; @@ -9,7 +9,7 @@ use std::sync::Arc; /// particularly fancy builder logic. pub struct BasicBlockchainBuilder { pub logger_factory: LoggerFactory, - pub name: String, + pub name: Word, pub chain_store: Arc, pub firehose_endpoints: FirehoseEndpoints, pub metrics_registry: Arc, diff --git a/graph/src/blockchain/client.rs b/graph/src/blockchain/client.rs index 4f853569e87..0208d4a9697 100644 --- a/graph/src/blockchain/client.rs +++ b/graph/src/blockchain/client.rs @@ -17,15 +17,16 @@ pub enum ChainClient { impl ChainClient { pub fn new_firehose(firehose_endpoints: FirehoseEndpoints) -> Self { - Self::new(firehose_endpoints, C::Client::default()) + todo!() + // Self::new(firehose_endpoints, C::Client::default()) } - pub fn new(firehose_endpoints: FirehoseEndpoints, adapters: C::Client) -> Self { + pub async fn new(firehose_endpoints: FirehoseEndpoints, adapters: C::Client) -> Self { // If we can get a firehose endpoint then we should prioritise it. // the reason we want to test this by getting an adapter is because // adapter limits in the configuration can effectively disable firehose // by setting a limit to 0. // In this case we should fallback to an rpc client. - let firehose_available = firehose_endpoints.endpoint().is_ok(); + let firehose_available = firehose_endpoints.endpoint().await.is_ok(); match firehose_available { true => Self::Firehose(firehose_endpoints), @@ -40,9 +41,9 @@ impl ChainClient { } } - pub fn firehose_endpoint(&self) -> anyhow::Result> { + pub async fn firehose_endpoint(&self) -> anyhow::Result> { match self { - ChainClient::Firehose(endpoints) => endpoints.endpoint(), + ChainClient::Firehose(endpoints) => endpoints.endpoint().await, _ => Err(anyhow!("firehose endpoint requested on rpc chain client")), } } diff --git a/graph/src/blockchain/firehose_block_ingestor.rs b/graph/src/blockchain/firehose_block_ingestor.rs index 23f59b3cd22..f27f763e91b 100644 --- a/graph/src/blockchain/firehose_block_ingestor.rs +++ b/graph/src/blockchain/firehose_block_ingestor.rs @@ -2,7 +2,7 @@ use std::{marker::PhantomData, sync::Arc, time::Duration}; use crate::{ blockchain::Block as BlockchainBlock, - components::store::ChainStore, + components::{adapter::ChainId, store::ChainStore}, firehose::{self, decode_firehose_block, HeaderOnly}, prelude::{error, info, Logger}, util::backoff::ExponentialBackoff, @@ -43,7 +43,7 @@ where client: Arc>, logger: Logger, default_transforms: Vec, - chain_name: String, + chain_name: ChainId, phantom: PhantomData, } @@ -56,7 +56,7 @@ where chain_store: Arc, client: Arc>, logger: Logger, - chain_name: String, + chain_name: ChainId, ) -> FirehoseBlockIngestor { FirehoseBlockIngestor { chain_store, @@ -169,7 +169,7 @@ where ExponentialBackoff::new(Duration::from_millis(250), Duration::from_secs(30)); loop { - let endpoint = match self.client.firehose_endpoint() { + let endpoint = match self.client.firehose_endpoint().await { Ok(endpoint) => endpoint, Err(err) => { error!( @@ -182,7 +182,7 @@ where }; let logger = self.logger.new( - o!("provider" => endpoint.provider.to_string(), "network_name"=> self.network_name()), + o!("provider" => endpoint.provider.to_string(), "network_name"=> self.network_name().to_string()), ); info!( @@ -226,7 +226,7 @@ where } } - fn network_name(&self) -> String { + fn network_name(&self) -> ChainId { self.chain_name.clone() } } diff --git a/graph/src/blockchain/firehose_block_stream.rs b/graph/src/blockchain/firehose_block_stream.rs index 159eca7666b..254ccd42f82 100644 --- a/graph/src/blockchain/firehose_block_stream.rs +++ b/graph/src/blockchain/firehose_block_stream.rs @@ -214,7 +214,7 @@ fn stream_blocks>( try_stream! { loop { - let endpoint = client.firehose_endpoint()?; + let endpoint = client.firehose_endpoint().await?; let logger = logger.new(o!("deployment" => deployment.clone(), "provider" => endpoint.provider.to_string())); info!( diff --git a/graph/src/blockchain/mock.rs b/graph/src/blockchain/mock.rs index 6bde388b99a..eae747bcef8 100644 --- a/graph/src/blockchain/mock.rs +++ b/graph/src/blockchain/mock.rs @@ -379,7 +379,7 @@ impl Blockchain for MockBlockchain { todo!() } - fn block_ingestor(&self) -> anyhow::Result> { + async fn block_ingestor(&self) -> anyhow::Result> { todo!() } } diff --git a/graph/src/blockchain/mod.rs b/graph/src/blockchain/mod.rs index 68776c70ce5..65dd527647e 100644 --- a/graph/src/blockchain/mod.rs +++ b/graph/src/blockchain/mod.rs @@ -18,6 +18,7 @@ mod types; use crate::{ cheap_clone::CheapClone, components::{ + adapter::ChainId, metrics::subgraph::SubgraphInstanceMetrics, store::{DeploymentCursorTracker, DeploymentLocator, StoredDynamicDataSource}, subgraph::{HostMetrics, InstanceDSTemplateInfo, MappingError}, @@ -25,6 +26,7 @@ use crate::{ }, data::subgraph::{UnifiedMappingApiVersion, MIN_SPEC_VERSION}, data_source::{self, DataSourceTemplateInfo}, + log::logger, prelude::DataSourceContext, runtime::{gas::GasCounter, AscHeap, HostExportError}, }; @@ -37,7 +39,7 @@ use async_trait::async_trait; use graph_derive::CheapClone; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use slog::Logger; +use slog::{error, info, o, Logger}; use std::{ any::Any, collections::{HashMap, HashSet}, @@ -61,7 +63,7 @@ use self::{ #[async_trait] pub trait BlockIngestor: 'static + Send + Sync { async fn run(self: Box); - fn network_name(&self) -> String; + fn network_name(&self) -> ChainId; } pub trait TriggersAdapterSelector: Sync + Send { @@ -147,7 +149,7 @@ pub trait Blockchain: Debug + Sized + Send + Sync + Unpin + 'static { const KIND: BlockchainKind; const ALIASES: &'static [&'static str] = &[]; - type Client: Debug + Default + Sync + Send; + type Client: Debug + Sync + Send; // The `Clone` bound is used when reprocessing a block, because `triggers_in_block` requires an // owned `Block`. It would be good to come up with a way to remove this bound. type Block: Block + Clone + Debug + Default; @@ -211,7 +213,7 @@ pub trait Blockchain: Debug + Sized + Send + Sync + Unpin + 'static { fn chain_client(&self) -> Arc>; - fn block_ingestor(&self) -> anyhow::Result>; + async fn block_ingestor(&self) -> anyhow::Result>; } #[derive(Error, Debug)] @@ -510,18 +512,36 @@ impl BlockchainKind { /// A collection of blockchains, keyed by `BlockchainKind` and network. #[derive(Default, Debug, Clone)] -pub struct BlockchainMap(HashMap<(BlockchainKind, String), Arc>); +pub struct BlockchainMap(pub HashMap<(BlockchainKind, ChainId), Arc>); impl BlockchainMap { pub fn new() -> Self { Self::default() } - pub fn insert(&mut self, network: String, chain: Arc) { + pub fn insert(&mut self, network: ChainId, chain: Arc) { self.0.insert((C::KIND, network), chain); } - pub fn get(&self, network: String) -> Result, Error> { + pub fn get_all_by_kind( + &self, + kind: BlockchainKind, + ) -> Result>, Error> { + self.0 + .iter() + .flat_map(|((k, _), chain)| { + if k.eq(&kind) { + Some(chain.cheap_clone().downcast().map_err(|_| { + anyhow!("unable to downcast, wrong type for blockchain {}", C::KIND) + })) + } else { + None + } + }) + .collect::>, Error>>() + } + + pub fn get(&self, network: ChainId) -> Result, Error> { self.0 .get(&(C::KIND, network.clone())) .with_context(|| format!("no network {} found on chain {}", network, C::KIND))? diff --git a/graph/src/blockchain/substreams_block_stream.rs b/graph/src/blockchain/substreams_block_stream.rs index 284cdb348c8..7121692fddf 100644 --- a/graph/src/blockchain/substreams_block_stream.rs +++ b/graph/src/blockchain/substreams_block_stream.rs @@ -194,7 +194,7 @@ fn stream_blocks>( )))?; } - let endpoint = client.firehose_endpoint()?; + let endpoint = client.firehose_endpoint().await?; let mut logger = logger.new(o!("deployment" => deployment.clone(), "provider" => endpoint.provider.to_string())); loop { diff --git a/graph/src/blockchain/types.rs b/graph/src/blockchain/types.rs index f369e2d9d0e..18d03b390ba 100644 --- a/graph/src/blockchain/types.rs +++ b/graph/src/blockchain/types.rs @@ -316,7 +316,7 @@ impl From for BlockNumber { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] /// A collection of attributes that (kind of) uniquely identify a blockchain. pub struct ChainIdentifier { pub net_version: String, diff --git a/graph/src/components/adapter.rs b/graph/src/components/adapter.rs new file mode 100644 index 00000000000..81bb6d62da4 --- /dev/null +++ b/graph/src/components/adapter.rs @@ -0,0 +1,762 @@ +use std::{ + collections::HashMap, + ops::{Add, Deref}, + sync::Arc, +}; + +use async_trait::async_trait; +use chrono::{DateTime, Duration, Utc}; + +use itertools::Itertools; +use slog::{o, warn, Discard, Logger}; +use thiserror::Error; + +use crate::{ + blockchain::{BlockHash, ChainIdentifier}, + cheap_clone::CheapClone, + data::value::Word, + prelude::error, + tokio::sync::RwLock, +}; + +use crate::components::store::{BlockStore as BlockStoreTrait, ChainStore as ChainStoreTrait}; + +const VALIDATION_ATTEMPT_TTL_SECONDS: i64 = 60 * 5; + +#[derive(Debug, Error)] +pub enum ProviderManagerError { + #[error("unknown error {0}")] + Unknown(#[from] anyhow::Error), + #[error("provider {provider} failed verification, expected ident {expected}, got {actual}")] + ProviderFailedValidation { + provider: ProviderName, + expected: ChainIdentifier, + actual: ChainIdentifier, + }, + #[error("no providers available for chain {0}")] + NoProvidersAvailable(ChainId), + #[error("all providers for chain_id {0} have failed")] + AllProvidersFailed(ChainId), +} + +#[async_trait] +pub trait NetIdentifiable: Sync + Send { + async fn net_identifiers(&self) -> Result; + fn provider_name(&self) -> ProviderName; +} + +#[async_trait] +impl NetIdentifiable for Arc { + async fn net_identifiers(&self) -> Result { + self.as_ref().net_identifiers().await + } + fn provider_name(&self) -> ProviderName { + self.as_ref().provider_name() + } +} + +pub type ProviderName = Word; +pub type ChainId = Word; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +struct Ident { + provider: ProviderName, + chain_id: ChainId, +} + +#[derive(Error, Debug)] +pub enum IdentValidatorError { + #[error("Store ident wasn't set")] + UnsetIdent, + #[error("the net version for chain {chain_id} has changed from {store_net_version} to {chain_net_version} since the last time we ran")] + ChangedNetVersion { + chain_id: ChainId, + store_net_version: String, + chain_net_version: String, + }, + #[error("the genesis block hash for chain {chain_id} has changed from {store_hash} to {chain_hash} since the last time we ran")] + ChangedHash { + chain_id: ChainId, + store_hash: BlockHash, + chain_hash: BlockHash, + }, + #[error("unable to get store for chain {0}")] + UnavailableStore(ChainId), +} + +#[async_trait] +pub trait IdentValidator: Sync + Send { + fn check_ident( + &self, + chain_id: &ChainId, + ident: &ChainIdentifier, + ) -> Result<(), IdentValidatorError>; +} + +impl> IdentValidator for B { + fn check_ident( + &self, + chain_id: &ChainId, + ident: &ChainIdentifier, + ) -> Result<(), IdentValidatorError> { + let network_chain = self + .chain_store(&chain_id) + .ok_or_else(|| IdentValidatorError::UnavailableStore(chain_id.clone()))?; + let store_ident = network_chain.chain_identifier(); + + if store_ident == &ChainIdentifier::default() { + return Err(IdentValidatorError::UnsetIdent); + } + + if store_ident.net_version != ident.net_version { + if store_ident.net_version != "0" { + return Err(IdentValidatorError::ChangedNetVersion { + chain_id: chain_id.clone(), + store_net_version: store_ident.net_version.clone(), + chain_net_version: ident.net_version.clone(), + }); + } + } + + let store_hash = &store_ident.genesis_block_hash; + let chain_hash = &ident.genesis_block_hash; + if store_hash != chain_hash { + return Err(IdentValidatorError::ChangedHash { + chain_id: chain_id.clone(), + store_hash: store_hash.clone(), + chain_hash: chain_hash.clone(), + }); + } + + return Ok(()); + } +} + +pub struct MockIdentValidator; + +impl IdentValidator for MockIdentValidator { + fn check_ident( + &self, + _chain_id: &ChainId, + _ident: &ChainIdentifier, + ) -> Result<(), IdentValidatorError> { + Ok(()) + } +} + +/// ProviderCorrectness will maintain a list of providers which have had their +/// ChainIdentifiers checked. The first identifier is considered correct, if a later +/// provider for the same chain offers a different ChainIdentifier, this will be considered a +/// failed validation and it will be disabled. +#[derive(Clone, Debug)] +pub struct ProviderManager { + inner: Arc>, +} + +impl CheapClone for ProviderManager { + fn cheap_clone(&self) -> Self { + Self { + inner: self.inner.cheap_clone(), + } + } +} + +impl Default for ProviderManager { + fn default() -> Self { + Self { + inner: Arc::new(Inner { + logger: Logger::root(Discard, o!()), + adapters: HashMap::default(), + status: vec![], + validator: Arc::new(MockIdentValidator {}), + }), + } + } +} + +impl ProviderManager { + pub fn new( + logger: Logger, + adapters: impl Iterator)>, + validator: Arc, + ) -> Self { + let mut status: Vec<(Ident, RwLock)> = Vec::new(); + + let adapters = HashMap::from_iter(adapters.map(|(chain_id, adapters)| { + let adapters = adapters + .into_iter() + .map(|adapter| { + let name = adapter.provider_name(); + + // Get status index or add new status. + let index = match status + .iter() + .find_position(|(ident, _)| ident.provider.eq(&name)) + { + Some((index, _)) => index, + None => { + status.push(( + Ident { + provider: name, + chain_id: chain_id.clone(), + }, + RwLock::new(GenesisCheckStatus::NotChecked), + )); + status.len() - 1 + } + }; + (index, adapter) + }) + .collect_vec(); + + (chain_id, adapters) + })); + + Self { + inner: Arc::new(Inner { + logger, + adapters, + status, + validator, + }), + } + } + + pub fn len(&self, chain_id: &ChainId) -> usize { + self.inner + .adapters + .get(chain_id) + .map(|a| a.len()) + .unwrap_or_default() + } + + #[cfg(test)] + pub async fn mark_all_valid(&self) { + for (_, status) in self.inner.status.iter() { + let mut s = status.write().await; + *s = GenesisCheckStatus::Valid; + } + } + + async fn verify(&self, adapters: &Vec<(usize, T)>) -> Result<(), ProviderManagerError> { + let mut tasks = vec![]; + + for (index, adapter) in adapters.into_iter() { + let inner = self.inner.cheap_clone(); + let adapter = adapter.clone(); + let index = *index; + tasks.push(inner.verify_provider(index, adapter)); + } + + crate::futures03::future::join_all(tasks) + .await + .into_iter() + .collect::, ProviderManagerError>>()?; + + Ok(()) + } + + // pub fn get_all_sync(&self, chain_id: &ChainId) -> Result, ProviderManagerError> { + // crate::block_on(self.get_all(chain_id)) + // } + + pub async fn get_all(&self, chain_id: &ChainId) -> Result, ProviderManagerError> { + let adapters = match self.inner.adapters.get(chain_id) { + Some(adapters) if !adapters.is_empty() => adapters, + _ => return Ok(vec![]), + }; + + // Optimistic check + if self.inner.is_all_verified(&adapters).await { + return Ok(adapters.iter().map(|v| &v.1).collect()); + } + + match self.verify(adapters).await { + Ok(_) => {} + Err(error) => error!( + self.inner.logger, + "unable to verify genesis for adapter: {}", + error.to_string() + ), + } + + self.inner.get_verified_for_chain(&chain_id).await + } +} + +struct Inner { + logger: Logger, + // Most operations start by getting the value so we keep track of the index to minimize the + // locked surface. + adapters: HashMap>, + // Status per (ChainId, ProviderName) pair. The RwLock here helps prevent multiple concurrent + // checks for the same provider, when one provider is being checked, all other uses will wait, + // this is correct because no provider should be used until they have been validated. + // There shouldn't be many values here so Vec is fine even if less ergonomic, because we track + // the index alongside the adapter it should be O(1) after initialization. + status: Vec<(Ident, RwLock)>, + // Validator used to compare the existing identifier to the one returned by an adapter. + validator: Arc, +} + +impl std::fmt::Debug for Inner { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + +impl Inner { + async fn is_all_verified(&self, adapters: &Vec<(usize, T)>) -> bool { + for (index, _) in adapters.iter() { + let status = self.status.get(*index).unwrap().1.read().await; + if *status != GenesisCheckStatus::Valid { + return false; + } + } + + true + } + + /// Returns any adapters that have been validated, empty if none are defined or an error if + /// all adapters have failed or are unavailable, returns different errors for these use cases + /// so that that caller can handle the different situations, as one is permanent and the other + /// is retryable. + async fn get_verified_for_chain( + &self, + chain_id: &ChainId, + ) -> Result, ProviderManagerError> { + let mut out = vec![]; + let adapters = match self.adapters.get(chain_id) { + Some(adapters) if !adapters.is_empty() => adapters, + _ => return Ok(vec![]), + }; + + let mut failed = 0; + for (index, adapter) in adapters.iter() { + let status = self.status.get(*index).unwrap().1.read().await; + match status.deref() { + GenesisCheckStatus::Valid => {} + GenesisCheckStatus::Failed => { + failed += 1; + continue; + } + GenesisCheckStatus::NotChecked | GenesisCheckStatus::TemporaryFailure { .. } => { + continue + } + } + out.push(adapter); + } + + if out.is_empty() { + if failed == adapters.len() { + return Err(ProviderManagerError::AllProvidersFailed(chain_id.clone())); + } + + return Err(ProviderManagerError::NoProvidersAvailable(chain_id.clone())); + } + + Ok(out) + } + + async fn get_ident_status(&self, index: usize) -> (Ident, GenesisCheckStatus) { + match self.status.get(index) { + Some(status) => (status.0.clone(), status.1.read().await.clone()), + None => (Ident::default(), GenesisCheckStatus::Failed), + } + } + + fn ttl_has_elapsed(checked_at: &DateTime) -> bool { + checked_at.add(Duration::seconds(VALIDATION_ATTEMPT_TTL_SECONDS)) < Utc::now() + } + + fn should_verify(status: &GenesisCheckStatus) -> bool { + match status { + GenesisCheckStatus::TemporaryFailure { checked_at } + if Self::ttl_has_elapsed(checked_at) => + { + true + } + // Let check the provider + GenesisCheckStatus::NotChecked => true, + _ => false, + } + } + + async fn verify_provider( + self: Arc>, + index: usize, + adapter: T, + ) -> Result<(), ProviderManagerError> { + let (ident, status) = self.get_ident_status(index).await; + if !Self::should_verify(&status) { + return Ok(()); + } + + let mut status = self.status.get(index).unwrap().1.write().await; + // double check nothing has changed. + if !Self::should_verify(&status) { + return Ok(()); + } + + let chain_ident = match adapter.net_identifiers().await { + Ok(ident) => ident, + Err(err) => { + error!( + &self.logger, + "failed to get net identifiers: {}", + err.to_string() + ); + *status = GenesisCheckStatus::TemporaryFailure { + checked_at: Utc::now(), + }; + + return Err(err.into()); + } + }; + + match self.validator.check_ident(&ident.chain_id, &chain_ident) { + Ok(_) => { + *status = GenesisCheckStatus::Valid; + } + Err(err) => match err { + IdentValidatorError::UnsetIdent => { + // todo: update chain? + *status = GenesisCheckStatus::Valid; + } + IdentValidatorError::ChangedNetVersion { + chain_id, + store_net_version, + chain_net_version, + } if store_net_version == "0" => { + warn!(self.logger, + "the net version for chain {} has changed from 0 to {} since the last time we ran, ignoring difference because 0 means UNSET and firehose does not provide it", + chain_id, + chain_net_version, + ); + *status = GenesisCheckStatus::Valid; + } + IdentValidatorError::ChangedNetVersion { + store_net_version, + chain_net_version, + .. + } => { + *status = GenesisCheckStatus::Failed; + return Err(ProviderManagerError::ProviderFailedValidation { + provider: ident.provider, + expected: ChainIdentifier { + net_version: store_net_version, + genesis_block_hash: chain_ident.genesis_block_hash.clone(), + }, + actual: ChainIdentifier { + net_version: chain_net_version, + genesis_block_hash: chain_ident.genesis_block_hash, + }, + }); + } + IdentValidatorError::ChangedHash { + store_hash, + chain_hash, + .. + } => { + *status = GenesisCheckStatus::Failed; + return Err(ProviderManagerError::ProviderFailedValidation { + provider: ident.provider, + expected: ChainIdentifier { + net_version: chain_ident.net_version.clone(), + genesis_block_hash: store_hash, + }, + actual: ChainIdentifier { + net_version: chain_ident.net_version, + genesis_block_hash: chain_hash, + }, + }); + } + IdentValidatorError::UnavailableStore(_) => { + *status = GenesisCheckStatus::TemporaryFailure { + checked_at: Utc::now(), + }; + + return Err(ProviderManagerError::Unknown(crate::anyhow::anyhow!( + "chain store unavailable" + ))); + } + }, + } + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum GenesisCheckStatus { + NotChecked, + TemporaryFailure { checked_at: DateTime }, + Valid, + Failed, +} + +#[cfg(test)] +mod test { + use std::{ops::Sub, sync::Arc}; + + use crate::{ + components::adapter::{ChainId, GenesisCheckStatus, MockIdentValidator}, + data::value::Word, + prelude::lazy_static, + }; + use async_trait::async_trait; + use chrono::{DateTime, Duration, Utc}; + use slog::{o, Discard, Logger}; + + use crate::{ + blockchain::{BlockHash, ChainIdentifier}, + components::adapter::ProviderManagerError, + }; + + use super::{NetIdentifiable, ProviderManager, ProviderName, VALIDATION_ATTEMPT_TTL_SECONDS}; + + const FAILED_CHAIN: &str = "failed"; + const VALID_CHAIN: &str = "valid"; + const TESTABLE_CHAIN: &str = "testable"; + const UNTESTABLE_CHAIN: &str = "untestable"; + const EMPTY_CHAIN: &str = "empty"; + + lazy_static! { + static ref VALID_IDENT: ChainIdentifier = ChainIdentifier { + net_version: VALID_CHAIN.into(), + genesis_block_hash: BlockHash::default(), + }; + static ref FAILED_IDENT: ChainIdentifier = ChainIdentifier { + net_version: FAILED_CHAIN.into(), + genesis_block_hash: BlockHash::default(), + }; + static ref UNTESTABLE_ADAPTER: MockAdapter = + MockAdapter{ + provider: UNTESTABLE_CHAIN.into(), + ident: FAILED_IDENT.clone(), + checked_at: Some(Utc::now()), + }; + + // way past TTL, ready to check again + static ref TESTABLE_ADAPTER: MockAdapter = + MockAdapter{ + provider: TESTABLE_CHAIN.into(), + ident: VALID_IDENT.clone(), + checked_at: Some(Utc::now().sub(Duration::seconds(10000000))), + }; + static ref VALID_ADAPTER: MockAdapter = MockAdapter::valid(); + static ref FAILED_ADAPTER: MockAdapter = MockAdapter::failed(); + } + + #[derive(Clone, PartialEq, Eq, Debug)] + struct MockAdapter { + provider: Word, + ident: ChainIdentifier, + checked_at: Option>, + } + + impl MockAdapter { + fn failed() -> Self { + Self { + provider: FAILED_CHAIN.into(), + ident: FAILED_IDENT.clone(), + checked_at: None, + } + } + + fn valid() -> Self { + Self { + provider: VALID_CHAIN.into(), + ident: VALID_IDENT.clone(), + checked_at: None, + } + } + + fn testable(ttl: DateTime) -> Self { + Self { + provider: TESTABLE_CHAIN.into(), + ident: VALID_IDENT.clone(), + checked_at: Some(ttl), + } + } + } + + #[async_trait] + impl NetIdentifiable for MockAdapter { + async fn net_identifiers(&self) -> Result { + match self.checked_at { + Some(checked_at) + if checked_at + > Utc::now().sub(Duration::seconds(VALIDATION_ATTEMPT_TTL_SECONDS)) => + { + unreachable!("should never check if ttl has not elapsed") + } + _ => {} + } + + Ok(self.ident.clone()) + } + fn provider_name(&self) -> ProviderName { + self.provider.clone() + } + } + + #[tokio::test] + async fn test_provider_manager() { + struct Case<'a> { + name: &'a str, + chain_id: &'a str, + status: Vec<(ProviderName, GenesisCheckStatus)>, + adapters: Vec<(ChainId, Vec)>, + idents: Vec<(ChainId, Option)>, + expected: Result, ProviderManagerError>, + } + + let cases = vec![ + Case { + name: "no adapters", + chain_id: EMPTY_CHAIN, + status: vec![], + adapters: vec![], + idents: vec![(VALID_CHAIN.into(), None)], + expected: Ok(vec![]), + }, + Case { + name: "adapter temporary failure with Ident None", + chain_id: VALID_CHAIN, + status: vec![( + UNTESTABLE_CHAIN.into(), + GenesisCheckStatus::TemporaryFailure { + checked_at: UNTESTABLE_ADAPTER.checked_at.unwrap(), + }, + )], + // UNTESTABLE_ADAPTER has failed ident, will be valid cause idents has None value + adapters: vec![(VALID_CHAIN.into(), vec![UNTESTABLE_ADAPTER.clone()])], + idents: vec![(VALID_CHAIN.into(), None)], + expected: Err(ProviderManagerError::NoProvidersAvailable( + VALID_CHAIN.into(), + )), + }, + Case { + name: "adapter temporary failure", + chain_id: VALID_CHAIN, + status: vec![( + UNTESTABLE_CHAIN.into(), + GenesisCheckStatus::TemporaryFailure { + checked_at: Utc::now(), + }, + )], + adapters: vec![(VALID_CHAIN.into(), vec![UNTESTABLE_ADAPTER.clone()])], + idents: vec![(VALID_CHAIN.into(), Some(FAILED_IDENT.clone()))], + expected: Err(ProviderManagerError::NoProvidersAvailable( + VALID_CHAIN.into(), + )), + }, + Case { + name: "chain ident None", + chain_id: VALID_CHAIN, + // Failed adapter has VALID_CHAIN as the ident, which is not validated if + // the expected ident is None + status: vec![], + adapters: vec![(VALID_CHAIN.into(), vec![FAILED_ADAPTER.clone()])], + idents: vec![(VALID_CHAIN.into(), None)], + expected: Ok(vec![&FAILED_ADAPTER]), + }, + Case { + name: "wrong chain ident", + chain_id: VALID_CHAIN, + status: vec![], + adapters: vec![(VALID_CHAIN.into(), vec![MockAdapter::failed()])], + idents: vec![(VALID_CHAIN.into(), Some(VALID_IDENT.clone()))], + expected: Err(ProviderManagerError::AllProvidersFailed(VALID_CHAIN.into())), + }, + Case { + name: "all adapters ok or not checkable yet", + chain_id: VALID_CHAIN, + status: vec![( + FAILED_CHAIN.into(), + GenesisCheckStatus::TemporaryFailure { + checked_at: Utc::now(), + }, + )], + adapters: vec![( + VALID_CHAIN.into(), + vec![VALID_ADAPTER.clone(), FAILED_ADAPTER.clone()], + )], + idents: vec![(VALID_CHAIN.into(), Some(VALID_IDENT.clone()))], + expected: Ok(vec![&VALID_ADAPTER]), + }, + Case { + name: "all adapters ok or checkable", + chain_id: VALID_CHAIN, + status: vec![( + TESTABLE_CHAIN.into(), + GenesisCheckStatus::TemporaryFailure { + checked_at: TESTABLE_ADAPTER.checked_at.unwrap(), + }, + )], + adapters: vec![( + VALID_CHAIN.into(), + vec![VALID_ADAPTER.clone(), TESTABLE_ADAPTER.clone()], + )], + idents: vec![(VALID_CHAIN.into(), Some(VALID_IDENT.clone()))], + expected: Ok(vec![&VALID_ADAPTER, &TESTABLE_ADAPTER]), + }, + ]; + + for case in cases.into_iter() { + let Case { + name, + chain_id, + status, + adapters, + idents, + expected, + } = case; + + let logger = Logger::root(Discard, o!()); + let chain_id = chain_id.into(); + + let validator = Arc::new(MockIdentValidator); + let manager = ProviderManager::new(logger, adapters.into_iter(), validator); + + for (provider, status) in status.iter() { + let slot = manager + .inner + .status + .iter() + .find(|(ident, _)| ident.provider.eq(provider)) + .expect(&format!( + "case: {} - there should be a status for provider \"{}\"", + name, provider + )); + let mut s = slot.1.write().await; + *s = status.clone(); + } + + let result = manager.get_all(&chain_id).await; + match (expected, result) { + (Ok(expected), Ok(result)) => assert_eq!( + expected, result, + "case {} failed. Result: {:?}", + name, result + ), + (Err(expected), Err(result)) => assert_eq!( + expected.to_string(), + result.to_string(), + "case {} failed. Result: {:?}", + name, + result + ), + (Ok(expected), Err(result)) => panic!( + "case {} failed. Result: {}, Expected: {:?}", + name, result, expected + ), + (Err(expected), Ok(result)) => panic!( + "case {} failed. Result: {:?}, Expected: {}", + name, result, expected + ), + } + } + } +} diff --git a/graph/src/components/mod.rs b/graph/src/components/mod.rs index 71b2f143ceb..ad6480d1d0e 100644 --- a/graph/src/components/mod.rs +++ b/graph/src/components/mod.rs @@ -60,6 +60,8 @@ pub mod metrics; /// Components dealing with versioning pub mod versions; +pub mod adapter; + /// A component that receives events of type `T`. pub trait EventConsumer { /// Get the event sink. diff --git a/graph/src/firehose/endpoints.rs b/graph/src/firehose/endpoints.rs index b6d1de09086..4f9dd50bc9c 100644 --- a/graph/src/firehose/endpoints.rs +++ b/graph/src/firehose/endpoints.rs @@ -1,9 +1,12 @@ use crate::{ blockchain::block_stream::FirehoseCursor, blockchain::Block as BlockchainBlock, - blockchain::BlockPtr, + blockchain::{BlockPtr, ChainIdentifier}, cheap_clone::CheapClone, - components::store::BlockNumber, + components::{ + adapter::{ChainId, NetIdentifiable, ProviderManager, ProviderName}, + store::BlockNumber, + }, data::value::Word, endpoint::{ConnectionType, EndpointMetrics, Provider, RequestLabels}, firehose::decode_firehose_block, @@ -13,6 +16,7 @@ use crate::{ use crate::firehose::fetch_client::FetchClient; use crate::firehose::interceptors::AuthInterceptor; +use async_trait::async_trait; use futures03::StreamExt; use http::uri::{Scheme, Uri}; use itertools::Itertools; @@ -57,6 +61,16 @@ pub struct FirehoseEndpoint { #[derive(Debug)] pub struct ConnectionHeaders(HashMap, MetadataValue>); +#[async_trait] +impl NetIdentifiable for FirehoseEndpoint { + async fn net_identifiers(&self) -> Result { + unimplemented!() + } + fn provider_name(&self) -> ProviderName { + unimplemented!() + } +} + impl ConnectionHeaders { pub fn new() -> Self { Self(HashMap::new()) @@ -458,25 +472,47 @@ impl FirehoseEndpoint { } } -#[derive(Clone, Debug)] -pub struct FirehoseEndpoints(Vec>); +#[derive(Clone, Debug, Default)] +pub struct FirehoseEndpoints(ChainId, ProviderManager>); impl FirehoseEndpoints { - pub fn new() -> Self { - Self(vec![]) + #[cfg(debug_assertions)] + pub fn for_testing(adapters: Vec>) -> Self { + use slog::{o, Discard}; + + use crate::components::adapter::MockIdentValidator; + let chain_id: Word = "testing".into(); + + Self( + chain_id.clone(), + ProviderManager::new( + Logger::root(Discard, o!()), + vec![(chain_id, adapters)].into_iter(), + Arc::new(MockIdentValidator), + ), + ) + } + + pub fn new( + chain_id: ChainId, + provider_manager: ProviderManager>, + ) -> Self { + Self(chain_id, provider_manager) } pub fn len(&self) -> usize { - self.0.len() + self.1.len(&self.0) } /// This function will attempt to grab an endpoint based on the Lowest error count // with high capacity available. If an adapter cannot be found `endpoint` will // return an error. - pub fn endpoint(&self) -> anyhow::Result> { + pub async fn endpoint(&self) -> anyhow::Result> { let endpoint = self - .0 - .iter() + .1 + .get_all(&self.0) + .await? + .into_iter() .sorted_by_key(|x| x.current_error_count()) .try_fold(None, |acc, adapter| { match adapter.get_capacity() { @@ -498,24 +534,13 @@ impl FirehoseEndpoints { adapter.cloned().ok_or(anyhow!("unable to get a connection, increase the firehose conn_pool_size or limit for the node")) } } - - pub fn remove(&mut self, provider: &str) { - self.0 - .retain(|network_endpoint| network_endpoint.provider.as_str() != provider); - } -} - -impl From>> for FirehoseEndpoints { - fn from(val: Vec>) -> Self { - FirehoseEndpoints(val) - } } #[derive(Clone, Debug)] pub struct FirehoseNetworks { /// networks contains a map from chain id (`near-mainnet`, `near-testnet`, `solana-mainnet`, etc.) /// to a list of FirehoseEndpoint (type wrapper around `Arc>`). - pub networks: BTreeMap, + pub networks: BTreeMap>>, } impl FirehoseNetworks { @@ -526,18 +551,9 @@ impl FirehoseNetworks { } pub fn insert(&mut self, chain_id: String, endpoint: Arc) { - let endpoints = self - .networks - .entry(chain_id) - .or_insert_with(FirehoseEndpoints::new); + let endpoints = self.networks.entry(chain_id).or_insert_with(|| Vec::new()); - endpoints.0.push(endpoint); - } - - pub fn remove(&mut self, chain_id: &str, provider: &str) { - if let Some(endpoints) = self.networks.get_mut(chain_id) { - endpoints.remove(provider); - } + endpoints.push(endpoint); } /// Returns a `HashMap` where the key is the chain's id and the key is an endpoint for this chain. @@ -549,7 +565,7 @@ impl FirehoseNetworks { self.networks .iter() .flat_map(|(chain_id, firehose_endpoints)| { - firehose_endpoints.0.iter().map(move |endpoint| { + firehose_endpoints.iter().map(move |endpoint| { ( (chain_id.clone(), endpoint.provider.clone()), endpoint.clone(), @@ -567,7 +583,9 @@ mod test { use slog::{o, Discard, Logger}; use crate::{ - components::metrics::MetricsRegistry, endpoint::EndpointMetrics, firehose::SubgraphLimit, + components::{adapter::NetIdentifiable, metrics::MetricsRegistry}, + endpoint::EndpointMetrics, + firehose::SubgraphLimit, }; use super::{AvailableCapacity, FirehoseEndpoint, FirehoseEndpoints, SUBGRAPHS_PER_CONN}; @@ -585,23 +603,22 @@ mod test { Arc::new(EndpointMetrics::mock()), ))]; - let mut endpoints = FirehoseEndpoints::from(endpoint); + let endpoints = FirehoseEndpoints::for_testing(endpoint); let mut keep = vec![]; for _i in 0..SUBGRAPHS_PER_CONN { - keep.push(endpoints.endpoint().unwrap()); + keep.push(endpoints.endpoint().await.unwrap()); } - let err = endpoints.endpoint().unwrap_err(); + let err = endpoints.endpoint().await.unwrap_err(); assert!(err.to_string().contains("conn_pool_size")); mem::drop(keep); - endpoints.endpoint().unwrap(); + endpoints.endpoint().await.unwrap(); - // Fails when empty too - endpoints.remove(""); + let endpoints = FirehoseEndpoints::for_testing(vec![]); - let err = endpoints.endpoint().unwrap_err(); + let err = endpoints.endpoint().await.unwrap_err(); assert!(err.to_string().contains("unable to get a connection")); } @@ -618,24 +635,18 @@ mod test { Arc::new(EndpointMetrics::mock()), ))]; - let mut endpoints = FirehoseEndpoints::from(endpoint); + let mut endpoints = FirehoseEndpoints::for_testing(endpoint); let mut keep = vec![]; for _ in 0..2 { - keep.push(endpoints.endpoint().unwrap()); + keep.push(endpoints.endpoint().await.unwrap()); } - let err = endpoints.endpoint().unwrap_err(); + let err = endpoints.endpoint().await.unwrap_err(); assert!(err.to_string().contains("conn_pool_size")); mem::drop(keep); - endpoints.endpoint().unwrap(); - - // Fails when empty too - endpoints.remove(""); - - let err = endpoints.endpoint().unwrap_err(); - assert!(err.to_string().contains("unable to get a connection")); + endpoints.endpoint().await.unwrap(); } #[tokio::test] @@ -651,16 +662,10 @@ mod test { Arc::new(EndpointMetrics::mock()), ))]; - let mut endpoints = FirehoseEndpoints::from(endpoint); + let mut endpoints = FirehoseEndpoints::for_testing(endpoint); - let err = endpoints.endpoint().unwrap_err(); + let err = endpoints.endpoint().await.unwrap_err(); assert!(err.to_string().contains("conn_pool_size")); - - // Fails when empty too - endpoints.remove(""); - - let err = endpoints.endpoint().unwrap_err(); - assert!(err.to_string().contains("unable to get a connection")); } #[tokio::test] @@ -715,25 +720,35 @@ mod test { endpoint_metrics.report_for_test(&high_error_adapter1.provider, false); - let mut endpoints = FirehoseEndpoints::from(vec![ + let endpoints = FirehoseEndpoints::for_testing(vec![ high_error_adapter1.clone(), - high_error_adapter2, + high_error_adapter2.clone(), low_availability.clone(), high_availability.clone(), ]); - let res = endpoints.endpoint().unwrap(); + let res = endpoints.endpoint().await.unwrap(); assert_eq!(res.provider, high_availability.provider); // Removing high availability without errors should fallback to low availability - endpoints.remove(&high_availability.provider); + let endpoints = FirehoseEndpoints::for_testing( + vec![ + high_error_adapter1.clone(), + high_error_adapter2, + low_availability.clone(), + high_availability.clone(), + ] + .into_iter() + .filter(|a| a.provider_name() != high_availability.provider) + .collect(), + ); // Ensure we're in a low capacity situation assert_eq!(low_availability.get_capacity(), AvailableCapacity::Low); // In the scenario where the only high level adapter has errors we keep trying that // because the others will be low or unavailable - let res = endpoints.endpoint().unwrap(); + let res = endpoints.endpoint().await.unwrap(); // This will match both high error adapters assert_eq!(res.provider, high_error_adapter1.provider); } diff --git a/graph/src/lib.rs b/graph/src/lib.rs index 1ee333fa64b..c6ebac6bd37 100644 --- a/graph/src/lib.rs +++ b/graph/src/lib.rs @@ -112,6 +112,7 @@ pub mod prelude { pub use crate::blockchain::{BlockHash, BlockPtr}; + pub use crate::components::adapter; pub use crate::components::ethereum::{ EthereumBlock, EthereumBlockWithCalls, EthereumCall, LightEthereumBlock, LightEthereumBlockExt, diff --git a/node/src/bin/manager.rs b/node/src/bin/manager.rs index 02922a9ea12..b08ca11a40b 100644 --- a/node/src/bin/manager.rs +++ b/node/src/bin/manager.rs @@ -2,6 +2,7 @@ use clap::{Parser, Subcommand}; use config::PoolSize; use git_testament::{git_testament, render_testament}; use graph::bail; +use graph::cheap_clone::CheapClone; use graph::endpoint::EndpointMetrics; use graph::env::ENV_VARS; use graph::log::logger_with_levels; @@ -14,9 +15,9 @@ use graph::{ }, url::Url, }; -use graph_chain_ethereum::{EthereumAdapter, EthereumNetworks}; +use graph_chain_ethereum::EthereumAdapter; use graph_graphql::prelude::GraphQlRunner; -use graph_node::config::{self, Config as Cfg}; +use graph_node::config::{self, Config as Cfg, Networks}; use graph_node::manager::color::Terminal; use graph_node::manager::commands; use graph_node::{ @@ -910,7 +911,7 @@ impl Context { (primary_pool, mgr) } - fn store(self) -> Arc { + fn store(&self) -> Arc { let (store, _) = self.store_and_pools(); store } @@ -931,12 +932,12 @@ impl Context { .await } - fn store_and_pools(self) -> (Arc, HashMap) { + fn store_and_pools(&self) -> (Arc, HashMap) { let (subgraph_store, pools, _) = StoreBuilder::make_subgraph_store_and_pools( &self.logger, &self.node_id, &self.config, - self.fork_base, + self.fork_base.clone(), self.registry.clone(), ); @@ -949,8 +950,8 @@ impl Context { pools.clone(), subgraph_store, HashMap::default(), - BTreeMap::new(), - self.registry, + Vec::new(), + self.registry.cheap_clone(), ); (store, pools) @@ -987,11 +988,13 @@ impl Context { )) } - async fn ethereum_networks(&self) -> anyhow::Result { + async fn networks(&self, block_store: Arc) -> anyhow::Result { let logger = self.logger.clone(); let registry = self.metrics_registry(); let metrics = Arc::new(EndpointMetrics::mock()); - create_all_ethereum_networks(logger, registry, &self.config, metrics).await + self.config + .networks(logger, registry, metrics, block_store) + .await } fn chain_store(self, chain_name: &str) -> anyhow::Result> { @@ -1006,12 +1009,13 @@ impl Context { self, chain_name: &str, ) -> anyhow::Result<(Arc, Arc)> { - let ethereum_networks = self.ethereum_networks().await?; + let block_store = self.store().block_store(); + let networks = self.networks(block_store).await?; let chain_store = self.chain_store(chain_name)?; - let ethereum_adapter = ethereum_networks - .networks - .get(chain_name) - .and_then(|adapters| adapters.cheapest()) + let ethereum_adapter = networks + .ethereum_rpcs(chain_name.into()) + .cheapest() + .await .ok_or(anyhow::anyhow!( "Failed to obtain an Ethereum adapter for chain '{}'", chain_name diff --git a/node/src/chain.rs b/node/src/chain.rs index 6b95e564797..4e86d27b855 100644 --- a/node/src/chain.rs +++ b/node/src/chain.rs @@ -1,21 +1,40 @@ -use crate::config::{Config, ProviderDetails}; -use ethereum::{EthereumNetworks, ProviderEthRpcMetrics}; -use graph::anyhow::{bail, Error}; -use graph::blockchain::{Block as BlockchainBlock, BlockchainKind, ChainIdentifier}; +use crate::config::{ + AdapterConfiguration, Config, EthAdapterConfig, FirehoseAdapterConfig, Networks, + ProviderDetails, +}; +use ethereum::chain::{ + EthereumAdapterSelector, EthereumBlockRefetcher, EthereumRuntimeAdapterBuilder, + EthereumStreamBuilder, +}; +use ethereum::network::EthereumNetworkAdapter; +use ethereum::ProviderEthRpcMetrics; +use graph::anyhow::bail; +use graph::blockchain::client::ChainClient; +use graph::blockchain::{ + BasicBlockchainBuilder, Blockchain as _, BlockchainBuilder as _, BlockchainKind, BlockchainMap, + ChainIdentifier, +}; use graph::cheap_clone::CheapClone; +use graph::components::adapter::ChainId; +use graph::components::store::{BlockStore as _, ChainStore}; +use graph::data::store::NodeId; use graph::endpoint::EndpointMetrics; -use graph::firehose::{FirehoseEndpoint, FirehoseNetworks, SubgraphLimit}; -use graph::futures03::future::{join_all, try_join_all}; +use graph::env::{EnvVars, ENV_VARS}; +use graph::firehose::{FirehoseEndpoint, SubgraphLimit}; +use graph::futures03::future::try_join_all; use graph::futures03::TryFutureExt; use graph::ipfs_client::IpfsClient; -use graph::prelude::{anyhow, tokio}; -use graph::prelude::{prost, MetricsRegistry}; +use graph::itertools::Itertools; +use graph::log::factory::LoggerFactory; +use graph::prelude::anyhow; +use graph::prelude::MetricsRegistry; use graph::slog::{debug, error, info, o, Logger}; use graph::url::Url; -use graph::util::futures::retry; use graph::util::security::SafeDisplay; -use graph_chain_ethereum::{self as ethereum, EthereumAdapterTrait, Transport}; -use std::collections::{btree_map, BTreeMap}; +use graph_chain_ethereum::{self as ethereum, Transport}; +use graph_store_postgres::{ChainHeadUpdateListener, Store}; +use std::cmp::Ordering; +use std::collections::BTreeMap; use std::sync::Arc; use std::time::Duration; @@ -108,7 +127,7 @@ pub fn create_substreams_networks( logger: Logger, config: &Config, endpoint_metrics: Arc, -) -> BTreeMap { +) -> Vec { debug!( logger, "Creating firehose networks [{} chains, ingestor {}]", @@ -116,9 +135,11 @@ pub fn create_substreams_networks( config.chains.ingestor, ); - let mut networks_by_kind = BTreeMap::new(); + let mut networks_by_kind: BTreeMap<(BlockchainKind, ChainId), Vec>> = + BTreeMap::new(); for (name, chain) in &config.chains.chains { + let name: ChainId = name.as_str().into(); for provider in &chain.providers { if let ProviderDetails::Substreams(ref firehose) = provider.details { info!( @@ -128,38 +149,44 @@ pub fn create_substreams_networks( ); let parsed_networks = networks_by_kind - .entry(chain.protocol) - .or_insert_with(FirehoseNetworks::new); + .entry((chain.protocol, name.clone())) + .or_insert_with(Vec::new); for _ in 0..firehose.conn_pool_size { - parsed_networks.insert( - name.to_string(), - Arc::new(FirehoseEndpoint::new( - // This label needs to be the original label so that the metrics - // can be deduped. - &provider.label, - &firehose.url, - firehose.token.clone(), - firehose.key.clone(), - firehose.filters_enabled(), - firehose.compression_enabled(), - SubgraphLimit::Unlimited, - endpoint_metrics.clone(), - )), - ); + parsed_networks.push(Arc::new(FirehoseEndpoint::new( + // This label needs to be the original label so that the metrics + // can be deduped. + &provider.label, + &firehose.url, + firehose.token.clone(), + firehose.key.clone(), + firehose.filters_enabled(), + firehose.compression_enabled(), + SubgraphLimit::Unlimited, + endpoint_metrics.clone(), + ))); } } } } networks_by_kind + .into_iter() + .map(|((kind, chain_id), endpoints)| { + AdapterConfiguration::Substreams(FirehoseAdapterConfig { + chain_id, + kind, + adapters: endpoints.into(), + }) + }) + .collect() } pub fn create_firehose_networks( logger: Logger, config: &Config, endpoint_metrics: Arc, -) -> BTreeMap { +) -> Vec { debug!( logger, "Creating firehose networks [{} chains, ingestor {}]", @@ -167,9 +194,11 @@ pub fn create_firehose_networks( config.chains.ingestor, ); - let mut networks_by_kind = BTreeMap::new(); + let mut networks_by_kind: BTreeMap<(BlockchainKind, ChainId), Vec>> = + BTreeMap::new(); for (name, chain) in &config.chains.chains { + let name: ChainId = name.as_str().into(); for provider in &chain.providers { if let ProviderDetails::Firehose(ref firehose) = provider.details { info!( @@ -179,8 +208,8 @@ pub fn create_firehose_networks( ); let parsed_networks = networks_by_kind - .entry(chain.protocol) - .or_insert_with(FirehoseNetworks::new); + .entry((chain.protocol, name.clone())) + .or_insert_with(Vec::new); // Create n FirehoseEndpoints where n is the size of the pool. If a // subgraph limit is defined for this endpoint then each endpoint @@ -189,240 +218,33 @@ pub fn create_firehose_networks( // of FirehoseEndpoint and each of those instance can be used in 2 different // SubgraphInstances. for _ in 0..firehose.conn_pool_size { - parsed_networks.insert( - name.to_string(), - Arc::new(FirehoseEndpoint::new( - // This label needs to be the original label so that the metrics - // can be deduped. - &provider.label, - &firehose.url, - firehose.token.clone(), - firehose.key.clone(), - firehose.filters_enabled(), - firehose.compression_enabled(), - firehose.limit_for(&config.node), - endpoint_metrics.cheap_clone(), - )), - ); + parsed_networks.push(Arc::new(FirehoseEndpoint::new( + // This label needs to be the original label so that the metrics + // can be deduped. + &provider.label, + &firehose.url, + firehose.token.clone(), + firehose.key.clone(), + firehose.filters_enabled(), + firehose.compression_enabled(), + firehose.limit_for(&config.node), + endpoint_metrics.cheap_clone(), + ))); } } } } networks_by_kind -} - -/// Try to connect to all the providers in `eth_networks` and get their net -/// version and genesis block. Return the same `eth_networks` and the -/// retrieved net identifiers grouped by network name. Remove all providers -/// for which trying to connect resulted in an error from the returned -/// `EthereumNetworks`, since it's likely pointless to try and connect to -/// them. If the connection attempt to a provider times out after -/// `NET_VERSION_WAIT_TIME`, keep the provider, but don't report a -/// version for it. -pub async fn connect_ethereum_networks( - logger: &Logger, - mut eth_networks: EthereumNetworks, -) -> Result<(EthereumNetworks, BTreeMap), anyhow::Error> { - // This has one entry for each provider, and therefore multiple entries - // for each network - let statuses = join_all( - eth_networks - .flatten() - .into_iter() - .map(|(network_name, capabilities, eth_adapter)| { - (network_name, capabilities, eth_adapter, logger.clone()) + .into_iter() + .map(|((kind, chain_id), endpoints)| { + AdapterConfiguration::Firehose(FirehoseAdapterConfig { + chain_id, + kind, + adapters: endpoints.into(), }) - .map(|(network, capabilities, eth_adapter, logger)| async move { - let logger = logger.new(o!("provider" => eth_adapter.provider().to_string())); - info!( - logger, "Connecting to Ethereum to get network identifier"; - "capabilities" => &capabilities - ); - match tokio::time::timeout(NET_VERSION_WAIT_TIME, eth_adapter.net_identifiers()) - .await - .map_err(Error::from) - { - // An `Err` means a timeout, an `Ok(Err)` means some other error (maybe a typo - // on the URL) - Ok(Err(e)) | Err(e) => { - error!(logger, "Connection to provider failed. Not using this provider"; - "error" => e.to_string()); - ProviderNetworkStatus::Broken { - chain_id: network, - provider: eth_adapter.provider().to_string(), - } - } - Ok(Ok(ident)) => { - info!( - logger, - "Connected to Ethereum"; - "network_version" => &ident.net_version, - "capabilities" => &capabilities - ); - ProviderNetworkStatus::Version { - chain_id: network, - ident, - } - } - } - }), - ) - .await; - - // Group identifiers by network name - let idents: BTreeMap = - statuses - .into_iter() - .try_fold(BTreeMap::new(), |mut networks, status| { - match status { - ProviderNetworkStatus::Broken { - chain_id: network, - provider, - } => eth_networks.remove(&network, &provider), - ProviderNetworkStatus::Version { - chain_id: network, - ident, - } => match networks.entry(network.clone()) { - btree_map::Entry::Vacant(entry) => { - entry.insert(ident); - } - btree_map::Entry::Occupied(entry) => { - if &ident != entry.get() { - return Err(anyhow!( - "conflicting network identifiers for chain {}: `{}` != `{}`", - network, - ident, - entry.get() - )); - } - } - }, - } - Ok(networks) - })?; - Ok((eth_networks, idents)) -} - -/// Try to connect to all the providers in `firehose_networks` and get their net -/// version and genesis block. Return the same `eth_networks` and the -/// retrieved net identifiers grouped by network name. Remove all providers -/// for which trying to connect resulted in an error from the returned -/// `EthereumNetworks`, since it's likely pointless to try and connect to -/// them. If the connection attempt to a provider times out after -/// `NET_VERSION_WAIT_TIME`, keep the provider, but don't report a -/// version for it. -pub async fn connect_firehose_networks( - logger: &Logger, - mut firehose_networks: FirehoseNetworks, -) -> Result<(FirehoseNetworks, BTreeMap), Error> -where - M: prost::Message + BlockchainBlock + Default + 'static, -{ - // This has one entry for each provider, and therefore multiple entries - // for each network - let statuses = join_all( - firehose_networks - .flatten() - .into_iter() - .map(|(chain_id, endpoint)| (chain_id, endpoint, logger.clone())) - .map(|((chain_id, _), endpoint, logger)| async move { - let logger = logger.new(o!("provider" => endpoint.provider.to_string())); - info!( - logger, "Connecting to Firehose to get chain identifier"; - "provider" => &endpoint.provider.to_string(), - ); - - let retry_endpoint = endpoint.clone(); - let retry_logger = logger.clone(); - let req = retry("firehose startup connection test", &logger) - .no_limit() - .no_timeout() - .run(move || { - let retry_endpoint = retry_endpoint.clone(); - let retry_logger = retry_logger.clone(); - async move { retry_endpoint.genesis_block_ptr::(&retry_logger).await } - }); - - match tokio::time::timeout(NET_VERSION_WAIT_TIME, req) - .await - .map_err(Error::from) - { - // An `Err` means a timeout, an `Ok(Err)` means some other error (maybe a typo - // on the URL) - Ok(Err(e)) | Err(e) => { - error!(logger, "Connection to provider failed. Not using this provider"; - "error" => format!("{:#}", e)); - ProviderNetworkStatus::Broken { - chain_id, - provider: endpoint.provider.to_string(), - } - } - Ok(Ok(ptr)) => { - info!( - logger, - "Connected to Firehose"; - "provider" => &endpoint.provider.to_string(), - "genesis_block" => format_args!("{}", &ptr), - ); - - // BUG: Firehose doesn't provide the net_version. - // See also: firehose-no-net-version - let ident = ChainIdentifier { - net_version: "0".to_string(), - genesis_block_hash: ptr.hash, - }; - - ProviderNetworkStatus::Version { chain_id, ident } - } - } - }), - ) - .await; - - // Group identifiers by chain id - let idents: BTreeMap = - statuses - .into_iter() - .try_fold(BTreeMap::new(), |mut networks, status| { - match status { - ProviderNetworkStatus::Broken { chain_id, provider } => { - firehose_networks.remove(&chain_id, &provider) - } - ProviderNetworkStatus::Version { chain_id, ident } => { - match networks.entry(chain_id.clone()) { - btree_map::Entry::Vacant(entry) => { - entry.insert(ident); - } - btree_map::Entry::Occupied(entry) => { - if &ident != entry.get() { - return Err(anyhow!( - "conflicting network identifiers for chain {}: `{}` != `{}`", - chain_id, - ident, - entry.get() - )); - } - } - } - } - } - Ok(networks) - })?; - - // Clean-up chains with 0 provider - firehose_networks.networks.retain(|chain_id, endpoints| { - if endpoints.len() == 0 { - error!( - logger, - "No non-broken providers available for chain {}; ignoring this chain", chain_id - ); - } - - endpoints.len() > 0 - }); - - Ok((firehose_networks, idents)) + }) + .collect() } /// Parses all Ethereum connection strings and returns their network names and @@ -432,7 +254,7 @@ pub async fn create_all_ethereum_networks( registry: Arc, config: &Config, endpoint_metrics: Arc, -) -> anyhow::Result { +) -> anyhow::Result> { let eth_rpc_metrics = Arc::new(ProviderEthRpcMetrics::new(registry)); let eth_networks_futures = config .chains @@ -449,14 +271,7 @@ pub async fn create_all_ethereum_networks( ) }); - Ok(try_join_all(eth_networks_futures) - .await? - .into_iter() - .reduce(|mut a, b| { - a.extend(b); - a - }) - .unwrap_or_else(|| EthereumNetworks::new(endpoint_metrics))) + Ok(try_join_all(eth_networks_futures).await?) } /// Parses a single Ethereum connection string and returns its network name and `EthereumAdapter`. @@ -466,20 +281,21 @@ pub async fn create_ethereum_networks_for_chain( config: &Config, network_name: &str, endpoint_metrics: Arc, -) -> anyhow::Result { - let mut parsed_networks = EthereumNetworks::new(endpoint_metrics.cheap_clone()); +) -> anyhow::Result { let chain = config .chains .chains .get(network_name) .ok_or_else(|| anyhow!("unknown network {}", network_name))?; + let mut adapters = vec![]; + let mut call_only_adapters = vec![]; for provider in &chain.providers { let (web3, call_only) = match &provider.details { ProviderDetails::Web3Call(web3) => (web3, true), ProviderDetails::Web3(web3) => (web3, false), _ => { - parsed_networks.insert_empty(network_name.to_string()); + // parsed_networks.insert_empty(network_name.to_string()); continue; } }; @@ -511,9 +327,8 @@ pub async fn create_ethereum_networks_for_chain( }; let supports_eip_1898 = !web3.features.contains("no_eip1898"); - - parsed_networks.insert( - network_name.to_string(), + let adapter = EthereumNetworkAdapter::new( + endpoint_metrics.cheap_clone(), capabilities, Arc::new( graph_chain_ethereum::EthereumAdapter::new( @@ -528,20 +343,265 @@ pub async fn create_ethereum_networks_for_chain( ), web3.limit_for(&config.node), ); + + if call_only { + call_only_adapters.push(adapter); + } else { + adapters.push(adapter); + } } - parsed_networks.sort(); - Ok(parsed_networks) + adapters.sort_by(|a, b| { + a.capabilities + .partial_cmp(&b.capabilities) + // We can't define a total ordering over node capabilities, + // so incomparable items are considered equal and end up + // near each other. + .unwrap_or(Ordering::Equal) + }); + + Ok(AdapterConfiguration::Rpc(EthAdapterConfig { + chain_id: network_name.into(), + adapters, + call_only: call_only_adapters, + polling_interval: Some(chain.polling_interval), + })) +} + +pub async fn networks_as_chains( + config: &Arc, + blockchain_map: &mut BlockchainMap, + node_id: &NodeId, + logger: &Logger, + networks: &Networks, + store: &Store, + logger_factory: &LoggerFactory, + metrics_registry: Arc, + chain_head_update_listener: Arc, +) { + let adapters = networks + .adapters + .iter() + .group_by(|a| a.chain_id()) + .into_iter() + .map(|(chain_id, adapters)| (chain_id, adapters.into_iter().collect_vec())) + .collect_vec(); + + let firehose: Vec<&FirehoseAdapterConfig> = networks + .adapters + .iter() + .flat_map(|a| a.as_firehose()) + .collect(); + + let substreams: Vec<&FirehoseAdapterConfig> = networks + .adapters + .iter() + .flat_map(|a| a.as_substreams()) + .collect(); + + adapters + .into_iter() + .filter_map(|(chain_id, adapters)| { + let adapters: Vec<&AdapterConfiguration> = adapters.into_iter().collect(); + let kind = adapters + .iter() + .map(|a| a.kind()) + .reduce(|a1, a2| match (a1, a2) { + (BlockchainKind::Substreams, k) => k, + (k, BlockchainKind::Substreams) => k, + (k, _) => k, + }) + .expect("each chain should have at least 1 adapter"); + store + .block_store() + .chain_store(chain_id) + .map(|chain_store| (chain_id, chain_store, adapters, kind)) + .or_else(|| { + error!( + logger, + "No store configured for {} chain {}; ignoring this chain", kind, chain_id + ); + None + }) + }) + .for_each(|(chain_id, chain_store, adapters, kind)| match kind { + BlockchainKind::Arweave => { + let firehose_endpoints = networks.firehose_endpoints(chain_id.clone()); + + blockchain_map.insert::( + chain_id.clone(), + Arc::new( + BasicBlockchainBuilder { + logger_factory: logger_factory.clone(), + name: chain_id.clone(), + chain_store, + firehose_endpoints, + metrics_registry: metrics_registry.clone(), + } + .build(config), + ), + ); + } + BlockchainKind::Ethereum => { + // polling interval is set per chain so if set all adapter configuration will have + // the same value. + let polling_interval = adapters + .first() + .and_then(|a| a.as_rpc().and_then(|a| a.polling_interval)) + .unwrap_or(config.ingestor_polling_interval); + + let firehose_endpoints = networks.firehose_endpoints(chain_id.clone()); + let eth_adapters = networks.ethereum_rpcs(chain_id.clone()); + + let client = Arc::new(graph::block_on( + ChainClient::::new( + firehose_endpoints, + eth_adapters.clone(), + ), + )); + let adapter_selector = EthereumAdapterSelector::new( + logger_factory.clone(), + client.clone(), + metrics_registry.clone(), + chain_store.clone(), + ); + + let call_cache = chain_store.cheap_clone(); + + let chain = ethereum::Chain::new( + logger_factory.clone(), + chain_id.clone(), + node_id.clone(), + metrics_registry.clone(), + chain_store.cheap_clone(), + call_cache, + client, + chain_head_update_listener.clone(), + Arc::new(EthereumStreamBuilder {}), + Arc::new(EthereumBlockRefetcher {}), + Arc::new(adapter_selector), + Arc::new(EthereumRuntimeAdapterBuilder {}), + Arc::new(eth_adapters.clone()), + ENV_VARS.reorg_threshold, + polling_interval, + true, + ); + + blockchain_map + .insert::(chain_id.clone(), Arc::new(chain)); + } + BlockchainKind::Near => { + let firehose_endpoints = networks.firehose_endpoints(chain_id.clone()); + blockchain_map.insert::( + chain_id.clone(), + Arc::new( + BasicBlockchainBuilder { + logger_factory: logger_factory.clone(), + name: chain_id.clone(), + chain_store, + firehose_endpoints, + metrics_registry: metrics_registry.clone(), + } + .build(config), + ), + ); + } + BlockchainKind::Cosmos => { + let firehose_endpoints = networks.firehose_endpoints(chain_id.clone()); + blockchain_map.insert::( + chain_id.clone(), + Arc::new( + BasicBlockchainBuilder { + logger_factory: logger_factory.clone(), + name: chain_id.clone(), + chain_store, + firehose_endpoints, + metrics_registry: metrics_registry.clone(), + } + .build(config), + ), + ); + } + BlockchainKind::Substreams => {} + BlockchainKind::Starknet => { + let firehose_endpoints = networks.firehose_endpoints(chain_id.clone()); + blockchain_map.insert::( + chain_id.clone(), + Arc::new( + BasicBlockchainBuilder { + logger_factory: logger_factory.clone(), + name: chain_id.clone(), + chain_store, + firehose_endpoints, + metrics_registry: metrics_registry.clone(), + } + .build(config), + ), + ); + } + }); + fn chain_store( + blockchain_map: &BlockchainMap, + kind: &BlockchainKind, + network: ChainId, + ) -> anyhow::Result> { + let chain_store: Arc = match kind { + BlockchainKind::Arweave => blockchain_map + .get::(network) + .map(|c| c.chain_store())?, + BlockchainKind::Ethereum => blockchain_map + .get::(network) + .map(|c| c.chain_store())?, + BlockchainKind::Near => blockchain_map + .get::(network) + .map(|c| c.chain_store())?, + BlockchainKind::Cosmos => blockchain_map + .get::(network) + .map(|c| c.chain_store())?, + BlockchainKind::Substreams => blockchain_map + .get::(network) + .map(|c| c.chain_store())?, + BlockchainKind::Starknet => blockchain_map + .get::(network) + .map(|c| c.chain_store())?, + }; + + Ok(chain_store) + } + + for FirehoseAdapterConfig { + chain_id, + kind, + adapters, + } in substreams.iter() + { + let chain_store = chain_store(&blockchain_map, kind, chain_id.clone()).expect(&format!( + "{} requires an rpc or firehose endpoint defined", + chain_id + )); + let substreams_endpoints = networks.substreams_endpoints(chain_id.clone()); + + blockchain_map.insert::( + chain_id.clone(), + Arc::new(graph_chain_substreams::Chain::new( + logger_factory.clone(), + substreams_endpoints, + metrics_registry.clone(), + chain_store, + Arc::new(graph_chain_substreams::BlockStreamBuilder::new()), + )), + ); + } } #[cfg(test)] mod test { - use crate::chain::create_all_ethereum_networks; - use crate::config::{Config, Opt}; + use crate::config::{AdapterConfiguration, Config, Opt}; + use diesel::Identifiable; + use graph::components::adapter::{ChainId, MockIdentValidator}; use graph::endpoint::EndpointMetrics; use graph::log::logger; use graph::prelude::{tokio, MetricsRegistry}; - use graph::prometheus::Registry; use graph_chain_ethereum::NodeCapabilities; use std::sync::Arc; @@ -570,17 +630,18 @@ mod test { let metrics = Arc::new(EndpointMetrics::mock()); let config = Config::load(&logger, &opt).expect("can create config"); - let prometheus_registry = Arc::new(Registry::new()); - let metrics_registry = Arc::new(MetricsRegistry::new( - logger.clone(), - prometheus_registry.clone(), - )); - - let ethereum_networks = - create_all_ethereum_networks(logger, metrics_registry, &config, metrics) - .await - .expect("Correctly parse Ethereum network args"); - let mut network_names = ethereum_networks.networks.keys().collect::>(); + let metrics_registry = Arc::new(MetricsRegistry::mock()); + let ident_validator = Arc::new(MockIdentValidator); + + let networks = config + .networks(logger, metrics_registry, metrics, ident_validator) + .await + .expect("can parse config"); + let mut network_names = networks + .adapters + .iter() + .map(|a| a.chain_id()) + .collect::>(); network_names.sort(); let traces = NodeCapabilities { @@ -592,45 +653,26 @@ mod test { traces: false, }; - let has_mainnet_with_traces = ethereum_networks - .adapter_with_capabilities("mainnet".to_string(), &traces) - .is_ok(); - let has_goerli_with_archive = ethereum_networks - .adapter_with_capabilities("goerli".to_string(), &archive) - .is_ok(); - let has_mainnet_with_archive = ethereum_networks - .adapter_with_capabilities("mainnet".to_string(), &archive) - .is_ok(); - let has_goerli_with_traces = ethereum_networks - .adapter_with_capabilities("goerli".to_string(), &traces) - .is_ok(); - - assert_eq!(has_mainnet_with_traces, true); - assert_eq!(has_goerli_with_archive, true); - assert_eq!(has_mainnet_with_archive, false); - assert_eq!(has_goerli_with_traces, false); - - let goerli_capability = ethereum_networks - .networks - .get("goerli") - .unwrap() + let mainnet: Vec<&AdapterConfiguration> = networks .adapters - .first() - .unwrap() - .capabilities; - let mainnet_capability = ethereum_networks - .networks - .get("mainnet") - .unwrap() + .iter() + .filter(|a| a.chain_id().as_str().eq("mainnet")) + .collect(); + assert_eq!(mainnet.len(), 1); + let mainnet = mainnet.first().unwrap().as_rpc().unwrap(); + assert_eq!(mainnet.adapters.len(), 1); + let mainnet = mainnet.adapters.first().unwrap(); + assert_eq!(mainnet.capabilities, traces); + + let goerli: Vec<&AdapterConfiguration> = networks .adapters - .first() - .unwrap() - .capabilities; - assert_eq!( - network_names, - vec![&"goerli".to_string(), &"mainnet".to_string()] - ); - assert_eq!(goerli_capability, archive); - assert_eq!(mainnet_capability, traces); + .iter() + .filter(|a| a.chain_id().as_str().eq("goerli")) + .collect(); + assert_eq!(goerli.len(), 1); + let goerli = goerli.first().unwrap().as_rpc().unwrap(); + assert_eq!(goerli.adapters.len(), 1); + let goerli = goerli.adapters.first().unwrap(); + assert_eq!(goerli.capabilities, archive); } } diff --git a/node/src/config.rs b/node/src/config.rs index 6fb0135d99e..e7f81d58770 100644 --- a/node/src/config.rs +++ b/node/src/config.rs @@ -1,9 +1,20 @@ +use ethereum::{ + network::{EthereumNetworkAdapter, EthereumNetworkAdapters}, + BlockIngestor, +}; use graph::{ - anyhow::Error, - blockchain::BlockchainKind, - env::ENV_VARS, - firehose::{SubgraphLimit, SUBGRAPHS_PER_CONN}, + anyhow::{self, Error}, + blockchain::{Blockchain, BlockchainKind, BlockchainMap}, + cheap_clone::CheapClone, + components::{ + adapter::{ChainId, IdentValidator, MockIdentValidator, ProviderManager}, + metrics::MetricsRegistry, + }, + endpoint::EndpointMetrics, + env::{EnvVars, ENV_VARS}, + firehose::{FirehoseEndpoint, FirehoseEndpoints, SubgraphLimit, SUBGRAPHS_PER_CONN}, itertools::Itertools, + log::factory::LoggerFactory, prelude::{ anyhow::{anyhow, bail, Context, Result}, info, @@ -14,18 +25,28 @@ use graph::{ }, serde_json, serde_regex, toml, Logger, NodeId, StoreError, }, + slog::{o, Discard}, }; use graph_chain_ethereum::{self as ethereum, NodeCapabilities}; -use graph_store_postgres::{DeploymentPlacer, Shard as ShardName, PRIMARY_SHARD}; +use graph_store_postgres::{ + ChainHeadUpdateListener, DeploymentPlacer, Shard as ShardName, Store, PRIMARY_SHARD, +}; use graph::http::{HeaderMap, Uri}; use std::{ + any::Any, collections::{BTreeMap, BTreeSet}, fmt, + sync::Arc, }; use std::{fs::read_to_string, time::Duration}; use url::Url; +use crate::chain::{ + create_all_ethereum_networks, create_firehose_networks, create_substreams_networks, + networks_as_chains, +}; + const ANY_NAME: &str = ".*"; /// A regular expression that matches nothing const NO_NAME: &str = ".^"; @@ -100,7 +121,312 @@ fn validate_name(s: &str) -> Result<()> { Ok(()) } +#[derive(Debug, Clone)] +pub struct EthAdapterConfig { + pub chain_id: ChainId, + pub adapters: Vec, + pub call_only: Vec, + // polling interval is set per chain so if set all adapter configuration will have + // the same value. + pub polling_interval: Option, +} + +#[derive(Debug, Clone)] +pub struct FirehoseAdapterConfig { + pub chain_id: ChainId, + pub kind: BlockchainKind, + pub adapters: Vec>, +} + +#[derive(Debug, Clone)] +pub enum AdapterConfiguration { + Rpc(EthAdapterConfig), + Firehose(FirehoseAdapterConfig), + Substreams(FirehoseAdapterConfig), +} + +impl AdapterConfiguration { + pub fn kind(&self) -> &BlockchainKind { + match self { + AdapterConfiguration::Rpc(_) => &BlockchainKind::Ethereum, + AdapterConfiguration::Firehose(fh) | AdapterConfiguration::Substreams(fh) => &fh.kind, + } + } + pub fn chain_id(&self) -> &ChainId { + match self { + AdapterConfiguration::Rpc(EthAdapterConfig { chain_id, .. }) + | AdapterConfiguration::Firehose(FirehoseAdapterConfig { chain_id, .. }) + | AdapterConfiguration::Substreams(FirehoseAdapterConfig { chain_id, .. }) => chain_id, + } + } + + pub fn as_rpc(&self) -> Option<&EthAdapterConfig> { + match self { + AdapterConfiguration::Rpc(rpc) => Some(rpc), + _ => None, + } + } + + pub fn as_firehose(&self) -> Option<&FirehoseAdapterConfig> { + match self { + AdapterConfiguration::Firehose(fh) => Some(fh), + _ => None, + } + } + + pub fn as_substreams(&self) -> Option<&FirehoseAdapterConfig> { + match self { + AdapterConfiguration::Substreams(fh) => Some(fh), + _ => None, + } + } +} + +pub struct Networks { + pub adapters: Vec, + rpc_provider_manager: ProviderManager, + firehose_provider_manager: ProviderManager>, + substreams_provider_manager: ProviderManager>, +} + +impl Networks { + // noop is important for query_nodes as it shortcuts a lot of the process. + fn noop() -> Self { + Self { + adapters: vec![], + rpc_provider_manager: ProviderManager::new( + Logger::root(Discard, o!()), + vec![].into_iter(), + Arc::new(MockIdentValidator), + ), + firehose_provider_manager: ProviderManager::new( + Logger::root(Discard, o!()), + vec![].into_iter(), + Arc::new(MockIdentValidator), + ), + substreams_provider_manager: ProviderManager::new( + Logger::root(Discard, o!()), + vec![].into_iter(), + Arc::new(MockIdentValidator), + ), + } + } + + fn new( + logger: &Logger, + adapters: Vec, + validator: Arc, + ) -> Self { + let adapters2 = adapters.clone(); + let eth_adapters = adapters.iter().flat_map(|a| a.as_rpc()).cloned().map( + |EthAdapterConfig { + chain_id, + adapters, + call_only: _, + polling_interval: _, + }| { (chain_id, adapters) }, + ); + let firehose_adapters = adapters + .iter() + .flat_map(|a| a.as_firehose()) + .cloned() + .map( + |FirehoseAdapterConfig { + chain_id, + kind: _, + adapters, + }| { (chain_id, adapters) }, + ) + .collect_vec(); + + let substreams_adapters = adapters + .iter() + .flat_map(|a| a.as_substreams()) + .cloned() + .map( + |FirehoseAdapterConfig { + chain_id, + kind: _, + adapters, + }| { (chain_id, adapters) }, + ) + .collect_vec(); + + Self { + adapters: adapters2, + rpc_provider_manager: ProviderManager::new( + logger.clone(), + eth_adapters, + validator.cheap_clone(), + ), + firehose_provider_manager: ProviderManager::new( + logger.clone(), + firehose_adapters + .into_iter() + .map(|(chain_id, endpoints)| (chain_id, endpoints)), + validator.cheap_clone(), + ), + substreams_provider_manager: ProviderManager::new( + logger.clone(), + substreams_adapters + .into_iter() + .map(|(chain_id, endpoints)| (chain_id, endpoints)), + validator.cheap_clone(), + ), + } + } + + pub async fn block_ingestors( + logger: &Logger, + blockchain_map: &Arc, + ) -> anyhow::Result>> { + async fn block_ingestor( + logger: &Logger, + chain_id: &ChainId, + chain: &Arc, + ingestors: &mut Vec>, + ) -> anyhow::Result<()> { + let chain: Arc = chain.cheap_clone().downcast().map_err(|_| { + anyhow!("unable to downcast, wrong type for blockchain {}", C::KIND) + })?; + + let logger = logger.new(o!("network_name" => chain_id.to_string())); + + match chain.block_ingestor().await { + Ok(ingestor) => { + info!(&logger, "Started block ingestor"); + ingestors.push(ingestor) + } + Err(err) => graph::slog::error!( + &logger, + "unable to start block_ingestor for {}: {}", + chain_id, + err.to_string() + ), + } + + Ok(()) + } + + let mut res = vec![]; + for ((kind, id), chain) in blockchain_map.0.iter() { + match kind { + BlockchainKind::Arweave => { + block_ingestor::(logger, id, chain, &mut res) + .await? + } + BlockchainKind::Ethereum => { + block_ingestor::(logger, id, chain, &mut res) + .await? + } + BlockchainKind::Near => { + block_ingestor::(logger, id, chain, &mut res).await? + } + BlockchainKind::Cosmos => { + block_ingestor::(logger, id, chain, &mut res).await? + } + BlockchainKind::Substreams => { + block_ingestor::(logger, id, chain, &mut res) + .await? + } + BlockchainKind::Starknet => { + block_ingestor::(logger, id, chain, &mut res) + .await? + } + } + } + + Ok(res) + } + + pub fn blockchain_map( + &self, + config: &Arc, + node_id: &NodeId, + logger: &Logger, + networks: &Networks, + store: &Store, + logger_factory: &LoggerFactory, + metrics_registry: Arc, + chain_head_update_listener: Arc, + ) -> BlockchainMap { + let mut bm = BlockchainMap::new(); + + networks_as_chains( + config, + &mut bm, + node_id, + logger, + networks, + store, + logger_factory, + metrics_registry, + chain_head_update_listener, + ); + + bm + } + + pub fn firehose_endpoints(&self, chain_id: ChainId) -> FirehoseEndpoints { + FirehoseEndpoints::new(chain_id, self.firehose_provider_manager.cheap_clone()) + } + + pub fn substreams_endpoints(&self, chain_id: ChainId) -> FirehoseEndpoints { + FirehoseEndpoints::new(chain_id, self.substreams_provider_manager.cheap_clone()) + } + + pub fn ethereum_rpcs(&self, chain_id: ChainId) -> EthereumNetworkAdapters { + let eth_adapters = self + .adapters + .iter() + .filter(|a| a.chain_id().eq(&chain_id)) + .flat_map(|a| a.as_rpc()) + .flat_map(|eth_c| eth_c.call_only.clone()) + .collect_vec(); + + EthereumNetworkAdapters::new( + chain_id, + self.rpc_provider_manager.cheap_clone(), + eth_adapters, + None, + ) + } +} + impl Config { + pub async fn networks( + &self, + logger: Logger, + registry: Arc, + endpoint_metrics: Arc, + store: Arc, + ) -> Result { + if self.query_only(&self.node) { + return Ok(Networks::noop()); + } + + let eth = create_all_ethereum_networks( + logger.cheap_clone(), + registry, + &self, + endpoint_metrics.cheap_clone(), + ) + .await?; + let firehose = + create_firehose_networks(logger.cheap_clone(), &self, endpoint_metrics.cheap_clone()); + let substreams = create_substreams_networks(logger.cheap_clone(), &self, endpoint_metrics); + let adapters = eth + .into_iter() + .chain(firehose.into_iter()) + .chain(substreams.into_iter()) + .collect(); + + Ok(Networks::new(&logger, adapters, store)) + } + pub fn chain_ids(&self) -> Vec { + unimplemented!() + } + /// Check that the config is valid. fn validate(&mut self) -> Result<()> { if !self.stores.contains_key(PRIMARY_SHARD.as_str()) { diff --git a/node/src/main.rs b/node/src/main.rs index 28a637ea4c1..8734b96d520 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -1,48 +1,28 @@ use clap::Parser as _; -use ethereum::chain::{ - EthereumAdapterSelector, EthereumBlockRefetcher, EthereumRuntimeAdapterBuilder, - EthereumStreamBuilder, -}; -use ethereum::{BlockIngestor, EthereumNetworks}; use git_testament::{git_testament, render_testament}; -use graph::blockchain::client::ChainClient; +use graph::components::adapter::IdentValidator; use graph::futures01::Future as _; use graph::futures03::compat::Future01CompatExt; use graph::futures03::future::TryFutureExt; -use graph_chain_ethereum::codec::HeaderOnlyBlock; -use graph::blockchain::{ - BasicBlockchainBuilder, Blockchain, BlockchainBuilder, BlockchainKind, BlockchainMap, - ChainIdentifier, -}; +use graph::blockchain::{Blockchain, BlockchainKind}; use graph::components::link_resolver::{ArweaveClient, FileSizeLimit}; -use graph::components::store::BlockStore; use graph::components::subgraph::Settings; use graph::data::graphql::load_manager::LoadManager; use graph::endpoint::EndpointMetrics; use graph::env::EnvVars; -use graph::firehose::{FirehoseEndpoints, FirehoseNetworks}; use graph::log::logger; use graph::prelude::*; use graph::prometheus::Registry; use graph::url::Url; -use graph_chain_arweave::{self as arweave, Block as ArweaveBlock}; -use graph_chain_cosmos::{self as cosmos, Block as CosmosFirehoseBlock}; -use graph_chain_ethereum as ethereum; -use graph_chain_near::{self as near, HeaderOnlyBlock as NearFirehoseHeaderOnlyBlock}; -use graph_chain_starknet::{self as starknet, Block as StarknetBlock}; -use graph_chain_substreams as substreams; use graph_core::polling_monitor::{arweave_service, ipfs_service}; use graph_core::{ SubgraphAssignmentProvider as IpfsSubgraphAssignmentProvider, SubgraphInstanceManager, SubgraphRegistrar as IpfsSubgraphRegistrar, }; use graph_graphql::prelude::GraphQlRunner; -use graph_node::chain::{ - connect_ethereum_networks, connect_firehose_networks, create_all_ethereum_networks, - create_firehose_networks, create_ipfs_clients, create_substreams_networks, -}; -use graph_node::config::Config; +use graph_node::chain::create_ipfs_clients; +use graph_node::config::{Config, Networks}; use graph_node::opt; use graph_node::store_builder::StoreBuilder; use graph_server_http::GraphQLServer as GraphQLQueryServer; @@ -50,9 +30,7 @@ use graph_server_index_node::IndexNodeServer; use graph_server_json_rpc::JsonRpcServer; use graph_server_metrics::PrometheusMetricsServer; use graph_server_websocket::SubscriptionServer as GraphQLSubscriptionServer; -use graph_store_postgres::{register_jobs as register_store_jobs, ChainHeadUpdateListener, Store}; -use std::collections::BTreeMap; -use std::collections::HashMap; +use graph_store_postgres::register_jobs as register_store_jobs; use std::io::{BufRead, BufReader}; use std::path::Path; use std::time::Duration; @@ -98,24 +76,6 @@ fn read_expensive_queries( Ok(queries) } -macro_rules! collect_ingestors { - ($acc:ident, $logger:ident, $($chain:ident),+) => { - $( - $chain.iter().for_each(|(network_name, chain)| { - let logger = $logger.new(o!("network_name" => network_name.clone())); - match chain.block_ingestor() { - Ok(ingestor) =>{ - info!(logger, "Started block ingestor"); - $acc.push(ingestor); - } - Err(err) => error!(&logger, - "Failed to create block ingestor {}",err), - } - }); - )+ - }; -} - #[tokio::main] async fn main() { env_logger::init(); @@ -173,7 +133,6 @@ async fn main() { let node_id = NodeId::new(opt.node_id.clone()) .expect("Node ID must be between 1 and 63 characters in length"); - let query_only = config.query_only(&node_id); // Obtain subgraph related command-line arguments let subgraph = opt.subgraph.clone(); @@ -274,33 +233,6 @@ async fn main() { metrics_registry.cheap_clone(), )); - // Ethereum clients; query nodes ignore all ethereum clients and never - // connect to them directly - let eth_networks = if query_only { - EthereumNetworks::new(endpoint_metrics.cheap_clone()) - } else { - create_all_ethereum_networks( - logger.clone(), - metrics_registry.clone(), - &config, - endpoint_metrics.cheap_clone(), - ) - .await - .expect("Failed to parse Ethereum networks") - }; - - let mut firehose_networks_by_kind = if query_only { - BTreeMap::new() - } else { - create_firehose_networks(logger.clone(), &config, endpoint_metrics.cheap_clone()) - }; - - let mut substreams_networks_by_kind = if query_only { - BTreeMap::new() - } else { - create_substreams_networks(logger.clone(), &config, endpoint_metrics.clone()) - }; - let graphql_metrics_registry = metrics_registry.clone(); let contention_logger = logger.clone(); @@ -323,185 +255,55 @@ async fn main() { let chain_head_update_listener = store_builder.chain_head_update_listener(); let primary_pool = store_builder.primary_pool(); - // To support the ethereum block ingestor, ethereum networks are referenced both by the - // `blockchain_map` and `ethereum_chains`. Future chains should be referred to only in - // `blockchain_map`. - let mut blockchain_map = BlockchainMap::new(); - - // Unwraps: `connect_ethereum_networks` and `connect_firehose_networks` only fail if - // mismatching chain identifiers are returned for a same network, which indicates a serious - // inconsistency between providers. - let (arweave_networks, arweave_idents) = connect_firehose_networks::( - &logger, - firehose_networks_by_kind - .remove(&BlockchainKind::Arweave) - .unwrap_or_else(FirehoseNetworks::new), - ) - .await - .unwrap(); - - // This only has idents for chains with rpc adapters. - let (eth_networks, ethereum_idents) = connect_ethereum_networks(&logger, eth_networks) - .await - .unwrap(); - - let (eth_firehose_only_networks, eth_firehose_only_idents) = - connect_firehose_networks::( - &logger, - firehose_networks_by_kind - .remove(&BlockchainKind::Ethereum) - .unwrap_or_else(FirehoseNetworks::new), + let network_store = store_builder.network_store(config.chain_ids()); + let validator: Arc = network_store.block_store(); + let network_adapters = config + .networks( + logger.cheap_clone(), + metrics_registry.cheap_clone(), + endpoint_metrics, + validator, ) .await - .unwrap(); - - let (near_networks, near_idents) = - connect_firehose_networks::( - &logger, - firehose_networks_by_kind - .remove(&BlockchainKind::Near) - .unwrap_or_else(FirehoseNetworks::new), - ) - .await - .unwrap(); - - let (cosmos_networks, cosmos_idents) = connect_firehose_networks::( - &logger, - firehose_networks_by_kind - .remove(&BlockchainKind::Cosmos) - .unwrap_or_else(FirehoseNetworks::new), - ) - .await - .unwrap(); - - let substreams_networks = substreams_networks_by_kind - .remove(&BlockchainKind::Substreams) - .unwrap_or_else(FirehoseNetworks::new); + .expect("unable to parse network configuration"); - let (starknet_networks, starknet_idents) = connect_firehose_networks::( - &logger, - firehose_networks_by_kind - .remove(&BlockchainKind::Starknet) - .unwrap_or_else(FirehoseNetworks::new), - ) - .await - .unwrap(); - - let substream_idents = substreams_networks - .networks - .keys() - .map(|name| { - ( - name.clone(), - ChainIdentifier { - net_version: name.to_string(), - genesis_block_hash: BlockHash::default(), - }, - ) - }) - .collect::>(); - - // Note that both `eth_firehose_only_idents` and `ethereum_idents` contain Ethereum - // networks. If the same network is configured in both RPC and Firehose, the RPC ident takes - // precedence. This is necessary because Firehose endpoints currently have no `net_version`. - // See also: firehose-no-net-version. - let mut network_identifiers = eth_firehose_only_idents; - network_identifiers.extend(ethereum_idents); - network_identifiers.extend(arweave_idents); - network_identifiers.extend(near_idents); - network_identifiers.extend(cosmos_idents); - network_identifiers.extend(substream_idents); - network_identifiers.extend(starknet_idents); - - let network_store = store_builder.network_store(network_identifiers); - - let arweave_chains = networks_as_chains::( + let blockchain_map = network_adapters.blockchain_map( &env_vars, - &mut blockchain_map, + &node_id, &logger, - &arweave_networks, - substreams_networks_by_kind.get(&BlockchainKind::Arweave), - network_store.as_ref(), + &network_adapters, + &network_store, &logger_factory, - metrics_registry.clone(), - ); - - let eth_firehose_only_networks = if eth_firehose_only_networks.networks.len() == 0 { - None - } else { - Some(ð_firehose_only_networks) - }; - - if !opt.disable_block_ingestor && eth_networks.networks.len() != 0 { - let eth_network_names = Vec::from_iter(eth_networks.networks.keys()); - let fh_only = match eth_firehose_only_networks { - Some(firehose_only) => Some(Vec::from_iter(firehose_only.networks.keys())), - None => None, - }; - network_store - .block_store() - .cleanup_ethereum_shallow_blocks(eth_network_names, fh_only) - .unwrap(); - } - - let ethereum_chains = ethereum_networks_as_chains( - &mut blockchain_map, - &logger, - &config, - node_id.clone(), - metrics_registry.clone(), - eth_firehose_only_networks, - substreams_networks_by_kind.get(&BlockchainKind::Ethereum), - ð_networks, - network_store.as_ref(), + metrics_registry.cheap_clone(), chain_head_update_listener, - &logger_factory, - metrics_registry.clone(), ); - let near_chains = networks_as_chains::( - &env_vars, - &mut blockchain_map, - &logger, - &near_networks, - substreams_networks_by_kind.get(&BlockchainKind::Near), - network_store.as_ref(), - &logger_factory, - metrics_registry.clone(), - ); - - let cosmos_chains = networks_as_chains::( - &env_vars, - &mut blockchain_map, - &logger, - &cosmos_networks, - substreams_networks_by_kind.get(&BlockchainKind::Cosmos), - network_store.as_ref(), - &logger_factory, - metrics_registry.clone(), - ); - - let substreams_chains = networks_as_chains::( - &env_vars, - &mut blockchain_map, - &logger, - &substreams_networks, - None, - network_store.as_ref(), - &logger_factory, - metrics_registry.clone(), - ); - - let starknet_chains = networks_as_chains::( - &env_vars, - &mut blockchain_map, - &logger, - &starknet_networks, - substreams_networks_by_kind.get(&BlockchainKind::Starknet), - network_store.as_ref(), - &logger_factory, - metrics_registry.clone(), - ); + // see comment on cleanup_ethereum_shallow_blocks + if !opt.disable_block_ingestor { + match blockchain_map + .get_all_by_kind::(BlockchainKind::Ethereum) + .ok() + .map(|chains| { + chains + .iter() + .flat_map(|c| { + if !c.chain_client().is_firehose() { + Some(c.name.to_string()) + } else { + None + } + }) + .collect() + }) { + Some(eth_network_names) => { + network_store + .block_store() + .cleanup_ethereum_shallow_blocks(eth_network_names) + .unwrap(); + } + None => todo!(), + } + } let blockchain_map = Arc::new(blockchain_map); @@ -532,21 +334,13 @@ async fn main() { if !opt.disable_block_ingestor { let logger = logger.clone(); - let mut ingestors: Vec> = vec![]; - collect_ingestors!( - ingestors, - logger, - ethereum_chains, - arweave_chains, - near_chains, - cosmos_chains, - substreams_chains, - starknet_chains - ); + let ingestors = Networks::block_ingestors(&logger, &blockchain_map) + .await + .expect("unable to start block ingestors"); ingestors.into_iter().for_each(|ingestor| { let logger = logger.clone(); - info!(logger,"Starting block ingestor for network";"network_name" => &ingestor.network_name()); + info!(logger,"Starting block ingestor for network";"network_name" => &ingestor.network_name().as_str()); graph::spawn(ingestor.run()); }); @@ -727,186 +521,3 @@ async fn main() { graph::futures03::future::pending::<()>().await; } - -/// Return the hashmap of chains and also add them to `blockchain_map`. -fn networks_as_chains( - config: &Arc, - blockchain_map: &mut BlockchainMap, - logger: &Logger, - firehose_networks: &FirehoseNetworks, - substreams_networks: Option<&FirehoseNetworks>, - store: &Store, - logger_factory: &LoggerFactory, - metrics_registry: Arc, -) -> HashMap> -where - C: Blockchain, - BasicBlockchainBuilder: BlockchainBuilder, -{ - let chains: Vec<_> = firehose_networks - .networks - .iter() - .filter_map(|(chain_id, endpoints)| { - store - .block_store() - .chain_store(chain_id) - .map(|chain_store| (chain_id, chain_store, endpoints)) - .or_else(|| { - error!( - logger, - "No store configured for {} chain {}; ignoring this chain", - C::KIND, - chain_id - ); - None - }) - }) - .map(|(chain_id, chain_store, endpoints)| { - ( - chain_id.clone(), - Arc::new( - BasicBlockchainBuilder { - logger_factory: logger_factory.clone(), - name: chain_id.clone(), - chain_store, - firehose_endpoints: endpoints.clone(), - metrics_registry: metrics_registry.clone(), - } - .build(config), - ), - ) - }) - .collect(); - - for (chain_id, chain) in chains.iter() { - blockchain_map.insert::(chain_id.clone(), chain.clone()) - } - - if let Some(substreams_networks) = substreams_networks { - for (network_name, firehose_endpoints) in substreams_networks.networks.iter() { - let chain_store = blockchain_map - .get::(network_name.clone()) - .expect(&format!( - "{} requires an rpc or firehose endpoint defined", - network_name - )) - .chain_store(); - - blockchain_map.insert::( - network_name.clone(), - Arc::new(substreams::Chain::new( - logger_factory.clone(), - firehose_endpoints.clone(), - metrics_registry.clone(), - chain_store, - Arc::new(substreams::BlockStreamBuilder::new()), - )), - ); - } - } - - HashMap::from_iter(chains) -} - -/// Return the hashmap of ethereum chains and also add them to `blockchain_map`. -fn ethereum_networks_as_chains( - blockchain_map: &mut BlockchainMap, - logger: &Logger, - config: &Config, - node_id: NodeId, - registry: Arc, - firehose_networks: Option<&FirehoseNetworks>, - substreams_networks: Option<&FirehoseNetworks>, - eth_networks: &EthereumNetworks, - store: &Store, - chain_head_update_listener: Arc, - logger_factory: &LoggerFactory, - metrics_registry: Arc, -) -> HashMap> { - let chains: Vec<_> = eth_networks - .networks - .iter() - .filter_map(|(network_name, eth_adapters)| { - store - .block_store() - .chain_store(network_name) - .map(|chain_store| { - let is_ingestible = chain_store.is_ingestible(); - (network_name, eth_adapters, chain_store, is_ingestible) - }) - .or_else(|| { - error!( - logger, - "No store configured for Ethereum chain {}; ignoring this chain", - network_name - ); - None - }) - }) - .map(|(network_name, eth_adapters, chain_store, is_ingestible)| { - let firehose_endpoints = firehose_networks - .and_then(|v| v.networks.get(network_name)) - .map_or_else(FirehoseEndpoints::new, |v| v.clone()); - - let client = Arc::new(ChainClient::::new( - firehose_endpoints, - eth_adapters.clone(), - )); - let adapter_selector = EthereumAdapterSelector::new( - logger_factory.clone(), - client.clone(), - registry.clone(), - chain_store.clone(), - ); - - let call_cache = chain_store.cheap_clone(); - - let chain_config = config.chains.chains.get(network_name).unwrap(); - let chain = ethereum::Chain::new( - logger_factory.clone(), - network_name.clone(), - node_id.clone(), - registry.clone(), - chain_store.cheap_clone(), - call_cache, - client, - chain_head_update_listener.clone(), - Arc::new(EthereumStreamBuilder {}), - Arc::new(EthereumBlockRefetcher {}), - Arc::new(adapter_selector), - Arc::new(EthereumRuntimeAdapterBuilder {}), - Arc::new(eth_adapters.clone()), - ENV_VARS.reorg_threshold, - chain_config.polling_interval, - is_ingestible, - ); - (network_name.clone(), Arc::new(chain)) - }) - .collect(); - - for (network_name, chain) in chains.iter().cloned() { - blockchain_map.insert::(network_name, chain) - } - - if let Some(substreams_networks) = substreams_networks { - for (network_name, firehose_endpoints) in substreams_networks.networks.iter() { - let chain_store = blockchain_map - .get::(network_name.clone()) - .expect("any substreams endpoint needs an rpc or firehose chain defined") - .chain_store(); - - blockchain_map.insert::( - network_name.clone(), - Arc::new(substreams::Chain::new( - logger_factory.clone(), - firehose_endpoints.clone(), - metrics_registry.clone(), - chain_store, - Arc::new(substreams::BlockStreamBuilder::new()), - )), - ); - } - } - - HashMap::from_iter(chains) -} diff --git a/node/src/manager/commands/chain.rs b/node/src/manager/commands/chain.rs index 98493c29d6e..4830e129c89 100644 --- a/node/src/manager/commands/chain.rs +++ b/node/src/manager/commands/chain.rs @@ -194,7 +194,7 @@ pub fn change_block_cache_shard( // Create a new chain with the name in the destination shard - let _= add_chain(conn, &chain_name, &ident, &shard)?; + let _ = add_chain(conn, &chain_name, &shard)?; // Re-add the foreign key constraint sql_query( diff --git a/node/src/manager/commands/config.rs b/node/src/manager/commands/config.rs index 7f595e97e5d..52da5bc9012 100644 --- a/node/src/manager/commands/config.rs +++ b/node/src/manager/commands/config.rs @@ -2,7 +2,10 @@ use std::{collections::BTreeMap, sync::Arc}; use graph::{ anyhow::{bail, Context}, - components::subgraph::{Setting, Settings}, + components::{ + adapter::{ChainId, MockIdentValidator}, + subgraph::{Setting, Settings}, + }, endpoint::EndpointMetrics, env::EnvVars, itertools::Itertools, @@ -12,10 +15,10 @@ use graph::{ }, slog::Logger, }; -use graph_chain_ethereum::{NodeCapabilities, ProviderEthRpcMetrics}; +use graph_chain_ethereum::NodeCapabilities; use graph_store_postgres::DeploymentPlacer; -use crate::{chain::create_ethereum_networks_for_chain, config::Config}; +use crate::config::Config; pub fn place(placer: &dyn DeploymentPlacer, name: &str, network: &str) -> Result<(), Error> { match placer.place(name, network).map_err(|s| anyhow!(s))? { @@ -138,15 +141,13 @@ pub async fn provider( let metrics = Arc::new(EndpointMetrics::mock()); let caps = caps_from_features(features)?; - let eth_rpc_metrics = Arc::new(ProviderEthRpcMetrics::new(registry)); - let networks = - create_ethereum_networks_for_chain(&logger, eth_rpc_metrics, config, &network, metrics) - .await?; - let adapters = networks - .networks - .get(&network) - .ok_or_else(|| anyhow!("unknown network {}", network))?; - let adapters = adapters.all_cheapest_with(&caps); + let networks = config + .networks(logger, registry, metrics, Arc::new(MockIdentValidator)) + .await?; + let network: ChainId = network.into(); + let adapters = networks.ethereum_rpcs(network.clone()); + + let adapters = adapters.all_cheapest_with(&caps).await; println!( "deploy on network {} with features [{}] on node {}\neligible providers: {}", network, diff --git a/node/src/manager/commands/run.rs b/node/src/manager/commands/run.rs index 639b5c0e3d9..91318d5d99c 100644 --- a/node/src/manager/commands/run.rs +++ b/node/src/manager/commands/run.rs @@ -3,8 +3,7 @@ use std::sync::Arc; use std::time::Duration; use crate::chain::{ - connect_ethereum_networks, create_ethereum_networks_for_chain, create_firehose_networks, - create_ipfs_clients, + create_ethereum_networks_for_chain, create_firehose_networks, create_ipfs_clients, }; use crate::config::Config; use crate::manager::PanicSubscriptionManager; @@ -19,9 +18,11 @@ use graph::anyhow::{bail, format_err}; use graph::blockchain::client::ChainClient; use graph::blockchain::{BlockchainKind, BlockchainMap}; use graph::cheap_clone::CheapClone; +use graph::components::adapter::IdentValidator; use graph::components::link_resolver::{ArweaveClient, FileSizeLimit}; -use graph::components::store::{BlockStore as _, DeploymentLocator}; +use graph::components::store::{BlockStore as _, DeploymentLocator, Store}; use graph::components::subgraph::Settings; +use graph::data::value::Word; use graph::endpoint::EndpointMetrics; use graph::env::EnvVars; use graph::firehose::FirehoseEndpoints; @@ -100,34 +101,38 @@ pub async fn run( // possible temporary DNS failures, make the resolver retry let link_resolver = Arc::new(IpfsResolver::new(ipfs_clients, env_vars.cheap_clone())); + let network_store = store_builder.network_store( + config + .chain_ids() + .into_iter() + .map(|c| c.to_string()) + .collect(), + ); let eth_rpc_metrics = Arc::new(ProviderEthRpcMetrics::new(metrics_registry.clone())); - let eth_networks = create_ethereum_networks_for_chain( - &logger, - eth_rpc_metrics, - &config, - &network_name, - endpoint_metrics.cheap_clone(), - ) - .await - .expect("Failed to parse Ethereum networks"); - let firehose_networks_by_kind = - create_firehose_networks(logger.clone(), &config, endpoint_metrics); - let firehose_networks = firehose_networks_by_kind.get(&BlockchainKind::Ethereum); - let firehose_endpoints = firehose_networks - .and_then(|v| v.networks.get(&network_name)) - .map_or_else(FirehoseEndpoints::new, |v| v.clone()); - - let eth_adapters = match eth_networks.networks.get(&network_name) { - Some(adapters) => adapters.clone(), - None => { - return Err(format_err!( - "No ethereum adapters found, but required in this state of graphman run command" - )) - } - }; - - let eth_adapters2 = eth_adapters.clone(); - let (_, ethereum_idents) = connect_ethereum_networks(&logger, eth_networks).await?; + let ident_validator: Arc = network_store.block_store(); + let networks = config + .networks(logger, metrics_registry, endpoint_metrics, ident_validator) + .await + .expect("unable to parse network configuration"); + + // let firehose_networks_by_kind = + // create_firehose_networks(logger.clone(), &config, endpoint_metrics); + // let firehose_networks = firehose_networks_by_kind.get(&BlockchainKind::Ethereum); + // let firehose_endpoints = firehose_networks + // .and_then(|v| v.networks.get(&network_name)) + // .map_or_else(FirehoseEndpoints::new, |v| v.clone()); + + // let eth_adapters = match eth_networks.networks.get(&network_name) { + // Some(adapters) => adapters.clone(), + // None => { + // return Err(format_err!( + // "No ethereum adapters found, but required in this state of graphman run command" + // )) + // } + // }; + + // let eth_adapters2 = eth_adapters.clone(); + // let (_, ethereum_idents) = connect_ethereum_networks(&logger, eth_networks).await?; // let (near_networks, near_idents) = connect_firehose_networks::( // &logger, // firehose_networks_by_kind @@ -136,166 +141,168 @@ pub async fn run( // ) // .await; - let chain_head_update_listener = store_builder.chain_head_update_listener(); - let network_identifiers = ethereum_idents.into_iter().collect(); - let network_store = store_builder.network_store(network_identifiers); - - let subgraph_store = network_store.subgraph_store(); - let chain_store = network_store - .block_store() - .chain_store(network_name.as_ref()) - .unwrap_or_else(|| panic!("No chain store for {}", &network_name)); - - let client = Arc::new(ChainClient::new(firehose_endpoints, eth_adapters)); - - let call_cache = Arc::new(ethereum::BufferedCallCache::new(chain_store.cheap_clone())); - - let chain_config = config.chains.chains.get(&network_name).unwrap(); - let chain = ethereum::Chain::new( - logger_factory.clone(), - network_name.clone(), - node_id.clone(), - metrics_registry.clone(), - chain_store.cheap_clone(), - call_cache.cheap_clone(), - client.clone(), - chain_head_update_listener, - Arc::new(EthereumStreamBuilder {}), - Arc::new(EthereumBlockRefetcher {}), - Arc::new(EthereumAdapterSelector::new( - logger_factory.clone(), - client, - metrics_registry.clone(), - chain_store.cheap_clone(), - )), - Arc::new(EthereumRuntimeAdapterBuilder {}), - Arc::new(eth_adapters2), - graph::env::ENV_VARS.reorg_threshold, - chain_config.polling_interval, - // We assume the tested chain is always ingestible for now - true, - ); - - let mut blockchain_map = BlockchainMap::new(); - blockchain_map.insert(network_name.clone(), Arc::new(chain)); - - let static_filters = ENV_VARS.experimental_static_filters; - - let sg_metrics = Arc::new(SubgraphCountMetric::new(metrics_registry.clone())); - - let blockchain_map = Arc::new(blockchain_map); - let subgraph_instance_manager = SubgraphInstanceManager::new( - &logger_factory, - env_vars.cheap_clone(), - subgraph_store.clone(), - blockchain_map.clone(), - sg_metrics.cheap_clone(), - metrics_registry.clone(), - link_resolver.cheap_clone(), - ipfs_service, - arweave_service, - static_filters, - ); - - // Create IPFS-based subgraph provider - let subgraph_provider = Arc::new(IpfsSubgraphAssignmentProvider::new( - &logger_factory, - link_resolver.cheap_clone(), - subgraph_instance_manager, - sg_metrics, - )); - - let panicking_subscription_manager = Arc::new(PanicSubscriptionManager {}); - - let subgraph_registrar = Arc::new(IpfsSubgraphRegistrar::new( - &logger_factory, - link_resolver.cheap_clone(), - subgraph_provider.clone(), - subgraph_store.clone(), - panicking_subscription_manager, - blockchain_map, - node_id.clone(), - SubgraphVersionSwitchingMode::Instant, - Arc::new(Settings::default()), - )); - - let (name, hash) = if subgraph.contains(':') { - let mut split = subgraph.split(':'); - (split.next().unwrap(), split.next().unwrap().to_owned()) - } else { - ("cli", subgraph) - }; - - let subgraph_name = SubgraphName::new(name) - .expect("Subgraph name must contain only a-z, A-Z, 0-9, '-' and '_'"); - let subgraph_hash = - DeploymentHash::new(hash.clone()).expect("Subgraph hash must be a valid IPFS hash"); - - info!(&logger, "Creating subgraph {}", name); - let create_result = - SubgraphRegistrar::create_subgraph(subgraph_registrar.as_ref(), subgraph_name.clone()) - .await?; - - info!( - &logger, - "Looking up subgraph deployment {} (Deployment hash => {}, id => {})", - name, - subgraph_hash, - create_result.id, - ); - - SubgraphRegistrar::create_subgraph_version( - subgraph_registrar.as_ref(), - subgraph_name.clone(), - subgraph_hash.clone(), - node_id.clone(), - None, - None, - None, - None, - ) - .await?; - - let locator = locate(subgraph_store.as_ref(), &hash)?; - - SubgraphAssignmentProvider::start(subgraph_provider.as_ref(), locator, Some(stop_block)) - .await?; - - loop { - tokio::time::sleep(Duration::from_millis(1000)).await; - - let block_ptr = subgraph_store - .least_block_ptr(&subgraph_hash) - .await - .unwrap() - .unwrap(); - - debug!(&logger, "subgraph block: {:?}", block_ptr); - - if block_ptr.number >= stop_block { - info!( - &logger, - "subgraph now at block {}, reached stop block {}", block_ptr.number, stop_block - ); - break; - } - } - - info!(&logger, "Removing subgraph {}", name); - subgraph_store.clone().remove_subgraph(subgraph_name)?; - - if let Some(host) = metrics_ctx.prometheus_host { - let mfs = metrics_ctx.prometheus.gather(); - let job_name = match metrics_ctx.job_name { - Some(name) => name, - None => "graphman run".into(), - }; - - tokio::task::spawn_blocking(move || { - prometheus::push_metrics(&job_name, HashMap::new(), &host, mfs, None) - }) - .await??; - } + todo!(); + + // let chain_head_update_listener = store_builder.chain_head_update_listener(); + // let network_identifiers = ethereum_idents.into_iter().collect(); + // let network_store = store_builder.network_store(network_identifiers); + + // let subgraph_store = network_store.subgraph_store(); + // let chain_store = network_store + // .block_store() + // .chain_store(network_name.as_ref()) + // .unwrap_or_else(|| panic!("No chain store for {}", &network_name)); + + // let client = Arc::new(ChainClient::new(firehose_endpoints, eth_adapters)); + + // let call_cache = Arc::new(ethereum::BufferedCallCache::new(chain_store.cheap_clone())); + + // let chain_config = config.chains.chains.get(&network_name).unwrap(); + // let chain = ethereum::Chain::new( + // logger_factory.clone(), + // network_name.clone(), + // node_id.clone(), + // metrics_registry.clone(), + // chain_store.cheap_clone(), + // call_cache.cheap_clone(), + // client.clone(), + // chain_head_update_listener, + // Arc::new(EthereumStreamBuilder {}), + // Arc::new(EthereumBlockRefetcher {}), + // Arc::new(EthereumAdapterSelector::new( + // logger_factory.clone(), + // client, + // metrics_registry.clone(), + // chain_store.cheap_clone(), + // )), + // Arc::new(EthereumRuntimeAdapterBuilder {}), + // Arc::new(eth_adapters2), + // graph::env::ENV_VARS.reorg_threshold, + // chain_config.polling_interval, + // // We assume the tested chain is always ingestible for now + // true, + // ); + + // let mut blockchain_map = BlockchainMap::new(); + // blockchain_map.insert(network_name.clone(), Arc::new(chain)); + + // let static_filters = ENV_VARS.experimental_static_filters; + + // let sg_metrics = Arc::new(SubgraphCountMetric::new(metrics_registry.clone())); + + // let blockchain_map = Arc::new(blockchain_map); + // let subgraph_instance_manager = SubgraphInstanceManager::new( + // &logger_factory, + // env_vars.cheap_clone(), + // subgraph_store.clone(), + // blockchain_map.clone(), + // sg_metrics.cheap_clone(), + // metrics_registry.clone(), + // link_resolver.cheap_clone(), + // ipfs_service, + // arweave_service, + // static_filters, + // ); + + // // Create IPFS-based subgraph provider + // let subgraph_provider = Arc::new(IpfsSubgraphAssignmentProvider::new( + // &logger_factory, + // link_resolver.cheap_clone(), + // subgraph_instance_manager, + // sg_metrics, + // )); + + // let panicking_subscription_manager = Arc::new(PanicSubscriptionManager {}); + + // let subgraph_registrar = Arc::new(IpfsSubgraphRegistrar::new( + // &logger_factory, + // link_resolver.cheap_clone(), + // subgraph_provider.clone(), + // subgraph_store.clone(), + // panicking_subscription_manager, + // blockchain_map, + // node_id.clone(), + // SubgraphVersionSwitchingMode::Instant, + // Arc::new(Settings::default()), + // )); + + // let (name, hash) = if subgraph.contains(':') { + // let mut split = subgraph.split(':'); + // (split.next().unwrap(), split.next().unwrap().to_owned()) + // } else { + // ("cli", subgraph) + // }; + + // let subgraph_name = SubgraphName::new(name) + // .expect("Subgraph name must contain only a-z, A-Z, 0-9, '-' and '_'"); + // let subgraph_hash = + // DeploymentHash::new(hash.clone()).expect("Subgraph hash must be a valid IPFS hash"); + + // info!(&logger, "Creating subgraph {}", name); + // let create_result = + // SubgraphRegistrar::create_subgraph(subgraph_registrar.as_ref(), subgraph_name.clone()) + // .await?; + + // info!( + // &logger, + // "Looking up subgraph deployment {} (Deployment hash => {}, id => {})", + // name, + // subgraph_hash, + // create_result.id, + // ); + + // SubgraphRegistrar::create_subgraph_version( + // subgraph_registrar.as_ref(), + // subgraph_name.clone(), + // subgraph_hash.clone(), + // node_id.clone(), + // None, + // None, + // None, + // None, + // ) + // .await?; + + // let locator = locate(subgraph_store.as_ref(), &hash)?; + + // SubgraphAssignmentProvider::start(subgraph_provider.as_ref(), locator, Some(stop_block)) + // .await?; + + // loop { + // tokio::time::sleep(Duration::from_millis(1000)).await; + + // let block_ptr = subgraph_store + // .least_block_ptr(&subgraph_hash) + // .await + // .unwrap() + // .unwrap(); + + // debug!(&logger, "subgraph block: {:?}", block_ptr); + + // if block_ptr.number >= stop_block { + // info!( + // &logger, + // "subgraph now at block {}, reached stop block {}", block_ptr.number, stop_block + // ); + // break; + // } + // } + + // info!(&logger, "Removing subgraph {}", name); + // subgraph_store.clone().remove_subgraph(subgraph_name)?; + + // if let Some(host) = metrics_ctx.prometheus_host { + // let mfs = metrics_ctx.prometheus.gather(); + // let job_name = match metrics_ctx.job_name { + // Some(name) => name, + // None => "graphman run".into(), + // }; + + // tokio::task::spawn_blocking(move || { + // prometheus::push_metrics(&job_name, HashMap::new(), &host, mfs, None) + // }) + // .await??; + // } Ok(()) } diff --git a/node/src/store_builder.rs b/node/src/store_builder.rs index 6423e64b620..99062ae7c80 100644 --- a/node/src/store_builder.rs +++ b/node/src/store_builder.rs @@ -167,14 +167,14 @@ impl StoreBuilder { pools: HashMap, subgraph_store: Arc, chains: HashMap, - networks: BTreeMap, + networks: Vec, registry: Arc, ) -> Arc { let networks = networks .into_iter() - .map(|(name, idents)| { + .map(|name| { let shard = chains.get(&name).unwrap_or(&*PRIMARY_SHARD).clone(); - (name, idents, shard) + (name, shard) }) .collect(); @@ -281,13 +281,13 @@ impl StoreBuilder { /// Return a store that combines both a `Store` for subgraph data /// and a `BlockStore` for all chain related data - pub fn network_store(self, networks: BTreeMap) -> Arc { + pub fn network_store(self, networks: Vec>) -> Arc { Self::make_store( &self.logger, self.pools, self.subgraph_store, self.chains, - networks, + networks.into_iter().map(Into::into).collect(), self.registry, ) } diff --git a/server/index-node/src/resolver.rs b/server/index-node/src/resolver.rs index 3dd363db493..6d91de2e6b7 100644 --- a/server/index-node/src/resolver.rs +++ b/server/index-node/src/resolver.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::convert::TryInto; +use graph::components::adapter::ChainId; use graph::data::query::Trace; use graph::data::store::Id; use graph::schema::EntityType; @@ -267,7 +268,7 @@ impl IndexNodeResolver { let chain = if let Ok(c) = self .blockchain_map - .get::(network.clone()) + .get::(network.as_str().into()) { c } else { @@ -659,7 +660,7 @@ impl IndexNodeResolver { ) -> Result, QueryExecutionError> { macro_rules! try_resolve_for_chain { ( $typ:path ) => { - let blockchain = self.blockchain_map.get::<$typ>(network.to_string()).ok(); + let blockchain = self.blockchain_map.get::<$typ>(network.as_str().into()).ok(); if let Some(blockchain) = blockchain { debug!( diff --git a/store/postgres/src/block_store.rs b/store/postgres/src/block_store.rs index 9b98153efb0..d099264e3c6 100644 --- a/store/postgres/src/block_store.rs +++ b/store/postgres/src/block_store.rs @@ -12,7 +12,7 @@ use diesel::{ use graph::{ blockchain::ChainIdentifier, components::store::{BlockStore as BlockStoreTrait, QueryPermit}, - prelude::{error, info, warn, BlockNumber, BlockPtr, Logger, ENV_VARS}, + prelude::{error, info, BlockNumber, BlockPtr, Logger, ENV_VARS}, slog::o, }; use graph::{constraint_violation, prelude::CheapClone}; @@ -112,9 +112,9 @@ pub mod primary { pub fn add_chain( conn: &mut PooledConnection>, name: &str, - ident: &ChainIdentifier, shard: &Shard, ) -> Result { + let ident = ChainIdentifier::default(); // For tests, we want to have a chain that still uses the // shared `ethereum_blocks` table #[cfg(debug_assertions)] @@ -216,7 +216,7 @@ impl BlockStore { pub fn new( logger: Logger, // (network, ident, shard) - chains: Vec<(String, ChainIdentifier, Shard)>, + chains: Vec<(String, Shard)>, // shard -> pool pools: HashMap, sender: Arc, @@ -246,7 +246,7 @@ impl BlockStore { logger: &Logger, chain: &primary::Chain, shard: &Shard, - ident: &ChainIdentifier, + // ident: &ChainIdentifier, ) -> bool { if &chain.shard != shard { error!( @@ -258,43 +258,43 @@ impl BlockStore { ); return false; } - if chain.net_version != ident.net_version { - if chain.net_version == "0" { - warn!(logger, - "the net version for chain {} has changed from 0 to {} since the last time we ran, ignoring difference because 0 means UNSET and firehose does not provide it", - chain.name, - ident.net_version, - ) - } else { - error!(logger, - "the net version for chain {} has changed from {} to {} since the last time we ran", - chain.name, - chain.net_version, - ident.net_version - ); - return false; - } - } - if chain.genesis_block != ident.genesis_block_hash.hash_hex() { - error!(logger, - "the genesis block hash for chain {} has changed from {} to {} since the last time we ran", - chain.name, - chain.genesis_block, - ident.genesis_block_hash - ); - return false; - } + // if chain.net_version != ident.net_version { + // if chain.net_version == "0" { + // warn!(logger, + // "the net version for chain {} has changed from 0 to {} since the last time we ran, ignoring difference because 0 means UNSET and firehose does not provide it", + // chain.name, + // ident.net_version, + // ) + // } else { + // error!(logger, + // "the net version for chain {} has changed from {} to {} since the last time we ran", + // chain.name, + // chain.net_version, + // ident.net_version + // ); + // return false; + // } + // } + // if chain.genesis_block != ident.genesis_block_hash.hash_hex() { + // error!(logger, + // "the genesis block hash for chain {} has changed from {} to {} since the last time we ran", + // chain.name, + // chain.genesis_block, + // ident.genesis_block_hash + // ); + // return false; + // } true } // For each configured chain, add a chain store - for (chain_name, ident, shard) in chains { + for (chain_name, shard) in chains { match existing_chains .iter() .find(|chain| chain.name == chain_name) { Some(chain) => { - let status = if chain_ingestible(&block_store.logger, chain, &shard, &ident) { + let status = if chain_ingestible(&block_store.logger, chain, &shard) { ChainStatus::Ingestible } else { ChainStatus::ReadOnly @@ -303,7 +303,7 @@ impl BlockStore { } None => { let mut conn = block_store.mirror.primary().get()?; - let chain = primary::add_chain(&mut conn, &chain_name, &ident, &shard)?; + let chain = primary::add_chain(&mut conn, &chain_name, &shard)?; block_store.add_chain_store(&chain, ChainStatus::Ingestible, true)?; } }; @@ -509,18 +509,12 @@ impl BlockStore { // Discussed here: https://github.com/graphprotocol/graph-node/pull/4790 pub fn cleanup_ethereum_shallow_blocks( &self, - ethereum_networks: Vec<&String>, - firehose_only_networks: Option>, + eth_rpc_only_nets: Vec, ) -> Result<(), StoreError> { for store in self.stores.read().unwrap().values() { - if !ethereum_networks.contains(&&store.chain) { + if !eth_rpc_only_nets.contains(&&store.chain) { continue; }; - if let Some(fh_nets) = firehose_only_networks.clone() { - if fh_nets.contains(&&store.chain) { - continue; - }; - } if let Some(head_block) = store.remove_cursor(&&store.chain)? { let lower_bound = head_block.saturating_sub(ENV_VARS.reorg_threshold * 2); diff --git a/store/test-store/src/store.rs b/store/test-store/src/store.rs index 0c499b81fda..aa8cf0dc24e 100644 --- a/store/test-store/src/store.rs +++ b/store/test-store/src/store.rs @@ -629,8 +629,8 @@ fn build_store() -> (Arc, ConnectionPool, Config, Arc, pub network_store: Arc, chain_store: Arc, @@ -398,17 +400,17 @@ pub async fn stores(test_name: &str, store_config_path: &str) -> Stores { let store_builder = StoreBuilder::new(&logger, &node_id, &config, None, mock_registry.clone()).await; - let network_name: String = config.chains.chains.iter().next().unwrap().0.to_string(); + let network_name: ChainId = config + .chains + .chains + .iter() + .next() + .unwrap() + .0 + .as_str() + .into(); let chain_head_listener = store_builder.chain_head_update_listener(); - let network_identifiers = vec![( - network_name.clone(), - ChainIdentifier { - net_version: "".into(), - genesis_block_hash: test_ptr(0).hash, - }, - )] - .into_iter() - .collect(); + let network_identifiers: Vec = vec![network_name.clone()].into_iter().collect(); let network_store = store_builder.network_store(network_identifiers); let chain_store = network_store .block_store()