From a0b928489c19b57ff17245de82d510b5e392bfed Mon Sep 17 00:00:00 2001 From: Darun Seethammagari Date: Fri, 2 Aug 2024 04:25:39 +0530 Subject: [PATCH 01/10] fix: Require exact config response for lifecycle transitions (#956) We've noticed that it is occasionally possible for the indexer config returned by the contract to be parseable/null with a success status code, even though the indexer itself was not in fact deleted. This triggers the deprovisioning workflow anyway, which currently throws the Indexer into a repairing state, where it is no longer operating. This PR updates the contract to retain configuration if the indexer was deleted. Coordinator will now expect a specific configuration to take deletion actions, instead of the previous method which left things open to interpretation. In the near future, the contract will be migrated (#964) to use an enum instead of contract format, to avoid wastefully storing things like code/schema for deleted indexers forever. --- coordinator/src/indexer_config.rs | 6 + coordinator/src/indexer_state.rs | 1 + coordinator/src/lifecycle.rs | 55 ++--- coordinator/src/registry.rs | 37 +-- registry/contract/src/lib.rs | 366 +++++++++++++++++++++++------- registry/types/src/lib.rs | 5 +- 6 files changed, 340 insertions(+), 130 deletions(-) diff --git a/coordinator/src/indexer_config.rs b/coordinator/src/indexer_config.rs index 7d199ee7c..a29eab2d0 100644 --- a/coordinator/src/indexer_config.rs +++ b/coordinator/src/indexer_config.rs @@ -13,6 +13,7 @@ pub struct IndexerConfig { pub rule: Rule, pub updated_at_block_height: Option, pub created_at_block_height: u64, + pub deleted_at_block_height: Option, } impl KeyProvider for IndexerConfig { @@ -39,6 +40,7 @@ impl Default for IndexerConfig { }, created_at_block_height: 1, updated_at_block_height: Some(2), + deleted_at_block_height: Some(3), start_block: StartBlock::Height(100), } } @@ -53,4 +55,8 @@ impl IndexerConfig { self.updated_at_block_height .unwrap_or(self.created_at_block_height) } + + pub fn is_deleted(&self) -> bool { + self.deleted_at_block_height.is_some() + } } diff --git a/coordinator/src/indexer_state.rs b/coordinator/src/indexer_state.rs index 1200648f0..9e7b7a341 100644 --- a/coordinator/src/indexer_state.rs +++ b/coordinator/src/indexer_state.rs @@ -213,6 +213,7 @@ mod tests { }, created_at_block_height: 1, updated_at_block_height: None, + deleted_at_block_height: None, start_block: StartBlock::Continue, }; diff --git a/coordinator/src/lifecycle.rs b/coordinator/src/lifecycle.rs index 65b8ace4b..80ebeddce 100644 --- a/coordinator/src/lifecycle.rs +++ b/coordinator/src/lifecycle.rs @@ -90,15 +90,13 @@ impl<'a> LifecycleManager<'a> { #[tracing::instrument(name = "initializing", skip_all)] async fn handle_initializing( &self, - config: Option<&IndexerConfig>, + config: &IndexerConfig, _state: &IndexerState, ) -> LifecycleState { - if config.is_none() { + if config.is_deleted() { return LifecycleState::Deleting; } - let config = config.unwrap(); - if self .data_layer_handler .ensure_provisioned(config) @@ -114,15 +112,13 @@ impl<'a> LifecycleManager<'a> { #[tracing::instrument(name = "running", skip_all)] async fn handle_running( &self, - config: Option<&IndexerConfig>, + config: &IndexerConfig, state: &mut IndexerState, ) -> LifecycleState { - if config.is_none() { + if config.is_deleted() { return LifecycleState::Deleting; } - let config = config.unwrap(); - if !state.enabled { return LifecycleState::Stopping; } @@ -149,13 +145,11 @@ impl<'a> LifecycleManager<'a> { } #[tracing::instrument(name = "stopping", skip_all)] - async fn handle_stopping(&self, config: Option<&IndexerConfig>) -> LifecycleState { - if config.is_none() { + async fn handle_stopping(&self, config: &IndexerConfig) -> LifecycleState { + if config.is_deleted() { return LifecycleState::Deleting; } - let config = config.unwrap(); - if let Err(error) = self .block_streams_handler .stop_if_needed(config.account_id.clone(), config.function_name.clone()) @@ -178,12 +172,8 @@ impl<'a> LifecycleManager<'a> { } #[tracing::instrument(name = "stopped", skip_all)] - async fn handle_stopped( - &self, - config: Option<&IndexerConfig>, - state: &IndexerState, - ) -> LifecycleState { - if config.is_none() { + async fn handle_stopped(&self, config: &IndexerConfig, state: &IndexerState) -> LifecycleState { + if config.is_deleted() { return LifecycleState::Deleting; } @@ -199,13 +189,12 @@ impl<'a> LifecycleManager<'a> { #[tracing::instrument(name = "repairing", skip_all)] async fn handle_repairing( &self, - _config: Option<&IndexerConfig>, + config: &IndexerConfig, _state: &IndexerState, ) -> LifecycleState { - // TODO: Re-enable auto deprovision once guard rails in place - // if config.is_none() { - // return LifecycleState::Deleting; - // } + if config.is_deleted() { + return LifecycleState::Deleting; + } // TODO Add more robust error handling, for now just stop LifecycleState::Repairing @@ -230,7 +219,7 @@ impl<'a> LifecycleManager<'a> { } tracing::error!("Temporarily preventing indexer deprovision due to service instability"); - LifecycleState::Repairing + LifecycleState::Deleted // if self.state_manager.delete_state(state).await.is_err() { // // Retry @@ -283,7 +272,11 @@ impl<'a> LifecycleManager<'a> { ) .await { - Ok(config) => config, + Ok(Some(config)) => config, + Ok(None) => { + warn!("No matching indexer config was found"); + continue; + } Err(error) => { warn!(?error, "Failed to fetch config"); continue; @@ -304,13 +297,11 @@ impl<'a> LifecycleManager<'a> { } let desired_lifecycle_state = match state.lifecycle_state { - LifecycleState::Initializing => { - self.handle_initializing(config.as_ref(), &state).await - } - LifecycleState::Running => self.handle_running(config.as_ref(), &mut state).await, - LifecycleState::Stopping => self.handle_stopping(config.as_ref()).await, - LifecycleState::Stopped => self.handle_stopped(config.as_ref(), &state).await, - LifecycleState::Repairing => self.handle_repairing(config.as_ref(), &state).await, + LifecycleState::Initializing => self.handle_initializing(&config, &state).await, + LifecycleState::Running => self.handle_running(&config, &mut state).await, + LifecycleState::Stopping => self.handle_stopping(&config).await, + LifecycleState::Stopped => self.handle_stopped(&config, &state).await, + LifecycleState::Repairing => self.handle_repairing(&config, &state).await, LifecycleState::Deleting => self.handle_deleting(&state).await, LifecycleState::Deleted => LifecycleState::Deleted, }; diff --git a/coordinator/src/registry.rs b/coordinator/src/registry.rs index de891ffa3..33762fcdc 100644 --- a/coordinator/src/registry.rs +++ b/coordinator/src/registry.rs @@ -1,6 +1,7 @@ #![cfg_attr(test, allow(dead_code))] use anyhow::Context; +use serde_json::Value; use std::collections::hash_map::Iter; use std::collections::HashMap; @@ -130,6 +131,7 @@ impl RegistryImpl { rule: indexer.rule, updated_at_block_height: indexer.updated_at_block_height, created_at_block_height: indexer.created_at_block_height, + deleted_at_block_height: indexer.deleted_at_block_height, }, ) }) @@ -194,21 +196,30 @@ impl RegistryImpl { .context("Failed to fetch indexer")?; if let QueryResponseKind::CallResult(call_result) = response.kind { - let indexer = serde_json::from_slice::>( - &call_result.result, - )? - .map(|indexer| IndexerConfig { + // Handle case where call returns successfully but returns null due to not matching + let raw_json: Value = serde_json::from_slice(&call_result.result) + .context("Failed to deserialize config from JSON provided by RPC call")?; + if raw_json.is_null() { + return Ok(None); + } + + // Handle case where we now expect returned JSON to actually parse into config + let config: registry_types::IndexerConfig = + serde_json::from_slice::(&call_result.result) + .context("Failed to deserialize config from JSON provided by RPC call")?; + let indexer = IndexerConfig { account_id: account_id.clone(), function_name: function_name.to_string(), - code: indexer.code, - schema: indexer.schema, - rule: indexer.rule, - start_block: indexer.start_block, - updated_at_block_height: indexer.updated_at_block_height, - created_at_block_height: indexer.created_at_block_height, - }); - - return Ok(indexer); + code: config.code, + schema: config.schema, + rule: config.rule, + start_block: config.start_block, + updated_at_block_height: config.updated_at_block_height, + created_at_block_height: config.created_at_block_height, + deleted_at_block_height: config.deleted_at_block_height, + }; + + return Ok(Some(indexer)); } anyhow::bail!("Invalid registry response") diff --git a/registry/contract/src/lib.rs b/registry/contract/src/lib.rs index 4f552915e..1b16f08c0 100644 --- a/registry/contract/src/lib.rs +++ b/registry/contract/src/lib.rs @@ -46,6 +46,8 @@ enum StorageKeys { AccountV3(CryptoHash), RegistryV4, AccountV4(CryptoHash), + RegistryV5, + AccountV5(CryptoHash), } /// These roles are used to control access across the various contract methods. @@ -74,7 +76,7 @@ pub struct AccountRole { impl Default for Contract { fn default() -> Self { Self { - registry: IndexersByAccount::new(StorageKeys::RegistryV4), + registry: IndexersByAccount::new(StorageKeys::RegistryV5), account_roles: vec![ AccountRole { account_id: "morgs.near".parse().unwrap(), @@ -84,10 +86,6 @@ impl Default for Contract { account_id: "nearpavel.near".parse().unwrap(), role: Role::Owner, }, - AccountRole { - account_id: "roshaan.near".parse().unwrap(), - role: Role::Owner, - }, AccountRole { account_id: "flatirons.near".parse().unwrap(), role: Role::Owner, @@ -117,11 +115,11 @@ impl Contract { pub fn migrate() -> Self { let state: OldContract = env::state_read().expect("failed to parse existing state"); - let mut registry = IndexersByAccount::new(StorageKeys::RegistryV4); + let mut registry = IndexersByAccount::new(StorageKeys::RegistryV5); for (account_id, indexers) in state.registry.iter() { let mut new_indexers: IndexerConfigByFunctionName = IndexerConfigByFunctionName::new( - StorageKeys::AccountV4(env::sha256_array(account_id.as_bytes())), + StorageKeys::AccountV5(env::sha256_array(account_id.as_bytes())), ); for (function_name, indexer_config) in indexers.iter() { @@ -275,7 +273,7 @@ impl Contract { let account_indexers = self.registry .entry(account_id.clone()) - .or_insert(IndexerConfigByFunctionName::new(StorageKeys::AccountV3( + .or_insert(IndexerConfigByFunctionName::new(StorageKeys::AccountV5( env::sha256_array(account_id.as_bytes()), ))); @@ -289,6 +287,7 @@ impl Contract { start_block, updated_at_block_height: Some(env::block_height()), created_at_block_height: indexer.created_at_block_height, + deleted_at_block_height: None, forked_from, }); } @@ -300,6 +299,7 @@ impl Contract { start_block, updated_at_block_height: None, created_at_block_height: env::block_height(), + deleted_at_block_height: None, forked_from, }); } @@ -328,7 +328,7 @@ impl Contract { env::panic_str(format!("Account {} does not have any functions", account_id).as_str()) }); - user_functions.remove(&function_name).unwrap_or_else(|| { + let indexer_function = user_functions.get_mut(&function_name).unwrap_or_else(|| { env::panic_str( format!( "Function {} does not exist on account {}", @@ -338,9 +338,7 @@ impl Contract { ) }); - if user_functions.is_empty() { - self.registry.remove(&account_id); - } + indexer_function.deleted_at_block_height = Some(env::block_height()); } pub fn list_indexer_functions(&self, account_id: Option) -> AccountOrAllIndexers { @@ -359,11 +357,12 @@ impl Contract { pub fn list_by_account(&self, account_id: AccountId) -> AccountIndexers { self.registry .get(&account_id) - .unwrap_or(&IndexerConfigByFunctionName::new(StorageKeys::AccountV3( + .unwrap_or(&IndexerConfigByFunctionName::new(StorageKeys::AccountV4( env::sha256_array(account_id.as_bytes()), ))) .iter() .map(|(function_name, config)| (function_name.clone(), config.clone())) + .filter(|(_, config)| config.deleted_at_block_height.is_none()) .collect() } @@ -376,6 +375,7 @@ impl Contract { account_indexers .iter() .map(|(function_name, config)| (function_name.clone(), config.clone())) + .filter(|(_, config)| config.deleted_at_block_height.is_none()) .collect(), ) }) @@ -391,9 +391,9 @@ mod tests { #[test] fn migrate() { - let mut registry = OldIndexersByAccount::new(StorageKeys::RegistryV3); + let mut registry = OldIndexersByAccount::new(StorageKeys::RegistryV4); let account_id = "morgs.near".parse::().unwrap(); - let mut functions = OldIndexerConfigByFunctionName::new(StorageKeys::AccountV3( + let mut functions = OldIndexerConfigByFunctionName::new(StorageKeys::AccountV4( env::sha256_array(account_id.as_bytes()), )); @@ -410,6 +410,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 10, + forked_from: None, }, ); functions.insert( @@ -424,6 +425,7 @@ mod tests { }, updated_at_block_height: Some(20), created_at_block_height: 10, + forked_from: None, }, ); registry.insert(account_id.clone(), functions); @@ -453,6 +455,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 10, + deleted_at_block_height: None, forked_from: None, } ); @@ -473,6 +476,7 @@ mod tests { }, updated_at_block_height: Some(20), created_at_block_height: 10, + deleted_at_block_height: None, forked_from: None, } ); @@ -719,6 +723,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }; @@ -762,6 +767,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: Some(IndexerIdentity { account_id: "some_other_account.near".parse().unwrap(), function_name: String::from("some_other_function"), @@ -930,7 +936,7 @@ mod tests { } #[test] - fn sets_updated_at_and_created_at_for_new_account() { + fn sets_created_updated_and_deleted_at_for_new_account() { let mut contract = Contract { registry: IndexersByAccount::new(StorageKeys::Registry), account_roles: vec![AccountRole { @@ -949,6 +955,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }; @@ -975,6 +982,7 @@ mod tests { assert_eq!(indexer_config.updated_at_block_height, None); assert_eq!(indexer_config.created_at_block_height, env::block_height()); + assert_eq!(indexer_config.deleted_at_block_height, None); } #[test] @@ -997,6 +1005,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }; @@ -1045,6 +1054,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }, ); @@ -1068,6 +1078,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }; @@ -1173,21 +1184,51 @@ mod tests { let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( env::sha256_array(account_id.as_bytes()), )); - account_indexers.insert( - "test".to_string(), - IndexerConfig { - start_block: StartBlock::Latest, - code: "var x= 1;".to_string(), - schema: String::new(), - rule: Rule::ActionAny { - affected_account_id: "social.near".to_string(), - status: Status::Success, - }, - updated_at_block_height: None, - created_at_block_height: 0, - forked_from: None, + let mut config = IndexerConfig { + start_block: StartBlock::Latest, + code: "var x= 1;".to_string(), + schema: String::new(), + rule: Rule::ActionAny { + affected_account_id: "social.near".to_string(), + status: Status::Success, }, + updated_at_block_height: None, + created_at_block_height: 0, + deleted_at_block_height: None, + forked_from: None, + }; + account_indexers.insert("test".to_string(), config.clone()); + let mut registry = IndexersByAccount::new(StorageKeys::Registry); + registry.insert(account_id, account_indexers); + let mut contract = Contract { + registry, + account_roles: vec![AccountRole { + account_id: "bob.near".parse().unwrap(), + role: Role::User, + }], + }; + + contract.remove_indexer_function("test".to_string(), None); + config.deleted_at_block_height = Some(env::block_height()); + + assert_eq!( + contract + .registry + .get(&"bob.near".parse::().unwrap()) + .unwrap() + .get("test") + .unwrap() + .clone(), + config, ); + } + + #[test] + fn users_can_remove_and_then_register_their_own_functions() { + let account_id = "bob.near".parse::().unwrap(); + let account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( + env::sha256_array(account_id.as_bytes()), + )); let mut registry = IndexersByAccount::new(StorageKeys::Registry); registry.insert(account_id, account_indexers); let mut contract = Contract { @@ -1198,12 +1239,59 @@ mod tests { }], }; + let mut config = IndexerConfig { + start_block: StartBlock::Latest, + code: "var x= 1;".to_string(), + schema: String::new(), + rule: Rule::ActionAny { + affected_account_id: "social.near".to_string(), + status: Status::Success, + }, + updated_at_block_height: None, + created_at_block_height: 0, + deleted_at_block_height: None, + forked_from: None, + }; + + contract.register( + "test".to_string(), + None, + config.code.clone(), + config.schema.clone(), + Rule::ActionAny { + affected_account_id: "social.near".to_string(), + status: Status::Success, + }, + config.start_block.clone(), + None, + ); + contract.remove_indexer_function("test".to_string(), None); - assert!(contract - .registry - .get(&"bob.near".parse::().unwrap()) - .is_none()); + contract.register( + "test".to_string(), + None, + config.code.clone(), + config.schema.clone(), + Rule::ActionAny { + affected_account_id: "social.near".to_string(), + status: Status::Success, + }, + config.start_block.clone(), + None, + ); + config.updated_at_block_height = Some(env::block_height()); + + assert_eq!( + contract + .registry + .get(&"bob.near".parse::().unwrap()) + .unwrap() + .get("test") + .unwrap() + .clone(), + config, + ); } #[test] @@ -1212,21 +1300,20 @@ mod tests { let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( env::sha256_array(account_id.as_bytes()), )); - account_indexers.insert( - "test".to_string(), - IndexerConfig { - start_block: StartBlock::Latest, - code: "var x= 1;".to_string(), - schema: String::new(), - rule: Rule::ActionAny { - affected_account_id: "social.near".to_string(), - status: Status::Success, - }, - updated_at_block_height: None, - created_at_block_height: 0, - forked_from: None, + let mut config = IndexerConfig { + start_block: StartBlock::Latest, + code: "var x= 1;".to_string(), + schema: String::new(), + rule: Rule::ActionAny { + affected_account_id: "social.near".to_string(), + status: Status::Success, }, - ); + updated_at_block_height: None, + created_at_block_height: 0, + deleted_at_block_height: None, + forked_from: None, + }; + account_indexers.insert("test".to_string(), config.clone()); let mut registry = IndexersByAccount::new(StorageKeys::Registry); registry.insert(account_id, account_indexers); let mut contract = Contract { @@ -1238,11 +1325,18 @@ mod tests { }; contract.remove_indexer_function("test".to_string(), None); + config.deleted_at_block_height = Some(env::block_height()); - assert!(contract - .registry - .get(&"bob.near".parse::().unwrap()) - .is_none()); + assert_eq!( + contract + .registry + .get(&"bob.near".parse::().unwrap()) + .unwrap() + .get("test") + .unwrap() + .clone(), + config, + ); } #[test] @@ -1264,6 +1358,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }, ); @@ -1286,21 +1381,20 @@ mod tests { let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( env::sha256_array(account_id.as_bytes()), )); - account_indexers.insert( - "test".to_string(), - IndexerConfig { - start_block: StartBlock::Latest, - code: "var x= 1;".to_string(), - schema: String::new(), - rule: Rule::ActionAny { - affected_account_id: "social.near".to_string(), - status: Status::Success, - }, - updated_at_block_height: None, - created_at_block_height: 0, - forked_from: None, + let mut config = IndexerConfig { + start_block: StartBlock::Latest, + code: "var x= 1;".to_string(), + schema: String::new(), + rule: Rule::ActionAny { + affected_account_id: "social.near".to_string(), + status: Status::Success, }, - ); + updated_at_block_height: None, + created_at_block_height: 0, + deleted_at_block_height: None, + forked_from: None, + }; + account_indexers.insert("test".to_string(), config.clone()); let mut registry = IndexersByAccount::new(StorageKeys::Registry); registry.insert(account_id, account_indexers); let mut contract = Contract { @@ -1312,11 +1406,18 @@ mod tests { }; contract.remove_indexer_function("test".to_string(), Some("alice.near".to_string())); + config.deleted_at_block_height = Some(env::block_height()); - assert!(contract - .registry - .get(&"alice.near".parse::().unwrap()) - .is_none()); + assert_eq!( + contract + .registry + .get(&"alice.near".parse::().unwrap()) + .unwrap() + .get("test") + .unwrap() + .clone(), + config, + ); } #[test] @@ -1337,6 +1438,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }, ); @@ -1356,21 +1458,20 @@ mod tests { let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( env::sha256_array(account_id.as_bytes()), )); - account_indexers.insert( - "test".to_string(), - IndexerConfig { - start_block: StartBlock::Latest, - code: "var x= 1;".to_string(), - schema: String::new(), - rule: Rule::ActionAny { - affected_account_id: "social.near".to_string(), - status: Status::Success, - }, - updated_at_block_height: None, - created_at_block_height: 0, - forked_from: None, + let mut indexer_to_delete = IndexerConfig { + start_block: StartBlock::Latest, + code: "var x= 1;".to_string(), + schema: String::new(), + rule: Rule::ActionAny { + affected_account_id: "social.near".to_string(), + status: Status::Success, }, - ); + updated_at_block_height: None, + created_at_block_height: 0, + deleted_at_block_height: None, + forked_from: None, + }; + account_indexers.insert("test".to_string(), indexer_to_delete.clone()); account_indexers.insert( "test2".to_string(), IndexerConfig { @@ -1383,6 +1484,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }, ); @@ -1397,6 +1499,7 @@ mod tests { }; contract.remove_indexer_function("test".to_string(), None); + indexer_to_delete.deleted_at_block_height = Some(env::block_height()); assert_eq!( contract @@ -1404,8 +1507,27 @@ mod tests { .get(&"bob.near".parse::().unwrap()) .unwrap() .len(), - 1 + 2 ); + assert_eq!( + contract + .registry + .get(&"bob.near".parse::().unwrap()) + .unwrap() + .get("test") + .unwrap() + .clone(), + indexer_to_delete, + ); + assert!(contract + .registry + .get(&"bob.near".parse::().unwrap()) + .unwrap() + .get("test2") + .unwrap() + .clone() + .deleted_at_block_height + .is_none()); } #[test] @@ -1439,6 +1561,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }; @@ -1472,6 +1595,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }; let account_id = "alice.near".parse::().unwrap(); @@ -1513,6 +1637,20 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, + forked_from: None, + }; + let deleted_config = IndexerConfig { + start_block: StartBlock::Latest, + code: "var x= 1;".to_string(), + schema: String::new(), + rule: Rule::ActionAny { + affected_account_id: "social.near".to_string(), + status: Status::Success, + }, + updated_at_block_height: None, + created_at_block_height: 0, + deleted_at_block_height: Some(1), forked_from: None, }; let account_id = "bob.near".parse::().unwrap(); @@ -1548,6 +1686,20 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, + forked_from: None, + }; + let deleted_config = IndexerConfig { + start_block: StartBlock::Latest, + code: "var x= 1;".to_string(), + schema: String::new(), + rule: Rule::ActionAny { + affected_account_id: "social.near".to_string(), + status: Status::Success, + }, + updated_at_block_height: None, + created_at_block_height: 0, + deleted_at_block_height: Some(1), forked_from: None, }; let account_id = "bob.near".parse::().unwrap(); @@ -1580,6 +1732,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: None, }; let account_id = "alice.near".parse::().unwrap(); @@ -1619,6 +1772,22 @@ mod tests { StartBlock::Latest, None, ); + contract.register( + "delete_this".to_string(), + Some(IndexerIdentity { + account_id: "some_other_account.near".parse().unwrap(), + function_name: String::from("some_other_function"), + }), + String::from("code"), + String::from("schema"), + Rule::ActionAny { + affected_account_id: String::from("social.near"), + status: Status::Any, + }, + StartBlock::Latest, + None, + ); + contract.remove_indexer_function("delete_this".to_string(), None); assert_eq!( contract.list_all(), @@ -1636,6 +1805,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: Some(IndexerIdentity { account_id: "some_other_account.near".parse().unwrap(), function_name: String::from("some_other_function"), @@ -1672,6 +1842,33 @@ mod tests { ); } + #[test] + fn list_only_deleted_account_indexers() { + let mut contract = Contract::default(); + + contract.register( + "test".to_string(), + Some(IndexerIdentity { + account_id: "some_other_account.near".parse().unwrap(), + function_name: String::from("some_other_function"), + }), + String::from("code"), + String::from("schema"), + Rule::ActionAny { + affected_account_id: String::from("social.near"), + status: Status::Any, + }, + StartBlock::Latest, + None, + ); + contract.remove_indexer_function("test".to_string(), None); + + assert_eq!( + contract.list_by_account(env::signer_account_id()), + HashMap::new() + ); + } + #[test] fn list_account_indexers() { let mut contract = Contract::default(); @@ -1706,6 +1903,7 @@ mod tests { }, updated_at_block_height: None, created_at_block_height: 0, + deleted_at_block_height: None, forked_from: Some(IndexerIdentity { account_id: "some_other_account.near".parse().unwrap(), function_name: String::from("some_other_function"), diff --git a/registry/types/src/lib.rs b/registry/types/src/lib.rs index d66f42fd8..66eb7ff06 100644 --- a/registry/types/src/lib.rs +++ b/registry/types/src/lib.rs @@ -68,6 +68,7 @@ pub struct OldIndexerConfig { pub rule: Rule, pub updated_at_block_height: Option, pub created_at_block_height: u64, + pub forked_from: Option, } #[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -78,6 +79,7 @@ pub struct IndexerConfig { pub rule: Rule, pub updated_at_block_height: Option, pub created_at_block_height: u64, + pub deleted_at_block_height: Option, pub forked_from: Option, } @@ -90,7 +92,8 @@ impl From for IndexerConfig { rule: config.rule, created_at_block_height: config.created_at_block_height, updated_at_block_height: config.updated_at_block_height, - forked_from: None, + deleted_at_block_height: None, + forked_from: config.forked_from, } } } From 6a1810b15219730693d9a5c2b8e9c72a200045c4 Mon Sep 17 00:00:00 2001 From: Kevin Zhang <42101107+Kevin101Zhang@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:39:45 -0400 Subject: [PATCH 02/10] feat: events workflow in wizard (#969) added events into launchpad. --- .../Editor/EditorComponents/Editor.tsx | 4 +- .../Editor/EditorComponents/GenerateCode.tsx | 164 ------ frontend/src/pages/api/generateCode.ts | 6 +- frontend/src/pages/query-api-editor/index.js | 5 - frontend/src/test/api/generateCode.test.ts | 55 +- frontend/widgets/src/QueryApi.Dashboard.jsx | 3 + frontend/widgets/src/QueryApi.Editor.jsx | 5 +- frontend/widgets/src/QueryApi.Launchpad.jsx | 527 ++++++++++-------- 8 files changed, 348 insertions(+), 421 deletions(-) delete mode 100644 frontend/src/components/Editor/EditorComponents/GenerateCode.tsx diff --git a/frontend/src/components/Editor/EditorComponents/Editor.tsx b/frontend/src/components/Editor/EditorComponents/Editor.tsx index 64fec42d5..79fa2a760 100644 --- a/frontend/src/components/Editor/EditorComponents/Editor.tsx +++ b/frontend/src/components/Editor/EditorComponents/Editor.tsx @@ -144,13 +144,13 @@ const Editor: React.FC = (): ReactElement => { const fetchData = async () => { try { const response = await fetchWizardData(''); - const { wizardContractFilter, wizardMethods } = response; + const { wizardContractFilter, wizardMethods, wizardEvents } = response; if (wizardContractFilter === 'noFilter') { return; } - const codeResponse = await generateCode(wizardContractFilter, wizardMethods); + const codeResponse = await generateCode(wizardContractFilter, wizardMethods, wizardEvents); setIndexingCode(codeResponse.jsCode); setSchema(codeResponse.sqlCode); } catch (error: unknown) { diff --git a/frontend/src/components/Editor/EditorComponents/GenerateCode.tsx b/frontend/src/components/Editor/EditorComponents/GenerateCode.tsx deleted file mode 100644 index bf41fedbf..000000000 --- a/frontend/src/components/Editor/EditorComponents/GenerateCode.tsx +++ /dev/null @@ -1,164 +0,0 @@ -//Dummy component -import { useState } from 'react'; - -type Schema = { - type: string; - properties?: Record; - required?: string[]; -}; - -type Method = { - method_name: string; - schema: Schema; -}; - -type Event = { - event_name: string; - schema: Schema; -}; - -const GenerateCode = () => { - const [contractFilter, setContractFilter] = useState(''); - const [selectedMethods, setSelectedMethods] = useState([]); - const [selectedEvents, setSelectedEvents] = useState([]); - const [generatedCode, setGeneratedCode] = useState<{ jsCode: string; sqlCode: string }>({ jsCode: '', sqlCode: '' }); - - const handleGenerateCode = async () => { - const response = await fetch('/api/generateCode', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ contractFilter, selectedMethods, selectedEvents }), - }); - const data = await response.json(); - setGeneratedCode(data); - }; - - const handleMethodChange = (index: number, field: keyof Method, value: string) => { - const updatedMethods = [...selectedMethods]; - if (field === 'schema') { - try { - updatedMethods[index] = { ...updatedMethods[index], schema: JSON.parse(value) }; - } catch (e) { - console.error('Invalid JSON format'); - } - } else { - updatedMethods[index] = { ...updatedMethods[index], [field]: value }; - } - setSelectedMethods(updatedMethods); - }; - - const handleEventChange = (index: number, field: keyof Event, value: string) => { - const updatedEvents = [...selectedEvents]; - if (field === 'schema') { - try { - updatedEvents[index] = { ...updatedEvents[index], schema: JSON.parse(value) }; - } catch (e) { - console.error('Invalid JSON format'); - } - } else { - updatedEvents[index] = { ...updatedEvents[index], [field]: value }; - } - setSelectedEvents(updatedEvents); - }; - - const addMethod = () => { - setSelectedMethods([...selectedMethods, { method_name: '', schema: { type: 'object' } }]); - }; - - const addEvent = () => { - setSelectedEvents([...selectedEvents, { event_name: '', schema: { type: 'object' } }]); - }; - - return ( -
-
-

Generate Code

- -
- setContractFilter(e.target.value)} - className="w-full p-3 border border-gray-300 rounded-md" - /> -
- -
-

Selected Methods

- {selectedMethods.map((method, index) => ( -
- handleMethodChange(index, 'method_name', e.target.value)} - className="w-full p-3 border border-gray-300 rounded-md mb-2" - /> -