@@ -234,7 +234,15 @@ async fn verify_queue_hash_chains(
234234 let batch_start_index = on_chain_batches
235235 . map ( |batches| batches[ pending_batch_index] . start_index )
236236 . unwrap_or ( 0 ) ;
237- let start_offset = batch_start_index + ( num_inserted_zkps * zkp_batch_size) ;
237+
238+ // For AddressV2 queues, batch.start_index is 1-based (tree leaf index) but
239+ // address_queues.queue_index is 0-based. Apply -1 offset when querying.
240+ // See: src/ingester/persist/persisted_batch_event/address.rs lines 51-55
241+ let start_offset = if queue_type == QueueType :: AddressV2 {
242+ batch_start_index. saturating_sub ( 1 ) + ( num_inserted_zkps * zkp_batch_size)
243+ } else {
244+ batch_start_index + ( num_inserted_zkps * zkp_batch_size)
245+ } ;
238246
239247 let cached_chains =
240248 queue_hash_cache:: get_cached_hash_chains ( db, tree_pubkey, queue_type, batch_start_index)
@@ -249,13 +257,15 @@ async fn verify_queue_hash_chains(
249257 let start_zkp_batch_idx = num_inserted_zkps as usize ;
250258
251259 let mut computed_chains = Vec :: with_capacity ( on_chain_chains. len ( ) ) ;
252- let mut chains_to_cache = Vec :: new ( ) ;
260+ let mut newly_computed: Vec < ( usize , u64 , [ u8 ; 32 ] ) > = Vec :: new ( ) ;
261+ let mut used_cached_indices: Vec < i32 > = Vec :: new ( ) ;
253262
254263 for zkp_batch_idx in 0 ..on_chain_chains. len ( ) {
255264 let actual_zkp_idx = start_zkp_batch_idx + zkp_batch_idx;
256265
257266 if let Some ( & cached_chain) = cached_map. get ( & ( actual_zkp_idx as i32 ) ) {
258267 computed_chains. push ( cached_chain) ;
268+ used_cached_indices. push ( actual_zkp_idx as i32 ) ;
259269 } else {
260270 let chain_offset = start_offset + ( zkp_batch_idx as u64 * zkp_batch_size) ;
261271 let chains = compute_hash_chains_from_db (
@@ -270,30 +280,22 @@ async fn verify_queue_hash_chains(
270280
271281 if !chains. is_empty ( ) {
272282 computed_chains. push ( chains[ 0 ] ) ;
273- chains_to_cache . push ( ( actual_zkp_idx, chain_offset, chains[ 0 ] ) ) ;
283+ newly_computed . push ( ( actual_zkp_idx, chain_offset, chains[ 0 ] ) ) ;
274284 }
275285 }
276286 }
277287
278- if !chains_to_cache. is_empty ( ) {
279- if let Err ( e) = queue_hash_cache:: store_hash_chains_batch (
280- db,
281- tree_pubkey,
282- queue_type,
283- batch_start_index,
284- chains_to_cache,
285- )
286- . await
287- {
288- error ! ( "Failed to cache hash chains: {:?}" , e) ;
289- }
290- }
288+ // Validate computed chains against on-chain values BEFORE caching
289+ let mut valid_chains_to_cache: Vec < ( usize , u64 , [ u8 ; 32 ] ) > = Vec :: new ( ) ;
290+ let mut invalid_cached_indices: Vec < i32 > = Vec :: new ( ) ;
291291
292292 for ( zkp_batch_idx, ( on_chain, computed) ) in on_chain_chains
293293 . iter ( )
294294 . zip ( computed_chains. iter ( ) )
295295 . enumerate ( )
296296 {
297+ let actual_zkp_idx = start_zkp_batch_idx + zkp_batch_idx;
298+
297299 if on_chain != computed {
298300 divergences. push ( HashChainDivergence {
299301 queue_info : QueueHashChainInfo {
@@ -306,6 +308,55 @@ async fn verify_queue_hash_chains(
306308 actual_hash_chain : * on_chain,
307309 zkp_batch_index : zkp_batch_idx,
308310 } ) ;
311+
312+ // If this was from cache, mark for deletion
313+ if used_cached_indices. contains ( & ( actual_zkp_idx as i32 ) ) {
314+ invalid_cached_indices. push ( actual_zkp_idx as i32 ) ;
315+ }
316+ } else {
317+ // Only cache newly computed chains that match on-chain
318+ if let Some ( entry) = newly_computed
319+ . iter ( )
320+ . find ( |( idx, _, _) | * idx == actual_zkp_idx)
321+ {
322+ valid_chains_to_cache. push ( * entry) ;
323+ }
324+ }
325+ }
326+
327+ // Delete invalid cached chains
328+ if !invalid_cached_indices. is_empty ( ) {
329+ debug ! (
330+ "Deleting {} invalid cached hash chains for tree {} type {:?}" ,
331+ invalid_cached_indices. len( ) ,
332+ tree_pubkey,
333+ queue_type
334+ ) ;
335+ if let Err ( e) = queue_hash_cache:: delete_hash_chains (
336+ db,
337+ tree_pubkey,
338+ queue_type,
339+ batch_start_index,
340+ invalid_cached_indices,
341+ )
342+ . await
343+ {
344+ error ! ( "Failed to delete invalid cached hash chains: {:?}" , e) ;
345+ }
346+ }
347+
348+ // Only cache validated chains
349+ if !valid_chains_to_cache. is_empty ( ) {
350+ if let Err ( e) = queue_hash_cache:: store_hash_chains_batch (
351+ db,
352+ tree_pubkey,
353+ queue_type,
354+ batch_start_index,
355+ valid_chains_to_cache,
356+ )
357+ . await
358+ {
359+ error ! ( "Failed to cache hash chains: {:?}" , e) ;
309360 }
310361 }
311362
0 commit comments