blvm-node 0.1.2

Bitcoin Commons BLVM: Minimal Bitcoin node implementation using blvm-protocol and blvm-consensus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
//! Pruning manager for blockchain storage
//!
//! Implements configurable pruning modes:
//! - Disabled: No pruning (archival node)
//! - Normal: Conservative pruning (keep recent blocks)
//! - Aggressive: Prune with UTXO commitments (requires utxo-commitments feature)
//! - Custom: Fine-grained control over what to keep

use crate::config::{PruningConfig, PruningMode};
#[cfg(feature = "bip158")]
use crate::network::filter_service::BlockFilterService;
use crate::storage::blockstore::BlockStore;
#[cfg(feature = "utxo-commitments")]
use crate::storage::commitment_store::CommitmentStore;
#[cfg(feature = "utxo-commitments")]
use crate::storage::utxostore::UtxoStore;
use anyhow::{anyhow, Result};
#[cfg(feature = "utxo-commitments")]
use blvm_protocol::Hash;
#[cfg(feature = "utxo-commitments")]
use hex;
use std::sync::Arc;
use tracing::{debug, info, warn};

/// Pruning statistics
#[derive(Debug, Clone, Default)]
pub struct PruningStats {
    /// Number of blocks pruned
    pub blocks_pruned: u64,
    /// Number of headers kept
    pub headers_kept: u64,
    /// Number of blocks kept
    pub blocks_kept: u64,
    /// Storage space freed (bytes, approximate)
    pub storage_freed: u64,
    /// Last pruning height
    pub last_prune_height: Option<u64>,
}

/// Pruning manager
pub struct PruningManager {
    pub config: PruningConfig,
    blockstore: Arc<BlockStore>,
    #[cfg(feature = "utxo-commitments")]
    commitment_store: Option<Arc<CommitmentStore>>,
    #[cfg(feature = "utxo-commitments")]
    utxostore: Option<Arc<UtxoStore>>,
    #[cfg(feature = "bip158")]
    filter_service: Option<Arc<BlockFilterService>>,
    stats: std::sync::Mutex<PruningStats>,
}

impl PruningManager {
    /// Create a new pruning manager
    pub fn new(config: PruningConfig, blockstore: Arc<BlockStore>) -> Self {
        Self {
            config,
            blockstore,
            #[cfg(feature = "utxo-commitments")]
            commitment_store: None,
            #[cfg(feature = "utxo-commitments")]
            utxostore: None,
            #[cfg(feature = "bip158")]
            filter_service: None,
            stats: std::sync::Mutex::new(PruningStats::default()),
        }
    }

    /// Create a new pruning manager with UTXO commitments support
    #[cfg(feature = "utxo-commitments")]
    pub fn with_utxo_commitments(
        config: PruningConfig,
        blockstore: Arc<BlockStore>,
        commitment_store: Arc<CommitmentStore>,
        utxostore: Arc<UtxoStore>,
    ) -> Self {
        Self {
            config,
            blockstore,
            commitment_store: Some(commitment_store),
            utxostore: Some(utxostore),
            #[cfg(feature = "bip158")]
            filter_service: None,
            stats: std::sync::Mutex::new(PruningStats::default()),
        }
    }

    /// Create a new pruning manager with all optional features
    pub fn with_features(
        config: PruningConfig,
        blockstore: Arc<BlockStore>,
        #[cfg(feature = "utxo-commitments")] commitment_store: Option<Arc<CommitmentStore>>,
        #[cfg(feature = "utxo-commitments")] utxostore: Option<Arc<UtxoStore>>,
        #[cfg(feature = "bip158")] filter_service: Option<Arc<BlockFilterService>>,
    ) -> Self {
        Self {
            config,
            blockstore,
            #[cfg(feature = "utxo-commitments")]
            commitment_store,
            #[cfg(feature = "utxo-commitments")]
            utxostore,
            #[cfg(feature = "bip158")]
            filter_service,
            stats: std::sync::Mutex::new(PruningStats::default()),
        }
    }

    /// Get pruning statistics
    pub fn get_stats(&self) -> PruningStats {
        self.stats.lock().unwrap().clone()
    }

    /// Get commitment store (if available)
    #[cfg(feature = "utxo-commitments")]
    pub fn commitment_store(&self) -> Option<Arc<CommitmentStore>> {
        self.commitment_store.as_ref().map(Arc::clone)
    }

