1use crate::block::connect_block;
4use crate::error::Result;
5use crate::segwit::Witness;
6use crate::types::*;
7use blvm_spec_lock::spec_locked;
8use std::collections::HashMap;
9
10#[deprecated(
16 since = "0.1.33",
17 note = "Use reorganize_chain_with_witnesses with explicit witness data for SegWit chains"
18)]
19#[spec_locked("11.3")]
20pub fn reorganize_chain(
21 new_chain: &[Block],
22 current_chain: &[Block],
23 current_utxo_set: UtxoSet,
24 current_height: Natural,
25 network: crate::types::Network,
26) -> Result<ReorganizationResult> {
27 use crate::segwit::is_segwit_transaction;
28 use crate::transaction::is_coinbase;
29
30 for block in new_chain {
31 for tx in &block.transactions {
32 if !is_coinbase(tx) && is_segwit_transaction(tx) {
33 return Err(crate::error::ConsensusError::BlockValidation(
34 "reorganize_chain: SegWit transactions require reorganize_chain_with_witnesses"
35 .into(),
36 ));
37 }
38 }
39 }
40
41 assert!(
43 current_height <= i64::MAX as u64,
44 "Current height {current_height} must fit in i64"
45 );
46 assert!(
47 current_utxo_set.len() <= u32::MAX as usize,
48 "Current UTXO set size {} exceeds maximum",
49 current_utxo_set.len()
50 );
51 assert!(
52 new_chain.len() <= 10_000,
53 "New chain length {} must be reasonable",
54 new_chain.len()
55 );
56 assert!(
57 current_chain.len() <= 10_000,
58 "Current chain length {} must be reasonable",
59 current_chain.len()
60 );
61
62 let empty_witnesses: Vec<Vec<Vec<Witness>>> = new_chain
64 .iter()
65 .map(|block| {
66 block
67 .transactions
68 .iter()
69 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
70 .collect()
71 })
72 .collect();
73 assert!(
75 empty_witnesses.len() == new_chain.len(),
76 "Witness count {} must match new chain block count {}",
77 empty_witnesses.len(),
78 new_chain.len()
79 );
80
81 reorganize_chain_with_witnesses(
82 new_chain,
83 &empty_witnesses,
84 None, current_chain,
86 current_utxo_set,
87 current_height,
88 None::<fn(&Block) -> Option<Vec<Witness>>>, None::<fn(Natural) -> Option<Vec<BlockHeader>>>, None::<fn(&Hash) -> Option<BlockUndoLog>>, None::<fn(&Hash, &BlockUndoLog) -> Result<()>>, new_chain
93 .iter()
94 .map(|b| b.header.timestamp)
95 .max()
96 .unwrap_or(0)
97 .saturating_add(crate::constants::MAX_FUTURE_BLOCK_TIME),
98 network,
99 )
100}
101
102#[allow(clippy::too_many_arguments)]
125#[spec_locked("11.3")]
126pub fn reorganize_chain_with_witnesses(
127 new_chain: &[Block],
128 new_chain_witnesses: &[Vec<Vec<Witness>>], new_chain_headers: Option<&[BlockHeader]>,
131 current_chain: &[Block],
132 current_utxo_set: UtxoSet,
133 current_height: Natural,
134 _get_witnesses_for_block: Option<impl Fn(&Block) -> Option<Vec<Witness>>>,
135 _get_headers_for_height: Option<impl Fn(Natural) -> Option<Vec<BlockHeader>>>,
136 get_undo_log_for_block: Option<impl Fn(&Hash) -> Option<BlockUndoLog>>,
137 store_undo_log_for_block: Option<impl Fn(&Hash, &BlockUndoLog) -> Result<()>>,
138 network_time: u64,
139 network: crate::types::Network,
140) -> Result<ReorganizationResult> {
141 assert!(
143 current_height <= i64::MAX as u64,
144 "Current height {current_height} must fit in i64"
145 );
146 assert!(
147 current_utxo_set.len() <= u32::MAX as usize,
148 "Current UTXO set size {} exceeds maximum",
149 current_utxo_set.len()
150 );
151 assert!(
152 new_chain.len() <= 10_000,
153 "New chain length {} must be reasonable",
154 new_chain.len()
155 );
156 assert!(
157 current_chain.len() <= 10_000,
158 "Current chain length {} must be reasonable",
159 current_chain.len()
160 );
161 assert!(
162 new_chain_witnesses.len() == new_chain.len(),
163 "New chain witness count {} must match block count {}",
164 new_chain_witnesses.len(),
165 new_chain.len()
166 );
167
168 let common_ancestor = find_common_ancestor(new_chain, current_chain)?;
170 let common_ancestor_header = common_ancestor.header;
171 let common_ancestor_index = common_ancestor.new_chain_index;
172 let current_ancestor_index = common_ancestor.current_chain_index;
173
174 assert!(
176 common_ancestor_index < new_chain.len(),
177 "Common ancestor index {} must be < new chain length {}",
178 common_ancestor_index,
179 new_chain.len()
180 );
181 assert!(
182 current_ancestor_index < current_chain.len(),
183 "Common ancestor index {} must be < current chain length {}",
184 current_ancestor_index,
185 current_chain.len()
186 );
187
188 let mut utxo_set = current_utxo_set;
194 assert!(
196 utxo_set.len() <= u32::MAX as usize,
197 "UTXO set size {} must not exceed maximum",
198 utxo_set.len()
199 );
200
201 let disconnect_start = current_ancestor_index + 1;
203 assert!(
205 disconnect_start <= current_chain.len(),
206 "Disconnect start {} must be <= current chain length {}",
207 disconnect_start,
208 current_chain.len()
209 );
210
211 let mut disconnected_undo_logs: HashMap<Hash, BlockUndoLog> = HashMap::new();
212 assert!(
214 disconnected_undo_logs.is_empty(),
215 "Disconnected undo logs must start empty"
216 );
217
218 for i in (disconnect_start..current_chain.len()).rev() {
219 assert!(i < current_chain.len(), "Block index {i} out of bounds");
221 if let Some(block) = current_chain.get(i) {
222 assert!(
224 !block.transactions.is_empty(),
225 "Block at index {i} must have at least one transaction"
226 );
227
228 let block_hash = calculate_block_hash(&block.header);
229 assert!(block_hash != [0u8; 32], "Block hash must be non-zero");
231
232 let undo_log = if let Some(ref get_undo_log) = get_undo_log_for_block {
235 get_undo_log(&block_hash).unwrap_or_else(|| {
236 BlockUndoLog::new()
240 })
241 } else {
242 BlockUndoLog::new()
245 };
246
247 utxo_set = disconnect_block(block, &undo_log, utxo_set, (i as Natural) + 1)?;
248 disconnected_undo_logs.insert(block_hash, undo_log);
249 }
250 }
251
252 let blocks_after_ancestor = (current_chain.len() - 1 - current_ancestor_index) as Natural;
259 let common_ancestor_height = current_height.saturating_sub(blocks_after_ancestor);
260 let mut new_height = common_ancestor_height;
261 let mut connected_blocks = Vec::new();
262 let mut connected_undo_logs: HashMap<Hash, BlockUndoLog> = HashMap::new();
263 let mut mtp_header_buf: Vec<BlockHeader> = Vec::new();
264
265 if new_chain_witnesses.len() != new_chain.len() {
267 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
268 format!(
269 "Witness count {} does not match block count {}",
270 new_chain_witnesses.len(),
271 new_chain.len()
272 )
273 .into(),
274 ));
275 }
276
277 for (i, block) in new_chain.iter().enumerate().skip(common_ancestor_index + 1) {
279 new_height += 1;
280 let witnesses = new_chain_witnesses[i].clone();
282
283 mtp_header_buf.clear();
285 let recent_headers: Option<&[BlockHeader]> = if let Some(headers) = new_chain_headers {
286 Some(headers)
287 } else if i > 0 {
288 let start = i.saturating_sub(crate::bip113::MEDIAN_TIME_BLOCKS - 1);
289 mtp_header_buf.extend(new_chain[start..i].iter().map(|b| b.header.clone()));
290 Some(mtp_header_buf.as_slice())
291 } else {
292 None
293 };
294
295 let context = crate::block::block_validation_context_for_connect_ibd(
296 recent_headers,
297 network_time,
298 network,
299 );
300 let (validation_result, new_utxo_set, undo_log) =
301 connect_block(block, &witnesses, utxo_set, new_height, &context)?;
302
303 if !matches!(validation_result, ValidationResult::Valid) {
304 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
305 format!("Invalid block at height {new_height} during reorganization").into(),
306 ));
307 }
308
309 let block_hash = calculate_block_hash(&block.header);
311
312 if let Some(ref store_undo_log) = store_undo_log_for_block {
314 if let Err(e) = store_undo_log(&block_hash, &undo_log) {
315 #[cfg(any(debug_assertions, feature = "profile"))]
318 eprintln!("Warning: Failed to store undo log for block {block_hash:?}: {e}");
319 #[cfg(not(any(debug_assertions, feature = "profile")))]
320 let _ = e;
321 }
322 }
323
324 connected_undo_logs.insert(block_hash, undo_log);
326
327 utxo_set = new_utxo_set;
328 connected_blocks.push(block.clone());
329 }
330
331 Ok(ReorganizationResult {
333 new_utxo_set: utxo_set,
334 new_height,
335 common_ancestor: common_ancestor_header,
336 disconnected_blocks: current_chain[disconnect_start..].to_vec(),
337 connected_blocks,
338 reorganization_depth: current_chain.len() - disconnect_start,
339 connected_block_undo_logs: connected_undo_logs,
340 })
341}
342
343pub fn update_mempool_after_reorg<F>(
408 mempool: &mut crate::mempool::Mempool,
409 reorg_result: &ReorganizationResult,
410 utxo_set: &UtxoSet,
411 get_tx_by_id: Option<F>,
412) -> Result<Vec<Hash>>
413where
414 F: Fn(&Hash) -> Option<Transaction>,
415{
416 use crate::mempool::update_mempool_after_block;
417
418 let mut all_removed = Vec::new();
419
420 for block in &reorg_result.connected_blocks {
422 let removed = update_mempool_after_block(mempool, block, utxo_set)?;
423 all_removed.extend(removed);
424 }
425
426 let mut spent_outpoints = std::collections::HashSet::new();
429 for block in &reorg_result.connected_blocks {
430 for tx in &block.transactions {
431 if !crate::transaction::is_coinbase(tx) {
432 for input in &tx.inputs {
433 spent_outpoints.insert(input.prevout);
434 }
435 }
436 }
437 }
438
439 if let Some(lookup) = get_tx_by_id {
441 let mut invalid_tx_ids = Vec::new();
442 for &tx_id in mempool.iter() {
443 if let Some(tx) = lookup(&tx_id) {
444 for input in &tx.inputs {
446 if spent_outpoints.contains(&input.prevout) {
447 invalid_tx_ids.push(tx_id);
448 break;
449 }
450 }
451 }
452 }
453
454 for tx_id in invalid_tx_ids {
456 if mempool.remove(&tx_id) {
457 all_removed.push(tx_id);
458 }
459 }
460 }
461
462 Ok(all_removed)
465}
466
467#[derive(Debug, Clone)]
469pub struct ReorgMempoolReaddParams {
470 pub height: Natural,
471 pub time_context: Option<TimeContext>,
472 pub network: Network,
473}
474
475pub fn update_mempool_after_reorg_with_readd<F, G>(
480 mempool: &mut crate::mempool::Mempool,
481 reorg_result: &ReorganizationResult,
482 utxo_set: &UtxoSet,
483 get_tx_by_id: Option<F>,
484 readd: &ReorgMempoolReaddParams,
485 get_witnesses: Option<G>,
486) -> Result<(Vec<Hash>, Vec<Hash>)>
487where
488 F: Fn(&Hash) -> Option<Transaction>,
489 G: Fn(&Hash) -> Option<Vec<Witness>>,
490{
491 let removed = update_mempool_after_reorg(mempool, reorg_result, utxo_set, get_tx_by_id)?;
492 let readded = readd_disconnected_to_mempool(
493 mempool,
494 &reorg_result.disconnected_blocks,
495 utxo_set,
496 readd,
497 get_witnesses,
498 )?;
499 Ok((removed, readded))
500}
501
502fn readd_disconnected_to_mempool<G>(
503 mempool: &mut crate::mempool::Mempool,
504 disconnected_blocks: &[Block],
505 utxo_set: &UtxoSet,
506 readd: &ReorgMempoolReaddParams,
507 get_witnesses: Option<G>,
508) -> Result<Vec<Hash>>
509where
510 G: Fn(&Hash) -> Option<Vec<Witness>>,
511{
512 use crate::block::calculate_tx_id;
513 use crate::mempool::{MempoolResult, accept_to_memory_pool};
514 use crate::transaction::is_coinbase;
515
516 let mut readded = Vec::new();
517 for block in disconnected_blocks {
518 for tx in block.transactions.iter() {
519 if is_coinbase(tx) {
520 continue;
521 }
522 let tx_id = calculate_tx_id(tx);
523 if mempool.contains(&tx_id) {
524 continue;
525 }
526 let witness_storage = get_witnesses.as_ref().and_then(|lookup| lookup(&tx_id));
527 let witnesses = witness_storage.as_deref();
528 match accept_to_memory_pool(
529 tx,
530 witnesses,
531 utxo_set,
532 mempool,
533 readd.height,
534 readd.time_context,
535 readd.network,
536 )? {
537 MempoolResult::Accepted => {
538 if mempool.insert_transaction(tx) {
539 readded.push(tx_id);
540 }
541 }
542 MempoolResult::Rejected(_) => {}
543 }
544 }
545 }
546 Ok(readded)
547}
548
549pub fn update_mempool_after_reorg_simple(
551 mempool: &mut crate::mempool::Mempool,
552 reorg_result: &ReorganizationResult,
553 utxo_set: &UtxoSet,
554) -> Result<Vec<Hash>> {
555 update_mempool_after_reorg(
556 mempool,
557 reorg_result,
558 utxo_set,
559 None::<fn(&Hash) -> Option<Transaction>>,
560 )
561}
562
563struct CommonAncestorResult {
565 header: BlockHeader,
566 new_chain_index: usize,
567 current_chain_index: usize,
568}
569
570fn find_common_ancestor(
578 new_chain: &[Block],
579 current_chain: &[Block],
580) -> Result<CommonAncestorResult> {
581 if new_chain.is_empty() || current_chain.is_empty() {
582 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
583 "Cannot find common ancestor: empty chain".into(),
584 ));
585 }
586
587 let max_compare = new_chain.len().min(current_chain.len());
588 let mut last_match: Option<usize> = None;
589 for i in 0..max_compare {
590 let new_hash = calculate_block_hash(&new_chain[i].header);
591 let current_hash = calculate_block_hash(¤t_chain[i].header);
592 if new_hash == current_hash {
593 last_match = Some(i);
594 } else {
595 break;
596 }
597 }
598
599 if let Some(idx) = last_match {
600 return Ok(CommonAncestorResult {
601 header: new_chain[idx].header.clone(),
602 new_chain_index: idx,
603 current_chain_index: idx,
604 });
605 }
606
607 Err(crate::error::ConsensusError::ConsensusRuleViolation(
608 "Chains do not share a common ancestor".into(),
609 ))
610}
611
612#[spec_locked("11.3.1", "DisconnectBlock")]
625fn disconnect_block(
626 _block: &Block,
627 undo_log: &BlockUndoLog,
628 mut utxo_set: UtxoSet,
629 _height: Natural,
630) -> Result<UtxoSet> {
631 assert!(
633 !_block.transactions.is_empty(),
634 "Block must have at least one transaction"
635 );
636 assert!(
637 _height <= i64::MAX as u64,
638 "Block height {_height} must fit in i64"
639 );
640 assert!(
641 utxo_set.len() <= u32::MAX as usize,
642 "UTXO set size {} must not exceed maximum",
643 utxo_set.len()
644 );
645 assert!(
647 undo_log.entries.len() <= 10_000,
648 "Undo log entry count {} must be reasonable",
649 undo_log.entries.len()
650 );
651
652 for (i, entry) in undo_log.entries.iter().enumerate() {
655 assert!(i < undo_log.entries.len(), "Entry index {i} out of bounds");
657 if entry.new_utxo.is_some() {
659 utxo_set.remove(&entry.outpoint);
660 }
661
662 if let Some(previous_utxo) = &entry.previous_utxo {
664 utxo_set.insert(entry.outpoint, std::sync::Arc::clone(previous_utxo));
665 }
666 }
667
668 Ok(utxo_set)
669}
670
671#[track_caller] #[allow(clippy::redundant_comparisons)] #[spec_locked("11.3", "ShouldReorganize")]
675pub fn should_reorganize(new_chain: &[Block], current_chain: &[Block]) -> Result<bool> {
676 assert!(
678 new_chain.len() <= 10_000,
679 "New chain length {} must be reasonable",
680 new_chain.len()
681 );
682 assert!(
683 current_chain.len() <= 10_000,
684 "Current chain length {} must be reasonable",
685 current_chain.len()
686 );
687
688 let new_work = calculate_chain_work(new_chain)?;
690 let current_work = calculate_chain_work(current_chain)?;
691 Ok(new_work > current_work)
692}
693
694pub fn work_for_bits(bits: Natural) -> Result<crate::pow::U256> {
696 crate::pow::get_block_proof(bits)
697}
698
699#[spec_locked("11.3", "CalculateChainWork")]
707pub fn calculate_chain_work(chain: &[Block]) -> Result<crate::pow::U256> {
708 let mut total_work = crate::pow::U256::zero();
709
710 for block in chain {
711 let work_contribution = work_for_bits(block.header.bits)?;
712 let old_total = total_work;
713 total_work = total_work.saturating_add(work_contribution);
714
715 debug_assert!(
717 total_work >= old_total,
718 "Total work ({total_work:?}) must be >= previous total ({old_total:?})"
719 );
720 }
721
722 Ok(total_work)
723}
724
725#[allow(dead_code)] fn calculate_tx_id(tx: &Transaction) -> Hash {
728 let mut hash = [0u8; 32];
729 hash[0] = (tx.version & 0xff) as u8;
730 hash[1] = (tx.inputs.len() & 0xff) as u8;
731 hash[2] = (tx.outputs.len() & 0xff) as u8;
732 hash[3] = (tx.lock_time & 0xff) as u8;
733 hash
734}
735
736fn calculate_block_hash(header: &BlockHeader) -> Hash {
741 use sha2::{Digest, Sha256};
742
743 let mut bytes = Vec::with_capacity(80);
745 bytes.extend_from_slice(&header.version.to_le_bytes());
746 bytes.extend_from_slice(&header.prev_block_hash);
747 bytes.extend_from_slice(&header.merkle_root);
748 bytes.extend_from_slice(&header.timestamp.to_le_bytes());
749 bytes.extend_from_slice(&header.bits.to_le_bytes());
750 bytes.extend_from_slice(&header.nonce.to_le_bytes());
751
752 let first_hash = Sha256::digest(&bytes);
754 let second_hash = Sha256::digest(first_hash);
755
756 let mut hash = [0u8; 32];
757 hash.copy_from_slice(&second_hash);
758 hash
759}
760
761mod undo_entry_serde {
766 use crate::types::UTXO;
767 use serde::{Deserialize, Deserializer, Serialize, Serializer};
768 use std::sync::Arc;
769
770 pub fn serialize<S>(opt: &Option<Arc<UTXO>>, s: S) -> Result<S::Ok, S::Error>
771 where
772 S: Serializer,
773 {
774 opt.as_ref().map(|a| a.as_ref()).serialize(s)
775 }
776
777 pub fn deserialize<'de, D>(d: D) -> Result<Option<Arc<UTXO>>, D::Error>
778 where
779 D: Deserializer<'de>,
780 {
781 Option::<UTXO>::deserialize(d).map(|opt| opt.map(Arc::new))
782 }
783}
784
785#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
790pub struct UndoEntry {
791 pub outpoint: OutPoint,
793 #[serde(with = "undo_entry_serde")]
795 pub previous_utxo: Option<std::sync::Arc<UTXO>>,
796 #[serde(with = "undo_entry_serde")]
798 pub new_utxo: Option<std::sync::Arc<UTXO>>,
799}
800
801#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
809pub struct BlockUndoLog {
810 pub entries: Vec<UndoEntry>,
813}
814
815impl BlockUndoLog {
816 pub fn new() -> Self {
818 Self {
819 entries: Vec::new(),
820 }
821 }
822
823 pub fn push(&mut self, entry: UndoEntry) {
825 self.entries.push(entry);
826 }
827
828 pub fn is_empty(&self) -> bool {
830 self.entries.is_empty()
831 }
832}
833
834impl Default for BlockUndoLog {
835 fn default() -> Self {
836 Self::new()
837 }
838}
839
840#[derive(Debug, Clone)]
842pub struct ReorganizationResult {
843 pub new_utxo_set: UtxoSet,
844 pub new_height: Natural,
845 pub common_ancestor: BlockHeader,
846 pub disconnected_blocks: Vec<Block>,
847 pub connected_blocks: Vec<Block>,
848 pub reorganization_depth: usize,
849 pub connected_block_undo_logs: HashMap<Hash, BlockUndoLog>,
852}
853
854#[cfg(test)]
868mod property_tests {
869 use super::*;
870 use proptest::prelude::*;
871
872 fn chain_len_range() -> std::ops::Range<usize> {
874 if std::env::var("CARGO_TARPAULIN").is_ok() || std::env::var("TARPAULIN").is_ok() {
875 1..3 } else {
877 1..5
878 }
879 }
880
881 fn chain_len_range_det() -> std::ops::Range<usize> {
883 if std::env::var("CARGO_TARPAULIN").is_ok() || std::env::var("TARPAULIN").is_ok() {
884 0..3 } else {
886 0..10
887 }
888 }
889
890 fn arb_block() -> impl Strategy<Value = Block> {
892 (0x00000000u64..0x1d00ffffu64).prop_map(|bits| Block {
893 header: BlockHeader {
894 version: 1,
895 prev_block_hash: [0u8; 32],
896 merkle_root: [0u8; 32],
897 timestamp: 0,
898 bits,
899 nonce: 0,
900 },
901 transactions: Box::new([]),
902 })
903 }
904
905 proptest! {
907 #[test]
908 fn prop_should_reorganize_max_work(
909 new_chain in proptest::collection::vec(arb_block(), chain_len_range()),
910 current_chain in proptest::collection::vec(arb_block(), chain_len_range())
911 ) {
912 let new_work = calculate_chain_work(&new_chain);
914 let current_work = calculate_chain_work(¤t_chain);
915
916 if let (Ok(new_w), Ok(current_w)) = (new_work, current_work) {
918 let should_reorg = should_reorganize(&new_chain, ¤t_chain).unwrap_or(false);
920
921 if new_w > current_w {
923 prop_assert!(should_reorg, "Must reorganize when new chain has more work");
924 } else {
925 prop_assert!(!should_reorg, "Must not reorganize when new chain has less or equal work");
926 }
927 }
928 }
930 }
931
932 proptest! {
934 #[test]
935 fn prop_calculate_chain_work_deterministic(
936 chain in proptest::collection::vec(arb_block(), chain_len_range_det())
937 ) {
938 let work1 = calculate_chain_work(&chain);
940 let work2 = calculate_chain_work(&chain);
941
942 match (work1, work2) {
944 (Ok(w1), Ok(w2)) => {
945 prop_assert_eq!(w1, w2, "Chain work calculation must be deterministic");
946 },
947 (Err(_), Err(_)) => {
948 },
950 _ => {
951 prop_assert!(false, "Chain work calculation must be deterministic (both succeed or both fail)");
952 }
953 }
954 }
955 }
956
957 proptest! {
959 #[test]
960 fn prop_expand_target_valid_range(
961 bits in 0x00000000u64..0x1d00ffffu64
962 ) {
963 let result = crate::pow::expand_target(bits);
964
965 match result {
966 Ok(target) => {
967 let _ = target;
968 },
969 Err(_) => {
970 }
972 }
973 }
974 }
975
976 proptest! {
978 #[test]
979 fn prop_should_reorganize_equal_length(
980 chain1 in proptest::collection::vec(arb_block(), 1..3),
981 chain2 in proptest::collection::vec(arb_block(), 1..3)
982 ) {
983 let len = chain1.len().min(chain2.len());
985 let chain1 = &chain1[..len];
986 let chain2 = &chain2[..len];
987
988 let work1 = calculate_chain_work(chain1);
989 let work2 = calculate_chain_work(chain2);
990
991 if let (Ok(w1), Ok(w2)) = (work1, work2) {
993 let should_reorg = should_reorganize(chain1, chain2).unwrap_or(false);
994
995 if w1 > w2 {
997 prop_assert!(should_reorg, "Must reorganize when first chain has more work");
998 } else {
999 prop_assert!(!should_reorg, "Must not reorganize when first chain has less or equal work");
1000 }
1001 }
1002 }
1004 }
1005}
1006
1007#[cfg(test)]
1008mod tests {
1009 use super::*;
1010
1011 fn witnesses_for_chain(chain: &[Block]) -> Vec<Vec<Vec<Witness>>> {
1012 chain
1013 .iter()
1014 .map(|block| {
1015 block
1016 .transactions
1017 .iter()
1018 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1019 .collect()
1020 })
1021 .collect()
1022 }
1023
1024 fn reorg_network_time(chain: &[Block]) -> u64 {
1025 chain
1026 .iter()
1027 .map(|b| b.header.timestamp)
1028 .max()
1029 .unwrap_or(0)
1030 .saturating_add(crate::constants::MAX_FUTURE_BLOCK_TIME)
1031 }
1032
1033 fn reorganize_chain_test(
1034 new_chain: &[Block],
1035 current_chain: &[Block],
1036 utxo_set: UtxoSet,
1037 current_height: Natural,
1038 ) -> Result<ReorganizationResult> {
1039 reorganize_chain_with_witnesses(
1040 new_chain,
1041 &witnesses_for_chain(new_chain),
1042 None,
1043 current_chain,
1044 utxo_set,
1045 current_height,
1046 None::<fn(&Block) -> Option<Vec<Witness>>>,
1047 None::<fn(Natural) -> Option<Vec<BlockHeader>>>,
1048 None::<fn(&Hash) -> Option<BlockUndoLog>>,
1049 None::<fn(&Hash, &BlockUndoLog) -> Result<()>>,
1050 reorg_network_time(new_chain),
1051 crate::types::Network::Regtest,
1052 )
1053 }
1054
1055 #[test]
1056 fn test_should_reorganize_more_cumulative_work() {
1057 let new_chain = vec![create_test_block(), create_test_block()];
1058 let current_chain = vec![create_test_block()];
1059
1060 assert!(should_reorganize(&new_chain, ¤t_chain).unwrap());
1061 }
1062
1063 #[test]
1064 fn test_should_reorganize_same_length_more_work() {
1065 let mut new_chain = vec![create_test_block()];
1066 let mut current_chain = vec![create_test_block()];
1067
1068 new_chain[0].header.bits = 0x0300ffff;
1070 current_chain[0].header.bits = 0x0400ffff;
1071
1072 assert!(should_reorganize(&new_chain, ¤t_chain).unwrap());
1073 }
1074
1075 #[test]
1076 fn test_should_not_reorganize_less_work() {
1077 let new_chain = vec![create_test_block()];
1078 let current_chain = vec![create_test_block(), create_test_block()];
1079
1080 assert!(!should_reorganize(&new_chain, ¤t_chain).unwrap());
1081 }
1082
1083 #[test]
1084 fn test_find_common_ancestor() {
1085 let new_chain = vec![create_test_block()];
1086 let current_chain = vec![create_test_block()];
1087
1088 let ancestor = find_common_ancestor(&new_chain, ¤t_chain).unwrap();
1089 assert_eq!(ancestor.header.version, 4);
1090 assert_eq!(ancestor.new_chain_index, 0);
1091 assert_eq!(ancestor.current_chain_index, 0);
1092 }
1093
1094 #[test]
1095 fn test_find_common_ancestor_shared_prefix_unequal_length() {
1096 let b0 = create_test_block_at_height(0);
1097 let b1 = create_test_block_at_height(1);
1098 let b2 = create_test_block_at_height(2);
1099 let b3 = create_test_block_at_height(3);
1100
1101 let new_chain = vec![b0.clone(), b1.clone(), b2, b3];
1102 let current_chain = vec![b0, b1];
1103
1104 let ancestor = find_common_ancestor(&new_chain, ¤t_chain).unwrap();
1105 assert_eq!(ancestor.new_chain_index, 1);
1106 assert_eq!(ancestor.current_chain_index, 1);
1107 }
1108
1109 #[test]
1110 fn test_find_common_ancestor_empty_chain() {
1111 let new_chain = vec![];
1112 let current_chain = vec![create_test_block()];
1113
1114 let result = find_common_ancestor(&new_chain, ¤t_chain);
1115 assert!(result.is_err());
1116 }
1117
1118 #[test]
1119 fn test_calculate_chain_work() {
1120 let mut block = create_test_block();
1121 block.header.bits = 0x0300ffff;
1123 let chain = vec![block];
1124 let work = calculate_chain_work(&chain).unwrap();
1125 assert!(work > crate::pow::U256::zero());
1126 }
1127
1128 #[test]
1129 fn test_reorganize_chain() {
1130 let ancestor = create_test_block_at_height(0);
1133 let mut new_block = create_test_block_at_height(1);
1134 new_block.header.nonce = 42; let new_chain = vec![ancestor.clone(), new_block];
1138 let current_chain = vec![ancestor];
1139 let utxo_set = UtxoSet::default();
1140
1141 let result = reorganize_chain_test(&new_chain, ¤t_chain, utxo_set, 1);
1143 match result {
1144 Ok(reorg_result) => {
1145 assert_eq!(reorg_result.new_height, 2);
1148 assert_eq!(reorg_result.connected_blocks.len(), 1);
1149 assert_eq!(reorg_result.connected_block_undo_logs.len(), 1);
1150 }
1151 Err(_) => {
1152 }
1154 }
1155 }
1156
1157 #[test]
1158 fn test_reorganize_chain_deep_reorg() {
1159 let mut block1 = create_test_block();
1161 block1.header.nonce = 1;
1162 let mut block2 = create_test_block();
1163 block2.header.nonce = 2;
1164 let mut block3 = create_test_block();
1165 block3.header.nonce = 3;
1166 let new_chain = vec![block1, block2, block3];
1167
1168 let mut current_block1 = create_test_block();
1169 current_block1.header.nonce = 10;
1170 let mut current_block2 = create_test_block();
1171 current_block2.header.nonce = 11;
1172 let current_chain = vec![current_block1, current_block2];
1173 let utxo_set = UtxoSet::default();
1174
1175 let result = reorganize_chain_test(&new_chain, ¤t_chain, utxo_set, 2);
1176 match result {
1177 Ok(reorg_result) => {
1178 assert_eq!(reorg_result.connected_blocks.len(), 3);
1179 assert_eq!(reorg_result.reorganization_depth, 2);
1180 assert_eq!(reorg_result.connected_block_undo_logs.len(), 3);
1182 }
1183 Err(_) => {
1184 }
1186 }
1187 }
1188
1189 #[test]
1190 fn test_undo_log_storage_and_retrieval() {
1191 use crate::block::connect_block;
1192 use crate::segwit::Witness;
1193
1194 let block = create_test_block_at_height(1);
1195 let mut utxo_set = UtxoSet::default();
1196
1197 let tx_id = calculate_tx_id(&block.transactions[0]);
1199 let outpoint = OutPoint {
1200 hash: tx_id,
1201 index: 0,
1202 };
1203 let utxo = UTXO {
1204 value: 5_000_000_000,
1205 script_pubkey: vec![0x51].into(),
1206 height: 1,
1207 is_coinbase: false,
1208 };
1209 utxo_set.insert(outpoint, std::sync::Arc::new(utxo.clone()));
1210
1211 let witnesses: Vec<Vec<Witness>> = block
1213 .transactions
1214 .iter()
1215 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1216 .collect();
1217 let ctx = crate::block::BlockValidationContext::for_network(crate::types::Network::Regtest);
1218 let (result, new_utxo_set, undo_log) =
1219 connect_block(&block, &witnesses, utxo_set.clone(), 1, &ctx).unwrap();
1220
1221 assert!(matches!(result, crate::types::ValidationResult::Valid));
1222
1223 assert!(
1225 !undo_log.entries.is_empty(),
1226 "Undo log should contain entries"
1227 );
1228
1229 let block_hash = calculate_block_hash(&block.header);
1231
1232 let mut undo_log_storage: HashMap<Hash, BlockUndoLog> = HashMap::new();
1234 undo_log_storage.insert(block_hash, undo_log.clone());
1235
1236 let retrieved_undo_log = undo_log_storage.get(&block_hash);
1238 assert!(
1239 retrieved_undo_log.is_some(),
1240 "Should be able to retrieve undo log"
1241 );
1242 assert_eq!(
1243 retrieved_undo_log.unwrap().entries.len(),
1244 undo_log.entries.len()
1245 );
1246
1247 let disconnected_utxo_set = disconnect_block(&block, &undo_log, new_utxo_set, 1).unwrap();
1249
1250 assert!(
1252 disconnected_utxo_set.contains_key(&outpoint),
1253 "Disconnected UTXO set should contain restored UTXO"
1254 );
1255 }
1256
1257 #[test]
1258 fn test_reorganize_with_undo_log_callback() {
1259 use crate::block::connect_block;
1260 use crate::segwit::Witness;
1261
1262 let block = create_test_block_at_height(1);
1264 let utxo_set = UtxoSet::default();
1265 let witnesses: Vec<Vec<Witness>> = block
1266 .transactions
1267 .iter()
1268 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1269 .collect();
1270
1271 let ctx = crate::block::BlockValidationContext::for_network(crate::types::Network::Regtest);
1272 let (result, connected_utxo_set, undo_log) =
1273 connect_block(&block, &witnesses, utxo_set.clone(), 1, &ctx).unwrap();
1274
1275 if !matches!(result, crate::types::ValidationResult::Valid) {
1276 eprintln!("Block validation failed: {result:?}");
1277 }
1278 assert!(matches!(result, crate::types::ValidationResult::Valid));
1279
1280 let block_hash = calculate_block_hash(&block.header);
1282 let mut undo_log_storage: HashMap<Hash, BlockUndoLog> = HashMap::new();
1283 undo_log_storage.insert(block_hash, undo_log);
1284
1285 let get_undo_log =
1287 |hash: &Hash| -> Option<BlockUndoLog> { undo_log_storage.get(hash).cloned() };
1288
1289 let mut new_block = create_test_block_at_height(2);
1292 new_block.header.nonce = 42; let new_chain = vec![block.clone(), new_block];
1294 let current_chain = vec![block];
1295 let empty_witnesses: Vec<Vec<Vec<Witness>>> = new_chain
1296 .iter()
1297 .map(|b| {
1298 b.transactions
1299 .iter()
1300 .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
1301 .collect()
1302 })
1303 .collect();
1304
1305 let reorg_result = reorganize_chain_with_witnesses(
1306 &new_chain,
1307 &empty_witnesses,
1308 None,
1309 ¤t_chain,
1310 connected_utxo_set,
1311 1,
1312 None::<fn(&Block) -> Option<Vec<Witness>>>,
1313 None::<fn(Natural) -> Option<Vec<BlockHeader>>>,
1314 Some(get_undo_log),
1315 None::<fn(&Hash, &BlockUndoLog) -> Result<()>>, 2_000_000_000,
1317 crate::types::Network::Regtest,
1318 );
1319
1320 match reorg_result {
1322 Ok(result) => {
1323 assert!(!result.connected_block_undo_logs.is_empty());
1325 }
1326 Err(_) => {
1327 }
1329 }
1330 }
1331
1332 #[test]
1333 fn test_reorganize_chain_empty_new_chain() {
1334 let new_chain = vec![];
1335 let current_chain = vec![create_test_block()];
1336 let utxo_set = UtxoSet::default();
1337
1338 let result = reorganize_chain_test(&new_chain, ¤t_chain, utxo_set, 1);
1339 assert!(result.is_err());
1340 }
1341
1342 #[test]
1343 fn test_reorganize_chain_empty_current_chain() {
1344 let new_chain = vec![create_test_block()];
1345 let current_chain = vec![];
1346 let utxo_set = UtxoSet::default();
1347
1348 let result = reorganize_chain_test(&new_chain, ¤t_chain, utxo_set, 0);
1349 assert!(result.is_err());
1350 }
1351
1352 #[test]
1353 fn test_disconnect_block() {
1354 let block = create_test_block();
1355 let mut utxo_set = UtxoSet::default();
1356
1357 let tx_id = calculate_tx_id(&block.transactions[0]);
1359 let outpoint = OutPoint {
1360 hash: tx_id,
1361 index: 0,
1362 };
1363 let utxo = UTXO {
1364 value: 50_000_000_000,
1365 script_pubkey: vec![0x51].into(),
1366 height: 1,
1367 is_coinbase: false,
1368 };
1369 utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
1370
1371 let empty_undo_log = BlockUndoLog::new();
1373 let result = disconnect_block(&block, &empty_undo_log, utxo_set, 1);
1374 assert!(result.is_ok());
1375 }
1376
1377 #[test]
1378 fn test_calculate_chain_work_empty_chain() {
1379 let chain = vec![];
1380 let work = calculate_chain_work(&chain).unwrap();
1381 assert_eq!(work, crate::pow::U256::zero());
1382 }
1383
1384 #[test]
1385 fn test_calculate_chain_work_multiple_blocks() {
1386 let mut chain = vec![create_test_block(), create_test_block()];
1387 chain[0].header.bits = 0x0300ffff;
1389 chain[1].header.bits = 0x0400ffff;
1390
1391 let work = calculate_chain_work(&chain).unwrap();
1392 assert!(work > crate::pow::U256::zero());
1393 }
1394
1395 #[test]
1396 fn test_expand_target_edge_cases() {
1397 let result = crate::pow::expand_target(0x03000000);
1399 assert!(result.is_ok());
1400 assert!(result.unwrap().is_zero());
1401
1402 let result = crate::pow::expand_target(0x03ffffff);
1404 assert!(result.is_ok());
1405
1406 let result = crate::pow::expand_target(0x2100ffff);
1408 assert!(result.is_err());
1409 }
1410
1411 #[test]
1412 fn test_calculate_tx_id_different_transactions() {
1413 let tx1 = Transaction {
1414 version: 1,
1415 inputs: vec![].into(),
1416 outputs: vec![].into(),
1417 lock_time: 0,
1418 };
1419
1420 let tx2 = Transaction {
1421 version: 2,
1422 inputs: vec![].into(),
1423 outputs: vec![].into(),
1424 lock_time: 0,
1425 };
1426
1427 let id1 = calculate_tx_id(&tx1);
1428 let id2 = calculate_tx_id(&tx2);
1429
1430 assert_ne!(id1, id2);
1431 }
1432
1433 fn encode_bip34_height(height: u64) -> Vec<u8> {
1438 if height == 0 {
1439 return vec![0x00, 0xff]; }
1442 let mut height_bytes = Vec::new();
1443 let mut n = height;
1444 while n > 0 {
1445 height_bytes.push((n & 0xff) as u8);
1446 n >>= 8;
1447 }
1448 if height_bytes.last().is_some_and(|&b| b & 0x80 != 0) {
1450 height_bytes.push(0x00);
1451 }
1452 let mut script_sig = Vec::with_capacity(1 + height_bytes.len() + 1);
1453 script_sig.push(height_bytes.len() as u8); script_sig.extend_from_slice(&height_bytes);
1455 if script_sig.len() < 2 {
1457 script_sig.push(0xff);
1458 }
1459 script_sig
1460 }
1461
1462 fn create_test_block_at_height(height: u64) -> Block {
1464 use crate::mining::calculate_merkle_root;
1465
1466 let script_sig = encode_bip34_height(height);
1467 let coinbase_tx = Transaction {
1468 version: 1,
1469 inputs: vec![TransactionInput {
1470 prevout: OutPoint {
1471 hash: [0; 32],
1472 index: 0xffffffff,
1473 },
1474 script_sig,
1475 sequence: 0xffffffff,
1476 }]
1477 .into(),
1478 outputs: vec![TransactionOutput {
1479 value: 5_000_000_000,
1480 script_pubkey: vec![0x51],
1481 }]
1482 .into(),
1483 lock_time: 0,
1484 };
1485
1486 let merkle_root =
1487 calculate_merkle_root(&[coinbase_tx.clone()]).expect("Failed to calculate merkle root");
1488
1489 Block {
1490 header: BlockHeader {
1491 version: 4,
1492 prev_block_hash: [0; 32],
1493 merkle_root,
1494 timestamp: 1231006505,
1495 bits: 0x0300ffff, nonce: 0,
1497 },
1498 transactions: vec![coinbase_tx].into_boxed_slice(),
1499 }
1500 }
1501
1502 fn create_test_block() -> Block {
1504 create_test_block_at_height(0)
1505 }
1506}