diff --git a/graph/src/components/adapter.rs b/graph/src/components/adapter.rs index 9642615b998..06830c887eb 100644 --- a/graph/src/components/adapter.rs +++ b/graph/src/components/adapter.rs @@ -102,6 +102,12 @@ pub trait IdentValidator: Sync + Send { chain_id: &ChainId, ident: &ChainIdentifier, ) -> Result<(), IdentValidatorError>; + + fn update_ident( + &self, + chain_id: &ChainId, + ident: &ChainIdentifier, + ) -> Result<(), anyhow::Error>; } impl> IdentValidator for B { @@ -145,6 +151,20 @@ impl> IdentValidator for return Ok(()); } + + fn update_ident( + &self, + chain_id: &ChainId, + ident: &ChainIdentifier, + ) -> Result<(), anyhow::Error> { + let network_chain = self + .chain_store(&chain_id) + .ok_or_else(|| IdentValidatorError::UnavailableStore(chain_id.clone()))?; + + network_chain.set_chain_identifier(ident)?; + + Ok(()) + } } pub struct MockIdentValidator; @@ -157,6 +177,14 @@ impl IdentValidator for MockIdentValidator { ) -> Result<(), IdentValidatorError> { Ok(()) } + + fn update_ident( + &self, + _chain_id: &ChainId, + _ident: &ChainIdentifier, + ) -> Result<(), anyhow::Error> { + Ok(()) + } } /// ProviderCorrectness will maintain a list of providers which have had their @@ -453,7 +481,9 @@ impl Inner { } Err(err) => match err { IdentValidatorError::UnsetIdent => { - // todo: update chain? + self.validator + .update_ident(&ident.chain_id, &chain_ident) + .map_err(ProviderManagerError::from)?; *status = GenesisCheckStatus::Valid; } IdentValidatorError::ChangedNetVersion { @@ -531,15 +561,24 @@ enum GenesisCheckStatus { #[cfg(test)] mod test { - use std::{ops::Sub, sync::Arc}; + use std::{ + ops::Sub, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + }; use crate::{ + bail, + blockchain::BlockHash, components::adapter::{ChainId, GenesisCheckStatus, MockIdentValidator}, data::value::Word, prelude::lazy_static, }; use async_trait::async_trait; use chrono::{Duration, Utc}; + use ethabi::ethereum_types::H256; use slog::{o, Discard, Logger}; use crate::{blockchain::ChainIdentifier, components::adapter::ProviderManagerError}; @@ -566,10 +605,12 @@ mod test { }; static ref VALID_ADAPTER: MockAdapter = MockAdapter {provider: "valid".into(), status: GenesisCheckStatus::Valid,}; static ref FAILED_ADAPTER: MockAdapter = MockAdapter {provider: "FAILED".into(), status: GenesisCheckStatus::Failed,}; + static ref NEW_CHAIN_IDENT: ChainIdentifier =ChainIdentifier { net_version: "123".to_string(), genesis_block_hash: BlockHash::from( H256::repeat_byte(1))}; } struct TestValidator { - result: Result<(), IdentValidatorError>, + check_result: Result<(), IdentValidatorError>, + expected_new_ident: Option, } impl IdentValidator for TestValidator { @@ -578,7 +619,19 @@ mod test { _chain_id: &ChainId, _ident: &ChainIdentifier, ) -> Result<(), IdentValidatorError> { - self.result.clone() + self.check_result.clone() + } + + fn update_ident( + &self, + _chain_id: &ChainId, + ident: &ChainIdentifier, + ) -> Result<(), anyhow::Error> { + match self.expected_new_ident.as_ref() { + None => unreachable!("unexpected call to update_ident"), + Some(ident_expected) if ident_expected.eq(ident) => Ok(()), + Some(_) => bail!("update_ident called with unexpected value"), + } } } @@ -598,7 +651,7 @@ mod test { { unreachable!("should never check if ttl has not elapsed"); } - _ => Ok(ChainIdentifier::default()), + _ => Ok(NEW_CHAIN_IDENT.clone()), } } @@ -626,7 +679,17 @@ mod test { expected: Ok(vec![]), }, Case { - name: "adapter temporary failure with Ident None", + name: "no adapters", + chain_id: TEST_CHAIN_ID, + adapters: vec![(TEST_CHAIN_ID.into(), vec![TESTABLE_ADAPTER.clone()])], + validator: Some(TestValidator { + check_result: Err(IdentValidatorError::UnsetIdent), + expected_new_ident: Some(NEW_CHAIN_IDENT.clone()), + }), + expected: Ok(vec![&TESTABLE_ADAPTER]), + }, + Case { + name: "adapter temporary failure with Ident unset", chain_id: TEST_CHAIN_ID, // UNTESTABLE_ADAPTER has failed ident, will be valid cause idents has None value adapters: vec![(TEST_CHAIN_ID.into(), vec![UNTESTABLE_ADAPTER.clone()])], @@ -649,11 +712,12 @@ mod test { chain_id: TEST_CHAIN_ID, adapters: vec![(TEST_CHAIN_ID.into(), vec![FAILED_ADAPTER.clone()])], validator: Some(TestValidator { - result: Err(IdentValidatorError::ChangedNetVersion { + check_result: Err(IdentValidatorError::ChangedNetVersion { chain_id: TEST_CHAIN_ID.into(), store_net_version: "".to_string(), chain_net_version: "".to_string(), }), + expected_new_ident: None, }), expected: Err(ProviderManagerError::AllProvidersFailed( TEST_CHAIN_ID.into(), @@ -668,11 +732,12 @@ mod test { )], // if a check is performed (which it shouldn't) the test will fail validator: Some(TestValidator { - result: Err(IdentValidatorError::ChangedNetVersion { + check_result: Err(IdentValidatorError::ChangedNetVersion { chain_id: TEST_CHAIN_ID.into(), store_net_version: "".to_string(), chain_net_version: "".to_string(), }), + expected_new_ident: None, }), expected: Ok(vec![&VALID_ADAPTER]), }, @@ -749,4 +814,69 @@ mod test { } } } + + #[tokio::test] + async fn test_provider_manager_updates_on_unset() { + #[derive(Clone, Debug, Eq, PartialEq)] + struct MockAdapter {} + + #[async_trait] + impl NetIdentifiable for MockAdapter { + async fn net_identifiers(&self) -> Result { + Ok(NEW_CHAIN_IDENT.clone()) + } + fn provider_name(&self) -> ProviderName { + TEST_CHAIN_ID.into() + } + } + + struct TestValidator { + called: AtomicBool, + err: IdentValidatorError, + } + + impl IdentValidator for TestValidator { + fn check_ident( + &self, + _chain_id: &ChainId, + _ident: &ChainIdentifier, + ) -> Result<(), IdentValidatorError> { + Err(self.err.clone()) + } + + fn update_ident( + &self, + _chain_id: &ChainId, + ident: &ChainIdentifier, + ) -> Result<(), anyhow::Error> { + if NEW_CHAIN_IDENT.eq(ident) { + self.called.store(true, Ordering::SeqCst); + return Ok(()); + } + + unreachable!("unexpected call to update_ident ot unexpected ident passed"); + } + } + + let logger = Logger::root(Discard, o!()); + let chain_id = TEST_CHAIN_ID.into(); + + // Ensure the provider updates the chain ident when it wasn't set yet. + let validator = Arc::new(TestValidator { + called: AtomicBool::default(), + err: IdentValidatorError::UnsetIdent, + }); + let adapter = MockAdapter {}; + + let manager = ProviderManager::new( + logger, + vec![(TEST_CHAIN_ID.into(), vec![adapter.clone()])].into_iter(), + validator.clone(), + ); + + let mut result = manager.get_all(&chain_id).await.unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(&adapter, result.pop().unwrap()); + assert_eq!(validator.called.load(Ordering::SeqCst), true); + } } diff --git a/graph/src/components/store/traits.rs b/graph/src/components/store/traits.rs index 4b7417b09cc..198ae728d7b 100644 --- a/graph/src/components/store/traits.rs +++ b/graph/src/components/store/traits.rs @@ -529,7 +529,7 @@ pub trait ChainStore: Send + Sync + 'static { fn chain_identifier(&self) -> Result; /// Update the chain identifier for this store. - fn set_chain_identifier(&self, ident: ChainIdentifier) -> Result<(), Error>; + fn set_chain_identifier(&self, ident: &ChainIdentifier) -> Result<(), Error>; } pub trait EthereumCallCache: Send + Sync + 'static { diff --git a/store/postgres/src/chain_store.rs b/store/postgres/src/chain_store.rs index ab8d864374a..b68e27afe20 100644 --- a/store/postgres/src/chain_store.rs +++ b/store/postgres/src/chain_store.rs @@ -1765,7 +1765,7 @@ impl ChainStore { self.upsert_block(block).await.expect("can upsert block"); } - self.set_chain_identifier(ChainIdentifier { + self.set_chain_identifier(&ChainIdentifier { net_version: "0".to_string(), genesis_block_hash: BlockHash::try_from(genesis_hash).expect("valid block hash"), }) @@ -2256,14 +2256,14 @@ impl ChainStoreTrait for ChainStore { .await } - fn set_chain_identifier(&self, ident: ChainIdentifier) -> Result<(), Error> { + fn set_chain_identifier(&self, ident: &ChainIdentifier) -> Result<(), Error> { use public::ethereum_networks as n; let mut conn = self.pool.get()?; diesel::update(n::table.filter(n::name.eq(&self.chain))) .set(( n::genesis_block_hash.eq(ident.genesis_block_hash.hash_hex()), - n::net_version.eq(ident.net_version), + n::net_version.eq(&ident.net_version), )) .execute(&mut conn)?;