    /// Get UTXO store (if available)
    #[cfg(feature = "utxo-commitments")]
    pub fn utxostore(&self) -> Option<Arc<UtxoStore>> {
        self.utxostore.as_ref().map(Arc::clone)
    }

    /// Check if pruning is enabled
    pub fn is_enabled(&self) -> bool {
        !matches!(self.config.mode, PruningMode::Disabled)
    }

    /// Check if automatic pruning should run
    pub fn should_auto_prune(&self, current_height: u64, last_prune_height: Option<u64>) -> bool {
        if !self.config.auto_prune {
            return false;
        }

        if let Some(last_height) = last_prune_height {
            // Check if we've reached the auto-prune interval
            current_height >= last_height + self.config.auto_prune_interval
        } else {
            // First auto-prune after reaching interval
            current_height >= self.config.auto_prune_interval
        }
    }

    /// Check if incremental pruning during IBD is possible
    /// Requires: UTXO commitments feature enabled and available
    fn can_incremental_prune_during_ibd(&self) -> bool {
        #[cfg(feature = "utxo-commitments")]
        {
            // Check if UTXO commitments are available
            let has_commitments = self.commitment_store.is_some() && self.utxostore.is_some();

            // Check if mode supports incremental pruning
            let mode_supports = matches!(
                &self.config.mode,
                PruningMode::Aggressive { .. }
                    | PruningMode::Custom {
                        keep_commitments: true,
                        ..
                    }
            );

            has_commitments && mode_supports
        }
        #[cfg(not(feature = "utxo-commitments"))]
        {
            false
        }
    }

    /// Incremental prune during IBD
    ///
    /// This method prunes old blocks while keeping a sliding window of recent blocks.
    /// It's designed to be called periodically during IBD to prevent storage from growing
    /// unbounded. Only works when incremental_prune_during_ibd is enabled and UTXO
    /// commitments are available.
    ///
    /// # Arguments
    /// * `current_height` - Current chain tip height
    /// * `is_ibd` - Whether initial block download is in progress
    ///
    /// # Returns
    /// Pruning statistics if pruning occurred, None if not needed
    pub fn incremental_prune_during_ibd(
        &self,
        current_height: u64,
        is_ibd: bool,
    ) -> Result<Option<PruningStats>> {
        // Only run during IBD if enabled
        if !is_ibd || !self.config.incremental_prune_during_ibd {
            return Ok(None);
        }

        // Check if prerequisites are met
        if !self.can_incremental_prune_during_ibd() {
            return Ok(None);
        }

        // Check if we have enough blocks to start pruning
        if current_height < self.config.min_blocks_for_incremental_prune {
            return Ok(None);
        }

        // Calculate prune height: keep only the last prune_window_size blocks
        let prune_to_height = current_height.saturating_sub(self.config.prune_window_size);

        // Don't prune if window size is larger than current height
        if prune_to_height == 0 || prune_to_height >= current_height {
            return Ok(None);
        }

        info!(
            "Incremental pruning during IBD: current_height={}, prune_to_height={}, window_size={}",
            current_height, prune_to_height, self.config.prune_window_size
        );

        // Perform pruning
        let stats = self.prune_to_height(prune_to_height, current_height, is_ibd)?;
        Ok(Some(stats))
    }

