From ed7237419556e756e865747e82a2ddbc13689573 Mon Sep 17 00:00:00 2001 From: Morgan McCauley Date: Fri, 5 Jan 2024 08:54:55 +1300 Subject: [PATCH] feat: Add created/updated at block heights to registry (#458) This PR adds the `created_at_block_height` and `updated_at_block_height` fields to `IndexerConfig` within the registry contract. The motive behind this is to provide Coordinator V2 with a way for comparing the actual and desired states of the system, i.e. if there is a mismatch between the registry and the system, action should be taken. Without versions, there is no way of making this comparison. ## Compilation Errors ~~I ran in to several issues trying to compile the `wasm32` binary, and have outlined all these issues in https://github.com/near/near-sdk-rs/issues/1125, as well as in the `README.md` so that the fixes are documented. These fixes are a bit janky, but I've tested the deployed contract and all seems to be ok.~~ These have been resolved, see: https://github.com/near/queryapi/pull/458#issuecomment-1874977342 ## Account Roles Migration I've also included `account_roles` in this migration as we have some incorrect accounts as `Owner`s (`pavelnear.near`). All owners will be wiped and re-written from the contract default state. All `User`s will remain. ## Coordinator V1 Coordinator V1 has been tested to ensure that it can still parse the registry after these new fields have been applied. --- block-streamer/Cargo.toml | 2 +- registry/contract/Cargo.lock | 1 - registry/contract/Cargo.toml | 5 +- registry/contract/src/lib.rs | 326 ++++++++++++++++++++++++++++++++--- registry/types/Cargo.toml | 8 +- registry/types/src/lib.rs | 2 + 6 files changed, 312 insertions(+), 32 deletions(-) diff --git a/block-streamer/Cargo.toml b/block-streamer/Cargo.toml index 768929cc0..9bd263c34 100644 --- a/block-streamer/Cargo.toml +++ b/block-streamer/Cargo.toml @@ -24,7 +24,7 @@ tokio-stream = "0.1.14" tonic = "0.10.2" wildmatch = "2.1.1" -registry-types = { path = "../registry/types" } +registry-types = { path = "../registry/types", features = ["near-primitives"] } near-lake-framework = "0.7.4" diff --git a/registry/contract/Cargo.lock b/registry/contract/Cargo.lock index 0e810f77e..86b8c78d1 100644 --- a/registry/contract/Cargo.lock +++ b/registry/contract/Cargo.lock @@ -2059,7 +2059,6 @@ name = "registry-types" version = "0.1.0" dependencies = [ "borsh 1.2.1", - "near-primitives", "near-sdk", "serde", ] diff --git a/registry/contract/Cargo.toml b/registry/contract/Cargo.toml index 15ff97ab5..f361ba790 100644 --- a/registry/contract/Cargo.toml +++ b/registry/contract/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib"] borsh = "1.0.0" near-sdk = "5.0.0-alpha.1" uint = { version = "0.9.3", default-features = false } -registry-types = { path = "../types", features = ["nearsdk"] } +registry-types = { path = "../types", features = ["near-sdk"] } [profile.release] codegen-units = 1 @@ -20,6 +20,3 @@ lto = true debug = false panic = "abort" overflow-checks = true - -[workspace] -members = [] diff --git a/registry/contract/src/lib.rs b/registry/contract/src/lib.rs index a7a4cf761..c9b000bff 100644 --- a/registry/contract/src/lib.rs +++ b/registry/contract/src/lib.rs @@ -21,6 +21,25 @@ pub type IndexersByAccount = UnorderedMap; +#[derive(BorshDeserialize, BorshSerialize, Debug)] +pub struct OldState { + registry: OldIndexersByAccount, + account_roles: Vec, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(crate = "near_sdk::serde")] +pub struct OldIndexerConfig { + pub code: String, + pub start_block_height: Option, + pub schema: Option, + pub filter: IndexerRule, +} + +pub type OldIndexersByAccount = UnorderedMap; + +pub type OldIndexerConfigByFunctionName = UnorderedMap; + // Migration types #[derive(BorshStorageKey, BorshSerialize)] pub enum StorageKeys { @@ -28,6 +47,8 @@ pub enum StorageKeys { Account(CryptoHash), // can be removed after migration RegistryV1, AccountV1(CryptoHash), + RegistryV2, + AccountV2(CryptoHash), } /// These roles are used to control access across the various contract methods. @@ -75,14 +96,6 @@ impl Default for Contract { account_id: "flatirons.near".parse().unwrap(), role: Role::Owner, }, - AccountRole { - account_id: "root.near".parse().unwrap(), - role: Role::Owner, - }, - AccountRole { - account_id: "khorolets.near".parse().unwrap(), - role: Role::Owner, - }, AccountRole { account_id: "darunrs.near".parse().unwrap(), role: Role::Owner, @@ -99,6 +112,52 @@ impl Default for Contract { // Implement the contract structure #[near_bindgen] impl Contract { + #[private] + #[init(ignore_state)] + pub fn migrate() -> Self { + let state: OldState = env::state_read().expect("failed to parse existing state"); + + let mut registry = IndexersByAccount::new(StorageKeys::RegistryV2); + + for (account_id, indexers) in state.registry.iter() { + let mut new_indexers: IndexerConfigByFunctionName = IndexerConfigByFunctionName::new( + StorageKeys::AccountV2(env::sha256_array(account_id.as_bytes())), + ); + + for (function_name, indexer_config) in indexers.iter() { + new_indexers.insert( + function_name.to_string(), + IndexerConfig { + updated_at_block_height: None, + created_at_block_height: env::block_height(), + schema: indexer_config.schema.clone(), + code: indexer_config.code.clone(), + start_block_height: indexer_config.start_block_height, + filter: indexer_config.filter.clone(), + }, + ); + } + + registry.insert(account_id.clone(), new_indexers); + } + + let account_roles: Vec<_> = Contract::default() + .account_roles + .into_iter() + .chain( + state + .account_roles + .into_iter() + .filter(|account_role| account_role.role == Role::User), + ) + .collect(); + + Self { + registry, + account_roles, + } + } + pub fn near_social_indexer_rule() -> IndexerRule { let contract = "social.near"; let method = "set"; @@ -244,7 +303,7 @@ impl Contract { } }; - let filter_rule: IndexerRule = match filter_json { + let filter: IndexerRule = match filter_json { Some(filter_json) => { let filter_rule: IndexerRule = serde_json::from_str(&filter_json).unwrap_or_else(|_| { @@ -262,20 +321,36 @@ impl Contract { &account_id ); - self.registry - .entry(account_id.clone()) - .or_insert(IndexerConfigByFunctionName::new(StorageKeys::Account( - env::sha256_array(account_id.as_bytes()), - ))) - .insert( - function_name, - IndexerConfig { + let account_indexers = + self.registry + .entry(account_id.clone()) + .or_insert(IndexerConfigByFunctionName::new(StorageKeys::Account( + env::sha256_array(account_id.as_bytes()), + ))); + + match account_indexers.entry(function_name) { + near_sdk::store::unordered_map::Entry::Occupied(mut entry) => { + let indexer = entry.get(); + entry.insert(IndexerConfig { code, start_block_height, schema, - filter: filter_rule, - }, - ); + filter, + updated_at_block_height: Some(env::block_height()), + created_at_block_height: indexer.created_at_block_height, + }); + } + near_sdk::store::unordered_map::Entry::Vacant(entry) => { + entry.insert(IndexerConfig { + code, + start_block_height, + schema, + filter, + updated_at_block_height: None, + created_at_block_height: env::block_height(), + }); + } + } } pub fn remove_indexer_function(&mut self, function_name: String, account_id: Option) { @@ -368,6 +443,99 @@ mod tests { use std::collections::HashMap; + #[test] + fn migrate() { + let mut registry = OldIndexersByAccount::new(StorageKeys::RegistryV1); + let account_id = "morgs.near".parse::().unwrap(); + let mut functions = OldIndexerConfigByFunctionName::new(StorageKeys::AccountV1( + env::sha256_array(account_id.as_bytes()), + )); + + functions.insert( + "test".to_string(), + OldIndexerConfig { + code: "return block;".to_string(), + start_block_height: None, + schema: None, + filter: Contract::near_social_indexer_rule(), + }, + ); + functions.insert( + "test2".to_string(), + OldIndexerConfig { + code: "return block2;".to_string(), + start_block_height: None, + schema: None, + filter: Contract::near_social_indexer_rule(), + }, + ); + registry.insert(account_id.clone(), functions); + + env::state_write(&OldState { + registry, + account_roles: vec![ + AccountRole { + account_id: account_id.clone(), + role: Role::Owner, + }, + AccountRole { + account_id: "should-be-removed.near".parse().unwrap(), + role: Role::Owner, + }, + AccountRole { + account_id: "bob.near".parse().unwrap(), + role: Role::User, + }, + ], + }); + + let contract = Contract::migrate(); + + assert_eq!( + contract + .registry + .get(&account_id) + .unwrap() + .get("test") + .unwrap(), + &IndexerConfig { + code: "return block;".to_string(), + start_block_height: None, + schema: None, + filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: env::block_height(), + } + ); + assert_eq!( + contract + .registry + .get(&account_id) + .unwrap() + .get("test2") + .unwrap(), + &IndexerConfig { + code: "return block2;".to_string(), + start_block_height: None, + schema: None, + filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: env::block_height(), + } + ); + assert_eq!( + contract.account_roles, + Contract::default() + .account_roles + .into_iter() + .chain(std::iter::once(AccountRole { + account_id: "bob.near".parse().unwrap(), + role: Role::User, + })) + .collect::>() + ); + } + #[test] fn list_account_roles() { let admins = vec![ @@ -584,6 +752,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }; contract.register_indexer_function( @@ -615,6 +785,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }; contract.register_indexer_function( "test".to_string(), @@ -728,6 +900,8 @@ mod tests { start_block_height: None, schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }; contract.register_indexer_function( @@ -750,6 +924,36 @@ mod tests { ); } + #[test] + fn sets_updated_at_and_created_at_for_new_account() { + let mut contract = Contract { + registry: IndexersByAccount::new(StorageKeys::Registry), + account_roles: vec![AccountRole { + account_id: "bob.near".parse().unwrap(), + role: Role::User, + }], + }; + + contract.register_indexer_function( + "test".to_string(), + "".to_string(), + Some(100), + Some("".to_string()), + None, + None, + ); + + let indexer_config = contract + .registry + .get(&"bob.near".parse::().unwrap()) + .unwrap() + .get("test") + .unwrap(); + + assert_eq!(indexer_config.updated_at_block_height, None); + assert_eq!(indexer_config.created_at_block_height, env::block_height()); + } + #[test] fn register_indexer_function_with_filter_function_call() { let mut contract = Contract { @@ -773,6 +977,8 @@ mod tests { id: None, name: None, }, + updated_at_block_height: None, + created_at_block_height: 0, }; contract.register_indexer_function( @@ -817,6 +1023,8 @@ mod tests { id: None, name: None, }, + updated_at_block_height: None, + created_at_block_height: 0, }; contract.register_indexer_function( @@ -862,6 +1070,56 @@ mod tests { ); } + #[test] + fn sets_updated_at_and_created_at_for_existing_account() { + let account_id = "bob.near".parse::().unwrap(); + let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( + env::sha256_array(account_id.as_bytes()), + )); + account_indexers.insert( + "test".to_string(), + IndexerConfig { + code: "var x= 1;".to_string(), + start_block_height: Some(43434343), + schema: None, + filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 100, + }, + ); + 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.register_indexer_function( + "test".to_string(), + "".to_string(), + Some(100), + Some("".to_string()), + None, + None, + ); + + let indexer_config = contract + .registry + .get(&"bob.near".parse::().unwrap()) + .unwrap() + .get("test") + .unwrap(); + + assert_eq!( + indexer_config.updated_at_block_height, + Some(env::block_height()) + ); + assert_eq!(indexer_config.created_at_block_height, 100); + } + #[test] fn register_indexer_function_for_existing_account() { let account_id = "bob.near".parse::().unwrap(); @@ -875,6 +1133,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }, ); let mut registry = IndexersByAccount::new(StorageKeys::Registry); @@ -891,6 +1151,8 @@ mod tests { start_block_height: None, schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }; contract.register_indexer_function( @@ -925,6 +1187,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }, ); let mut registry = IndexersByAccount::new(StorageKeys::Registry); @@ -958,6 +1222,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }, ); let mut registry = IndexersByAccount::new(StorageKeys::Registry); @@ -992,6 +1258,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }, ); let mut registry = IndexersByAccount::new(StorageKeys::Registry); @@ -1020,6 +1288,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }, ); let mut registry = IndexersByAccount::new(StorageKeys::Registry); @@ -1054,6 +1324,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }, ); let mut registry = IndexersByAccount::new(StorageKeys::Registry); @@ -1079,6 +1351,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }, ); account_indexers.insert( @@ -1088,6 +1362,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }, ); let mut registry = IndexersByAccount::new(StorageKeys::Registry); @@ -1136,6 +1412,8 @@ mod tests { start_block_height: None, schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }; let account_id = "bob.near".parse::().unwrap(); let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( @@ -1162,6 +1440,8 @@ mod tests { start_block_height: None, schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }; let account_id = "alice.near".parse::().unwrap(); let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( @@ -1196,6 +1476,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }; let account_id = "bob.near".parse::().unwrap(); let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( @@ -1225,6 +1507,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }; let account_id = "bob.near".parse::().unwrap(); let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( @@ -1262,6 +1546,8 @@ mod tests { start_block_height: Some(43434343), schema: None, filter: Contract::near_social_indexer_rule(), + updated_at_block_height: None, + created_at_block_height: 0, }; let account_id = "alice.near".parse::().unwrap(); let mut account_indexers = IndexerConfigByFunctionName::new(StorageKeys::Account( diff --git a/registry/types/Cargo.toml b/registry/types/Cargo.toml index 2965f121a..2b6264f88 100644 --- a/registry/types/Cargo.toml +++ b/registry/types/Cargo.toml @@ -4,12 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -borsh = { version = "1.2.1", features = ["derive"], optional = true} -serde = { version = "1.0.193", optional = true } +borsh = { version = "1.2.1", features = ["derive"] } +serde = { version = "1.0.193" } near-primitives = { version = "0.17.0", optional = true} near-sdk = { version = "5.0.0-alpha.1", optional = true } - -[features] -default = ["near-primitives", "borsh", "serde"] -nearsdk = ["near-sdk"] diff --git a/registry/types/src/lib.rs b/registry/types/src/lib.rs index 6f42e09ba..ddbd16bd8 100644 --- a/registry/types/src/lib.rs +++ b/registry/types/src/lib.rs @@ -66,6 +66,8 @@ pub struct IndexerConfig { pub start_block_height: Option, pub schema: Option, pub filter: IndexerRule, + pub updated_at_block_height: Option, + pub created_at_block_height: u64, } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]