1use std::io::{Cursor, Read, Write};
6
7use crate::primitives::utils::{from_hex, to_hex};
8use crate::transaction::beef_tx::BeefTx;
9use crate::transaction::error::TransactionError;
10use crate::transaction::merkle_path::MerklePath;
11use crate::transaction::{read_u32_le, read_varint, write_u32_le, write_varint};
12
13pub const BEEF_V1: u32 = 4022206465;
15pub const BEEF_V2: u32 = 4022206466;
17pub const ATOMIC_BEEF: u32 = 0x01010101;
19
20#[derive(Debug, Clone)]
25pub struct Beef {
26 pub version: u32,
28 pub bumps: Vec<MerklePath>,
30 pub txs: Vec<BeefTx>,
32 pub atomic_txid: Option<String>,
34}
35
36impl Beef {
37 pub fn new(version: u32) -> Self {
39 Beef {
40 version,
41 bumps: Vec::new(),
42 txs: Vec::new(),
43 atomic_txid: None,
44 }
45 }
46
47 pub fn from_binary(reader: &mut impl Read) -> Result<Self, TransactionError> {
49 let mut version = read_u32_le(reader)?;
50 let mut atomic_txid = None;
51
52 if version == ATOMIC_BEEF {
53 let mut txid_bytes = [0u8; 32];
55 reader.read_exact(&mut txid_bytes)?;
56 txid_bytes.reverse();
57 atomic_txid = Some(to_hex(&txid_bytes));
58 version = read_u32_le(reader)?;
60 }
61
62 if version != BEEF_V1 && version != BEEF_V2 {
63 return Err(TransactionError::BeefError(format!(
64 "Serialized BEEF must start with {} or {} but starts with {}",
65 BEEF_V1, BEEF_V2, version
66 )));
67 }
68
69 let mut beef = Beef::new(version);
70
71 let bump_count = read_varint(reader)
73 .map_err(|e| TransactionError::InvalidFormat(e.to_string()))?
74 as usize;
75 for _ in 0..bump_count {
76 let bump = MerklePath::from_binary(reader)?;
77 beef.bumps.push(bump);
78 }
79
80 let tx_count = read_varint(reader)
82 .map_err(|e| TransactionError::InvalidFormat(e.to_string()))?
83 as usize;
84 for _ in 0..tx_count {
85 let beef_tx = if version == BEEF_V2 {
86 BeefTx::from_binary_v2(reader)?
87 } else {
88 BeefTx::from_binary_v1(reader)?
89 };
90 beef.txs.push(beef_tx);
91 }
92
93 beef.atomic_txid = atomic_txid;
94
95 beef.link_source_transactions();
98
99 Ok(beef)
100 }
101
102 pub fn to_binary(&self, writer: &mut impl Write) -> Result<(), TransactionError> {
104 if let Some(ref txid) = self.atomic_txid {
106 write_u32_le(writer, ATOMIC_BEEF)?;
107 let mut txid_bytes =
108 from_hex(txid).map_err(|e| TransactionError::InvalidFormat(e.to_string()))?;
109 txid_bytes.reverse(); writer.write_all(&txid_bytes)?;
111 }
112
113 write_u32_le(writer, self.version)?;
114
115 write_varint(writer, self.bumps.len() as u64)?;
117 for bump in &self.bumps {
118 bump.to_binary(writer)?;
119 }
120
121 write_varint(writer, self.txs.len() as u64)?;
123 for tx in &self.txs {
124 if self.version == BEEF_V2 {
125 tx.to_binary_v2(writer)?;
126 } else {
127 tx.to_binary_v1(writer)?;
128 }
129 }
130
131 Ok(())
132 }
133
134 pub fn from_hex(hex: &str) -> Result<Self, TransactionError> {
136 let bytes = from_hex(hex).map_err(|e| TransactionError::InvalidFormat(e.to_string()))?;
137 let mut cursor = Cursor::new(bytes);
138 Self::from_binary(&mut cursor)
139 }
140
141 pub fn to_hex(&self) -> Result<String, TransactionError> {
143 let mut buf = Vec::new();
144 self.to_binary(&mut buf)?;
145 Ok(to_hex(&buf))
146 }
147
148 pub fn into_transaction(
154 self,
155 ) -> Result<crate::transaction::transaction::Transaction, TransactionError> {
156 let subject_idx = if let Some(ref atomic_txid) = self.atomic_txid {
157 self.txs
158 .iter()
159 .position(|btx| btx.txid == *atomic_txid)
160 .ok_or_else(|| {
161 TransactionError::BeefError(format!(
162 "atomic txid {} not found in BEEF",
163 atomic_txid
164 ))
165 })?
166 } else {
167 if self.txs.is_empty() {
168 return Err(TransactionError::BeefError(
169 "BEEF contains no transactions".into(),
170 ));
171 }
172 self.txs.len() - 1
173 };
174
175 let mut tx = self.txs[subject_idx]
176 .tx
177 .clone()
178 .ok_or_else(|| TransactionError::BeefError("subject tx is txid-only".into()))?;
179
180 for input in &mut tx.inputs {
182 if let Some(ref source_txid) = input.source_txid {
183 if input.source_transaction.is_none() {
184 for btx in &self.txs {
185 if btx.txid == *source_txid {
186 if let Some(ref source_tx) = btx.tx {
187 input.source_transaction = Some(Box::new(source_tx.clone()));
188 }
189 break;
190 }
191 }
192 }
193 }
194 }
195
196 Ok(tx)
197 }
198
199 pub fn sort_txs(&mut self) {
204 use std::collections::{HashMap, VecDeque};
205
206 let n = self.txs.len();
207 if n <= 1 {
208 return;
209 }
210
211 let txid_to_idx: HashMap<&str, usize> = self
213 .txs
214 .iter()
215 .enumerate()
216 .map(|(i, btx)| (btx.txid.as_str(), i))
217 .collect();
218
219 let mut in_degree = vec![0usize; n];
221 let mut dependents: Vec<Vec<usize>> = vec![Vec::new(); n];
223
224 for (i, btx) in self.txs.iter().enumerate() {
225 for input_txid in &btx.input_txids {
226 if let Some(&dep_idx) = txid_to_idx.get(input_txid.as_str()) {
227 if dep_idx != i {
228 in_degree[i] += 1;
229 dependents[dep_idx].push(i);
230 }
231 }
232 }
233 }
234
235 let mut queue: VecDeque<usize> = VecDeque::new();
237 for (i, °) in in_degree.iter().enumerate() {
238 if deg == 0 {
239 queue.push_back(i);
240 }
241 }
242
243 let mut sorted_indices: Vec<usize> = Vec::with_capacity(n);
244 while let Some(idx) = queue.pop_front() {
245 sorted_indices.push(idx);
246 for &dep in &dependents[idx] {
247 in_degree[dep] -= 1;
248 if in_degree[dep] == 0 {
249 queue.push_back(dep);
250 }
251 }
252 }
253
254 if sorted_indices.len() < n {
256 for i in 0..n {
257 if !sorted_indices.contains(&i) {
258 sorted_indices.push(i);
259 }
260 }
261 }
262
263 let old_txs = std::mem::take(&mut self.txs);
265 self.txs = sorted_indices
266 .into_iter()
267 .map(|i| old_txs[i].clone())
268 .collect();
269 }
270
271 pub fn find_txid(&self, txid: &str) -> Option<&BeefTx> {
273 self.txs.iter().find(|btx| btx.txid == txid)
274 }
275
276 pub fn merge_bump(&mut self, bump: &MerklePath) -> Result<usize, TransactionError> {
286 let mut bump_index: Option<usize> = None;
287
288 for (i, existing) in self.bumps.iter_mut().enumerate() {
289 if existing.block_height == bump.block_height {
290 let root_a = existing.compute_root(None)?;
291 let root_b = bump.compute_root(None)?;
292 if root_a == root_b {
293 existing.combine(bump)?;
294 bump_index = Some(i);
295 break;
296 }
297 }
298 }
299
300 if bump_index.is_none() {
301 bump_index = Some(self.bumps.len());
302 self.bumps.push(bump.clone());
303 }
304
305 let bi = bump_index.expect("bump_index was just set");
306
307 let bump_ref = &self.bumps[bi];
309 let leaf_txids: Vec<String> = bump_ref.path[0]
310 .iter()
311 .filter_map(|leaf| leaf.hash.clone())
312 .collect();
313
314 for btx in &mut self.txs {
315 if btx.bump_index.is_none() && leaf_txids.contains(&btx.txid) {
316 btx.bump_index = Some(bi);
317 }
318 }
319
320 Ok(bi)
321 }
322
323 fn remove_existing_txid(&mut self, txid: &str) {
325 if let Some(pos) = self.txs.iter().position(|btx| btx.txid == txid) {
326 self.txs.remove(pos);
327 }
328 }
329
330 pub fn merge_raw_tx(
336 &mut self,
337 raw_tx: &[u8],
338 bump_index: Option<usize>,
339 ) -> Result<BeefTx, TransactionError> {
340 let mut cursor = std::io::Cursor::new(raw_tx);
341 let tx = crate::transaction::transaction::Transaction::from_binary(&mut cursor)?;
342 let new_tx = BeefTx::from_tx(tx, bump_index)?;
343 self.remove_existing_txid(&new_tx.txid);
344 let txid = new_tx.txid.clone();
345 self.txs.push(new_tx);
346
347 if bump_index.is_none() {
349 self.try_to_validate_bump_index(&txid);
350 }
351
352 Ok(self.txs.last().cloned().expect("just pushed"))
353 }
354
355 pub fn merge_beef(&mut self, other: &Beef) -> Result<(), TransactionError> {
360 for bump in &other.bumps {
361 self.merge_bump(bump)?;
362 }
363
364 for btx in &other.txs {
365 if btx.is_txid_only() {
366 if self.find_txid(&btx.txid).is_none() {
368 self.txs.push(BeefTx::from_txid(btx.txid.clone()));
369 }
370 } else if let Some(ref tx) = btx.tx {
371 let new_bump_index = self.find_bump_index_for_txid(&btx.txid);
373 let new_btx = BeefTx::from_tx(tx.clone(), new_bump_index)?;
374 self.remove_existing_txid(&btx.txid);
375 let txid = new_btx.txid.clone();
376 self.txs.push(new_btx);
377 if new_bump_index.is_none() {
378 self.try_to_validate_bump_index(&txid);
379 }
380 }
381 }
382
383 Ok(())
384 }
385
386 pub fn merge_beef_from_binary(&mut self, data: &[u8]) -> Result<(), TransactionError> {
388 let mut cursor = std::io::Cursor::new(data);
389 let other = Beef::from_binary(&mut cursor)?;
390 self.merge_beef(&other)
391 }
392
393 pub fn to_binary_atomic(&self, txid: &str) -> Result<Vec<u8>, TransactionError> {
400 if self.find_txid(txid).is_none() {
402 return Err(TransactionError::BeefError(format!(
403 "{} does not exist in this Beef",
404 txid
405 )));
406 }
407
408 let mut atomic_beef = self.clone();
410 atomic_beef.atomic_txid = Some(txid.to_string());
411
412 if let Some(pos) = atomic_beef.txs.iter().position(|btx| btx.txid == txid) {
414 atomic_beef.txs.truncate(pos + 1);
415 }
416
417 let mut buf = Vec::new();
418 atomic_beef.to_binary(&mut buf)?;
419 Ok(buf)
420 }
421
422 fn try_to_validate_bump_index(&mut self, txid: &str) {
424 for (i, bump) in self.bumps.iter().enumerate() {
425 let found = bump.path[0]
426 .iter()
427 .any(|leaf| leaf.hash.as_deref() == Some(txid));
428 if found {
429 if let Some(btx) = self.txs.iter_mut().find(|btx| btx.txid == txid) {
430 btx.bump_index = Some(i);
431 }
432 return;
433 }
434 }
435 }
436
437 fn find_bump_index_for_txid(&self, txid: &str) -> Option<usize> {
439 for (i, bump) in self.bumps.iter().enumerate() {
440 let found = bump.path[0]
441 .iter()
442 .any(|leaf| leaf.hash.as_deref() == Some(txid));
443 if found {
444 return Some(i);
445 }
446 }
447 None
448 }
449
450 fn link_source_transactions(&mut self) {
455 let txid_map: Vec<(String, usize)> = self
457 .txs
458 .iter()
459 .enumerate()
460 .map(|(i, btx)| (btx.txid.clone(), i))
461 .collect();
462
463 let tx_clones: Vec<Option<crate::transaction::transaction::Transaction>> =
467 self.txs.iter().map(|btx| btx.tx.clone()).collect();
468
469 for btx in &mut self.txs {
470 if let Some(ref mut tx) = btx.tx {
471 for input in &mut tx.inputs {
472 if let Some(ref source_txid) = input.source_txid {
473 if input.source_transaction.is_none() {
474 if let Some((_, idx)) =
476 txid_map.iter().find(|(tid, _)| tid == source_txid)
477 {
478 if let Some(ref source_tx) = tx_clones[*idx] {
479 input.source_transaction = Some(Box::new(source_tx.clone()));
480 }
481 }
482 }
483 }
484 }
485 }
486 }
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493 use serde::Deserialize;
494
495 #[derive(Deserialize)]
496 struct BeefVector {
497 name: String,
498 hex: String,
499 version: u32,
500 bump_count: usize,
501 tx_count: usize,
502 #[serde(default)]
503 txid: Option<String>,
504 }
505
506 fn load_test_vectors() -> Vec<BeefVector> {
507 let json = include_str!("../../test-vectors/beef_valid.json");
508 serde_json::from_str(json).expect("failed to parse beef_valid.json")
509 }
510
511 #[test]
512 fn test_beef_v1_round_trip() {
513 let vectors = load_test_vectors();
514 for v in vectors.iter().filter(|v| v.version == 1) {
515 let beef = Beef::from_hex(&v.hex)
516 .unwrap_or_else(|e| panic!("failed to parse '{}': {}", v.name, e));
517 assert_eq!(
518 beef.bumps.len(),
519 v.bump_count,
520 "bump count mismatch for '{}'",
521 v.name
522 );
523 assert_eq!(
524 beef.txs.len(),
525 v.tx_count,
526 "tx count mismatch for '{}'",
527 v.name
528 );
529
530 let result_hex = beef
531 .to_hex()
532 .unwrap_or_else(|e| panic!("failed to serialize '{}': {}", v.name, e));
533 assert_eq!(result_hex, v.hex, "round-trip failed for '{}'", v.name);
534 }
535 }
536
537 #[test]
538 fn test_beef_tx_count() {
539 let vectors = load_test_vectors();
540 for v in &vectors {
541 let beef = Beef::from_hex(&v.hex)
542 .unwrap_or_else(|e| panic!("failed to parse '{}': {}", v.name, e));
543 assert_eq!(
544 beef.bumps.len(),
545 v.bump_count,
546 "bump count mismatch for '{}'",
547 v.name
548 );
549 assert_eq!(
550 beef.txs.len(),
551 v.tx_count,
552 "tx count mismatch for '{}'",
553 v.name
554 );
555
556 if let Some(ref expected_txid) = v.txid {
558 let last_tx = &beef.txs[beef.txs.len() - 1];
559 assert_eq!(
560 &last_tx.txid, expected_txid,
561 "txid mismatch for '{}'",
562 v.name
563 );
564 }
565 }
566 }
567
568 #[test]
569 fn test_merge_beef_combines_bumps_and_txs() {
570 let vectors = load_test_vectors();
571 let beef_a = Beef::from_hex(&vectors[0].hex).expect("parse beef_a");
573 let beef_b = Beef::from_hex(&vectors[1].hex).expect("parse beef_b");
574
575 let mut merged = Beef::new(BEEF_V2);
576 merged.merge_beef(&beef_a).expect("merge beef_a");
577 merged.merge_beef(&beef_b).expect("merge beef_b");
578
579 assert!(
581 merged.txs.len() >= beef_a.txs.len(),
582 "merged should have at least as many txs as beef_a"
583 );
584 assert!(
585 merged.bumps.len() >= 1,
586 "merged should have at least one bump"
587 );
588
589 for btx in &beef_a.txs {
591 assert!(
592 merged.find_txid(&btx.txid).is_some(),
593 "merged should contain txid {} from beef_a",
594 btx.txid
595 );
596 }
597 for btx in &beef_b.txs {
598 assert!(
599 merged.find_txid(&btx.txid).is_some(),
600 "merged should contain txid {} from beef_b",
601 btx.txid
602 );
603 }
604 }
605
606 #[test]
607 fn test_merge_beef_deduplicates_same_txid() {
608 let vectors = load_test_vectors();
609 let beef_a = Beef::from_hex(&vectors[0].hex).expect("parse beef");
610
611 let mut merged = Beef::new(BEEF_V2);
612 merged.merge_beef(&beef_a).expect("merge first");
613 let count_after_first = merged.txs.len();
614
615 merged.merge_beef(&beef_a).expect("merge second");
617 assert_eq!(
618 merged.txs.len(),
619 count_after_first,
620 "merging same beef twice should not duplicate txs"
621 );
622 }
623
624 #[test]
625 fn test_merge_beef_from_binary() {
626 let vectors = load_test_vectors();
627 let beef_a = Beef::from_hex(&vectors[0].hex).expect("parse beef");
628 let binary = crate::primitives::utils::from_hex(&vectors[0].hex).expect("hex decode");
629
630 let mut merged = Beef::new(BEEF_V2);
631 merged
632 .merge_beef_from_binary(&binary)
633 .expect("merge from binary");
634
635 assert_eq!(merged.txs.len(), beef_a.txs.len());
636 assert_eq!(merged.bumps.len(), beef_a.bumps.len());
637 }
638
639 #[test]
640 fn test_merge_raw_tx() {
641 let vectors = load_test_vectors();
642 let beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
643
644 if let Some(ref tx) = beef.txs[0].tx {
646 let mut raw_tx_buf = Vec::new();
647 tx.to_binary(&mut raw_tx_buf).expect("serialize tx");
648
649 let mut new_beef = Beef::new(BEEF_V2);
650 let result = new_beef
651 .merge_raw_tx(&raw_tx_buf, None)
652 .expect("merge raw tx");
653 assert_eq!(result.txid, beef.txs[0].txid);
654 assert_eq!(new_beef.txs.len(), 1);
655 }
656 }
657
658 #[test]
659 fn test_merge_raw_tx_replaces_existing() {
660 let vectors = load_test_vectors();
661 let beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
662
663 if let Some(ref tx) = beef.txs[0].tx {
664 let mut raw_tx_buf = Vec::new();
665 tx.to_binary(&mut raw_tx_buf).expect("serialize tx");
666
667 let mut new_beef = Beef::new(BEEF_V2);
668 new_beef
669 .merge_raw_tx(&raw_tx_buf, None)
670 .expect("merge first");
671 new_beef
672 .merge_raw_tx(&raw_tx_buf, None)
673 .expect("merge second");
674
675 assert_eq!(
676 new_beef.txs.len(),
677 1,
678 "merging same raw tx twice should replace, not duplicate"
679 );
680 }
681 }
682
683 #[test]
684 fn test_to_binary_atomic() {
685 let vectors = load_test_vectors();
686 let beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
687
688 if let Some(ref expected_txid) = vectors[0].txid {
689 let atomic = beef
690 .to_binary_atomic(expected_txid)
691 .expect("to_binary_atomic");
692
693 assert!(atomic.len() > 36, "atomic output too short");
695 let prefix = u32::from_le_bytes([atomic[0], atomic[1], atomic[2], atomic[3]]);
696 assert_eq!(prefix, ATOMIC_BEEF, "should start with ATOMIC_BEEF prefix");
697
698 let mut txid_bytes =
700 crate::primitives::utils::from_hex(expected_txid).expect("hex decode txid");
701 txid_bytes.reverse(); assert_eq!(
703 &atomic[4..36],
704 &txid_bytes[..],
705 "atomic should contain txid in LE"
706 );
707
708 let mut cursor = Cursor::new(&atomic);
710 let parsed = Beef::from_binary(&mut cursor).expect("parse atomic beef");
711 assert_eq!(
712 parsed.atomic_txid.as_deref(),
713 Some(expected_txid.as_str()),
714 "parsed atomic txid should match"
715 );
716 assert_eq!(
717 parsed.txs.len(),
718 beef.txs.len(),
719 "parsed atomic should have same tx count"
720 );
721 }
722 }
723
724 #[test]
725 fn test_to_binary_atomic_nonexistent_txid() {
726 let vectors = load_test_vectors();
727 let beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
728
729 let result = beef
730 .to_binary_atomic("0000000000000000000000000000000000000000000000000000000000000000");
731 assert!(result.is_err(), "should error for nonexistent txid");
732 }
733
734 #[test]
735 fn test_find_txid() {
736 let vectors = load_test_vectors();
737 let beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
738
739 if let Some(ref expected_txid) = vectors[0].txid {
740 assert!(
741 beef.find_txid(expected_txid).is_some(),
742 "should find existing txid"
743 );
744 }
745
746 assert!(
747 beef.find_txid("0000000000000000000000000000000000000000000000000000000000000000")
748 .is_none(),
749 "should not find nonexistent txid"
750 );
751 }
752
753 #[test]
754 fn test_into_transaction_returns_last_tx() {
755 let vectors = load_test_vectors();
756 let beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
757 let expected_txid = beef.txs.last().unwrap().txid.clone();
758 let tx = beef.into_transaction().expect("into_transaction");
759 assert_eq!(
760 tx.id().unwrap(),
761 expected_txid,
762 "should return last (subject) tx"
763 );
764 }
765
766 #[test]
767 fn test_from_beef_hex() {
768 let vectors = load_test_vectors();
769 let beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
770 let expected_txid = beef.txs.last().unwrap().txid.clone();
771 let tx = crate::transaction::transaction::Transaction::from_beef(&vectors[0].hex)
772 .expect("from_beef");
773 assert_eq!(
774 tx.id().unwrap(),
775 expected_txid,
776 "from_beef should return subject tx"
777 );
778 }
779
780 #[test]
781 fn test_sort_txs_proven_before_unproven() {
782 let vectors = load_test_vectors();
783 let mut beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
784 beef.sort_txs();
785 let mut seen_unproven = false;
787 for btx in &beef.txs {
788 if btx.bump_index.is_some() {
789 assert!(!seen_unproven, "proven tx should not come after unproven");
790 } else {
791 seen_unproven = true;
792 }
793 }
794 }
795
796 #[test]
797 fn test_sort_txs_idempotent() {
798 let vectors = load_test_vectors();
799 let mut beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
800 beef.sort_txs();
801 let first_order: Vec<String> = beef.txs.iter().map(|t| t.txid.clone()).collect();
802 beef.sort_txs();
803 let second_order: Vec<String> = beef.txs.iter().map(|t| t.txid.clone()).collect();
804 assert_eq!(first_order, second_order, "sort_txs should be idempotent");
805 }
806
807 #[test]
808 fn test_merge_bump() {
809 let vectors = load_test_vectors();
810 let beef = Beef::from_hex(&vectors[0].hex).expect("parse beef");
811
812 let mut new_beef = Beef::new(BEEF_V2);
813 let idx = new_beef.merge_bump(&beef.bumps[0]).expect("merge bump");
815 assert_eq!(idx, 0, "first bump should be at index 0");
816 assert_eq!(new_beef.bumps.len(), 1);
817
818 let idx2 = new_beef
820 .merge_bump(&beef.bumps[0])
821 .expect("merge bump again");
822 assert_eq!(idx2, 0, "same bump should merge to index 0");
823 assert_eq!(
824 new_beef.bumps.len(),
825 1,
826 "should still be 1 bump after re-merge"
827 );
828 }
829}