    /// Prune blocks up to a specific height
    ///
    /// # Arguments
    /// * `prune_to_height` - Prune all blocks up to (but not including) this height
    /// * `current_height` - Current chain tip height
    /// * `is_ibd` - Whether initial block download is in progress
    ///
    /// # Returns
    /// Pruning statistics
    pub fn prune_to_height(
        &self,
        prune_to_height: u64,
        current_height: u64,
        is_ibd: bool,
    ) -> Result<PruningStats> {
        // Validate pruning is enabled
        if !self.is_enabled() {
            return Err(anyhow!("Pruning is disabled"));
        }

        // Check if incremental pruning during IBD is allowed
        if is_ibd {
            // Allow incremental pruning during IBD if:
            // 1. Incremental pruning is explicitly enabled
            // 2. UTXO commitments are available (for state verification)
            // 3. Aggressive or Custom mode with commitments enabled
            let can_incremental_prune =
                self.config.incremental_prune_during_ibd && self.can_incremental_prune_during_ibd();

            if !can_incremental_prune {
                return Err(anyhow!(
                    "Cannot prune during initial block download. Wait for IBD to complete, or enable incremental_prune_during_ibd with UTXO commitments."
                ));
            }

            // For incremental pruning during IBD, ensure we have enough blocks
            if current_height < self.config.min_blocks_for_incremental_prune {
                return Err(anyhow!(
                    "Cannot prune during IBD until at least {} blocks are synced (currently at {})",
                    self.config.min_blocks_for_incremental_prune,
                    current_height
                ));
            }
        }

        // Validate prune height
        if prune_to_height >= current_height {
            return Err(anyhow!(
                "Cannot prune to height >= current height ({} >= {})",
                prune_to_height,
                current_height
            ));
        }

        // Ensure we keep minimum blocks
        let blocks_to_keep = current_height.saturating_sub(prune_to_height);
        if blocks_to_keep < self.config.min_blocks_to_keep {
            return Err(anyhow!(
                "Pruning would leave {} blocks, but minimum is {}",
                blocks_to_keep,
                self.config.min_blocks_to_keep
            ));
        }

        info!(
            "Starting pruning: prune_to_height={}, current_height={}, mode={:?}",
            prune_to_height, current_height, self.config.mode
        );

        let stats = match &self.config.mode {
            PruningMode::Disabled => {
                return Err(anyhow!("Pruning is disabled"));
            }
            PruningMode::Normal {
                keep_from_height,
                min_recent_blocks,
            } => self.prune_normal(
                prune_to_height,
                current_height,
                *keep_from_height,
                *min_recent_blocks,
            )?,
            PruningMode::Aggressive {
                keep_from_height,
                keep_commitments,
                keep_filtered_blocks,
                min_blocks,
            } => {
                #[cfg(feature = "utxo-commitments")]
                {
                    self.prune_aggressive(
                        prune_to_height,
                        current_height,
                        *keep_from_height,
                        *keep_commitments,
                        *keep_filtered_blocks,
                        *min_blocks,
                    )?
                }
                #[cfg(not(feature = "utxo-commitments"))]
                {
                    // Suppress unused variable warnings when feature is disabled
                    let _ = (
                        keep_from_height,
                        keep_commitments,
                        keep_filtered_blocks,
                        min_blocks,
                    );
                    return Err(anyhow!(
                        "Aggressive pruning requires utxo-commitments feature"
                    ));
                }
            }
            PruningMode::Custom {
                keep_headers,
                keep_bodies_from_height,
                keep_commitments,
                keep_filters,
                keep_filtered_blocks,
                keep_witnesses,
                keep_tx_index,
            } => self.prune_custom(
                prune_to_height,
                current_height,
                *keep_headers,
                *keep_bodies_from_height,
                *keep_commitments,
                *keep_filters,
                *keep_filtered_blocks,
                *keep_witnesses,
                *keep_tx_index,
            )?,
        };

        // Update statistics
        {
            let mut stats_guard = self.stats.lock().unwrap();
            stats_guard.blocks_pruned += stats.blocks_pruned;
            stats_guard.headers_kept += stats.headers_kept;
            stats_guard.blocks_kept += stats.blocks_kept;
            stats_guard.storage_freed += stats.storage_freed;
            stats_guard.last_prune_height = Some(prune_to_height);
        }

        info!(
            "Pruning complete: pruned {} blocks, kept {} blocks, freed ~{} bytes",
            stats.blocks_pruned, stats.blocks_kept, stats.storage_freed
        );

        Ok(stats)
    }

    /// Normal pruning: Keep recent blocks, remove older blocks
    fn prune_normal(
        &self,
        prune_to_height: u64,
        current_height: u64,
        keep_from_height: u64,
        min_recent_blocks: u64,
    ) -> Result<PruningStats> {
        let mut stats = PruningStats::default();

        // Calculate actual keep height (max of keep_from_height and min_recent_blocks)
        let effective_keep_height =
            keep_from_height.max(current_height.saturating_sub(min_recent_blocks));

        // Ensure we don't prune below effective keep height
        let actual_prune_height = prune_to_height.min(effective_keep_height);

        debug!(
            "Normal pruning: prune_to={}, keep_from={}, min_recent={}, effective_keep={}, actual_prune={}",
            prune_to_height, keep_from_height, min_recent_blocks, effective_keep_height, actual_prune_height
        );

        // Prune blocks up to actual_prune_height
        for height in 0..actual_prune_height {
            if let Some(hash) = self.blockstore.get_hash_by_height(height)? {
                // Remove block body (keep header for PoW verification)
                if let Some(_block) = self.blockstore.get_block(&hash)? {
                    self.blockstore.remove_block_body(&hash)?;
                    stats.blocks_pruned += 1;
                    stats.storage_freed += 1024; // Approximate block size
                }
            }
        }

        // Count kept blocks
        stats.blocks_kept = current_height.saturating_sub(actual_prune_height);
        stats.headers_kept = current_height; // All headers are kept

        Ok(stats)
    }

