Skip to content

Commit e64bda7

Browse files
fix: make queue field nullable in accounts model (helius-labs#283)
* fix: make queue field nullable in accounts model * handle optional queue
1 parent f071136 commit e64bda7

9 files changed

Lines changed: 68 additions & 56 deletions

File tree

src/api/method/get_compressed_account_proof/v2.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ pub async fn get_compressed_account_proof_v2(
128128
// Enrich with account data if available
129129
if let Some(account) = account {
130130
result.tree_context.tree_type = account.tree_type.map(|t| t as u16).unwrap_or(0);
131-
result.tree_context.queue = SerializablePubkey::try_from(account.queue)?;
131+
result.tree_context.queue =
132+
SerializablePubkey::try_from(account.queue.unwrap_or_default())?;
132133
}
133134

134135
let response = GetCompressedAccountProofResponseV2 {

src/api/method/get_multiple_compressed_account_proofs/v2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ pub async fn get_multiple_compressed_account_proofs_v2(
148148
value.tree_context = TreeContextInfo {
149149
tree_type: account.tree_type.map(|t| t as u16).unwrap_or(0),
150150
tree: SerializablePubkey::try_from(account.tree.clone())?,
151-
queue: SerializablePubkey::try_from(account.queue.clone())?,
151+
queue: SerializablePubkey::try_from(account.queue.clone().unwrap_or_default())?,
152152
cpi_context: None,
153153
};
154154
}

src/api/method/get_validity_proof/v2.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,10 @@ pub async fn get_validity_proof_v2(
203203
tree_type: acc_model.tree_type.map(|t| t as u16).unwrap_or(0),
204204
tree: SerializablePubkey::try_from_slice(&acc_model.tree)
205205
.unwrap_or_default(),
206-
queue: SerializablePubkey::try_from_slice(&acc_model.queue)
207-
.unwrap_or_default(),
206+
queue: SerializablePubkey::try_from_slice(
207+
&acc_model.queue.clone().unwrap_or_default(),
208+
)
209+
.unwrap_or_default(),
208210
cpi_context: None,
209211
next_tree_context: None,
210212
},

src/common/typedefs/account/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl TryFrom<Model> for AccountWithContext {
137137
seq: account.seq.map(|seq| UnsignedInteger(seq as u64)),
138138
},
139139
context: AccountContext {
140-
queue: account.queue.try_into()?,
140+
queue: account.queue.unwrap_or_default().try_into()?,
141141
in_output_queue: account.in_output_queue,
142142
spent: account.spent,
143143
nullified_in_tree: account.nullified_in_tree,

src/common/typedefs/account/v2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl TryFrom<Model> for AccountV2 {
7070
merkle_context: MerkleContextV2 {
7171
tree_type: account.tree_type.map(|t| t as u16).unwrap_or(0),
7272
tree: account.tree.try_into()?,
73-
queue: account.queue.clone().try_into()?,
73+
queue: account.queue.unwrap_or_default().try_into()?,
7474
cpi_context: None,
7575
next_tree_context: None,
7676
},

src/dao/generated/accounts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub struct Model {
2525
pub nullified_in_tree: bool,
2626
pub nullifier_queue_index: Option<i64>,
2727
pub in_output_queue: bool,
28-
pub queue: Vec<u8>,
28+
pub queue: Option<Vec<u8>>,
2929
pub nullifier: Option<Vec<u8>>,
3030
pub tx_hash: Option<Vec<u8>>,
3131
}

src/ingester/persist/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ async fn append_output_accounts(
421421
data: Set(account.account.data.as_ref().map(|x| x.data.clone().0)),
422422
data_hash: Set(account.account.data.as_ref().map(|x| x.data_hash.to_vec())),
423423
tree: Set(account.account.tree.to_bytes_vec()),
424-
queue: Set(account.context.queue.to_bytes_vec()),
424+
queue: Set(Some(account.context.queue.to_bytes_vec())),
425425
leaf_index: Set(account.account.leaf_index.0 as i64),
426426
in_output_queue: Set(account.context.in_output_queue),
427427
nullifier_queue_index: Set(account.context.nullifier_queue_index.map(|x| x.0 as i64)),

tests/integration_tests/mock_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ async fn test_persist_token_data(
572572
discriminator: Set(Some(Decimal::from(1))),
573573
data_hash: Set(Some(Hash::new_unique().to_vec())),
574574
tree: Set(Pubkey::new_unique().to_bytes().to_vec()),
575-
queue: Set(Pubkey::new_unique().to_bytes().to_vec()),
575+
queue: Set(Some(Pubkey::new_unique().to_bytes().to_vec())),
576576
tree_type: Set(Some(TreeType::StateV1 as i32)),
577577
seq: Set(Some(0)),
578578
..Default::default()
Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
// This test demonstrates that re-indexing fixes the wrong zeroeth element in v1 trees.
2-
//
2+
//
33
// The bug: v1 trees were initialized with wrong zeroeth element (next_index = 0 instead of 1)
44
// The fix: Re-indexing now correctly sets next_index = 1 for v1 trees
55

66
use crate::utils::*;
7+
use function_name::named;
8+
use light_compressed_account::TreeType;
79
use photon_indexer::dao::generated::indexed_trees;
8-
use photon_indexer::ingester::persist::persisted_indexed_merkle_tree::persist_indexed_tree_updates;
10+
use photon_indexer::ingester::parser::indexer_events::RawIndexedElement;
11+
use photon_indexer::ingester::parser::state_update::IndexedTreeLeafUpdate;
912
use photon_indexer::ingester::persist::indexed_merkle_tree::{
10-
get_zeroeth_exclusion_range_v1,
11-
compute_range_node_hash_v1,
13+
compute_range_node_hash_v1, get_zeroeth_exclusion_range_v1,
1214
};
13-
use photon_indexer::ingester::parser::state_update::IndexedTreeLeafUpdate;
14-
use photon_indexer::ingester::parser::indexer_events::RawIndexedElement;
15-
use light_compressed_account::TreeType;
15+
use photon_indexer::ingester::persist::persisted_indexed_merkle_tree::persist_indexed_tree_updates;
16+
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait};
17+
use serial_test::serial;
1618
use solana_pubkey::Pubkey;
17-
use sea_orm::{EntityTrait, Set, QueryFilter, ColumnTrait, TransactionTrait};
1819
use std::collections::HashMap;
19-
use function_name::named;
20-
use serial_test::serial;
2120

2221
#[named]
2322
#[rstest]
@@ -30,17 +29,17 @@ async fn test_reindex_fixes_wrong_zeroeth_element(
3029
let name = trim_test_name(function_name!());
3130
let setup = setup(name.clone(), db_backend).await;
3231
let conn = setup.db_conn;
33-
32+
3433
// Create a v1 address tree
3534
let tree_pubkey = Pubkey::new_unique();
3635
let tree_bytes = tree_pubkey.to_bytes().to_vec();
37-
36+
3837
// Step 1: Insert tree with wrong zeroeth element (simulating the bug)
3938
let txn = conn.begin().await.unwrap();
40-
39+
4140
// Get the correct v1 zeroeth element to use its values
4241
let correct_v1_zeroeth = get_zeroeth_exclusion_range_v1(tree_bytes.clone());
43-
42+
4443
// Insert wrong zeroeth element (using v2 style with next_index = 0 instead of 1)
4544
let wrong_zeroeth = indexed_trees::ActiveModel {
4645
tree: Set(tree_bytes.clone()),
@@ -54,10 +53,11 @@ async fn test_reindex_fixes_wrong_zeroeth_element(
5453
.exec(&txn)
5554
.await
5655
.unwrap();
57-
56+
5857
// Get top element for v1 trees
59-
let top_element_model = photon_indexer::ingester::persist::indexed_merkle_tree::get_top_element(tree_bytes.clone());
60-
58+
let top_element_model =
59+
photon_indexer::ingester::persist::indexed_merkle_tree::get_top_element(tree_bytes.clone());
60+
6161
// Insert top element at index 1 (required for v1 trees)
6262
let top_element = indexed_trees::ActiveModel {
6363
tree: Set(tree_bytes.clone()),
@@ -71,9 +71,9 @@ async fn test_reindex_fixes_wrong_zeroeth_element(
7171
.exec(&txn)
7272
.await
7373
.unwrap();
74-
74+
7575
txn.commit().await.unwrap();
76-
76+
7777
// Verify wrong data exists
7878
let wrong_zeroeth_from_db = indexed_trees::Entity::find()
7979
.filter(indexed_trees::Column::Tree.eq(tree_bytes.clone()))
@@ -82,20 +82,23 @@ async fn test_reindex_fixes_wrong_zeroeth_element(
8282
.await
8383
.unwrap()
8484
.expect("Should find zeroeth element");
85-
86-
assert_eq!(wrong_zeroeth_from_db.next_index, 0, "Zeroeth element should have wrong next_index");
87-
85+
86+
assert_eq!(
87+
wrong_zeroeth_from_db.next_index, 0,
88+
"Zeroeth element should have wrong next_index"
89+
);
90+
8891
// Step 2: Simulate re-indexing that will fix the zeroeth element
8992
let txn = conn.begin().await.unwrap();
90-
93+
9194
// Create the correct zeroeth element update
9295
let correct_zeroeth_leaf = RawIndexedElement {
9396
value: [0u8; 32],
9497
next_index: 1, // Correct value for v1
9598
next_value: wrong_zeroeth_from_db.next_value.clone().try_into().unwrap(),
9699
index: 0,
97100
};
98-
101+
99102
// Compute the correct hash for v1 tree
100103
let correct_zeroeth_model = indexed_trees::Model {
101104
tree: tree_bytes.clone(),
@@ -106,23 +109,23 @@ async fn test_reindex_fixes_wrong_zeroeth_element(
106109
seq: Some(2),
107110
};
108111
let correct_hash = compute_range_node_hash_v1(&correct_zeroeth_model).unwrap();
109-
112+
110113
let update = IndexedTreeLeafUpdate {
111114
tree: tree_pubkey,
112115
tree_type: TreeType::AddressV1,
113116
leaf: correct_zeroeth_leaf,
114117
hash: correct_hash.0,
115118
seq: 2, // Higher seq number to ensure update
116119
};
117-
120+
118121
// Create HashMap with the update
119122
let mut updates = HashMap::new();
120123
updates.insert((tree_pubkey, 0u64), update);
121-
124+
122125
// Persist the update which should fix the zeroeth element
123126
persist_indexed_tree_updates(&txn, updates).await.unwrap();
124127
txn.commit().await.unwrap();
125-
128+
126129
// Step 3: Verify the zeroeth element is now correct
127130
let fixed_zeroeth = indexed_trees::Entity::find()
128131
.filter(indexed_trees::Column::Tree.eq(tree_bytes.clone()))
@@ -131,15 +134,18 @@ async fn test_reindex_fixes_wrong_zeroeth_element(
131134
.await
132135
.unwrap()
133136
.expect("Should find zeroeth element");
134-
135-
assert_eq!(fixed_zeroeth.next_index, 1, "Zeroeth element should now have correct next_index");
137+
138+
assert_eq!(
139+
fixed_zeroeth.next_index, 1,
140+
"Zeroeth element should now have correct next_index"
141+
);
136142
assert_eq!(fixed_zeroeth.seq, Some(2), "Seq should be updated");
137-
143+
138144
// Verify it matches what get_zeroeth_exclusion_range_v1 would create
139145
let expected_zeroeth = get_zeroeth_exclusion_range_v1(tree_bytes.clone());
140146
assert_eq!(fixed_zeroeth.next_index, expected_zeroeth.next_index);
141147
assert_eq!(fixed_zeroeth.value, expected_zeroeth.value);
142-
148+
143149
println!("✅ Re-indexing successfully fixed zeroeth element from wrong v2-style (next_index=0) to correct v1-style (next_index=1)");
144150
}
145151

@@ -154,16 +160,16 @@ async fn test_reindex_preserves_correct_zeroeth_element(
154160
let name = trim_test_name(function_name!());
155161
let setup = setup(name.clone(), db_backend).await;
156162
let conn = setup.db_conn;
157-
163+
158164
// Create a v1 address tree
159165
let tree_pubkey = Pubkey::new_unique();
160166
let tree_bytes = tree_pubkey.to_bytes().to_vec();
161-
167+
162168
// Insert correct zeroeth element
163169
let txn = conn.begin().await.unwrap();
164-
170+
165171
let correct_v1_zeroeth = get_zeroeth_exclusion_range_v1(tree_bytes.clone());
166-
172+
167173
let correct_zeroeth = indexed_trees::ActiveModel {
168174
tree: Set(tree_bytes.clone()),
169175
leaf_index: Set(0),
@@ -176,19 +182,19 @@ async fn test_reindex_preserves_correct_zeroeth_element(
176182
.exec(&txn)
177183
.await
178184
.unwrap();
179-
185+
180186
txn.commit().await.unwrap();
181-
187+
182188
// Simulate re-indexing with the same correct data
183189
let txn = conn.begin().await.unwrap();
184-
190+
185191
let leaf = RawIndexedElement {
186192
value: correct_v1_zeroeth.value.clone().try_into().unwrap(),
187193
next_index: 1,
188194
next_value: correct_v1_zeroeth.next_value.clone().try_into().unwrap(),
189195
index: 0,
190196
};
191-
197+
192198
let model = indexed_trees::Model {
193199
tree: tree_bytes.clone(),
194200
leaf_index: 0,
@@ -198,21 +204,21 @@ async fn test_reindex_preserves_correct_zeroeth_element(
198204
seq: Some(1),
199205
};
200206
let hash = compute_range_node_hash_v1(&model).unwrap();
201-
207+
202208
let update = IndexedTreeLeafUpdate {
203209
tree: tree_pubkey,
204210
tree_type: TreeType::AddressV1,
205211
leaf,
206212
hash: hash.0,
207213
seq: 1,
208214
};
209-
215+
210216
let mut updates = HashMap::new();
211217
updates.insert((tree_pubkey, 0u64), update);
212-
218+
213219
persist_indexed_tree_updates(&txn, updates).await.unwrap();
214220
txn.commit().await.unwrap();
215-
221+
216222
// Verify it's still correct
217223
let zeroeth = indexed_trees::Entity::find()
218224
.filter(indexed_trees::Column::Tree.eq(tree_bytes.clone()))
@@ -221,9 +227,12 @@ async fn test_reindex_preserves_correct_zeroeth_element(
221227
.await
222228
.unwrap()
223229
.expect("Should find zeroeth element");
224-
225-
assert_eq!(zeroeth.next_index, 1, "Zeroeth element should still be correct");
230+
231+
assert_eq!(
232+
zeroeth.next_index, 1,
233+
"Zeroeth element should still be correct"
234+
);
226235
assert_eq!(zeroeth.seq, Some(1), "Seq should be updated");
227-
236+
228237
println!("✅ Re-indexing preserves already correct zeroeth elements");
229238
}

0 commit comments

Comments
 (0)