From df57256efc334b808919d50405c774ce1b1a9fd2 Mon Sep 17 00:00:00 2001 From: ananas Date: Sun, 28 Sep 2025 21:02:40 +0100 Subject: [PATCH] refactor: get compressed account return type Option --- scripts/install.sh | 3 +- sdk-libs/client/src/indexer/indexer_trait.rs | 6 +- sdk-libs/client/src/indexer/photon_indexer.rs | 31 +++---- sdk-libs/client/src/rpc/indexer.rs | 6 +- .../photon-api/src/models/account_list.rs | 4 +- .../program-test/src/indexer/test_indexer.rs | 74 +++++++++-------- .../program-test/src/program_test/indexer.rs | 6 +- sdk-tests/client-test/tests/light_client.rs | 80 +++++++++++++++++-- .../client-test/tests/light_program_test.rs | 79 ++++++++++++++++-- .../programs/sdk-anchor-test/tests/test.rs | 3 +- sdk-tests/sdk-native-test/tests/test.rs | 3 +- 11 files changed, 217 insertions(+), 78 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 4780e7cb28..78f6b57d8d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -210,7 +210,8 @@ install_photon() { if [ "$photon_installed" = false ] || [ "$photon_correct_version" = false ]; then echo "Installing Photon indexer (version $expected_version)..." # Use git commit for now as specified in constants.ts - cargo install --git https://github.com/helius-labs/photon.git --rev b0ad386858384c22b4bb6a3bbbcd6a65911dac68 --locked --force + # Allow dead code warnings for external dependency compilation + RUSTFLAGS="-A dead-code" cargo install --git https://github.com/helius-labs/photon.git --rev b0ad386858384c22b4bb6a3bbbcd6a65911dac68 --locked --force log "photon" else echo "Photon already installed with correct version, skipping..." diff --git a/sdk-libs/client/src/indexer/indexer_trait.rs b/sdk-libs/client/src/indexer/indexer_trait.rs index 577372b6ed..c2f5c873bc 100644 --- a/sdk-libs/client/src/indexer/indexer_trait.rs +++ b/sdk-libs/client/src/indexer/indexer_trait.rs @@ -21,14 +21,14 @@ pub trait Indexer: std::marker::Send + std::marker::Sync { &self, address: Address, config: Option, - ) -> Result, IndexerError>; + ) -> Result>, IndexerError>; /// Returns the compressed account with the given address or hash. async fn get_compressed_account_by_hash( &self, hash: Hash, config: Option, - ) -> Result, IndexerError>; + ) -> Result>, IndexerError>; /// Returns the owner’s compressed accounts. async fn get_compressed_accounts_by_owner( @@ -153,7 +153,7 @@ pub trait Indexer: std::marker::Send + std::marker::Sync { addresses: Option>, hashes: Option>, config: Option, - ) -> Result>, IndexerError>; + ) -> Result>>, IndexerError>; /// Returns proofs that the new addresses are not taken already and can be created. async fn get_multiple_new_address_proofs( diff --git a/sdk-libs/client/src/indexer/photon_indexer.rs b/sdk-libs/client/src/indexer/photon_indexer.rs index 3409a5df7d..f06355f5d5 100644 --- a/sdk-libs/client/src/indexer/photon_indexer.rs +++ b/sdk-libs/client/src/indexer/photon_indexer.rs @@ -179,7 +179,7 @@ impl Indexer for PhotonIndexer { &self, address: Address, config: Option, - ) -> Result, IndexerError> { + ) -> Result>, IndexerError> { let config = config.unwrap_or_default(); self.retry(config.retry_config, || async { let params = self.build_account_params(Some(address), None)?; @@ -201,11 +201,10 @@ impl Indexer for PhotonIndexer { if api_response.context.slot < config.slot { return Err(IndexerError::IndexerNotSyncedToSlot); } - let account_data = api_response - .value - .ok_or(IndexerError::AccountNotFound) - .map(|boxed| *boxed)?; - let account = CompressedAccount::try_from(&account_data)?; + let account = match api_response.value { + Some(boxed) => Some(CompressedAccount::try_from(&*boxed)?), + None => None, + }; Ok(Response { context: Context { @@ -221,7 +220,7 @@ impl Indexer for PhotonIndexer { &self, hash: Hash, config: Option, - ) -> Result, IndexerError> { + ) -> Result>, IndexerError> { let config = config.unwrap_or_default(); self.retry(config.retry_config, || async { let params = self.build_account_params(None, Some(hash))?; @@ -243,11 +242,10 @@ impl Indexer for PhotonIndexer { if api_response.context.slot < config.slot { return Err(IndexerError::IndexerNotSyncedToSlot); } - let account_data = api_response - .value - .ok_or(IndexerError::AccountNotFound) - .map(|boxed| *boxed)?; - let account = CompressedAccount::try_from(&account_data)?; + let account = match api_response.value { + Some(boxed) => Some(CompressedAccount::try_from(&*boxed)?), + None => None, + }; Ok(Response { context: Context { @@ -1209,7 +1207,7 @@ impl Indexer for PhotonIndexer { addresses: Option>, hashes: Option>, config: Option, - ) -> Result>, IndexerError> { + ) -> Result>>, IndexerError> { let config = config.unwrap_or_default(); self.retry(config.retry_config, || async { let hashes = hashes.clone(); @@ -1242,8 +1240,11 @@ impl Indexer for PhotonIndexer { .value .items .iter() - .map(CompressedAccount::try_from) - .collect::, IndexerError>>()?; + .map(|account_opt| match account_opt { + Some(account) => CompressedAccount::try_from(account).map(Some), + None => Ok(None), + }) + .collect::>, IndexerError>>()?; Ok(Response { context: Context { diff --git a/sdk-libs/client/src/rpc/indexer.rs b/sdk-libs/client/src/rpc/indexer.rs index 56963ed64c..fbcba6c5ab 100644 --- a/sdk-libs/client/src/rpc/indexer.rs +++ b/sdk-libs/client/src/rpc/indexer.rs @@ -67,7 +67,7 @@ impl Indexer for LightClient { &self, address: Address, config: Option, - ) -> Result, IndexerError> { + ) -> Result>, IndexerError> { Ok(self .indexer .as_ref() @@ -80,7 +80,7 @@ impl Indexer for LightClient { &self, hash: Hash, config: Option, - ) -> Result, IndexerError> { + ) -> Result>, IndexerError> { Ok(self .indexer .as_ref() @@ -136,7 +136,7 @@ impl Indexer for LightClient { addresses: Option>, hashes: Option>, config: Option, - ) -> Result>, IndexerError> { + ) -> Result>>, IndexerError> { Ok(self .indexer .as_ref() diff --git a/sdk-libs/photon-api/src/models/account_list.rs b/sdk-libs/photon-api/src/models/account_list.rs index ba61d13904..c2b45d9524 100644 --- a/sdk-libs/photon-api/src/models/account_list.rs +++ b/sdk-libs/photon-api/src/models/account_list.rs @@ -13,11 +13,11 @@ use crate::models; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct AccountList { #[serde(rename = "items")] - pub items: Vec, + pub items: Vec>, } impl AccountList { - pub fn new(items: Vec) -> AccountList { + pub fn new(items: Vec>) -> AccountList { AccountList { items } } } diff --git a/sdk-libs/program-test/src/indexer/test_indexer.rs b/sdk-libs/program-test/src/indexer/test_indexer.rs index 3ceaf1fdb0..eae058dc6d 100644 --- a/sdk-libs/program-test/src/indexer/test_indexer.rs +++ b/sdk-libs/program-test/src/indexer/test_indexer.rs @@ -188,16 +188,16 @@ impl Indexer for TestIndexer { &self, address: Address, _config: Option, - ) -> Result, IndexerError> { + ) -> Result>, IndexerError> { let account = self .compressed_accounts .iter() .find(|acc| acc.compressed_account.address == Some(address)); - let account_data = account - .ok_or(IndexerError::AccountNotFound)? - .clone() - .try_into()?; + let account_data = match account { + Some(acc) => Some(acc.clone().try_into()?), + None => None, + }; Ok(Response { context: Context { @@ -211,7 +211,7 @@ impl Indexer for TestIndexer { &self, hash: Hash, _config: Option, - ) -> Result, IndexerError> { + ) -> Result>, IndexerError> { let res = self .compressed_accounts .iter() @@ -228,10 +228,10 @@ impl Indexer for TestIndexer { res }; - let account_data = account - .ok_or(IndexerError::AccountNotFound)? - .clone() - .try_into()?; + let account_data = match account { + Some(acc) => Some(acc.clone().try_into()?), + None => None, + }; Ok(Response { context: Context { @@ -293,11 +293,14 @@ impl Indexer for TestIndexer { )) } }; + let account = account_response + .value + .ok_or(IndexerError::AccountNotFound)?; Ok(Response { context: Context { slot: self.get_current_slot(), }, - value: account_response.value.lamports, + value: account.lamports, }) } @@ -340,38 +343,42 @@ impl Indexer for TestIndexer { addresses: Option>, hashes: Option>, _config: Option, - ) -> Result>, IndexerError> { + ) -> Result>>, IndexerError> { match (addresses, hashes) { (Some(addresses), _) => { - let accounts = self - .compressed_accounts + let accounts: Result>, IndexerError> = addresses .iter() - .filter(|acc| { - acc.compressed_account - .address - .is_some_and(|addr| addresses.contains(&addr)) + .map(|addr| { + self.compressed_accounts + .iter() + .find(|acc| acc.compressed_account.address == Some(*addr)) + .map(|acc| acc.clone().try_into()) + .transpose() }) - .map(|acc| acc.clone().try_into()) - .collect::, IndexerError>>()?; + .collect(); Ok(Response { context: Context { slot: self.get_current_slot(), }, - value: Items { items: accounts }, + value: Items { items: accounts? }, }) } (_, Some(hashes)) => { - let accounts = self - .compressed_accounts + let accounts: Result>, IndexerError> = hashes .iter() - .filter(|acc| acc.hash().is_ok_and(|hash| hashes.contains(&hash))) - .map(|acc| acc.clone().try_into()) - .collect::, IndexerError>>()?; + .map(|hash| { + self.compressed_accounts + .iter() + .find(|acc| acc.hash() == Ok(*hash)) + .map(|acc| acc.clone().try_into()) + .transpose() + }) + .collect(); Ok(Response { context: Context { slot: self.get_current_slot(), }, - value: Items { items: accounts }, + value: Items { items: accounts? }, }) } (None, None) => Err(IndexerError::InvalidParameters( @@ -450,7 +457,8 @@ impl Indexer for TestIndexer { for hash in hashes.iter() { let account = self.get_compressed_account_by_hash(*hash, None).await?; - state_merkle_tree_pubkeys.push(account.value.tree_info.tree); + let account_data = account.value.ok_or(IndexerError::AccountNotFound)?; + state_merkle_tree_pubkeys.push(account_data.tree_info.tree); } let mut proof_inputs = vec![]; @@ -2089,13 +2097,9 @@ impl TestIndexer { let mut state_merkle_tree_pubkeys = Vec::new(); for hash in hashes.iter() { - state_merkle_tree_pubkeys.push( - self.get_compressed_account_by_hash(*hash, None) - .await? - .value - .tree_info - .tree, - ); + let account = self.get_compressed_account_by_hash(*hash, None).await?; + let account_data = account.value.ok_or(IndexerError::AccountNotFound)?; + state_merkle_tree_pubkeys.push(account_data.tree_info.tree); } let state_merkle_tree_pubkeys = if state_merkle_tree_pubkeys.is_empty() { diff --git a/sdk-libs/program-test/src/program_test/indexer.rs b/sdk-libs/program-test/src/program_test/indexer.rs index 744148bf66..28232fd616 100644 --- a/sdk-libs/program-test/src/program_test/indexer.rs +++ b/sdk-libs/program-test/src/program_test/indexer.rs @@ -67,7 +67,7 @@ impl Indexer for LightProgramTest { &self, address: Address, config: Option, - ) -> Result, IndexerError> { + ) -> Result>, IndexerError> { Ok(self .indexer .as_ref() @@ -80,7 +80,7 @@ impl Indexer for LightProgramTest { &self, hash: Hash, config: Option, - ) -> Result, IndexerError> { + ) -> Result>, IndexerError> { Ok(self .indexer .as_ref() @@ -132,7 +132,7 @@ impl Indexer for LightProgramTest { addresses: Option>, hashes: Option>, config: Option, - ) -> Result>, IndexerError> { + ) -> Result>>, IndexerError> { Ok(self .indexer .as_ref() diff --git a/sdk-tests/client-test/tests/light_client.rs b/sdk-tests/client-test/tests/light_client.rs index c63a4915e1..6dffc19318 100644 --- a/sdk-tests/client-test/tests/light_client.rs +++ b/sdk-tests/client-test/tests/light_client.rs @@ -137,6 +137,7 @@ async fn test_all_endpoints() { assert_eq!(accounts.items.len(), account_hashes.len()); for item in accounts.items.iter() { + let item = item.as_ref().unwrap(); assert!(initial_accounts.items.iter().any(|x| x.hash == item.hash)); } // Currently fails because photon doesn't deliver cpi context accounts. @@ -150,6 +151,7 @@ async fn test_all_endpoints() { .value; assert_eq!(accounts.items.len(), initial_accounts.items.len()); for item in accounts.items.iter() { + let item = item.as_ref().unwrap(); assert!(initial_accounts.items.iter().any(|x| x.hash == item.hash)); } // Currently fails because photon doesn't deliver cpi context accounts. @@ -220,12 +222,14 @@ async fn test_all_endpoints() { assert_eq!(result, expected_result); } // 4. get_compressed_account - let first_account = rpc - .get_compressed_account(accounts.items[0].address.unwrap(), None) + let first_account = accounts.items[0].as_ref().unwrap(); + let fetched_account = rpc + .get_compressed_account(first_account.address.unwrap(), None) .await .unwrap() - .value; - assert_eq!(first_account, accounts.items[0]); + .value + .unwrap(); + assert_eq!(fetched_account, *first_account); // 5. get_compressed_account_by_hash { @@ -233,8 +237,9 @@ async fn test_all_endpoints() { .get_compressed_account_by_hash(first_account.hash, None) .await .unwrap() - .value; - assert_eq!(account, first_account); + .value + .unwrap(); + assert_eq!(account, *first_account); } // 6. get_compressed_balance { @@ -302,7 +307,68 @@ async fn test_all_endpoints() { |s| s.signature == signature_1.to_string() || s.signature == signature.to_string() )); } - // 11. get_multiple_compressed_account_proofs + + // 11. Test that non-existent accounts return None + { + // Test get_compressed_account with non-existent address + let non_existent_address = [0u8; 32]; + let account = rpc + .get_compressed_account(non_existent_address, None) + .await + .unwrap() + .value; + assert!(account.is_none(), "Expected None for non-existent address"); + + // Test get_compressed_account_by_hash with non-existent hash + let non_existent_hash = [0u8; 32]; + let account = rpc + .get_compressed_account_by_hash(non_existent_hash, None) + .await + .unwrap() + .value; + assert!(account.is_none(), "Expected None for non-existent hash"); + + // Test get_multiple_compressed_accounts with mix of existing and non-existent + let mixed_hashes = vec![ + account_hashes[0], // existing + [0u8; 32], // non-existent + account_hashes[1], // existing + ]; + let accounts = rpc + .get_multiple_compressed_accounts(None, Some(mixed_hashes.clone()), None) + .await + .unwrap() + .value; + assert_eq!(accounts.items.len(), 3); + assert!(accounts.items[0].is_some(), "First account should exist"); + assert!( + accounts.items[1].is_none(), + "Second account should not exist" + ); + assert!(accounts.items[2].is_some(), "Third account should exist"); + + // Test with addresses + let first_existing_address = accounts.items[0].as_ref().unwrap().address.unwrap(); + let mixed_addresses = vec![ + first_existing_address, // existing + [0u8; 32], // non-existent + ]; + let accounts_by_addr = rpc + .get_multiple_compressed_accounts(Some(mixed_addresses), None, None) + .await + .unwrap() + .value; + assert_eq!(accounts_by_addr.items.len(), 2); + assert!( + accounts_by_addr.items[0].is_some(), + "First account should exist" + ); + assert!( + accounts_by_addr.items[1].is_none(), + "Second account should not exist" + ); + } + // 12. get_multiple_compressed_account_proofs { let proofs = rpc .get_multiple_compressed_account_proofs(account_hashes.to_vec(), None) diff --git a/sdk-tests/client-test/tests/light_program_test.rs b/sdk-tests/client-test/tests/light_program_test.rs index 52c4e1a8a3..e3af2ac78f 100644 --- a/sdk-tests/client-test/tests/light_program_test.rs +++ b/sdk-tests/client-test/tests/light_program_test.rs @@ -105,6 +105,7 @@ async fn test_all_endpoints() { assert_eq!(accounts.items.len(), account_hashes.len()); for item in accounts.items.iter() { + let item = item.as_ref().unwrap(); assert!(initial_accounts.items.iter().any(|x| x.hash == item.hash)); } // Currently fails because photon doesn't deliver cpi context accounts. @@ -118,6 +119,7 @@ async fn test_all_endpoints() { .value; assert_eq!(accounts.items.len(), initial_accounts.items.len()); for item in accounts.items.iter() { + let item = item.as_ref().unwrap(); assert!(initial_accounts.items.iter().any(|x| x.hash == item.hash)); } // Currently fails because photon doesn't deliver cpi context accounts. @@ -141,12 +143,14 @@ async fn test_all_endpoints() { assert_eq!(result.addresses.len(), new_addresses.len()); } // 4. get_compressed_account - let first_account = rpc - .get_compressed_account(accounts.items[0].address.unwrap(), None) + let first_account = accounts.items[0].as_ref().unwrap(); + let fetched_account = rpc + .get_compressed_account(first_account.address.unwrap(), None) .await .unwrap() - .value; - assert_eq!(first_account, accounts.items[0]); + .value + .unwrap(); + assert_eq!(fetched_account, *first_account); // 5. get_compressed_account_by_hash { @@ -154,8 +158,9 @@ async fn test_all_endpoints() { .get_compressed_account_by_hash(first_account.hash, None) .await .unwrap() - .value; - assert_eq!(account, first_account); + .value + .unwrap(); + assert_eq!(account, *first_account); } // 6. get_compressed_balance { @@ -166,7 +171,67 @@ async fn test_all_endpoints() { .value; assert_eq!(balance, first_account.lamports); } - // // 7. get_compressed_balance_by_owner + // 7. Test that non-existent accounts return None + { + // Test get_compressed_account with non-existent address + let non_existent_address = [0u8; 32]; + let account = rpc + .get_compressed_account(non_existent_address, None) + .await + .unwrap() + .value; + assert!(account.is_none(), "Expected None for non-existent address"); + + // Test get_compressed_account_by_hash with non-existent hash + let non_existent_hash = [0u8; 32]; + let account = rpc + .get_compressed_account_by_hash(non_existent_hash, None) + .await + .unwrap() + .value; + assert!(account.is_none(), "Expected None for non-existent hash"); + + // Test get_multiple_compressed_accounts with mix of existing and non-existent + let mixed_hashes = vec![ + account_hashes[0], // existing + [0u8; 32], // non-existent + account_hashes[1], // existing + ]; + let accounts = rpc + .get_multiple_compressed_accounts(None, Some(mixed_hashes.clone()), None) + .await + .unwrap() + .value; + assert_eq!(accounts.items.len(), 3); + assert!(accounts.items[0].is_some(), "First account should exist"); + assert!( + accounts.items[1].is_none(), + "Second account should not exist" + ); + assert!(accounts.items[2].is_some(), "Third account should exist"); + + // Test with addresses + let first_existing_address = accounts.items[0].as_ref().unwrap().address.unwrap(); + let mixed_addresses = vec![ + first_existing_address, // existing + [0u8; 32], // non-existent + ]; + let accounts_by_addr = rpc + .get_multiple_compressed_accounts(Some(mixed_addresses), None, None) + .await + .unwrap() + .value; + assert_eq!(accounts_by_addr.items.len(), 2); + assert!( + accounts_by_addr.items[0].is_some(), + "First account should exist" + ); + assert!( + accounts_by_addr.items[1].is_none(), + "Second account should not exist" + ); + } + // // 8. get_compressed_balance_by_owner // { // let balance = rpc // .get_compressed_balance_by_owner(&payer_pubkey, None) diff --git a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs index 92e268446e..ae00d464e2 100644 --- a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs +++ b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs @@ -41,7 +41,8 @@ async fn test_anchor_sdk_test() { .get_compressed_account(address, None) .await .unwrap() - .value; + .value + .unwrap(); let record = &compressed_account.data.as_ref().unwrap().data; let record = MyCompressedAccount::deserialize(&mut &record[..]).unwrap(); diff --git a/sdk-tests/sdk-native-test/tests/test.rs b/sdk-tests/sdk-native-test/tests/test.rs index ea1e690657..a38fdc3882 100644 --- a/sdk-tests/sdk-native-test/tests/test.rs +++ b/sdk-tests/sdk-native-test/tests/test.rs @@ -64,7 +64,8 @@ async fn test_sdk_native_test() { .await .unwrap() .value - .clone(); + .clone() + .unwrap(); assert_eq!(compressed_pda.address.unwrap(), address); update_pda(&payer, &mut rpc, [2u8; ARRAY_LEN], compressed_pda.into())