    /// Aggressive pruning: Prune with UTXO commitments
    #[cfg(feature = "utxo-commitments")]
    fn prune_aggressive(
        &self,
        prune_to_height: u64,
        current_height: u64,
        keep_from_height: u64,
        keep_commitments: bool,
        keep_filtered_blocks: bool,
        min_blocks: u64,
    ) -> Result<PruningStats> {
        let mut stats = PruningStats::default();

        // Calculate effective keep height
        let effective_keep_height = keep_from_height.max(current_height.saturating_sub(min_blocks));

        let actual_prune_height = prune_to_height.min(effective_keep_height);

        debug!(
            "Aggressive pruning: prune_to={}, keep_from={}, min_blocks={}, effective_keep={}, actual_prune={}",
            prune_to_height, keep_from_height, min_blocks, effective_keep_height, actual_prune_height
        );

        // Generate UTXO commitments before pruning if enabled
        if keep_commitments {
            if let (Some(commitment_store), Some(utxostore)) =
                (self.commitment_store.as_ref(), self.utxostore.as_ref())
            {
                info!("Generating UTXO commitments for blocks to be pruned...");
                self.generate_commitments_before_prune(
                    actual_prune_height,
                    current_height,
                    commitment_store,
                    utxostore,
                )?;
            } else {
                warn!(
                    "UTXO commitments requested but commitment store or UTXO store not available"
                );
            }
        }

        // Prune blocks up to actual_prune_height
        for height in 0..actual_prune_height {
            if let Some(hash) = self.blockstore.get_hash_by_height(height)? {
                // Remove block body (keep header)
                if let Some(_block) = self.blockstore.get_block(&hash)? {
                    self.blockstore.remove_block_body(&hash)?;
                    stats.blocks_pruned += 1;
                    stats.storage_freed += 1024;
                }

                // Remove witnesses if not keeping filtered blocks
                if !keep_filtered_blocks {
                    self.blockstore.remove_witness(&hash)?;
                }

                // Handle BIP158 filters if configured
                #[cfg(feature = "bip158")]
                if let Some(ref filter_service) = self.filter_service {
                    // Remove filter from cache but keep filter header
                    if filter_service.has_filter(&hash) {
                        filter_service.remove_filter_for_pruned_block(&hash)?;
                        debug!(
                            "Removed BIP158 filter for pruned block at height {} (header kept)",
                            height
                        );
                    }
                }
            }
        }

        stats.blocks_kept = current_height.saturating_sub(actual_prune_height);
        stats.headers_kept = current_height;

        Ok(stats)
    }

