Skip to main content

bsv_wallet_toolbox/chaintracks/
types.rs

1//! Core data structures for chaintracks block header management.
2
3use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5
6use crate::types::Chain;
7
8// ---------------------------------------------------------------------------
9// HeightRange
10// ---------------------------------------------------------------------------
11
12/// An inclusive range of block heights [low, high].
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct HeightRange {
16    pub low: u32,
17    pub high: u32,
18}
19
20impl HeightRange {
21    pub fn new(low: u32, high: u32) -> Self {
22        Self { low, high }
23    }
24
25    /// Number of heights in the range (inclusive on both ends).
26    pub fn count(&self) -> u32 {
27        self.high - self.low + 1
28    }
29
30    /// Returns true if `height` is within [low, high].
31    pub fn contains(&self, height: u32) -> bool {
32        height >= self.low && height <= self.high
33    }
34
35    /// Returns true if `other` overlaps this range.
36    pub fn overlaps(&self, other: &HeightRange) -> bool {
37        self.low <= other.high && other.low <= self.high
38    }
39
40    /// Merge two ranges if they overlap or are adjacent (gap <= 1).
41    /// Returns None if the gap is larger than 1.
42    pub fn merge(&self, other: &HeightRange) -> Option<HeightRange> {
43        // Adjacent: self.high + 1 >= other.low AND other.high + 1 >= self.low
44        let self_adj = self.high.saturating_add(1);
45        let other_adj = other.high.saturating_add(1);
46        if self_adj >= other.low && other_adj >= self.low {
47            Some(HeightRange::new(
48                self.low.min(other.low),
49                self.high.max(other.high),
50            ))
51        } else {
52            None
53        }
54    }
55
56    /// Subtract `other` from `self`, returning the remaining pieces.
57    pub fn subtract(&self, other: &HeightRange) -> Vec<HeightRange> {
58        // If no overlap, return self unchanged.
59        if !self.overlaps(other) {
60            return vec![self.clone()];
61        }
62
63        let mut result = Vec::new();
64
65        // Left piece: [self.low, other.low - 1]
66        if other.low > self.low {
67            result.push(HeightRange::new(self.low, other.low - 1));
68        }
69
70        // Right piece: [other.high + 1, self.high]
71        if other.high < self.high {
72            result.push(HeightRange::new(other.high + 1, self.high));
73        }
74
75        result
76    }
77}
78
79// ---------------------------------------------------------------------------
80// BaseBlockHeader
81// ---------------------------------------------------------------------------
82
83/// The six raw fields of a Bitcoin block header.
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct BaseBlockHeader {
87    pub version: u32,
88    /// Previous block hash in display (reversed) hex.
89    pub previous_hash: String,
90    /// Merkle root in display (reversed) hex.
91    pub merkle_root: String,
92    pub time: u32,
93    pub bits: u32,
94    pub nonce: u32,
95}
96
97/// Decode a display-order (big-endian) hex hash string to internal byte order
98/// (little-endian / reversed) as a 32-byte array.
99fn display_hex_to_internal(hex_str: &str) -> [u8; 32] {
100    let mut bytes = [0u8; 32];
101    let decoded = hex::decode(hex_str).unwrap_or_else(|_| vec![0u8; 32]);
102    let len = decoded.len().min(32);
103    bytes[..len].copy_from_slice(&decoded[..len]);
104    bytes.reverse();
105    bytes
106}
107
108impl BaseBlockHeader {
109    /// Serialize to the canonical 80-byte Bitcoin header format (all fields LE).
110    pub fn to_bytes(&self) -> [u8; 80] {
111        let mut buf = [0u8; 80];
112        let mut offset = 0;
113
114        // version: 4 bytes LE
115        buf[offset..offset + 4].copy_from_slice(&self.version.to_le_bytes());
116        offset += 4;
117
118        // previous_hash: 32 bytes internal order
119        let prev = display_hex_to_internal(&self.previous_hash);
120        buf[offset..offset + 32].copy_from_slice(&prev);
121        offset += 32;
122
123        // merkle_root: 32 bytes internal order
124        let merkle = display_hex_to_internal(&self.merkle_root);
125        buf[offset..offset + 32].copy_from_slice(&merkle);
126        offset += 32;
127
128        // time: 4 bytes LE
129        buf[offset..offset + 4].copy_from_slice(&self.time.to_le_bytes());
130        offset += 4;
131
132        // bits: 4 bytes LE
133        buf[offset..offset + 4].copy_from_slice(&self.bits.to_le_bytes());
134        offset += 4;
135
136        // nonce: 4 bytes LE
137        buf[offset..offset + 4].copy_from_slice(&self.nonce.to_le_bytes());
138
139        buf
140    }
141
142    /// Compute the block hash and wrap into a `BlockHeader` at the given height.
143    pub fn to_block_header_at_height(&self, height: u32) -> BlockHeader {
144        let bytes = self.to_bytes();
145        let hash = compute_block_hash(&bytes);
146        BlockHeader {
147            version: self.version,
148            previous_hash: self.previous_hash.clone(),
149            merkle_root: self.merkle_root.clone(),
150            time: self.time,
151            bits: self.bits,
152            nonce: self.nonce,
153            height,
154            hash,
155        }
156    }
157}
158
159// ---------------------------------------------------------------------------
160// BlockHeader
161// ---------------------------------------------------------------------------
162
163/// A `BaseBlockHeader` extended with height and computed hash.
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct BlockHeader {
167    pub version: u32,
168    pub previous_hash: String,
169    pub merkle_root: String,
170    pub time: u32,
171    pub bits: u32,
172    pub nonce: u32,
173    pub height: u32,
174    pub hash: String,
175}
176
177// ---------------------------------------------------------------------------
178// LiveBlockHeader
179// ---------------------------------------------------------------------------
180
181/// A `BlockHeader` with additional chain-tracking metadata.
182#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct LiveBlockHeader {
185    pub version: u32,
186    pub previous_hash: String,
187    pub merkle_root: String,
188    pub time: u32,
189    pub bits: u32,
190    pub nonce: u32,
191    pub height: u32,
192    pub hash: String,
193    pub chain_work: String,
194    pub is_chain_tip: bool,
195    pub is_active: bool,
196    pub header_id: Option<u64>,
197    pub previous_header_id: Option<u64>,
198}
199
200impl From<LiveBlockHeader> for BlockHeader {
201    fn from(live: LiveBlockHeader) -> Self {
202        BlockHeader {
203            version: live.version,
204            previous_hash: live.previous_hash,
205            merkle_root: live.merkle_root,
206            time: live.time,
207            bits: live.bits,
208            nonce: live.nonce,
209            height: live.height,
210            hash: live.hash,
211        }
212    }
213}
214
215// ---------------------------------------------------------------------------
216// InsertHeaderResult
217// ---------------------------------------------------------------------------
218
219/// Result returned when inserting a header into the chain store.
220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct InsertHeaderResult {
223    pub added: bool,
224    pub dupe: bool,
225    pub is_active_tip: bool,
226    pub reorg_depth: u32,
227    pub prior_tip: Option<BlockHeader>,
228    pub deactivated_headers: Vec<BlockHeader>,
229    pub no_prev: bool,
230    pub bad_prev: bool,
231    pub no_active_ancestor: bool,
232    pub no_tip: bool,
233}
234
235// ---------------------------------------------------------------------------
236// ChaintracksInfo
237// ---------------------------------------------------------------------------
238
239/// Summary information about a chaintracks instance.
240#[derive(Debug, Clone, Serialize, Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct ChaintracksInfo {
243    pub chain: Option<Chain>,
244    pub storage_type: String,
245    pub ingestor_count: u32,
246    pub tip_height: Option<u32>,
247    pub live_range: Option<HeightRange>,
248    pub is_listening: bool,
249    pub is_synchronized: bool,
250}
251
252// ---------------------------------------------------------------------------
253// Hash functions
254// ---------------------------------------------------------------------------
255
256/// Double-SHA256 of `header_bytes`, then reverse and hex-encode.
257/// Produces the standard Bitcoin block hash in display (big-endian) order.
258pub fn compute_block_hash(header_bytes: &[u8; 80]) -> String {
259    let first = Sha256::digest(header_bytes);
260    let second = Sha256::digest(&first);
261    let mut hash_bytes: [u8; 32] = second.into();
262    hash_bytes.reverse();
263    hex::encode(hash_bytes)
264}
265
266/// Calculate the work represented by a compact `bits` difficulty value.
267///
268/// Work = (~target) / (target + 1) + 1, where target is the 256-bit value
269/// encoded in compact form. Returns a 64-character hex string.
270pub fn calculate_work(bits: u32) -> String {
271    // Decode compact target into a 32-byte big-endian integer.
272    let exponent = ((bits >> 24) & 0xff) as usize;
273    let mantissa = bits & 0x007fffff;
274
275    let mut target = [0u8; 32];
276    if exponent > 0 && exponent <= 32 {
277        let start = 32usize.saturating_sub(exponent);
278        if start + 2 < 32 {
279            target[start] = ((mantissa >> 16) & 0xff) as u8;
280            target[start + 1] = ((mantissa >> 8) & 0xff) as u8;
281            target[start + 2] = (mantissa & 0xff) as u8;
282        }
283    }
284
285    // Compute work = (~target) / (target + 1) + 1 using big-endian u128 pairs.
286    let (t_hi, t_lo) = bytes32_to_u128pair(&target);
287
288    // ~target
289    let (not_hi, not_lo) = (!t_hi, !t_lo);
290
291    // target + 1
292    let (t1_lo, carry) = t_lo.overflowing_add(1u128);
293    let t1_hi = t_hi.wrapping_add(carry as u128);
294
295    // (~target) / (target + 1) — 256-bit division
296    let (div_hi, div_lo) = div256(not_hi, not_lo, t1_hi, t1_lo);
297
298    // + 1
299    let (result_lo, carry2) = div_lo.overflowing_add(1u128);
300    let result_hi = div_hi.wrapping_add(carry2 as u128);
301
302    // Encode as 64-char hex
303    format!(
304        "{:016x}{:016x}{:016x}{:016x}",
305        (result_hi >> 64) as u64,
306        result_hi as u64,
307        (result_lo >> 64) as u64,
308        result_lo as u64,
309    )
310}
311
312fn bytes32_to_u128pair(b: &[u8; 32]) -> (u128, u128) {
313    let hi = u128::from_be_bytes(b[0..16].try_into().unwrap());
314    let lo = u128::from_be_bytes(b[16..32].try_into().unwrap());
315    (hi, lo)
316}
317
318/// Unsigned 256-bit division: (a_hi:a_lo) / (b_hi:b_lo).
319/// Returns (quotient_hi, quotient_lo). Panics if divisor is zero.
320fn div256(a_hi: u128, a_lo: u128, b_hi: u128, b_lo: u128) -> (u128, u128) {
321    if b_hi == 0 && b_lo == 0 {
322        panic!("division by zero");
323    }
324
325    // Fast path: divisor fits in u128
326    if b_hi == 0 {
327        let (q_hi, r) = div128_by_u128(a_hi, 0, b_lo);
328        let (q_lo, _) = div128_by_u128(a_lo, r, b_lo);
329        return (q_hi, q_lo);
330    }
331
332    // General case: bit-by-bit long division on 32-byte arrays.
333    let a = u128pair_to_bytes32(a_hi, a_lo);
334    let b = u128pair_to_bytes32(b_hi, b_lo);
335    let (q, _r) = bytes32_divmod(&a, &b);
336    bytes32_to_u128pair(&q)
337}
338
339fn u128pair_to_bytes32(hi: u128, lo: u128) -> [u8; 32] {
340    let mut b = [0u8; 32];
341    b[0..16].copy_from_slice(&hi.to_be_bytes());
342    b[16..32].copy_from_slice(&lo.to_be_bytes());
343    b
344}
345
346/// Bit-by-bit long division of two 256-bit big-endian byte arrays.
347/// Returns (quotient, remainder).
348fn bytes32_divmod(a: &[u8; 32], b: &[u8; 32]) -> ([u8; 32], [u8; 32]) {
349    let mut q = [0u8; 32];
350    let mut r = [0u8; 32];
351
352    for i in 0..256usize {
353        // Shift r left by 1
354        shl1_bytes32(&mut r);
355        // Set LSB of r to bit (255 - i) of a (MSB first)
356        let byte_idx = i / 8;
357        let bit_idx = 7 - (i % 8);
358        let bit = (a[byte_idx] >> bit_idx) & 1;
359        r[31] |= bit;
360
361        // If r >= b, subtract b from r and set the corresponding quotient bit
362        if cmp_bytes32(&r, b) >= 0 {
363            sub_bytes32(&mut r, b);
364            let q_byte = i / 8;
365            let q_bit = 7 - (i % 8);
366            q[q_byte] |= 1 << q_bit;
367        }
368    }
369
370    (q, r)
371}
372
373fn shl1_bytes32(b: &mut [u8; 32]) {
374    let mut carry = 0u8;
375    for i in (0..32).rev() {
376        let new_carry = b[i] >> 7;
377        b[i] = (b[i] << 1) | carry;
378        carry = new_carry;
379    }
380}
381
382fn cmp_bytes32(a: &[u8; 32], b: &[u8; 32]) -> i32 {
383    for i in 0..32 {
384        if a[i] < b[i] {
385            return -1;
386        }
387        if a[i] > b[i] {
388            return 1;
389        }
390    }
391    0
392}
393
394fn sub_bytes32(a: &mut [u8; 32], b: &[u8; 32]) {
395    let mut borrow = 0i16;
396    for i in (0..32).rev() {
397        let diff = a[i] as i16 - b[i] as i16 - borrow;
398        if diff < 0 {
399            a[i] = (diff + 256) as u8;
400            borrow = 1;
401        } else {
402            a[i] = diff as u8;
403            borrow = 0;
404        }
405    }
406}
407
408/// Divide a 256-bit value (hi:lo) by a 128-bit divisor `d`.
409/// Returns (quotient_lo_128, remainder) where the quotient fits in 128 bits.
410fn div128_by_u128(hi: u128, lo: u128, d: u128) -> (u128, u128) {
411    // Split into four 64-bit limbs and use multi-precision long division.
412    let limbs = [
413        (hi >> 64) as u64,
414        hi as u64,
415        (lo >> 64) as u64,
416        lo as u64,
417    ];
418
419    let mut r: u128 = 0;
420    let mut q_limbs = [0u64; 4];
421    for (i, &limb) in limbs.iter().enumerate() {
422        let cur = (r << 64) | limb as u128;
423        q_limbs[i] = (cur / d) as u64;
424        r = cur % d;
425    }
426
427    let q = ((q_limbs[2] as u128) << 64) | q_limbs[3] as u128;
428    (q, r)
429}
430
431// ---------------------------------------------------------------------------
432// Tests
433// ---------------------------------------------------------------------------
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    #[test]
440    fn test_height_range_count() {
441        let r = HeightRange::new(10, 20);
442        assert_eq!(r.count(), 11);
443        let r = HeightRange::new(5, 5);
444        assert_eq!(r.count(), 1);
445    }
446
447    #[test]
448    fn test_height_range_contains() {
449        let r = HeightRange::new(10, 20);
450        assert!(r.contains(10));
451        assert!(r.contains(15));
452        assert!(r.contains(20));
453        assert!(!r.contains(9));
454        assert!(!r.contains(21));
455    }
456
457    #[test]
458    fn test_height_range_merge() {
459        let a = HeightRange::new(10, 20);
460        let b = HeightRange::new(15, 30);
461        assert_eq!(a.merge(&b), Some(HeightRange::new(10, 30)));
462        let a = HeightRange::new(10, 20);
463        let b = HeightRange::new(21, 30);
464        assert_eq!(a.merge(&b), Some(HeightRange::new(10, 30)));
465        let a = HeightRange::new(10, 20);
466        let b = HeightRange::new(25, 30);
467        assert_eq!(a.merge(&b), None);
468    }
469
470    #[test]
471    fn test_height_range_subtract() {
472        let a = HeightRange::new(10, 30);
473        let b = HeightRange::new(15, 20);
474        assert_eq!(
475            a.subtract(&b),
476            vec![HeightRange::new(10, 14), HeightRange::new(21, 30)]
477        );
478        let a = HeightRange::new(10, 30);
479        let b = HeightRange::new(10, 20);
480        assert_eq!(a.subtract(&b), vec![HeightRange::new(21, 30)]);
481        let a = HeightRange::new(10, 20);
482        let b = HeightRange::new(25, 30);
483        assert_eq!(a.subtract(&b), vec![HeightRange::new(10, 20)]);
484    }
485
486    #[test]
487    fn test_base_block_header_to_bytes() {
488        let genesis = BaseBlockHeader {
489            version: 1,
490            previous_hash: "0000000000000000000000000000000000000000000000000000000000000000"
491                .to_string(),
492            merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
493                .to_string(),
494            time: 1231006505,
495            bits: 0x1d00ffff,
496            nonce: 2083236893,
497        };
498        let bytes = genesis.to_bytes();
499        assert_eq!(bytes.len(), 80);
500    }
501
502    #[test]
503    fn test_compute_block_hash_genesis() {
504        let genesis = BaseBlockHeader {
505            version: 1,
506            previous_hash: "0000000000000000000000000000000000000000000000000000000000000000"
507                .to_string(),
508            merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
509                .to_string(),
510            time: 1231006505,
511            bits: 0x1d00ffff,
512            nonce: 2083236893,
513        };
514        let bytes = genesis.to_bytes();
515        let hash = compute_block_hash(&bytes);
516        assert_eq!(
517            hash,
518            "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
519        );
520    }
521
522    #[test]
523    fn test_to_block_header_at_height() {
524        let base = BaseBlockHeader {
525            version: 1,
526            previous_hash: "0000000000000000000000000000000000000000000000000000000000000000"
527                .to_string(),
528            merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
529                .to_string(),
530            time: 1231006505,
531            bits: 0x1d00ffff,
532            nonce: 2083236893,
533        };
534        let header = base.to_block_header_at_height(0);
535        assert_eq!(header.height, 0);
536        assert_eq!(
537            header.hash,
538            "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
539        );
540    }
541
542    #[test]
543    fn test_calculate_work() {
544        let work = calculate_work(0x1d00ffff);
545        assert!(!work.is_empty());
546        assert_eq!(work.len(), 64);
547    }
548
549    #[test]
550    fn test_block_header_serialization_roundtrip() {
551        let header = BlockHeader {
552            version: 1,
553            previous_hash: "abc".to_string(),
554            merkle_root: "def".to_string(),
555            time: 12345,
556            bits: 0x1d00ffff,
557            nonce: 99,
558            height: 10,
559            hash: "ghi".to_string(),
560        };
561        let json = serde_json::to_string(&header).unwrap();
562        assert!(json.contains("\"previousHash\""));
563        assert!(json.contains("\"merkleRoot\""));
564        let deserialized: BlockHeader = serde_json::from_str(&json).unwrap();
565        assert_eq!(deserialized.height, 10);
566    }
567}