use super::database::UtxoDatabase;
use super::disk_segment::DiskSegment;
use super::types::{outpoint_to_output_key, OutputHeader, OutputKV, OutputKey};
use crate::storage::database::Tree;
use crate::storage::disk_utxo::key_to_outpoint;
use anyhow::Result;
use blvm_protocol::types::UTXO;
use tracing::{info, warn};
#[allow(unused_imports)]
use libc;
#[allow(unused_imports)]
use libmimalloc_sys;
const SEED_BATCH: usize = 50_000;
pub fn seed_from_ibd_utxos(
db: &UtxoDatabase,
tree: &dyn Tree,
checkpoint_height: i32,
expected_count: Option<u64>,
) -> Result<usize> {
use std::sync::mpsc;
use std::thread;
let t0 = std::time::Instant::now();
let capacity = expected_count.unwrap_or(300_000_000) as usize;
let (seg_idx, seg_dir) = db.alloc_seed_seg();
let (tx, rx) = mpsc::sync_channel::<OutputKV>(SEED_BATCH * 2);
let writer = thread::Builder::new()
.name("ibd-seed-writer".to_string())
.spawn(move || -> anyhow::Result<DiskSegment> {
DiskSegment::write_from_iter(&seg_dir, seg_idx, capacity, rx.into_iter())
})?;
let mut total = 0usize;
let mut batch_items: Vec<(OutputKey, OutputHeader, Vec<u8>)> = Vec::with_capacity(SEED_BATCH);
let mut entry_buf = Vec::<OutputKV>::with_capacity(SEED_BATCH);
let mut send_err = false;
'outer: for kv in tree.iter() {
let (key_bytes, val_bytes) = kv?;
if key_bytes.len() != 40 {
continue;
}
let mut op_key = [0u8; 40];
op_key.copy_from_slice(&key_bytes);
let op = key_to_outpoint(&op_key);
let out_key = outpoint_to_output_key(&op);
let utxo: UTXO = bincode::deserialize(&val_bytes)
.map_err(|e| anyhow::anyhow!("seed deserialize UTXO: {e}"))?;
let header = OutputHeader {
height: utxo.height.min(i32::MAX as u64) as i32,
flags: if utxo.is_coinbase { 1 } else { 0 },
amount: utxo.value,
};
batch_items.push((out_key, header, utxo.script_pubkey.as_ref().to_vec()));
if batch_items.len() >= SEED_BATCH {
entry_buf.clear();
db.import_utxos(&batch_items, checkpoint_height, &mut entry_buf)?;
total += batch_items.len();
batch_items.clear();
for kv in entry_buf.drain(..) {
if tx.send(kv).is_err() {
send_err = true;
break 'outer;
}
}
}
}
if !send_err && !batch_items.is_empty() {
entry_buf.clear();
db.import_utxos(&batch_items, checkpoint_height, &mut entry_buf)?;
total += batch_items.len();
for kv in entry_buf.drain(..) {
if tx.send(kv).is_err() {
send_err = true;
break;
}
}
}
drop(tx);
let seg = writer
.join()
.map_err(|_| anyhow::anyhow!("ibd-seed-writer thread panicked"))??;
if send_err {
anyhow::bail!("ibd-seed-writer thread exited early; disk segment may be incomplete");
}
if total == 0 && checkpoint_height > 0 {
anyhow::bail!(
"checkpoint tree empty at height {} — export incomplete or wrong slot",
checkpoint_height
);
}
if let Some(expected) = expected_count {
if expected > 0 && total as u64 != expected {
warn!(
"IBD engine seed: imported {} UTXOs but chain_info expected {} at height {}",
total, expected, checkpoint_height
);
}
}
db.finalize_seed(seg, checkpoint_height);
db.flush_table_tail()?;
#[cfg(feature = "mimalloc")]
unsafe {
libmimalloc_sys::mi_collect(true);
}
unsafe {
libc::malloc_trim(0);
}
info!(
"IBD engine: seeded {} UTXOs from checkpoint h={} in {:.1}s \
(streaming, peak UTXO buffer ≈ 6 MB)",
total,
checkpoint_height,
t0.elapsed().as_secs_f64()
);
Ok(total)
}