    /// Custom pruning: Fine-grained control
    fn prune_custom(
        &self,
        prune_to_height: u64,
        current_height: u64,
        keep_headers: bool,
        keep_bodies_from_height: u64,
        keep_commitments: bool,
        keep_filters: bool,
        keep_filtered_blocks: bool,
        keep_witnesses: bool,
        _keep_tx_index: bool,
    ) -> Result<PruningStats> {
        let mut stats = PruningStats::default();

        // Headers must always be kept (for PoW verification)
        if !keep_headers {
            warn!("Custom pruning with keep_headers=false is not recommended (required for PoW)");
        }

        let actual_prune_height = prune_to_height.min(keep_bodies_from_height);

        debug!(
            "Custom pruning: prune_to={}, keep_bodies_from={}, actual_prune={}",
            prune_to_height, keep_bodies_from_height, actual_prune_height
        );

        // Prune blocks up to actual_prune_height
        for height in 0..actual_prune_height {
            if let Some(hash) = self.blockstore.get_hash_by_height(height)? {
                // Remove block body if not keeping from this height
                if height < keep_bodies_from_height {
                    if let Some(_block) = self.blockstore.get_block(&hash)? {
                        self.blockstore.remove_block_body(&hash)?;
                        stats.blocks_pruned += 1;
                        stats.storage_freed += 1024;
                    }
                }

                // Remove witnesses if not keeping
                if !keep_witnesses {
                    self.blockstore.remove_witness(&hash)?;
                }

                // Handle commitments if enabled
                if keep_commitments {
                    #[cfg(feature = "utxo-commitments")]
                    {
                        if let (Some(commitment_store), Some(utxostore)) =
                            (self.commitment_store.as_ref(), self.utxostore.as_ref())
                        {
                            // Generate commitment if not exists
                            if !commitment_store.has_commitment(&hash)? {
                                self.generate_commitment_for_block(
                                    &hash,
                                    height,
                                    commitment_store,
                                    utxostore,
                                )?;
                            }
                        }
                    }
                }
                // Handle BIP158 filters if enabled
                #[cfg(feature = "bip158")]
                if keep_filters {
                    if let Some(ref filter_service) = self.filter_service {
                        // Remove filter from cache (saves memory) but keep filter header
                        // Filter headers are always kept for chain verification
                        if filter_service.has_filter(&hash) {
                            filter_service.remove_filter_for_pruned_block(&hash)?;
                            debug!(
                                "Removed BIP158 filter for pruned block at height {} (header kept)",
                                height
                            );
                        }
                    }
                }
                #[cfg(not(feature = "bip158"))]
                {
                    // Suppress unused variable warning when feature is disabled
                    let _ = keep_filters;
                }

                // Handle filtered blocks if enabled
                // Note: Filtered blocks are lightweight versions of blocks used for SPV clients
                // If keep_filtered_blocks is false, we can remove them to save space
                if !keep_filtered_blocks {
                    // Filtered block storage not implemented; FilteredBlock is generated on-demand.
                    // No-op until we add persistent filtered block cache.
                }
            }
        }

        stats.blocks_kept = current_height.saturating_sub(actual_prune_height);
        stats.headers_kept = if keep_headers { current_height } else { 0 };

        Ok(stats)
    }

    /// Generate UTXO commitments for blocks before pruning
    #[cfg(feature = "utxo-commitments")]
    fn generate_commitments_before_prune(
        &self,
        prune_to_height: u64,
        _current_height: u64,
        commitment_store: &CommitmentStore,
        utxostore: &UtxoStore,
    ) -> Result<()> {
        info!(
            "Generating UTXO commitments for heights 0..{}",
            prune_to_height
        );

        // For each block to be pruned, generate a commitment
        // We'll generate commitments at checkpoint intervals to save computation
        let checkpoint_interval = 144; // Every ~1 day at 10 min/block

        for height in (0..prune_to_height).step_by(checkpoint_interval as usize) {
            if let Some(hash) = self.blockstore.get_hash_by_height(height)? {
                // Check if commitment already exists
                if commitment_store.has_commitment(&hash)? {
                    debug!("Commitment already exists for height {}", height);
                    continue;
                }

                // Generate commitment for this block
                self.generate_commitment_for_block(&hash, height, commitment_store, utxostore)?;
            }
        }

        info!("Finished generating UTXO commitments");
        Ok(())
    }

    /// Generate a single UTXO commitment for a block
    #[cfg(feature = "utxo-commitments")]
    fn generate_commitment_for_block(
        &self,
        block_hash: &Hash,
        height: u64,
        commitment_store: &CommitmentStore,
        utxostore: &UtxoStore,
    ) -> Result<()> {
        #[cfg(feature = "utxo-commitments")]
        use blvm_protocol::utxo_commitments::merkle_tree::UtxoMerkleTree;

        // Reconstruct UTXO set at historical height by replaying blocks
        let utxo_set = self.reconstruct_utxo_set_at_height(height, utxostore)?;

        // Build Merkle tree from UTXO set
        let mut utxo_tree = UtxoMerkleTree::new()
            .map_err(|e| anyhow::anyhow!("Failed to create UTXO Merkle tree: {:?}", e))?;

        for (outpoint, utxo) in &utxo_set {
            utxo_tree
                .insert(*outpoint, utxo.as_ref().clone())
                .map_err(|e| anyhow::anyhow!("Failed to insert UTXO: {:?}", e))?;
        }

        // Generate commitment
        let commitment = utxo_tree.generate_commitment(*block_hash, height);

        // Store commitment
        commitment_store.store_commitment(block_hash, height, &commitment)?;

        debug!(
            "Generated and stored commitment for height {} (hash: {})",
            height,
            hex::encode(block_hash)
        );

        Ok(())
    }

