1use abtc_domain::chain_params::Checkpoint;
19use abtc_domain::consensus::{decode_compact_u256, hash_meets_target};
20use abtc_domain::primitives::{BlockHash, BlockHeader};
21use std::collections::HashMap;
22
23#[derive(Debug, Clone)]
25pub struct BlockIndexEntry {
26 pub header: BlockHeader,
28 pub height: u32,
30 pub chain_work: u128,
33 pub status: BlockValidationStatus,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum BlockValidationStatus {
40 HeaderValid,
42 FullyValidated,
44 Invalid,
46 DataUnavailable,
48}
49
50pub struct BlockIndex {
55 entries: HashMap<BlockHash, BlockIndexEntry>,
57 best_tip: BlockHash,
59 active_chain: Vec<BlockHash>,
61 checkpoints: HashMap<u32, String>,
64 pow_limit_bits: u32,
68}
69
70impl BlockIndex {
71 pub fn new_with_pow_limit(pow_limit_bits: u32) -> Self {
77 BlockIndex {
78 entries: HashMap::new(),
79 best_tip: BlockHash::zero(),
80 active_chain: Vec::new(),
81 checkpoints: HashMap::new(),
82 pow_limit_bits,
83 }
84 }
85
86 pub fn new() -> Self {
91 Self::new_with_pow_limit(0)
92 }
93
94 pub fn load_checkpoints(&mut self, checkpoints: &[Checkpoint]) {
99 for cp in checkpoints {
100 self.checkpoints.insert(cp.height, cp.hash_hex.to_string());
101 }
102 }
103
104 pub fn last_checkpoint_height(&self) -> u32 {
106 self.checkpoints.keys().copied().max().unwrap_or(0)
107 }
108
109 pub fn init_genesis(&mut self, header: BlockHeader) {
111 let hash = header.block_hash();
112 let work = work_from_bits(header.bits);
113
114 let entry = BlockIndexEntry {
115 header,
116 height: 0,
117 chain_work: work,
118 status: BlockValidationStatus::FullyValidated,
119 };
120
121 self.entries.insert(hash, entry);
122 self.best_tip = hash;
123 self.active_chain = vec![hash];
124 }
125
126 pub fn add_header(
131 &mut self,
132 header: BlockHeader,
133 ) -> Result<(BlockHash, bool), BlockIndexError> {
134 let hash = header.block_hash();
135
136 if self.entries.contains_key(&hash) {
138 return Ok((hash, false));
139 }
140
141 let parent_hash = header.prev_block_hash;
143 let (parent_height, parent_work) = {
144 let parent = self
145 .entries
146 .get(&parent_hash)
147 .ok_or(BlockIndexError::OrphanHeader)?;
148 (parent.height, parent.chain_work)
149 };
150
151 let height = parent_height + 1;
152 let work = parent_work + work_from_bits(header.bits);
153
154 if self.pow_limit_bits != 0 {
157 let target_u256 = decode_compact_u256(header.bits);
158 let limit_u256 = decode_compact_u256(self.pow_limit_bits);
159
160 if limit_u256[31] < 0x7f {
168 if !hash_meets_target(&target_u256, &limit_u256) {
172 return Err(BlockIndexError::TargetExceedsPowLimit);
173 }
174
175 if !hash_meets_target(hash.as_bytes(), &target_u256) {
177 return Err(BlockIndexError::InsufficientProofOfWork);
178 }
179 }
180 }
181
182 if let Some(expected_hex) = self.checkpoints.get(&height) {
185 if hash.to_hex_reversed() != *expected_hex {
186 return Err(BlockIndexError::CheckpointMismatch {
187 height,
188 expected: expected_hex.clone(),
189 got: hash.to_hex_reversed(),
190 });
191 }
192 }
193
194 let entry = BlockIndexEntry {
195 header,
196 height,
197 chain_work: work,
198 status: BlockValidationStatus::HeaderValid,
199 };
200
201 self.entries.insert(hash, entry);
202
203 let current_best_work = self
205 .entries
206 .get(&self.best_tip)
207 .map(|e| e.chain_work)
208 .unwrap_or(0);
209
210 let reorged = if work > current_best_work {
211 let old_tip = self.best_tip;
212 self.best_tip = hash;
213 self.rebuild_active_chain();
214 old_tip != hash } else {
216 false
217 };
218
219 Ok((hash, reorged))
220 }
221
222 pub fn set_status(&mut self, hash: &BlockHash, status: BlockValidationStatus) {
224 if let Some(entry) = self.entries.get_mut(hash) {
225 entry.status = status;
226 }
227 }
228
229 pub fn get(&self, hash: &BlockHash) -> Option<&BlockIndexEntry> {
231 self.entries.get(hash)
232 }
233
234 pub fn best_tip(&self) -> BlockHash {
236 self.best_tip
237 }
238
239 pub fn best_tip_entry(&self) -> Option<&BlockIndexEntry> {
241 self.entries.get(&self.best_tip)
242 }
243
244 pub fn get_hash_at_height(&self, height: u32) -> Option<BlockHash> {
246 self.active_chain.get(height as usize).copied()
247 }
248
249 pub fn best_height(&self) -> u32 {
251 self.active_chain.len().saturating_sub(1) as u32
252 }
253
254 pub fn header_count(&self) -> usize {
256 self.entries.len()
257 }
258
259 pub fn contains(&self, hash: &BlockHash) -> bool {
261 self.entries.contains_key(hash)
262 }
263
264 pub fn get_ancestor_chain(&self, hash: &BlockHash) -> Vec<BlockHash> {
267 let mut chain = Vec::new();
268 let mut current = *hash;
269
270 while current != BlockHash::zero() {
271 chain.push(current);
272 match self.entries.get(¤t) {
273 Some(entry) => current = entry.header.prev_block_hash,
274 None => break,
275 }
276 }
277
278 chain
279 }
280
281 pub fn build_locator(&self) -> Vec<BlockHash> {
285 let mut locator = Vec::new();
286 let mut step = 1;
287 let mut height = self.best_height() as i64;
288
289 while height >= 0 {
290 if let Some(hash) = self.get_hash_at_height(height as u32) {
291 locator.push(hash);
292 }
293
294 if locator.len() >= 10 {
295 step *= 2;
296 }
297
298 height -= step;
299 }
300
301 if let Some(genesis) = self.active_chain.first() {
303 if locator.last() != Some(genesis) {
304 locator.push(*genesis);
305 }
306 }
307
308 locator
309 }
310
311 pub fn get_median_time_past(&self, height: u32) -> Option<u32> {
323 const MEDIAN_TIME_SPAN: u32 = 11;
324
325 let start = height.saturating_sub(MEDIAN_TIME_SPAN - 1);
326 let mut timestamps = Vec::with_capacity(MEDIAN_TIME_SPAN as usize);
327
328 for h in start..=height {
329 let hash = self.get_hash_at_height(h)?;
330 let entry = self.entries.get(&hash)?;
331 timestamps.push(entry.header.time);
332 }
333
334 timestamps.sort_unstable();
335 Some(timestamps[timestamps.len() / 2])
336 }
337
338 pub fn tip_median_time_past(&self) -> Option<u32> {
340 if self.active_chain.is_empty() {
341 return None;
342 }
343 self.get_median_time_past(self.best_height())
344 }
345
346 fn rebuild_active_chain(&mut self) {
348 let mut chain = Vec::new();
349 let mut current = self.best_tip;
350
351 while current != BlockHash::zero() {
352 chain.push(current);
353 match self.entries.get(¤t) {
354 Some(entry) => current = entry.header.prev_block_hash,
355 None => break,
356 }
357 }
358
359 chain.reverse();
360 self.active_chain = chain;
361 }
362}
363
364impl Default for BlockIndex {
365 fn default() -> Self {
366 Self::new()
367 }
368}
369
370#[derive(Debug, Clone, PartialEq, Eq)]
372pub enum BlockIndexError {
373 OrphanHeader,
375 DuplicateBlock,
377 CheckpointMismatch {
379 height: u32,
380 expected: String,
381 got: String,
382 },
383 TargetExceedsPowLimit,
385 InsufficientProofOfWork,
387}
388
389impl std::fmt::Display for BlockIndexError {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 match self {
392 BlockIndexError::OrphanHeader => write!(f, "orphan header (unknown parent)"),
393 BlockIndexError::DuplicateBlock => write!(f, "duplicate block"),
394 BlockIndexError::CheckpointMismatch {
395 height,
396 expected,
397 got,
398 } => {
399 write!(
400 f,
401 "checkpoint mismatch at height {}: expected {}, got {}",
402 height, expected, got
403 )
404 }
405 BlockIndexError::TargetExceedsPowLimit => {
406 write!(f, "header target exceeds proof-of-work limit")
407 }
408 BlockIndexError::InsufficientProofOfWork => {
409 write!(f, "block hash does not meet proof-of-work target")
410 }
411 }
412 }
413}
414
415impl std::error::Error for BlockIndexError {}
416
417fn work_from_bits(bits: u32) -> u128 {
427 let exponent = bits >> 24;
428 let mantissa = bits & 0x007fffff;
429
430 if mantissa == 0 || exponent == 0 {
431 return 0;
432 }
433
434 let shift = 8 * (exponent.saturating_sub(3));
442
443 if shift >= 256 {
444 return 1;
446 }
447
448 if shift < 128 {
449 let target = (mantissa as u128) << shift;
451 if target == 0 {
452 return u128::MAX;
453 }
454 u128::MAX / (target + 1)
455 } else {
456 let work_shift = 256 - shift;
459 if work_shift >= 128 {
460 u128::MAX / (mantissa as u128)
462 } else {
463 (1u128 << work_shift) / (mantissa as u128)
464 }
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471 use abtc_domain::primitives::{BlockHash, Hash256};
472
473 fn make_header(prev: BlockHash, nonce: u32) -> BlockHeader {
474 BlockHeader {
475 version: 1,
476 prev_block_hash: prev,
477 merkle_root: Hash256::from_bytes([nonce as u8; 32]),
478 time: 1231006505 + nonce,
479 bits: 0x1d00ffff, nonce,
481 }
482 }
483
484 #[test]
485 fn test_genesis_init() {
486 let mut index = BlockIndex::new();
487 let genesis_header = make_header(BlockHash::zero(), 0);
488 index.init_genesis(genesis_header.clone());
489
490 assert_eq!(index.best_height(), 0);
491 assert_eq!(index.header_count(), 1);
492
493 let tip = index.best_tip_entry().unwrap();
494 assert_eq!(tip.height, 0);
495 }
496
497 #[test]
498 fn test_add_headers_sequential() {
499 let mut index = BlockIndex::new();
500 let genesis = make_header(BlockHash::zero(), 0);
501 let genesis_hash = genesis.block_hash();
502 index.init_genesis(genesis);
503
504 let header1 = make_header(genesis_hash, 1);
506 let (hash1, reorged) = index.add_header(header1).unwrap();
507 assert!(reorged); assert_eq!(index.best_height(), 1);
509
510 let header2 = make_header(hash1, 2);
512 let (_, reorged) = index.add_header(header2).unwrap();
513 assert!(reorged);
514 assert_eq!(index.best_height(), 2);
515 }
516
517 #[test]
518 fn test_orphan_header() {
519 let mut index = BlockIndex::new();
520 let genesis = make_header(BlockHash::zero(), 0);
521 index.init_genesis(genesis);
522
523 let orphan = make_header(BlockHash::from_hash(Hash256::from_bytes([0xff; 32])), 99);
525 let result = index.add_header(orphan);
526 assert_eq!(result, Err(BlockIndexError::OrphanHeader));
527 }
528
529 #[test]
530 fn test_duplicate_header() {
531 let mut index = BlockIndex::new();
532 let genesis = make_header(BlockHash::zero(), 0);
533 let genesis_hash = genesis.block_hash();
534 index.init_genesis(genesis);
535
536 let header1 = make_header(genesis_hash, 1);
537 let (hash1, _) = index.add_header(header1.clone()).unwrap();
538
539 let (hash1_again, reorged) = index.add_header(header1).unwrap();
541 assert_eq!(hash1, hash1_again);
542 assert!(!reorged);
543 }
544
545 #[test]
546 fn test_fork_and_reorg() {
547 let mut index = BlockIndex::new();
548 let genesis = make_header(BlockHash::zero(), 0);
549 let genesis_hash = genesis.block_hash();
550 index.init_genesis(genesis);
551
552 let a1 = make_header(genesis_hash, 10);
554 let (a1_hash, _) = index.add_header(a1).unwrap();
555 let a2 = make_header(a1_hash, 20);
556 let (a2_hash, _) = index.add_header(a2).unwrap();
557 assert_eq!(index.best_tip(), a2_hash);
558 assert_eq!(index.best_height(), 2);
559
560 let b1 = make_header(genesis_hash, 100);
562 let (b1_hash, _) = index.add_header(b1).unwrap();
563 let b2 = make_header(b1_hash, 200);
564 let (b2_hash, _) = index.add_header(b2).unwrap();
565 let b3 = make_header(b2_hash, 300);
569 let (b3_hash, reorged) = index.add_header(b3).unwrap();
570 assert!(reorged); assert_eq!(index.best_tip(), b3_hash);
572 assert_eq!(index.best_height(), 3);
573 }
574
575 #[test]
576 fn test_height_lookup() {
577 let mut index = BlockIndex::new();
578 let genesis = make_header(BlockHash::zero(), 0);
579 let genesis_hash = genesis.block_hash();
580 index.init_genesis(genesis);
581
582 let h1 = make_header(genesis_hash, 1);
583 let (h1_hash, _) = index.add_header(h1).unwrap();
584
585 assert_eq!(index.get_hash_at_height(0), Some(genesis_hash));
586 assert_eq!(index.get_hash_at_height(1), Some(h1_hash));
587 assert_eq!(index.get_hash_at_height(2), None);
588 }
589
590 #[test]
591 fn test_block_locator() {
592 let mut index = BlockIndex::new();
593 let genesis = make_header(BlockHash::zero(), 0);
594 let genesis_hash = genesis.block_hash();
595 index.init_genesis(genesis);
596
597 let mut prev = genesis_hash;
599 for i in 1..=20 {
600 let h = make_header(prev, i);
601 let (hash, _) = index.add_header(h).unwrap();
602 prev = hash;
603 }
604
605 let locator = index.build_locator();
606 assert!(!locator.is_empty());
608 assert_eq!(locator[0], prev); assert_eq!(*locator.last().unwrap(), genesis_hash);
610 }
611
612 #[test]
613 fn test_work_from_bits() {
614 assert_eq!(work_from_bits(0), 0);
616
617 let work = work_from_bits(0x1d00ffff);
619 assert!(work > 0);
620
621 let work_easy = work_from_bits(0x1d00ffff);
623 let work_hard = work_from_bits(0x1c00ffff); assert!(work_hard > work_easy);
625 }
626
627 #[test]
630 fn test_checkpoint_match_passes() {
631 let mut index = BlockIndex::new();
632 let genesis = make_header(BlockHash::zero(), 0);
633 let genesis_hash = genesis.block_hash();
634 index.init_genesis(genesis);
635
636 let h1 = make_header(genesis_hash, 1);
638 let h1_hash = h1.block_hash();
639
640 index.load_checkpoints(&[Checkpoint {
642 height: 1,
643 hash_hex: Box::leak(h1_hash.to_hex_reversed().into_boxed_str()),
644 }]);
645
646 let result = index.add_header(h1);
648 assert!(result.is_ok());
649 assert_eq!(index.best_height(), 1);
650 }
651
652 #[test]
653 fn test_checkpoint_mismatch_rejected() {
654 let mut index = BlockIndex::new();
655 let genesis = make_header(BlockHash::zero(), 0);
656 let genesis_hash = genesis.block_hash();
657 index.init_genesis(genesis);
658
659 index.load_checkpoints(&[Checkpoint {
661 height: 1,
662 hash_hex: "0000000000000000000000000000000000000000000000000000000000abcdef",
663 }]);
664
665 let h1 = make_header(genesis_hash, 1);
667 let result = index.add_header(h1);
668 assert!(matches!(
669 result,
670 Err(BlockIndexError::CheckpointMismatch { .. })
671 ));
672 assert_eq!(index.best_height(), 0); }
674
675 #[test]
676 fn test_no_checkpoint_at_height_passes() {
677 let mut index = BlockIndex::new();
678 let genesis = make_header(BlockHash::zero(), 0);
679 let genesis_hash = genesis.block_hash();
680 index.init_genesis(genesis);
681
682 index.load_checkpoints(&[Checkpoint {
684 height: 5,
685 hash_hex: "0000000000000000000000000000000000000000000000000000000000abcdef",
686 }]);
687
688 let h1 = make_header(genesis_hash, 1);
689 let result = index.add_header(h1);
690 assert!(result.is_ok());
691 }
692
693 #[test]
694 fn test_last_checkpoint_height() {
695 let mut index = BlockIndex::new();
696 assert_eq!(index.last_checkpoint_height(), 0);
697
698 index.load_checkpoints(&[
699 Checkpoint {
700 height: 100,
701 hash_hex: "aaa",
702 },
703 Checkpoint {
704 height: 500,
705 hash_hex: "bbb",
706 },
707 Checkpoint {
708 height: 250,
709 hash_hex: "ccc",
710 },
711 ]);
712 assert_eq!(index.last_checkpoint_height(), 500);
713 }
714
715 fn make_header_with_time(prev: BlockHash, nonce: u32, time: u32) -> BlockHeader {
718 BlockHeader {
719 version: 1,
720 prev_block_hash: prev,
721 merkle_root: Hash256::from_bytes([nonce as u8; 32]),
722 time,
723 bits: 0x1d00ffff,
724 nonce,
725 }
726 }
727
728 #[test]
729 fn test_mtp_genesis_only() {
730 let mut index = BlockIndex::new();
731 let genesis = make_header_with_time(BlockHash::zero(), 0, 1231006505);
732 index.init_genesis(genesis);
733
734 assert_eq!(index.get_median_time_past(0), Some(1231006505));
736 assert_eq!(index.tip_median_time_past(), Some(1231006505));
737 }
738
739 #[test]
740 fn test_mtp_three_blocks() {
741 let mut index = BlockIndex::new();
742 let genesis = make_header_with_time(BlockHash::zero(), 0, 100);
743 let genesis_hash = genesis.block_hash();
744 index.init_genesis(genesis);
745
746 let h1 = make_header_with_time(genesis_hash, 1, 200);
747 let (h1_hash, _) = index.add_header(h1).unwrap();
748
749 let h2 = make_header_with_time(h1_hash, 2, 150); let (_h2_hash, _) = index.add_header(h2).unwrap();
751
752 assert_eq!(index.get_median_time_past(2), Some(150));
755 }
756
757 #[test]
758 fn test_mtp_eleven_blocks() {
759 let mut index = BlockIndex::new();
760 let genesis = make_header_with_time(BlockHash::zero(), 0, 1000);
761 let genesis_hash = genesis.block_hash();
762 index.init_genesis(genesis);
763
764 let timestamps = [
767 1000, 1010, 1020, 1005, 1030, 1015, 1025, 1035, 1008, 1040, 1050,
768 ];
769 let mut prev = genesis_hash;
770 for i in 1..=10u32 {
771 let h = make_header_with_time(prev, i, timestamps[i as usize]);
772 let (hash, _) = index.add_header(h).unwrap();
773 prev = hash;
774 }
775
776 assert_eq!(index.get_median_time_past(10), Some(1020));
780 }
781
782 #[test]
783 fn test_mtp_more_than_eleven_blocks() {
784 let mut index = BlockIndex::new();
785 let genesis = make_header_with_time(BlockHash::zero(), 0, 500);
786 let genesis_hash = genesis.block_hash();
787 index.init_genesis(genesis);
788
789 let mut prev = genesis_hash;
791 for i in 1..=15u32 {
792 let h = make_header_with_time(prev, i, 500 + i * 10);
793 let (hash, _) = index.add_header(h).unwrap();
794 prev = hash;
795 }
796
797 assert_eq!(index.get_median_time_past(15), Some(600));
801 }
802
803 #[test]
804 fn test_mtp_invalid_height() {
805 let mut index = BlockIndex::new();
806 let genesis = make_header_with_time(BlockHash::zero(), 0, 1000);
807 index.init_genesis(genesis);
808
809 assert_eq!(index.get_median_time_past(5), None);
811 }
812
813 #[test]
822 fn regression_pow_check_skipped_when_limit_is_zero() {
823 let mut index = BlockIndex::new();
826 let genesis = make_header(BlockHash::zero(), 0);
827 let genesis_hash = genesis.block_hash();
828 index.init_genesis(genesis);
829
830 let h1 = make_header(genesis_hash, 1);
833 assert!(index.add_header(h1).is_ok());
834 }
835
836 #[test]
837 fn regression_pow_check_enabled_with_limit() {
838 let mut index = BlockIndex::new_with_pow_limit(0x1d00ffff);
842 let genesis = make_header(BlockHash::zero(), 0);
843 let genesis_hash = genesis.block_hash();
844 index.init_genesis(genesis);
845
846 let h1 = make_header(genesis_hash, 42);
847 let result = index.add_header(h1);
848 assert_eq!(result, Err(BlockIndexError::InsufficientProofOfWork));
849 }
850
851 #[test]
852 fn regression_target_exceeds_pow_limit_rejected() {
853 let mut index = BlockIndex::new_with_pow_limit(0x1d00ffff);
856 let genesis = make_header(BlockHash::zero(), 0);
857 let genesis_hash = genesis.block_hash();
858 index.init_genesis(genesis);
859
860 let mut h1 = make_header(genesis_hash, 1);
861 h1.bits = 0x2100ffff; let result = index.add_header(h1);
863 assert_eq!(result, Err(BlockIndexError::TargetExceedsPowLimit));
864 }
865
866 #[test]
867 fn regression_regtest_skips_pow_check() {
868 let mut index = BlockIndex::new_with_pow_limit(0x207fffff);
871 let genesis = make_header(BlockHash::zero(), 0);
872 let genesis_hash = genesis.block_hash();
873 index.init_genesis(genesis);
874
875 let mut h1 = make_header(genesis_hash, 1);
876 h1.bits = 0x207fffff; let result = index.add_header(h1);
878 assert!(result.is_ok());
879 }
880}