use std::{fs::File, io::Read, path::Path};
use bitcoin::{block::Header, consensus::Decodable};
use brk_error::{Error, Result};
use brk_rpc::Client;
use brk_types::Height;
use tracing::warn;
use crate::{
BlkIndexToBlkPath, OUT_OF_ORDER_FILE_BACKOFF, XORBytes, XORIndex,
parse::HEADER_LEN, scan::find_magic,
};
const PROBE_BUF_LEN: usize = 4096;
pub(crate) fn first_block_height(
client: &Client,
blk_path: &Path,
xor_bytes: XORBytes,
) -> Result<Height> {
let mut file = File::open(blk_path)?;
let mut buf = [0u8; PROBE_BUF_LEN];
let n = file.read(&mut buf)?;
let mut xor_i = XORIndex::default();
let magic_end = find_magic(&buf[..n], &mut xor_i, xor_bytes)
.ok_or_else(|| Error::NotFound("No magic bytes found".into()))?;
let header_end = magic_end + 4 + HEADER_LEN;
if header_end > n {
warn!(
"first_block_height: {} has magic-shaped bytes at offset {} but \
not enough room in the {}-byte probe to decode the header, \
the file is probably corrupt",
blk_path.display(),
magic_end - 4,
PROBE_BUF_LEN,
);
return Err(Error::Parse(format!(
"blk file probe truncated before header at {}",
blk_path.display()
)));
}
xor_i.bytes(&mut buf[magic_end..header_end], xor_bytes);
let header = Header::consensus_decode_from_finite_reader(&mut &buf[magic_end + 4..header_end])?;
let height = client.get_block_info(&header.block_hash())?.height as u32;
Ok(Height::new(height))
}
pub(crate) fn find_start_blk_index(
client: &Client,
target_start: Height,
paths: &BlkIndexToBlkPath,
xor_bytes: XORBytes,
) -> u16 {
let entries: Vec<(u16, &Path)> = paths.iter().map(|(&i, p)| (i, p.as_path())).collect();
if entries.is_empty() {
return 0;
}
let mut left = 0;
let mut right = entries.len() - 1;
let mut best_start_idx = 0;
while left <= right {
let mid = (left + right) / 2;
let (blk_index, blk_path) = entries[mid];
match first_block_height(client, blk_path, xor_bytes) {
Ok(height) if height <= target_start => {
best_start_idx = mid;
left = mid + 1;
}
Ok(_) => {
if mid == 0 {
break;
}
right = mid - 1;
}
Err(e) => {
warn!("find_start_blk_index: read error at blk{blk_index:05}.dat: {e}");
break;
}
}
}
let final_idx = best_start_idx.saturating_sub(OUT_OF_ORDER_FILE_BACKOFF);
entries[final_idx].0
}