diff --git a/src/api/method/get_compressed_account_proof/mod.rs b/src/api/method/get_compressed_account_proof/mod.rs new file mode 100644 index 00000000..5ea27e27 --- /dev/null +++ b/src/api/method/get_compressed_account_proof/mod.rs @@ -0,0 +1,11 @@ +mod v1; +mod v2; + +pub use v1::{ + get_compressed_account_proof, GetCompressedAccountProofResponse, + GetCompressedAccountProofResponseValueV1, +}; +pub use v2::{ + get_compressed_account_proof_v2, GetCompressedAccountProofResponseV2, + GetCompressedAccountProofResponseValueV2, +}; diff --git a/src/api/method/get_compressed_account_proof.rs b/src/api/method/get_compressed_account_proof/v1.rs similarity index 52% rename from src/api/method/get_compressed_account_proof.rs rename to src/api/method/get_compressed_account_proof/v1.rs index 98c87816..2dc39acb 100644 --- a/src/api/method/get_compressed_account_proof.rs +++ b/src/api/method/get_compressed_account_proof/v1.rs @@ -1,7 +1,9 @@ -use super::{super::error::PhotonApiError, utils::HashRequest}; +use crate::api::error::PhotonApiError; +use crate::api::method::utils::HashRequest; use crate::common::typedefs::context::Context; -use crate::ingester::persist::get_multiple_compressed_leaf_proofs; -use crate::ingester::persist::persisted_state_tree::MerkleProofWithContext; +use crate::common::typedefs::hash::Hash; +use crate::common::typedefs::serializable_pubkey::SerializablePubkey; +use crate::ingester::persist::{get_multiple_compressed_leaf_proofs, MerkleProofWithContext}; use sea_orm::{ConnectionTrait, DatabaseBackend, DatabaseConnection, Statement, TransactionTrait}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -10,7 +12,32 @@ use utoipa::ToSchema; #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct GetCompressedAccountProofResponse { pub context: Context, - pub value: MerkleProofWithContext, + pub value: GetCompressedAccountProofResponseValueV1, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +#[allow(non_snake_case)] +pub struct GetCompressedAccountProofResponseValueV1 { + pub proof: Vec, + pub root: Hash, + pub leaf_index: u32, + pub hash: Hash, + pub merkle_tree: SerializablePubkey, + pub root_seq: u64, +} + +impl From for GetCompressedAccountProofResponseValueV1 { + fn from(proof: MerkleProofWithContext) -> Self { + GetCompressedAccountProofResponseValueV1 { + proof: proof.proof, + root: proof.root, + leaf_index: proof.leaf_index, + hash: proof.hash, + merkle_tree: proof.merkle_tree, + root_seq: proof.root_seq, + } + } } pub async fn get_compressed_account_proof( @@ -32,7 +59,7 @@ pub async fn get_compressed_account_proof( .into_iter() .next() .map(|account| GetCompressedAccountProofResponse { - value: account, + value: account.into(), context, }) .ok_or(PhotonApiError::RecordNotFound( diff --git a/src/api/method/get_compressed_account_proof/v2.rs b/src/api/method/get_compressed_account_proof/v2.rs new file mode 100644 index 00000000..4511fa54 --- /dev/null +++ b/src/api/method/get_compressed_account_proof/v2.rs @@ -0,0 +1,141 @@ +use crate::api::error::PhotonApiError; +use crate::api::method::get_validity_proof::ContextInfo; +use crate::api::method::utils::HashRequest; +use crate::common::typedefs::context::Context; +use crate::common::typedefs::hash::Hash; +use crate::common::typedefs::serializable_pubkey::SerializablePubkey; +use crate::dao::generated::{accounts, state_trees}; +use crate::ingester::persist::{ + get_multiple_compressed_leaf_proofs, get_multiple_compressed_leaf_proofs_by_indices, + MerkleProofWithContext, +}; +use jsonrpsee_core::Serialize; +use sea_orm::{ + ColumnTrait, ConnectionTrait, DatabaseBackend, DatabaseConnection, EntityTrait, QueryFilter, + Statement, TransactionTrait, +}; +use serde::Deserialize; +use utoipa::ToSchema; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct GetCompressedAccountProofResponseV2 { + pub context: Context, + pub value: GetCompressedAccountProofResponseValueV2, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct GetCompressedAccountProofResponseValueV2 { + pub proof: Vec, + pub root: Hash, + pub leaf_index: u32, + pub hash: Hash, + pub root_seq: u64, + pub prove_by_index: bool, + pub context: ContextInfo, +} + +impl From for GetCompressedAccountProofResponseValueV2 { + fn from(proof: MerkleProofWithContext) -> Self { + GetCompressedAccountProofResponseValueV2 { + proof: proof.proof, + root: proof.root, + leaf_index: proof.leaf_index, + hash: proof.hash, + root_seq: proof.root_seq, + prove_by_index: false, + // Default values to be overridden as needed + context: ContextInfo { + tree_type: 0, + merkle_tree: proof.merkle_tree, + queue: Default::default(), + cpi_context: None, + }, + } + } +} + +pub async fn get_compressed_account_proof_v2( + conn: &DatabaseConnection, + request: HashRequest, +) -> Result { + let context = Context::extract(conn).await?; + let hash = request.hash; + let tx = conn.begin().await?; + if tx.get_database_backend() == DatabaseBackend::Postgres { + tx.execute(Statement::from_string( + tx.get_database_backend(), + "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;".to_string(), + )) + .await?; + } + + let account = accounts::Entity::find() + .filter(accounts::Column::Hash.eq(hash.to_vec())) + .one(&tx) + .await?; + + if account.is_none() { + return Err(PhotonApiError::RecordNotFound( + "Account not found".to_string(), + )); + } + + let leaf_node = state_trees::Entity::find() + .filter( + state_trees::Column::Hash + .eq(hash.to_vec()) + .and(state_trees::Column::Level.eq(0)), + ) + .one(&tx) + .await?; + + // Determine how to generate the proof based on available data + let mut result: GetCompressedAccountProofResponseValueV2 = if leaf_node.is_some() { + let mut response: GetCompressedAccountProofResponseValueV2 = + get_multiple_compressed_leaf_proofs(&tx, vec![hash]) + .await? + .into_iter() + .next() + .ok_or(PhotonApiError::RecordNotFound( + "Account not found by hash".to_string(), + ))? + .into(); + response.prove_by_index = false; + response + } else if let Some(account) = account.clone() { + // Use index-based proof if we found the account in a queue but not in state_trees + let leaf_index = account.leaf_index as u64; + let merkle_tree = SerializablePubkey::try_from(account.tree.clone())?; + let mut response: GetCompressedAccountProofResponseValueV2 = + get_multiple_compressed_leaf_proofs_by_indices(&tx, merkle_tree, vec![leaf_index]) + .await? + .into_iter() + .next() + .ok_or(PhotonApiError::RecordNotFound( + "Account not found by index".to_string(), + ))? + .into(); + response.prove_by_index = true; + response + } else { + return Err(PhotonApiError::RecordNotFound( + "Account not found".to_string(), + )); + }; + + // Enrich with account data if available + if let Some(account) = account { + result.context.tree_type = account.tree_type as u16; + result.context.queue = SerializablePubkey::try_from(account.queue)?; + } + + let response = GetCompressedAccountProofResponseV2 { + value: result, + context, + }; + + tx.commit().await?; + Ok(response) +} diff --git a/src/api/method/get_multiple_compressed_account_proofs.rs b/src/api/method/get_multiple_compressed_account_proofs/mod.rs similarity index 59% rename from src/api/method/get_multiple_compressed_account_proofs.rs rename to src/api/method/get_multiple_compressed_account_proofs/mod.rs index 2c342d88..c3322043 100644 --- a/src/api/method/get_multiple_compressed_account_proofs.rs +++ b/src/api/method/get_multiple_compressed_account_proofs/mod.rs @@ -1,9 +1,14 @@ -use crate::ingester::persist::persisted_state_tree::MerkleProofWithContext; +mod v2; +pub use v2::{ + get_multiple_compressed_account_proofs_v2, GetMultipleCompressedAccountProofsResponseV2, +}; use super::{super::error::PhotonApiError, utils::PAGE_LIMIT}; use crate::common::typedefs::context::Context; use crate::common::typedefs::hash::Hash; +use crate::common::typedefs::serializable_pubkey::SerializablePubkey; use crate::ingester::persist::get_multiple_compressed_leaf_proofs; +use crate::ingester::persist::MerkleProofWithContext; use sea_orm::{ConnectionTrait, DatabaseBackend, DatabaseConnection, Statement, TransactionTrait}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -13,7 +18,31 @@ use utoipa::ToSchema; #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct GetMultipleCompressedAccountProofsResponse { pub context: Context, - pub value: Vec, + pub value: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct GetMultipleCompressedAccountProofsResponseValue { + pub proof: Vec, + pub root: Hash, + pub leaf_index: u32, + pub hash: Hash, + pub merkle_tree: SerializablePubkey, + pub root_seq: u64, +} + +impl From for GetMultipleCompressedAccountProofsResponseValue { + fn from(proof: MerkleProofWithContext) -> Self { + GetMultipleCompressedAccountProofsResponseValue { + proof: proof.proof, + root: proof.root, + leaf_index: proof.leaf_index, + hash: proof.hash, + merkle_tree: proof.merkle_tree, + root_seq: proof.root_seq, + } + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] @@ -43,7 +72,7 @@ pub async fn get_multiple_compressed_account_proofs( let proofs = get_multiple_compressed_leaf_proofs(&tx, request).await?; tx.commit().await?; Ok(GetMultipleCompressedAccountProofsResponse { - value: proofs, + value: proofs.into_iter().map(Into::into).collect(), context, }) } diff --git a/src/api/method/get_multiple_compressed_account_proofs/v2.rs b/src/api/method/get_multiple_compressed_account_proofs/v2.rs new file mode 100644 index 00000000..8b961e0d --- /dev/null +++ b/src/api/method/get_multiple_compressed_account_proofs/v2.rs @@ -0,0 +1,179 @@ +use crate::api::error::PhotonApiError; +use crate::api::method::get_compressed_account_proof::GetCompressedAccountProofResponseValueV2; +use crate::api::method::get_validity_proof::ContextInfo; +use crate::api::method::utils::PAGE_LIMIT; +use crate::common::typedefs::context::Context; +use crate::common::typedefs::hash::Hash; +use crate::common::typedefs::serializable_pubkey::SerializablePubkey; +use crate::dao::generated::{accounts, state_trees}; +use crate::ingester::persist::{ + get_multiple_compressed_leaf_proofs, get_multiple_compressed_leaf_proofs_by_indices, +}; +use jsonrpsee_core::Serialize; +use sea_orm::{ + ColumnTrait, ConnectionTrait, DatabaseBackend, DatabaseConnection, EntityTrait, QueryFilter, + Statement, TransactionTrait, +}; +use serde::Deserialize; +use std::collections::HashMap; +use utoipa::ToSchema; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct GetMultipleCompressedAccountProofsResponseV2 { + pub context: Context, + pub value: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] +pub struct HashList(pub Vec); + +pub async fn get_multiple_compressed_account_proofs_v2( + conn: &DatabaseConnection, + request: HashList, +) -> Result { + let hashes = request.0; + + // Validate input size + if hashes.len() > PAGE_LIMIT as usize { + return Err(PhotonApiError::ValidationError(format!( + "Too many hashes requested {}. Maximum allowed: {}", + hashes.len(), + PAGE_LIMIT + ))); + } + + let context = Context::extract(conn).await?; + let tx = conn.begin().await?; + + // Set transaction isolation level for PostgreSQL + if tx.get_database_backend() == DatabaseBackend::Postgres { + tx.execute(Statement::from_string( + tx.get_database_backend(), + "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;".to_string(), + )) + .await?; + } + + // Find accounts for all hashes + let accounts = accounts::Entity::find() + .filter(accounts::Column::Hash.is_in(hashes.iter().map(|h| h.to_vec()).collect::>())) + .all(&tx) + .await?; + + if accounts.len() != hashes.len() { + return Err(PhotonApiError::RecordNotFound( + "Some accounts not found".to_string(), + )); + } + + // Create a map from hash to account for easy lookup + let account_map: HashMap, accounts::Model> = accounts + .into_iter() + .map(|acc| (acc.hash.clone(), acc)) + .collect(); + + // Find leaf nodes in state_trees for all hashes + let leaf_nodes = state_trees::Entity::find() + .filter( + state_trees::Column::Hash + .is_in(hashes.iter().map(|h| h.to_vec()).collect::>()) + .and(state_trees::Column::Level.eq(0)), + ) + .all(&tx) + .await?; + + // Create a set of hashes found in state_trees + let state_tree_hashes: std::collections::HashSet> = + leaf_nodes.iter().map(|node| node.hash.clone()).collect(); + + // Split hashes into those found in state_trees (for hash-based proofs) + // and those only found in accounts (for index-based proofs) + let mut hash_based_proofs: Vec = Vec::new(); + let mut index_based_proofs: Vec<(Hash, SerializablePubkey, u64)> = Vec::new(); + + for hash in &hashes { + if state_tree_hashes.contains(&hash.to_vec()) { + // Found in state_trees, use hash-based proof + hash_based_proofs.push(hash.clone()); + } else if let Some(account) = account_map.get(&hash.to_vec()) { + // Found in accounts but not in state_trees, use index-based proof + let merkle_tree = SerializablePubkey::try_from(account.tree.clone())?; + let leaf_index = account.leaf_index as u64; + index_based_proofs.push((hash.clone(), merkle_tree, leaf_index)); + } + } + + // Get proofs for both methods + let mut hash_based_result = if !hash_based_proofs.is_empty() { + get_multiple_compressed_leaf_proofs(&tx, hash_based_proofs) + .await? + .into_iter() + .map(|proof| { + let mut response_value: GetCompressedAccountProofResponseValueV2 = proof.into(); + response_value.prove_by_index = false; + response_value + }) + .collect::>() + } else { + Vec::new() + }; + + // Process index-based proofs + let mut index_based_result = Vec::new(); + for (merkle_tree, indices) in index_based_proofs + .iter() + .map(|(_, tree, idx)| (tree, idx)) + .fold(HashMap::new(), |mut acc, (tree, idx)| { + acc.entry(*tree).or_insert_with(Vec::new).push(*idx); + acc + }) + { + let proofs = + get_multiple_compressed_leaf_proofs_by_indices(&tx, merkle_tree, indices).await?; + + for proof in proofs { + let mut response_value: GetCompressedAccountProofResponseValueV2 = proof.into(); + response_value.prove_by_index = true; + index_based_result.push(response_value); + } + } + + // Combine results + let mut result = Vec::new(); + result.append(&mut hash_based_result); + result.append(&mut index_based_result); + + // Enrich with account data + for value in &mut result { + if let Some(account) = account_map.get(&value.hash.to_vec()) { + value.context = ContextInfo { + tree_type: account.tree_type as u16, + merkle_tree: SerializablePubkey::try_from(account.tree.clone())?, + queue: SerializablePubkey::try_from(account.queue.clone())?, + cpi_context: None, + }; + } + } + + // Sort the result to match the original request order + let hash_to_index: HashMap, usize> = hashes + .iter() + .enumerate() + .map(|(i, hash)| (hash.to_vec(), i)) + .collect(); + + result.sort_by_key(|value| { + hash_to_index + .get(&value.hash.to_vec()) + .cloned() + .unwrap_or(usize::MAX) + }); + + tx.commit().await?; + + Ok(GetMultipleCompressedAccountProofsResponseV2 { + value: result, + context, + }) +} diff --git a/src/api/method/get_multiple_new_address_proofs.rs b/src/api/method/get_multiple_new_address_proofs.rs index bc37e21e..fca33450 100644 --- a/src/api/method/get_multiple_new_address_proofs.rs +++ b/src/api/method/get_multiple_new_address_proofs.rs @@ -87,7 +87,7 @@ pub async fn get_multiple_new_address_proofs_helper( proof: proof.proof, lowElementLeafIndex: model.leaf_index as u32, merkleTree: tree, - rootSeq: proof.rootSeq, + rootSeq: proof.root_seq, }; new_address_proofs.push(new_address_proof); } diff --git a/src/api/method/get_queue_elements.rs b/src/api/method/get_queue_elements.rs index ba9c01b3..a9f10547 100644 --- a/src/api/method/get_queue_elements.rs +++ b/src/api/method/get_queue_elements.rs @@ -1,7 +1,7 @@ use light_merkle_tree_metadata::queue::QueueType; use sea_orm::{ ColumnTrait, Condition, ConnectionTrait, DatabaseBackend, DatabaseConnection, EntityTrait, - FromQueryResult, QueryFilter, QueryOrder, QuerySelect, QueryTrait, Statement, TransactionTrait, + FromQueryResult, QueryFilter, QueryOrder, QuerySelect, Statement, TransactionTrait, }; use serde::{Deserialize, Serialize}; @@ -27,12 +27,12 @@ pub struct GetQueueElementsRequest { #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct GetQueueElementsResponse { pub context: Context, - pub value: Vec, + pub value: Vec, pub first_value_queue_index: u64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -pub struct MerkleProofWithContextV2 { +pub struct GetQueueElementsResponseValue { pub proof: Vec, pub root: Hash, pub leaf_index: u64, @@ -109,14 +109,6 @@ pub async fn get_queue_elements( } }; - let sql = query.build(conn.get_database_backend()).sql; - let values = query.build(conn.get_database_backend()).values; - println!("sql: {:?}", sql); - println!("values: {:?}", values); - - let queue_elements: Vec<_> = query.clone().all(&tx).await?; - println!("0 queue_elements: {:?}", queue_elements); - let queue_elements: Vec = query .limit(num_elements as u64) .into_model::() @@ -125,13 +117,7 @@ pub async fn get_queue_elements( .map_err(|e| { PhotonApiError::UnexpectedError(format!("DB error fetching queue elements: {}", e)) })?; - - println!("queue_elements: {:?}", queue_elements); - let indices: Vec = queue_elements.iter().map(|e| e.leaf_index as u64).collect(); - - println!("indices: {:?}", indices); - let (proofs, first_value_queue_index) = if !indices.is_empty() { let first_value_queue_index = match queue_type { QueueType::BatchedInput => Ok(queue_elements[0].nullifier_queue_index.ok_or( @@ -158,7 +144,7 @@ pub async fn get_queue_elements( tx.commit().await?; - let result: Vec = proofs + let result: Vec = proofs .into_iter() .zip(queue_elements.iter()) .map(|(proof, queue_element)| { @@ -167,13 +153,13 @@ pub async fn get_queue_elements( .as_ref() .map(|tx_hash| Hash::new(tx_hash.as_slice()).unwrap()); let account_hash = Hash::new(queue_element.hash.as_slice()).unwrap(); - Ok(MerkleProofWithContextV2 { + Ok(GetQueueElementsResponseValue { proof: proof.proof, root: proof.root, - leaf_index: proof.leafIndex as u64, + leaf_index: proof.leaf_index as u64, leaf: proof.hash, - merkle_tree: Hash::from(proof.merkleTree.0.to_bytes()), - root_seq: proof.rootSeq, + merkle_tree: Hash::from(proof.merkle_tree.0.to_bytes()), + root_seq: proof.root_seq, tx_hash, account_hash, }) diff --git a/src/api/method/get_validity_proof/common.rs b/src/api/method/get_validity_proof/common.rs index 9703518e..f47fb003 100644 --- a/src/api/method/get_validity_proof/common.rs +++ b/src/api/method/get_validity_proof/common.rs @@ -4,7 +4,8 @@ use crate::api::method::get_multiple_new_address_proofs::{ use crate::common::typedefs::context::Context; use crate::common::typedefs::hash::Hash; use crate::common::typedefs::serializable_pubkey::SerializablePubkey; -use crate::ingester::persist::persisted_state_tree::MerkleProofWithContext; +use crate::ingester::parser::tree_info::TreeInfo; +use crate::ingester::persist::MerkleProofWithContext; use borsh::BorshSerialize; use jsonrpsee_core::Serialize; use lazy_static::lazy_static; @@ -123,12 +124,16 @@ impl From for GetValidityProofResponseV2 { .value .merkleTrees .iter() - .map(|x| MerkleContextV2 { - tree_type: 0, // TODO: check - tree: SerializablePubkey::try_from(x.as_str()).unwrap(), // TODO: handle error - queue: SerializablePubkey::default(), - cpi_context: None, - next_context: None, + .map(|tree| { + let tree_info = TreeInfo::get(tree.as_str()).unwrap(); // TODO: remove unwrap + println!("tree_info: {:?}", tree_info); + MerkleContextV2 { + tree_type: tree_info.tree_type as u16, + tree: SerializablePubkey::from(tree_info.tree), + queue: SerializablePubkey::from(tree_info.queue), + cpi_context: None, + next_context: None, + } }) .collect(), }, @@ -192,7 +197,7 @@ pub fn convert_inclusion_proofs_to_hex( for i in 0..inclusion_proof_inputs.len() { let input = InclusionHexInputsForProver { root: hash_to_hex(&inclusion_proof_inputs[i].root), - path_index: inclusion_proof_inputs[i].leafIndex, + path_index: inclusion_proof_inputs[i].leaf_index, path_elements: inclusion_proof_inputs[i] .proof .iter() diff --git a/src/api/method/get_validity_proof/v1.rs b/src/api/method/get_validity_proof/v1.rs index 2a0cb286..1a5ebebc 100644 --- a/src/api/method/get_validity_proof/v1.rs +++ b/src/api/method/get_validity_proof/v1.rs @@ -199,13 +199,13 @@ pub async fn get_validity_proof( .collect(), rootIndices: account_proofs .iter() - .map(|x| x.rootSeq) + .map(|x| x.root_seq) .chain(new_address_proofs.iter().map(|x| x.rootSeq)) .map(|x| x % queue_size) .collect(), leafIndices: account_proofs .iter() - .map(|x| x.leafIndex) + .map(|x| x.leaf_index) .chain(new_address_proofs.iter().map(|x| x.lowElementLeafIndex)) .collect(), leaves: account_proofs @@ -219,7 +219,7 @@ pub async fn get_validity_proof( .collect(), merkleTrees: account_proofs .iter() - .map(|x| x.merkleTree.clone().to_string()) + .map(|x| x.merkle_tree.clone().to_string()) .chain( new_address_proofs .iter() diff --git a/src/api/method/utils.rs b/src/api/method/utils.rs index 0a8d0007..bcd5d828 100644 --- a/src/api/method/utils.rs +++ b/src/api/method/utils.rs @@ -1,6 +1,4 @@ -use crate::common::typedefs::account::{ - Account, AccountV2, -}; +use crate::common::typedefs::account::{Account, AccountV2}; use crate::common::typedefs::bs58_string::Base58String; use crate::common::typedefs::bs64_string::Base64String; use crate::common::typedefs::serializable_signature::SerializableSignature; diff --git a/src/common/typedefs/account/context.rs b/src/common/typedefs/account/context.rs index 584a87b3..237ec74f 100644 --- a/src/common/typedefs/account/context.rs +++ b/src/common/typedefs/account/context.rs @@ -1,24 +1,24 @@ +use crate::api::error::PhotonApiError; +use crate::api::method::utils::{parse_decimal, parse_leaf_index}; use crate::common::typedefs::account::{Account, AccountData}; use crate::common::typedefs::bs64_string::Base64String; use crate::common::typedefs::hash::Hash; use crate::common::typedefs::serializable_pubkey::SerializablePubkey; use crate::common::typedefs::unsigned_integer::UnsignedInteger; +use crate::dao::generated::accounts::Model; use crate::ingester::parser::indexer_events::CompressedAccount; use byteorder::{ByteOrder, LittleEndian}; use light_merkle_tree_metadata::merkle_tree::TreeType; use serde::Serialize; use solana_program::pubkey::Pubkey; use utoipa::ToSchema; -use crate::api::error::PhotonApiError; -use crate::api::method::utils::{parse_decimal, parse_leaf_index}; -use crate::dao::generated::accounts::Model; /// This is currently used internally: /// - Internal (state_updates,..) /// - GetTransactionWithCompressionInfo (internally) /// - GetTransactionWithCompressionInfoV2 (internally) /// All endpoints return AccountV2. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, ToSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, ToSchema, Default)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct AccountContext { pub queue: SerializablePubkey, @@ -103,7 +103,6 @@ impl AccountWithContext { } } - impl TryFrom for AccountWithContext { type Error = PhotonApiError; @@ -152,4 +151,3 @@ impl TryFrom for AccountWithContext { }) } } - diff --git a/src/common/typedefs/account/v1.rs b/src/common/typedefs/account/v1.rs index 39125b45..7075d826 100644 --- a/src/common/typedefs/account/v1.rs +++ b/src/common/typedefs/account/v1.rs @@ -61,7 +61,9 @@ impl TryFrom for Account { data, owner: account.owner.try_into()?, tree: account.tree.try_into()?, - leaf_index: UnsignedInteger(crate::api::method::utils::parse_leaf_index(account.leaf_index)?), + leaf_index: UnsignedInteger(crate::api::method::utils::parse_leaf_index( + account.leaf_index, + )?), lamports: UnsignedInteger(parse_decimal(account.lamports)?), slot_created: UnsignedInteger(account.slot_created as u64), seq: account.seq.map(|seq| UnsignedInteger(seq as u64)), diff --git a/src/common/typedefs/account/v2.rs b/src/common/typedefs/account/v2.rs index 91e7da80..c5debb59 100644 --- a/src/common/typedefs/account/v2.rs +++ b/src/common/typedefs/account/v2.rs @@ -60,7 +60,9 @@ impl TryFrom for AccountV2 { .transpose()?, data, owner: account.owner.try_into()?, - leaf_index: UnsignedInteger(crate::api::method::utils::parse_leaf_index(account.leaf_index)?), + leaf_index: UnsignedInteger(crate::api::method::utils::parse_leaf_index( + account.leaf_index, + )?), lamports: UnsignedInteger(parse_decimal(account.lamports)?), slot_created: UnsignedInteger(account.slot_created as u64), seq: account.seq.map(|seq| UnsignedInteger(seq as u64)), diff --git a/src/ingester/parser/mod.rs b/src/ingester/parser/mod.rs index d31787a9..0e2ffe82 100644 --- a/src/ingester/parser/mod.rs +++ b/src/ingester/parser/mod.rs @@ -10,12 +10,12 @@ use self::state_update::{StateUpdate, Transaction}; pub mod indexer_events; pub mod merkle_tree_events_parser; pub mod state_update; +pub mod tree_info; mod tx_event_parser; pub mod tx_event_parser_v2; use crate::ingester::parser::tx_event_parser_v2::parse_public_transaction_event_v2; use solana_program::pubkey; -pub use tx_event_parser::map_tree_and_queue_accounts; pub const ACCOUNT_COMPRESSION_PROGRAM_ID: Pubkey = pubkey!("compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq"); diff --git a/src/ingester/parser/tree_info.rs b/src/ingester/parser/tree_info.rs new file mode 100644 index 00000000..b121425a --- /dev/null +++ b/src/ingester/parser/tree_info.rs @@ -0,0 +1,195 @@ +use lazy_static::lazy_static; +use light_merkle_tree_metadata::merkle_tree::TreeType; +use solana_program::pubkey; +use solana_program::pubkey::Pubkey; +use std::collections::HashMap; + +pub const DEFAULT_TREE_HEIGHT: u32 = 32 + 1; + +#[derive(Debug, Clone)] +pub struct TreeInfo { + pub tree: Pubkey, + pub queue: Pubkey, + pub height: u32, + pub tree_type: TreeType, +} + +impl TreeInfo { + pub fn get(pubkey: &str) -> Option<&TreeInfo> { + QUEUE_TREE_MAPPING.get(pubkey) + } + + pub fn height(pubkey: &str) -> Option { + QUEUE_TREE_MAPPING.get(pubkey).map(|x| x.height + 1) + } +} + +// TODO: add a table which stores tree metadata: tree_pubkey | queue_pubkey | type | ... +lazy_static! { + pub static ref QUEUE_TREE_MAPPING: HashMap = { + let mut m = HashMap::new(); + + m.insert( + "6L7SzhYB3anwEQ9cphpJ1U7Scwj57bx2xueReg7R9cKU".to_string(), + TreeInfo { + tree: pubkey!("HLKs5NJ8FXkJg8BrzJt56adFYYuwg5etzDtBbQYTsixu"), + queue: pubkey!("6L7SzhYB3anwEQ9cphpJ1U7Scwj57bx2xueReg7R9cKU"), + height: 32, + tree_type: TreeType::BatchedState, + }, + ); + + m.insert( + "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT".to_string(), + TreeInfo { + tree: pubkey!("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT"), + queue: pubkey!("nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148"), + height: 26, + tree_type: TreeType::State, + }, + ); + + m.insert( + "smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho".to_string(), + TreeInfo { + tree: pubkey!("smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho"), + queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), + height: 26, + tree_type: TreeType::State, + }, + ); + + // TODO: update queue pubkeys + // m.insert( + // "smt3AFtReRGVcrP11D6bSLEaKdUmrGfaTNowMVccJeu".to_string(), + // TreeAndQueue { + // tree: pubkey!("smt3AFtReRGVcrP11D6bSLEaKdUmrGfaTNowMVccJeu"), + // queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), + // height: 26, + // tree_type: TreeType::State, + // }, + // ); + // + // m.insert( + // "smt4vjXvdjDFzvRMUxwTWnSy4c7cKkMaHuPrGsdDH7V".to_string(), + // TreeAndQueue { + // tree: pubkey!("smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho"), + // queue: pubkey!("smt4vjXvdjDFzvRMUxwTWnSy4c7cKkMaHuPrGsdDH7V"), + // height: 26, + // tree_type: TreeType::State, + // }, + // ); + // + // m.insert( + // "smt5uPaQT9n6b1qAkgyonmzRxtuazA53Rddwntqistc".to_string(), + // TreeAndQueue { + // tree: pubkey!("smt5uPaQT9n6b1qAkgyonmzRxtuazA53Rddwntqistc"), + // queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), + // height: 26, + // tree_type: TreeType::State, + // }, + // ); + // + // m.insert( + // "smt6ukQDSPPYHSshQovmiRUjG9jGFq2hW9vgrDFk5Yz".to_string(), + // TreeAndQueue { + // tree: pubkey!("smt6ukQDSPPYHSshQovmiRUjG9jGFq2hW9vgrDFk5Yz"), + // queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), + // height: 26, + // tree_type: TreeType::State, + // }, + // ); + // + // m.insert( + // "smt7onMFkvi3RbyhQCMajudYQkB1afAFt9CDXBQTLz6".to_string(), + // TreeAndQueue { + // tree: pubkey!("smt7onMFkvi3RbyhQCMajudYQkB1afAFt9CDXBQTLz6"), + // queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), + // height: 26, + // tree_type: TreeType::State, + // }, + // ); + // + // m.insert( + // "smt8TYxNy8SuhAdKJ8CeLtDkr2w6dgDmdz5ruiDw9Y9".to_string(), + // TreeAndQueue { + // tree: pubkey!("smt8TYxNy8SuhAdKJ8CeLtDkr2w6dgDmdz5ruiDw9Y9"), + // queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), + // height: 26, + // tree_type: TreeType::State, + // }, + // ); + // + // m.insert( + // "smt9ReAYRF5eFjTd5gBJMn5aKwNRcmp3ub2CQr2vW7j".to_string(), + // TreeAndQueue { + // tree: pubkey!("smt9ReAYRF5eFjTd5gBJMn5aKwNRcmp3ub2CQr2vW7j"), + // queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), + // height: 26, + // tree_type: TreeType::State, + // }, + // ); + // + // m.insert( + // "smtAvYA5UbTRyKAkAj5kHs1CmrA42t6WkVLi4c6mA1f".to_string(), + // TreeAndQueue { + // tree: pubkey!("smtAvYA5UbTRyKAkAj5kHs1CmrA42t6WkVLi4c6mA1f"), + // queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), + // height: 26, + // tree_type: TreeType::State, + // }, + // ); + + m.insert( + "amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2".to_string(), + TreeInfo { + tree: pubkey!("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2"), + queue: pubkey!("aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F"), + height: 26, + tree_type: TreeType::Address, + }, + ); + + m.insert( + "aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F".to_string(), + TreeInfo { + tree: pubkey!("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2"), + queue: pubkey!("aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F"), + height: 26, + tree_type: TreeType::Address, + }, + ); + + m.insert( + "HLKs5NJ8FXkJg8BrzJt56adFYYuwg5etzDtBbQYTsixu".to_string(), + TreeInfo { + tree: pubkey!("HLKs5NJ8FXkJg8BrzJt56adFYYuwg5etzDtBbQYTsixu"), + queue: pubkey!("6L7SzhYB3anwEQ9cphpJ1U7Scwj57bx2xueReg7R9cKU"), + height: 32, + tree_type: TreeType::BatchedState, + }, + ); + + m.insert( + "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148".to_string(), + TreeInfo { + tree: pubkey!("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT"), + queue: pubkey!("nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148"), + height: 26, + tree_type: TreeType::State, + }, + ); + + m.insert( + "nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X".to_string(), + TreeInfo { + tree: pubkey!("smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho"), + queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), + height: 26, + tree_type: TreeType::State, + }, + ); + + m + }; +} diff --git a/src/ingester/parser/tx_event_parser.rs b/src/ingester/parser/tx_event_parser.rs index 5f54c7bb..40502702 100644 --- a/src/ingester/parser/tx_event_parser.rs +++ b/src/ingester/parser/tx_event_parser.rs @@ -2,98 +2,15 @@ use crate::common::typedefs::account::AccountWithContext; use crate::ingester::error::IngesterError; use crate::ingester::parser::indexer_events::PublicTransactionEvent; use crate::ingester::parser::state_update::{AccountTransaction, StateUpdate}; +use crate::ingester::parser::tree_info::TreeInfo; +use crate::ingester::parser::{ACCOUNT_COMPRESSION_PROGRAM_ID, NOOP_PROGRAM_ID, SYSTEM_PROGRAM}; use crate::ingester::typedefs::block_info::{Instruction, TransactionInfo}; use anchor_lang::AnchorDeserialize; -use lazy_static::lazy_static; use light_merkle_tree_metadata::merkle_tree::TreeType; use log::info; -use solana_program::pubkey::Pubkey; -use solana_sdk::pubkey; use solana_sdk::signature::Signature; use std::collections::HashMap; -use super::{ACCOUNT_COMPRESSION_PROGRAM_ID, NOOP_PROGRAM_ID, SYSTEM_PROGRAM}; - -pub struct TreeAndQueue { - tree: Pubkey, - queue: Pubkey, - _height: u16, - pub(crate) tree_type: TreeType, -} - -// TODO: add a table which stores tree metadata: tree_pubkey | queue_pubkey | type | ... -lazy_static! { - pub static ref QUEUE_TREE_MAPPING: HashMap = { - let mut m = HashMap::new(); - - m.insert( - "6L7SzhYB3anwEQ9cphpJ1U7Scwj57bx2xueReg7R9cKU".to_string(), - TreeAndQueue { - tree: pubkey!("HLKs5NJ8FXkJg8BrzJt56adFYYuwg5etzDtBbQYTsixu"), - queue: pubkey!("6L7SzhYB3anwEQ9cphpJ1U7Scwj57bx2xueReg7R9cKU"), - _height: 32, - tree_type: TreeType::BatchedState, - }, - ); - - m.insert( - "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT".to_string(), - TreeAndQueue { - tree: pubkey!("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT"), - queue: pubkey!("nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148"), - _height: 26, - tree_type: TreeType::State, - }, - ); - - m.insert( - "smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho".to_string(), - TreeAndQueue { - tree: pubkey!("smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho"), - queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), - _height: 26, - tree_type: TreeType::State, - }, - ); - - m.insert( - "HLKs5NJ8FXkJg8BrzJt56adFYYuwg5etzDtBbQYTsixu".to_string(), - TreeAndQueue { - tree: pubkey!("HLKs5NJ8FXkJg8BrzJt56adFYYuwg5etzDtBbQYTsixu"), - queue: pubkey!("6L7SzhYB3anwEQ9cphpJ1U7Scwj57bx2xueReg7R9cKU"), - _height: 32, - tree_type: TreeType::BatchedState, - }, - ); - - m.insert( - "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148".to_string(), - TreeAndQueue { - tree: pubkey!("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT"), - queue: pubkey!("nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148"), - _height: 26, - tree_type: TreeType::State, - }, - ); - - m.insert( - "nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X".to_string(), - TreeAndQueue { - tree: pubkey!("smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho"), - queue: pubkey!("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X"), - _height: 26, - tree_type: TreeType::State, - }, - ); - - m - }; -} - -pub fn map_tree_and_queue_accounts<'a>(pubkey: String) -> Option<&'a TreeAndQueue> { - QUEUE_TREE_MAPPING.get(pubkey.as_str()) -} - pub fn parse_legacy_public_transaction_event( tx: &TransactionInfo, slot: u64, @@ -146,11 +63,11 @@ pub fn parse_public_transaction_event( let mut tree_to_seq_number = HashMap::new(); for seq in sequence_numbers.iter() { - if let Some(queue_to_tree) = map_tree_and_queue_accounts(seq.pubkey.to_string()) { - if queue_to_tree.tree_type == TreeType::BatchedState - || queue_to_tree.tree_type == TreeType::BatchedAddress + if let Some(tree_info) = TreeInfo::get(&seq.pubkey.to_string()) { + if tree_info.tree_type == TreeType::BatchedState + || tree_info.tree_type == TreeType::BatchedAddress { - tree_to_seq_number.insert(queue_to_tree.tree, seq.seq); + tree_to_seq_number.insert(tree_info.tree, seq.seq); has_batched_instructions = true; } } @@ -173,8 +90,9 @@ pub fn parse_public_transaction_event( .zip(transaction_event.output_leaf_indices.iter()) { let tree = pubkey_array[out_account.merkle_tree_index as usize]; - let tree_and_queue = map_tree_and_queue_accounts(tree.to_string()) - .ok_or(IngesterError::ParserError("Missing queue".to_string()))?; + let tree_and_queue = TreeInfo::get(&tree.to_string()) + .ok_or(IngesterError::ParserError("Missing queue".to_string()))? + .clone(); let mut seq = None; if tree_and_queue.tree_type == TreeType::State { diff --git a/src/ingester/persist/leaf_node.rs b/src/ingester/persist/leaf_node.rs index 0c72340b..d4dbbfe6 100644 --- a/src/ingester/persist/leaf_node.rs +++ b/src/ingester/persist/leaf_node.rs @@ -4,8 +4,9 @@ use crate::common::typedefs::serializable_pubkey::SerializablePubkey; use crate::dao::generated::state_trees; use crate::ingester::error::IngesterError; use crate::ingester::parser::state_update::LeafNullification; +use crate::ingester::parser::tree_info::{TreeInfo, DEFAULT_TREE_HEIGHT}; use crate::ingester::persist::persisted_state_tree::{get_proof_nodes, ZERO_BYTES}; -use crate::ingester::persist::{compute_parent_hash, get_node_direct_ancestors, get_tree_height}; +use crate::ingester::persist::{compute_parent_hash, get_node_direct_ancestors}; use crate::migration::OnConflict; use itertools::Itertools; use sea_orm::{ConnectionTrait, DatabaseTransaction, EntityTrait, QueryTrait, Set}; @@ -79,7 +80,9 @@ pub async fn persist_leaf_nodes( .map(|node| { ( node.tree.to_bytes_vec(), - node.node_index(get_tree_height(&node.tree.0)), + node.node_index( + TreeInfo::height(&node.tree.0.to_string()).unwrap_or(DEFAULT_TREE_HEIGHT), // TODO: Handle error + ), ) }) .collect::>(); @@ -93,7 +96,9 @@ pub async fn persist_leaf_nodes( let mut models_to_updates = HashMap::new(); for leaf_node in leaf_nodes.clone() { - let node_idx = leaf_node.node_index(get_tree_height(&leaf_node.tree.0)); + let node_idx = leaf_node.node_index( + TreeInfo::height(&leaf_node.tree.0.to_string()).unwrap_or(DEFAULT_TREE_HEIGHT), + ); // TODO: handle error let tree = leaf_node.tree; let key = (tree.to_bytes_vec(), node_idx); @@ -125,11 +130,13 @@ pub async fn persist_leaf_nodes( let all_ancestors = leaf_nodes .iter() .flat_map(|leaf_node| { - get_node_direct_ancestors(leaf_node.node_index(get_tree_height(&leaf_node.tree.0))) - .iter() - .enumerate() - .map(move |(i, &idx)| (leaf_node.tree.to_bytes_vec(), idx, i)) - .collect::, i64, usize)>>() + get_node_direct_ancestors(leaf_node.node_index( + TreeInfo::height(&leaf_node.tree.0.to_string()).unwrap_or(DEFAULT_TREE_HEIGHT), + )) // TODO: handle error + .iter() + .enumerate() + .map(move |(i, &idx)| (leaf_node.tree.to_bytes_vec(), idx, i)) + .collect::, i64, usize)>>() }) .sorted_by(|a, b| { // Need to sort elements before dedup diff --git a/src/ingester/persist/leaf_node_proof.rs b/src/ingester/persist/leaf_node_proof.rs index 54170435..06ebb195 100644 --- a/src/ingester/persist/leaf_node_proof.rs +++ b/src/ingester/persist/leaf_node_proof.rs @@ -2,11 +2,10 @@ use crate::api::error::PhotonApiError; use crate::common::typedefs::hash::Hash; use crate::common::typedefs::serializable_pubkey::SerializablePubkey; use crate::dao::generated::state_trees; -use crate::ingester::persist::get_tree_height; +use crate::ingester::parser::tree_info::TreeInfo; use crate::ingester::persist::leaf_node::{leaf_index_to_node_index, LeafNode}; -use crate::ingester::persist::persisted_state_tree::{ - get_proof_nodes, get_proof_path, MerkleProofWithContext, ZERO_BYTES, -}; +use crate::ingester::persist::persisted_state_tree::{get_proof_nodes, get_proof_path, ZERO_BYTES}; +use crate::ingester::persist::MerkleProofWithContext; use sea_orm::QueryFilter; use sea_orm::{ColumnTrait, DatabaseTransaction, EntityTrait}; use std::collections::HashMap; @@ -58,10 +57,14 @@ pub async fn get_multiple_compressed_leaf_proofs_by_indices( hash: Hash::from(ZERO_BYTES[0]), seq: None, }; - let node_idx = leaf_index_to_node_index( - zero_leaf.leaf_index, - get_tree_height(&merkle_tree_pubkey.0), - ); + let tree_height = TreeInfo::get(&merkle_tree_pubkey.to_string()) + .ok_or(PhotonApiError::RecordNotFound(format!( + "Tree info not found for tree: {}", + merkle_tree_pubkey + )))? + .height; + println!("tree_height: {}", tree_height); + let node_idx = leaf_index_to_node_index(zero_leaf.leaf_index, (tree_height + 1) as u32); leaf_nodes.push((zero_leaf.clone(), node_idx)); } } @@ -94,7 +97,7 @@ pub async fn get_multiple_compressed_leaf_proofs( "Leaf index not found".to_string(), ))? as u32, hash: Hash::try_from(x.hash.clone())?, - seq: Some(0), + seq: x.seq.map(|x| x as u32), }, x.node_idx, )) @@ -196,10 +199,10 @@ pub async fn get_multiple_compressed_leaf_proofs_from_full_leaf_info( Ok(MerkleProofWithContext { proof, root, - leafIndex: leaf_node.leaf_index, + leaf_index: leaf_node.leaf_index, hash: leaf_node.hash.clone(), - merkleTree: leaf_node.tree, - rootSeq: root_seq.unwrap_or(0i64) as u64, + merkle_tree: leaf_node.tree, + root_seq: root_seq.unwrap_or(0i64) as u64, }) }) .collect(); diff --git a/src/ingester/persist/merkle_proof_with_context.rs b/src/ingester/persist/merkle_proof_with_context.rs new file mode 100644 index 00000000..25fa7ffa --- /dev/null +++ b/src/ingester/persist/merkle_proof_with_context.rs @@ -0,0 +1,65 @@ +use crate::api::error::PhotonApiError; +use crate::common::typedefs::hash::Hash; +use crate::common::typedefs::serializable_pubkey::SerializablePubkey; +use crate::ingester::persist::compute_parent_hash; +use crate::ingester::persist::leaf_node::leaf_index_to_node_index; +use crate::metric; +use cadence_macros::statsd_count; +use log::info; + +#[derive(Debug, Clone)] +pub struct MerkleProofWithContext { + pub proof: Vec, + pub root: Hash, + pub leaf_index: u32, + pub hash: Hash, + pub merkle_tree: SerializablePubkey, + pub root_seq: u64, +} + +impl MerkleProofWithContext { + pub fn validate(&self) -> Result<(), PhotonApiError> { + info!( + "Validating proof for leaf index: {} tree: {}", + self.leaf_index, self.merkle_tree + ); + let leaf_index = self.leaf_index; + let tree_height = (self.proof.len() + 1) as u32; + let node_index = leaf_index_to_node_index(leaf_index, tree_height); + let mut computed_root = self.hash.to_vec(); + info!("leaf_index: {}, node_index: {}", leaf_index, node_index); + + for (idx, node) in self.proof.iter().enumerate() { + let is_left = (node_index >> idx) & 1 == 0; + computed_root = compute_parent_hash( + if is_left { + computed_root.clone() + } else { + node.to_vec() + }, + if is_left { + node.to_vec() + } else { + computed_root.clone() + }, + ) + .map_err(|e| { + PhotonApiError::UnexpectedError(format!( + "Failed to compute parent hash for proof: {}", + e + )) + })?; + } + if computed_root != self.root.to_vec() { + metric! { + statsd_count!("invalid_proof", 1); + } + return Err(PhotonApiError::UnexpectedError(format!( + "Computed root does not match the provided root. Proof; {:?}", + self + ))); + } + + Ok(()) + } +} diff --git a/src/ingester/persist/mod.rs b/src/ingester/persist/mod.rs index 98711c0f..76c5dd67 100644 --- a/src/ingester/persist/mod.rs +++ b/src/ingester/persist/mod.rs @@ -20,7 +20,6 @@ use ark_bn254::Fr; use borsh::BorshDeserialize; use cadence_macros::statsd_count; use error::IngesterError; -use lazy_static::lazy_static; use log::debug; use persisted_indexed_merkle_tree::update_indexed_tree_leaves; use sea_orm::{ @@ -30,12 +29,14 @@ use sea_orm::{ use solana_program::pubkey; use solana_sdk::{pubkey::Pubkey, signature::Signature}; use sqlx::types::Decimal; -use std::str::FromStr; use std::{cmp::max, collections::HashMap}; +mod merkle_proof_with_context; pub mod persisted_indexed_merkle_tree; pub mod persisted_state_tree; +pub use merkle_proof_with_context::MerkleProofWithContext; + mod leaf_node; mod leaf_node_proof; @@ -51,67 +52,6 @@ mod spend; pub const COMPRESSED_TOKEN_PROGRAM: Pubkey = pubkey!("cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m"); -const LEGACY_TREE_HEIGHT: u32 = 27; -const BATCH_STATE_TREE_HEIGHT: u32 = 33; - -lazy_static! { - static ref TREE_HEIGHTS: HashMap = { - let mut m = HashMap::new(); - m.insert( - Pubkey::from_str("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m.insert( - Pubkey::from_str("smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m.insert( - Pubkey::from_str("smt3AFtReRGVcrP11D6bSLEaKdUmrGfaTNowMVccJeu").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m.insert( - Pubkey::from_str("smt4vjXvdjDFzvRMUxwTWnSy4c7cKkMaHuPrGsdDH7V").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m.insert( - Pubkey::from_str("smt5uPaQT9n6b1qAkgyonmzRxtuazA53Rddwntqistc").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m.insert( - Pubkey::from_str("smt6ukQDSPPYHSshQovmiRUjG9jGFq2hW9vgrDFk5Yz").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m.insert( - Pubkey::from_str("smt7onMFkvi3RbyhQCMajudYQkB1afAFt9CDXBQTLz6").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m.insert( - Pubkey::from_str("smt8TYxNy8SuhAdKJ8CeLtDkr2w6dgDmdz5ruiDw9Y9").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m.insert( - Pubkey::from_str("smt9ReAYRF5eFjTd5gBJMn5aKwNRcmp3ub2CQr2vW7j").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m.insert( - Pubkey::from_str("smtAvYA5UbTRyKAkAj5kHs1CmrA42t6WkVLi4c6mA1f").unwrap(), - LEGACY_TREE_HEIGHT, - ); - - m.insert( - Pubkey::from_str("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2").unwrap(), - LEGACY_TREE_HEIGHT, - ); - m - }; -} - -pub fn get_tree_height(tree_pubkey: &Pubkey) -> u32 { - *TREE_HEIGHTS - .get(tree_pubkey) - .unwrap_or(&BATCH_STATE_TREE_HEIGHT) -} - // To avoid exceeding the 64k total parameter limit pub const MAX_SQL_INSERTS: usize = 500; diff --git a/src/ingester/persist/persisted_indexed_merkle_tree.rs b/src/ingester/persist/persisted_indexed_merkle_tree.rs index 0bf6ef70..64f60752 100644 --- a/src/ingester/persist/persisted_indexed_merkle_tree.rs +++ b/src/ingester/persist/persisted_indexed_merkle_tree.rs @@ -16,8 +16,7 @@ use solana_sdk::pubkey::Pubkey; use super::{ compute_parent_hash, get_multiple_compressed_leaf_proofs_from_full_leaf_info, - persisted_state_tree::{validate_proof, MerkleProofWithContext, ZERO_BYTES}, - MAX_SQL_INSERTS, + persisted_state_tree::ZERO_BYTES, MerkleProofWithContext, MAX_SQL_INSERTS, }; use crate::ingester::persist::leaf_node::{persist_leaf_nodes, LeafNode}; use crate::{ @@ -122,15 +121,15 @@ pub async fn get_exclusion_range_with_proof( root: Hash::try_from(root).map_err(|e| { PhotonApiError::UnexpectedError(format!("Failed to convert hash: {}", e)) })?, - leafIndex: 0, + leaf_index: 0, hash: zeroeth_element_hash, - merkleTree: SerializablePubkey::try_from(tree.clone()).map_err(|e| { + merkle_tree: SerializablePubkey::try_from(tree.clone()).map_err(|e| { PhotonApiError::UnexpectedError(format!("Failed to serialize pubkey: {}", e)) })?, // HACK: Fixed value while not supporting forester. - rootSeq: 3, + root_seq: 3, }; - validate_proof(&merkle_proof)?; + merkle_proof.validate()?; return Ok((zeroeth_element, merkle_proof)); } let range_node = btree.values().next().ok_or(PhotonApiError::RecordNotFound( diff --git a/src/ingester/persist/persisted_state_tree.rs b/src/ingester/persist/persisted_state_tree.rs index 250b3cdc..5f557f92 100644 --- a/src/ingester/persist/persisted_state_tree.rs +++ b/src/ingester/persist/persisted_state_tree.rs @@ -1,79 +1,14 @@ use std::collections::HashMap; -use cadence_macros::statsd_count; use itertools::Itertools; -use log::info; use sea_orm::{ConnectionTrait, DbErr, EntityTrait, Statement, TransactionTrait, Value}; -use serde::{Deserialize, Serialize}; use solana_program::pubkey::Pubkey; -use utoipa::ToSchema; -use super::{compute_parent_hash, get_tree_height}; -use crate::ingester::persist::leaf_node::leaf_index_to_node_index; +use crate::ingester::parser::tree_info::{TreeInfo, DEFAULT_TREE_HEIGHT}; use crate::{ - api::error::PhotonApiError, - common::typedefs::{hash::Hash, serializable_pubkey::SerializablePubkey}, - dao::generated::state_trees, - metric, + common::typedefs::serializable_pubkey::SerializablePubkey, dao::generated::state_trees, }; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(deny_unknown_fields, rename_all = "camelCase")] -#[allow(non_snake_case)] -pub struct MerkleProofWithContext { - pub proof: Vec, - pub root: Hash, - pub leafIndex: u32, - pub hash: Hash, - pub merkleTree: SerializablePubkey, - pub rootSeq: u64, -} - -pub fn validate_proof(proof: &MerkleProofWithContext) -> Result<(), PhotonApiError> { - info!( - "Validating proof for leaf index: {} tree: {}", - proof.leafIndex, proof.merkleTree - ); - let leaf_index = proof.leafIndex; - let tree_height = (proof.proof.len() + 1) as u32; - let node_index = leaf_index_to_node_index(leaf_index, tree_height); - let mut computed_root = proof.hash.to_vec(); - info!("leaf_index: {}, node_index: {}", leaf_index, node_index); - - for (idx, node) in proof.proof.iter().enumerate() { - let is_left = (node_index >> idx) & 1 == 0; - computed_root = compute_parent_hash( - if is_left { - computed_root.clone() - } else { - node.to_vec() - }, - if is_left { - node.to_vec() - } else { - computed_root.clone() - }, - ) - .map_err(|e| { - PhotonApiError::UnexpectedError(format!( - "Failed to compute parent hash for proof: {}", - e - )) - })?; - } - if computed_root != proof.root.to_vec() { - metric! { - statsd_count!("invalid_proof", 1); - } - return Err(PhotonApiError::UnexpectedError(format!( - "Computed root does not match the provided root. Proof; {:?}", - proof - ))); - } - - Ok(()) -} - pub fn get_proof_path(index: i64, include_leaf: bool) -> Vec { let mut indexes = vec![]; let mut idx = index; @@ -170,8 +105,8 @@ where ); let tree_pubkey = Pubkey::try_from(tree.clone()).unwrap(); - let tree_height = get_tree_height(&tree_pubkey); - println!("tree_height: {}", tree_height); + let tree_height = + TreeInfo::height(&tree_pubkey.to_string()).unwrap_or(DEFAULT_TREE_HEIGHT); // TODO: handle error let model = state_trees::Model { tree: tree.clone(), level: get_level_by_node_index(*index, tree_height), @@ -371,7 +306,9 @@ pub const ZERO_BYTES: ZeroBytes = [ #[cfg(test)] mod tests { use super::*; - use crate::ingester::persist::{BATCH_STATE_TREE_HEIGHT, LEGACY_TREE_HEIGHT}; + use crate::common::typedefs::hash::Hash; + use crate::ingester::persist::leaf_node::leaf_index_to_node_index; + use crate::ingester::persist::{compute_parent_hash, MerkleProofWithContext}; fn node_index_to_leaf_index(index: i64, tree_height: u32) -> i64 { index - 2_i64.pow(get_level_by_node_index(index, tree_height) as u32) @@ -381,13 +318,13 @@ mod tests { fn test_get_level_by_node_index() { // Tree of height 3 (root level is 0, max is 3) // Node indices in a binary tree: [1, 2, 3, 4, 5, 6, 7] - assert_eq!(get_level_by_node_index(1, BATCH_STATE_TREE_HEIGHT), 0); // Root node - assert_eq!(get_level_by_node_index(2, BATCH_STATE_TREE_HEIGHT), 1); // Level 1, left child of root - assert_eq!(get_level_by_node_index(3, BATCH_STATE_TREE_HEIGHT), 1); // Level 1, right child of root - assert_eq!(get_level_by_node_index(4, BATCH_STATE_TREE_HEIGHT), 2); // Level 2, left child of node 2 - assert_eq!(get_level_by_node_index(5, BATCH_STATE_TREE_HEIGHT), 2); // Level 2, right child of node 2 - assert_eq!(get_level_by_node_index(6, BATCH_STATE_TREE_HEIGHT), 2); // Level 2, left child of node 3 - assert_eq!(get_level_by_node_index(7, BATCH_STATE_TREE_HEIGHT), 2); // Level 2, right child of node 3 + assert_eq!(get_level_by_node_index(1, 33), 0); // Root node + assert_eq!(get_level_by_node_index(2, 33), 1); // Level 1, left child of root + assert_eq!(get_level_by_node_index(3, 33), 1); // Level 1, right child of root + assert_eq!(get_level_by_node_index(4, 33), 2); // Level 2, left child of node 2 + assert_eq!(get_level_by_node_index(5, 33), 2); // Level 2, right child of node 2 + assert_eq!(get_level_by_node_index(6, 33), 2); // Level 2, left child of node 3 + assert_eq!(get_level_by_node_index(7, 33), 2); // Level 2, right child of node 3 } // Test helper to convert byte arrays to hex strings for easier debugging @@ -565,32 +502,28 @@ mod tests { let proof_context = MerkleProofWithContext { proof, root: Hash::try_from(ZERO_BYTES[31].to_vec()).unwrap(), - leafIndex: test_leaf_index, + leaf_index: test_leaf_index, hash: Hash::try_from(ZERO_BYTES[0].to_vec()).unwrap(), - merkleTree: merkle_tree, - rootSeq: 0, + merkle_tree: merkle_tree, + root_seq: 0, }; // Validate the proof - let result = validate_proof(&proof_context); + let result = proof_context.validate(); assert!(result.is_ok(), "Proof validation failed: {:?}", result); } #[test] fn test_validate_leaf_index() { - // Test for legacy tree height - assert!(validate_leaf_index(0, LEGACY_TREE_HEIGHT)); - assert!(validate_leaf_index((1 << 26) - 1, LEGACY_TREE_HEIGHT)); - assert!(!validate_leaf_index(1 << 26, LEGACY_TREE_HEIGHT)); - - // Test for batch state tree height - assert!(validate_leaf_index(0, BATCH_STATE_TREE_HEIGHT)); - // assert!(validate_leaf_index((1 << 32) - 1, BATCH_STATE_TREE_HEIGHT)); + assert!(validate_leaf_index(0, 27)); + assert!(validate_leaf_index((1 << 26) - 1, 27)); + assert!(!validate_leaf_index(1 << 26, 27)); + assert!(validate_leaf_index(0, 33)); } #[test] fn test_merkle_proof_length() { - assert_eq!(get_merkle_proof_length(LEGACY_TREE_HEIGHT), 26); - assert_eq!(get_merkle_proof_length(BATCH_STATE_TREE_HEIGHT), 32); + assert_eq!(get_merkle_proof_length(27), 26); + assert_eq!(get_merkle_proof_length(33), 32); } } diff --git a/src/openapi/mod.rs b/src/openapi/mod.rs index abf0107c..bd2f108a 100644 --- a/src/openapi/mod.rs +++ b/src/openapi/mod.rs @@ -1,6 +1,9 @@ use std::collections::HashSet; use crate::api::api::PhotonApi; +use crate::api::method::get_compressed_account_proof::{ + GetCompressedAccountProofResponseValueV1, GetCompressedAccountProofResponseValueV2, +}; use crate::api::method::get_compressed_accounts_by_owner::DataSlice; use crate::api::method::get_compressed_accounts_by_owner::FilterSelector; use crate::api::method::get_compressed_accounts_by_owner::Memcmp; @@ -13,11 +16,12 @@ use crate::api::method::get_compressed_token_account_balance::TokenAccountBalanc use crate::api::method::get_compressed_token_balances_by_owner::TokenBalance; use crate::api::method::get_compressed_token_balances_by_owner::TokenBalanceList; use crate::api::method::get_compressed_token_balances_by_owner::TokenBalanceListV2; +use crate::api::method::get_multiple_compressed_account_proofs::GetMultipleCompressedAccountProofsResponseValue; use crate::api::method::get_multiple_compressed_accounts::{AccountList, AccountListV2}; use crate::api::method::get_multiple_new_address_proofs::AddressListWithTrees; use crate::api::method::get_multiple_new_address_proofs::AddressWithTree; use crate::api::method::get_multiple_new_address_proofs::MerkleContextWithNewAddressProof; -use crate::api::method::get_queue_elements::MerkleProofWithContextV2; +use crate::api::method::get_queue_elements::GetQueueElementsResponseValue; use crate::api::method::get_transaction_with_compression_info::CompressionInfoV2; use crate::api::method::get_transaction_with_compression_info::{ AccountWithOptionalTokenData, AccountWithOptionalTokenDataV2, ClosedAccount, @@ -48,7 +52,6 @@ use crate::common::typedefs::token_data::AccountState; use crate::common::typedefs::token_data::TokenData; use crate::common::typedefs::unix_timestamp::UnixTimestamp; use crate::common::typedefs::unsigned_integer::UnsignedInteger; -use crate::ingester::persist::persisted_state_tree::MerkleProofWithContext; use dirs; use utoipa::openapi::Components; use utoipa::openapi::Response; @@ -89,8 +92,7 @@ const JSON_CONTENT_TYPE: &str = "application/json"; AccountContext, AccountWithContext, AccountV2, - MerkleProofWithContext, - MerkleProofWithContextV2, + GetQueueElementsResponseValue, TokenAccountList, TokenAccountListV2, TokenAccount, @@ -134,6 +136,9 @@ const JSON_CONTENT_TYPE: &str = "application/json"; TokenBalanceListV2, MerkleContextV2, ContextInfo, + GetCompressedAccountProofResponseValueV1, + GetCompressedAccountProofResponseValueV2, + GetMultipleCompressedAccountProofsResponseValue )))] struct ApiDoc; diff --git a/tests/integration_tests/e2e_tests.rs b/tests/integration_tests/e2e_tests.rs index 9baaaf18..72fd8667 100644 --- a/tests/integration_tests/e2e_tests.rs +++ b/tests/integration_tests/e2e_tests.rs @@ -32,10 +32,11 @@ use serial_test::serial; use std::str::FromStr; use futures::StreamExt; +use photon_indexer::common::typedefs::limit::Limit; use photon_indexer::{ api::method::{ get_compression_signatures_for_token_owner::GetCompressionSignaturesForTokenOwnerRequest, - utils::{Limit, SignatureInfo}, + utils::SignatureInfo, }, common::typedefs::serializable_signature::SerializableSignature, }; @@ -207,7 +208,7 @@ async fn test_e2e_mint_and_transfer_transactions( }) .await .unwrap(); - validity_proof_v2.value.compressedProof = CompressedProof::default(); + validity_proof_v2.value.compressedProof = Some(CompressedProof::default()); assert_json_snapshot!( format!("{}-{}-validity-proof-v2", name.clone(), person), validity_proof_v2 @@ -427,7 +428,7 @@ async fn test_lamport_transfers( .await .unwrap(); - let limit = photon_indexer::api::method::utils::Limit::new(1).unwrap(); + let limit = Limit::new(1).unwrap(); let mut cursor = None; let mut paginated_signatures = Vec::new(); loop { diff --git a/tests/integration_tests/mock_tests.rs b/tests/integration_tests/mock_tests.rs index a8095ecd..b2432f6f 100644 --- a/tests/integration_tests/mock_tests.rs +++ b/tests/integration_tests/mock_tests.rs @@ -48,7 +48,7 @@ use photon_indexer::common::typedefs::token_data::{AccountState, TokenData}; use sqlx::types::Decimal; use light_merkle_tree_metadata::merkle_tree::TreeType; -use photon_indexer::api::method::utils::Limit; +use photon_indexer::common::typedefs::limit::Limit; use sea_orm::ColumnTrait; use solana_sdk::pubkey::Pubkey; use std::vec; @@ -922,8 +922,8 @@ async fn test_persisted_state_trees( assert_eq!(proof_hashes, leaf_hashes); for proof in proofs { - assert_eq!(proof.merkleTree, tree); - assert_eq!(num_nodes as u64 - 1, proof.rootSeq); + assert_eq!(proof.merkle_tree, tree); + assert_eq!(num_nodes as u64 - 1, proof.root_seq); assert_eq!(tree_height - 1, proof.proof.len() as u32); } @@ -955,8 +955,8 @@ async fn test_persisted_state_trees( assert_eq!(proof_hashes, leaf_hashes); for proof in proofs { - assert_eq!(proof.merkleTree, tree); - assert_eq!(num_nodes as u64 - 1 + num_nodes as u64, proof.rootSeq); + assert_eq!(proof.merkle_tree, tree); + assert_eq!(num_nodes as u64 - 1 + num_nodes as u64, proof.root_seq); assert_eq!(tree_height - 1, proof.proof.len() as u32); } } @@ -1166,7 +1166,7 @@ async fn test_get_multiple_new_address_proofs_interop( .await .unwrap(); // The Gnark prover has some randomness. - validity_proof_v2.value.compressedProof = CompressedProof::default(); + validity_proof_v2.value.compressedProof = Some(CompressedProof::default()); insta::assert_json_snapshot!(format!("{}-validity-proof-v2", name), validity_proof_v2); } @@ -1583,9 +1583,9 @@ async fn test_persist_and_verify( .unwrap_or(0) as u64; for proof in proofs { - assert_eq!(proof.merkleTree, tree, "Merkle tree should match"); + assert_eq!(proof.merkle_tree, tree, "Merkle tree should match"); assert_eq!( - max_seq, proof.rootSeq, + max_seq, proof.root_seq, "Root sequence should be the maximum sequence number" ); assert_eq!( diff --git a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-bob-accounts-v2.snap b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-bob-accounts-v2.snap index 80b11a74..7c0bf94c 100644 --- a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-bob-accounts-v2.snap +++ b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-bob-accounts-v2.snap @@ -1,6 +1,6 @@ --- source: tests/integration_tests/e2e_tests.rs -assertion_line: 164 +assertion_line: 165 expression: accounts_v2 --- { @@ -20,13 +20,17 @@ expression: accounts_v2 }, "owner": "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m", "lamports": 0, - "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", "leafIndex": 6, "seq": 7, "slotCreated": 0, - "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", "proveByIndex": false, - "treeType": 1 + "merkleContext": { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } }, "tokenData": { "mint": "2U35cKS3Cj2xs5EBdByXYU7LaKAitqjSZc1Jnvu4iPf4", @@ -48,13 +52,17 @@ expression: accounts_v2 }, "owner": "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m", "lamports": 0, - "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", "leafIndex": 3, "seq": 4, "slotCreated": 0, - "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", "proveByIndex": false, - "treeType": 1 + "merkleContext": { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } }, "tokenData": { "mint": "2U35cKS3Cj2xs5EBdByXYU7LaKAitqjSZc1Jnvu4iPf4", diff --git a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-bob-validity-proof-v2.snap b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-bob-validity-proof-v2.snap index d3f06e20..60841add 100644 --- a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-bob-validity-proof-v2.snap +++ b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-bob-validity-proof-v2.snap @@ -17,11 +17,11 @@ expression: validity_proof_v2 "rootIndices": [ { "rootIndex": 7, - "inTree": true + "proveByIndex": false }, { "rootIndex": 7, - "inTree": true + "proveByIndex": false } ], "leafIndices": [ @@ -32,13 +32,21 @@ expression: validity_proof_v2 "JReC6h68m3EdCKP7S35e7BE4pBPwQz1HfjUkboeQy9r", "2R46QL8CSripTWvEsESL39ccripkjs9MjGtwnbJMzJET" ], - "merkleTrees": [ - "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", - "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT" - ], - "queues": [ - "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", - "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148" + "merkleContext": [ + { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + }, + { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } ] }, "context": { diff --git a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-charles-accounts-v2.snap b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-charles-accounts-v2.snap index d0137641..e6941272 100644 --- a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-charles-accounts-v2.snap +++ b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-charles-accounts-v2.snap @@ -1,6 +1,6 @@ --- source: tests/integration_tests/e2e_tests.rs -assertion_line: 164 +assertion_line: 165 expression: accounts_v2 --- { @@ -20,13 +20,17 @@ expression: accounts_v2 }, "owner": "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m", "lamports": 0, - "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", "leafIndex": 4, "seq": 5, "slotCreated": 0, - "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", "proveByIndex": false, - "treeType": 1 + "merkleContext": { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } }, "tokenData": { "mint": "2U35cKS3Cj2xs5EBdByXYU7LaKAitqjSZc1Jnvu4iPf4", @@ -48,13 +52,17 @@ expression: accounts_v2 }, "owner": "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m", "lamports": 0, - "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", "leafIndex": 5, "seq": 6, "slotCreated": 0, - "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", "proveByIndex": false, - "treeType": 1 + "merkleContext": { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } }, "tokenData": { "mint": "2U35cKS3Cj2xs5EBdByXYU7LaKAitqjSZc1Jnvu4iPf4", diff --git a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-charles-validity-proof-v2.snap b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-charles-validity-proof-v2.snap index 8912464f..4106957f 100644 --- a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-charles-validity-proof-v2.snap +++ b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-charles-validity-proof-v2.snap @@ -17,11 +17,11 @@ expression: validity_proof_v2 "rootIndices": [ { "rootIndex": 7, - "inTree": true + "proveByIndex": false }, { "rootIndex": 7, - "inTree": true + "proveByIndex": false } ], "leafIndices": [ @@ -32,13 +32,21 @@ expression: validity_proof_v2 "2TxrLe9HDTRDpVXmbkXUgXNksCZ7RdU4Tc2Ea1B8ADs2", "2o5puhnQbtqyyrcZrsow4DSs6kafmyUTYmgdwXfMMehS" ], - "merkleTrees": [ - "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", - "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT" - ], - "queues": [ - "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", - "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148" + "merkleContext": [ + { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + }, + { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } ] }, "context": { diff --git a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-mint-transaction-v2.snap b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-mint-transaction-v2.snap index 4d3ca5e5..c3c45ee3 100644 --- a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-mint-transaction-v2.snap +++ b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-mint-transaction-v2.snap @@ -190,13 +190,17 @@ expression: parsed_transaction_v2 }, "owner": "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m", "lamports": 0, - "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", "leafIndex": 0, "seq": 1, "slotCreated": 40, - "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", "proveByIndex": false, - "treeType": 1 + "merkleContext": { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } }, "optionalTokenData": { "mint": "2U35cKS3Cj2xs5EBdByXYU7LaKAitqjSZc1Jnvu4iPf4", diff --git a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-transfer-transaction-v2.snap b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-transfer-transaction-v2.snap index 48d8142b..0ab34ea3 100644 --- a/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-transfer-transaction-v2.snap +++ b/tests/integration_tests/snapshots/integration_tests__e2e_tests__e2e_mint_and_transfer_transactions-transfer-transaction-v2.snap @@ -175,13 +175,17 @@ expression: parsed_transaction_v2 }, "owner": "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m", "lamports": 0, - "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", "leafIndex": 0, "seq": 1, "slotCreated": 0, - "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", "proveByIndex": false, - "treeType": 1 + "merkleContext": { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } }, "nullifier": "11111111111111111111111111111111", "txHash": "11111111111111111111111111111111" @@ -208,13 +212,17 @@ expression: parsed_transaction_v2 }, "owner": "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m", "lamports": 0, - "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", "leafIndex": 1, "seq": 2, "slotCreated": 41, - "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", "proveByIndex": false, - "treeType": 1 + "merkleContext": { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } }, "optionalTokenData": { "mint": "2U35cKS3Cj2xs5EBdByXYU7LaKAitqjSZc1Jnvu4iPf4", @@ -236,13 +244,17 @@ expression: parsed_transaction_v2 }, "owner": "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m", "lamports": 0, - "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", "leafIndex": 2, "seq": 3, "slotCreated": 41, - "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", "proveByIndex": false, - "treeType": 1 + "merkleContext": { + "treeType": 1, + "tree": "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", + "queue": "nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148", + "cpiContext": null, + "nextContext": null + } }, "optionalTokenData": { "mint": "2U35cKS3Cj2xs5EBdByXYU7LaKAitqjSZc1Jnvu4iPf4", diff --git a/tests/integration_tests/snapshots/integration_tests__mock_tests__get_multiple_new_address_proofs_interop-validity-proof-v2.snap b/tests/integration_tests/snapshots/integration_tests__mock_tests__get_multiple_new_address_proofs_interop-validity-proof-v2.snap index 4313d8cf..eeef33ca 100644 --- a/tests/integration_tests/snapshots/integration_tests__mock_tests__get_multiple_new_address_proofs_interop-validity-proof-v2.snap +++ b/tests/integration_tests/snapshots/integration_tests__mock_tests__get_multiple_new_address_proofs_interop-validity-proof-v2.snap @@ -1,6 +1,6 @@ --- source: tests/integration_tests/mock_tests.rs -assertion_line: 1157 +assertion_line: 1171 expression: validity_proof_v2 --- { @@ -16,7 +16,7 @@ expression: validity_proof_v2 "rootIndices": [ { "rootIndex": 3, - "inTree": true + "proveByIndex": false } ], "leafIndices": [ @@ -25,10 +25,15 @@ expression: validity_proof_v2 "leaves": [ "12nCKqGG85jHxbTeA8i2Z7D4vnNUUrQ4r5e8dv2o16X" ], - "merkleTrees": [ - "amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2" - ], - "queues": [] + "merkleContext": [ + { + "treeType": 2, + "tree": "amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2", + "queue": "aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F", + "cpiContext": null, + "nextContext": null + } + ] }, "context": { "slot": 0 diff --git a/tests/integration_tests/utils.rs b/tests/integration_tests/utils.rs index 4f235e8c..177ad5a4 100644 --- a/tests/integration_tests/utils.rs +++ b/tests/integration_tests/utils.rs @@ -402,7 +402,7 @@ pub fn compare_account_with_account_v2(account: &Account, account_v2: &AccountV2 assert_eq!(account.data, account_v2.data); assert_eq!(account.owner, account_v2.owner); assert_eq!(account.lamports, account_v2.lamports); - assert_eq!(account.tree, account_v2.tree); + assert_eq!(account.tree, account_v2.merkle_context.tree); assert_eq!(account.leaf_index, account_v2.leaf_index); assert_eq!(account.seq, account_v2.seq); assert_eq!(account.slot_created, account_v2.slot_created);