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 { 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)) } /// Bisects for the file whose first block height is <= `target_start`, /// then backs off [`OUT_OF_ORDER_FILE_BACKOFF`] files. A transient /// read error mid-bisect breaks out rather than narrowing further: /// the bound at `mid` is unknown, so any further step could skip /// valid lower indices. The backoff still provides a safe lower bound. 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 }