    /// Generate UTXO commitment from current UTXO set state
    ///
    /// This method generates a commitment from the current UTXO set without replaying blocks.
    /// Used during incremental sync when the UTXO set is maintained incrementally.
    ///
    /// # Arguments
    /// * `block_hash` - Hash of the block at this height
    /// * `height` - Block height
    /// * `utxo_set` - Current UTXO set (from incremental updates)
    /// * `commitment_store` - Commitment store to save the commitment
    ///
    /// # Returns
    /// Result indicating success or failure
    #[cfg(feature = "utxo-commitments")]
    pub fn generate_commitment_from_current_state(
        &self,
        block_hash: &Hash,
        height: u64,
        utxo_set: &blvm_protocol::UtxoSet,
        commitment_store: &CommitmentStore,
    ) -> Result<()> {
        use blvm_protocol::utxo_commitments::merkle_tree::UtxoMerkleTree;

        // Build Merkle tree from current UTXO set
        let mut utxo_tree = UtxoMerkleTree::new()
            .map_err(|e| anyhow::anyhow!("Failed to create UTXO Merkle tree: {:?}", e))?;

        for (outpoint, utxo) in utxo_set {
            utxo_tree
                .insert(*outpoint, utxo.as_ref().clone())
                .map_err(|e| anyhow::anyhow!("Failed to insert UTXO: {:?}", e))?;
        }

        // Generate commitment
        let commitment = utxo_tree.generate_commitment(*block_hash, height);

        // Store commitment
        commitment_store.store_commitment(block_hash, height, &commitment)?;

        debug!(
            "Generated commitment from current state for height {} (hash: {})",
            height,
            hex::encode(block_hash)
        );

        Ok(())
    }

    /// Reconstruct UTXO set at a specific historical height
    ///
    /// This function replays blocks from genesis to the target height
    /// to reconstruct the exact UTXO set at that point in history.
    ///
    /// Note: For historical replay, we use connect_block directly since these blocks
    /// are already validated. Protocol validation is applied during initial block processing.
    #[cfg(feature = "utxo-commitments")]
    fn reconstruct_utxo_set_at_height(
        &self,
        target_height: u64,
        utxostore: &UtxoStore,
    ) -> Result<blvm_protocol::UtxoSet> {
        use blvm_protocol::block::connect_block;
        use blvm_protocol::UtxoSet;

        // Start with empty UTXO set (genesis)
        let mut utxo_set = UtxoSet::default();

        // If target height is 0, return empty UTXO set (genesis)
        if target_height == 0 {
            return Ok(utxo_set);
        }

        // Replay blocks from genesis (height 0) to target_height
        // This gives us the exact UTXO set at that historical point
        for height in 0..target_height {
            // Get block hash at this height
            if let Some(block_hash) = self.blockstore.get_hash_by_height(height)? {
                // Get block
                if let Some(block) = self.blockstore.get_block(&block_hash)? {
                    // Get witnesses for this block
                    let witnesses =
                        self.blockstore
                            .get_witness(&block_hash)?
                            .unwrap_or_else(|| {
                                // If no witnesses stored, create empty witnesses
                                block.transactions.iter().map(|_| Vec::new()).collect()
                            });

                    // Apply block to UTXO set using connect_block
                    // This properly handles coinbase transactions and input/output processing
                    let ctx = blvm_protocol::block::BlockValidationContext::for_network(
                        blvm_protocol::types::Network::Mainnet,
                    );
                    let (validation_result, new_utxo_set, _undo_log) =
                        connect_block(&block, &witnesses, utxo_set, height, &ctx)?;

                    // Check validation result
                    if !matches!(validation_result, blvm_protocol::ValidationResult::Valid) {
                        warn!(
                            "Block at height {} failed validation during UTXO reconstruction",
                            height
                        );
                        // Continue anyway - this might be expected for some edge cases
                    }

                    // Update UTXO set for next iteration
                    utxo_set = new_utxo_set;
                } else {
                    // Block not found - this shouldn't happen if chain is intact
                    warn!("Block at height {} not found during UTXO reconstruction, using current UTXO set as fallback", height);
                    // Use current UTXO set as fallback
                    return utxostore.get_all_utxos();
                }
            } else {
                // Height not found - use current UTXO set as fallback
                warn!("Height {} not found in chain during UTXO reconstruction, using current UTXO set", height);
                return utxostore.get_all_utxos();
            }
        }

        debug!(
            "Reconstructed UTXO set at height {} with {} UTXOs",
            target_height,
            utxo_set.len()
        );
        Ok(utxo_set)
    }
}