1use std::collections::HashMap;
7
8use bsv_primitives::chainhash::Hash;
9use bsv_primitives::util::{BsvReader, BsvWriter, VarInt};
10use bsv_transaction::Transaction;
11
12use crate::error::SpvError;
13use crate::merkle_path::MerklePath;
14
15pub const BEEF_V1: u32 = 4022206465;
17pub const BEEF_V2: u32 = 4022206466;
19pub const ATOMIC_BEEF: u32 = 0x01010101;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[repr(u8)]
25pub enum DataFormat {
26 RawTx = 0,
28 RawTxAndBumpIndex = 1,
30 TxIDOnly = 2,
32}
33
34impl TryFrom<u8> for DataFormat {
35 type Error = SpvError;
36 fn try_from(v: u8) -> Result<Self, SpvError> {
37 match v {
38 0 => Ok(DataFormat::RawTx),
39 1 => Ok(DataFormat::RawTxAndBumpIndex),
40 2 => Ok(DataFormat::TxIDOnly),
41 _ => Err(SpvError::InvalidBeef(format!("invalid data format: {}", v))),
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct BeefTx {
49 pub data_format: DataFormat,
51 pub known_txid: Option<Hash>,
53 pub transaction: Option<Transaction>,
55 pub bump_index: usize,
57}
58
59#[derive(Debug, Clone)]
61pub struct Beef {
62 pub version: u32,
64 pub bumps: Vec<MerklePath>,
66 pub transactions: HashMap<Hash, BeefTx>,
68}
69
70impl Beef {
71 pub fn new() -> Self {
73 Self::new_with_version(BEEF_V2)
74 }
75
76 pub fn new_v1() -> Self {
78 Self::new_with_version(BEEF_V1)
79 }
80
81 pub fn new_v2() -> Self {
83 Self::new_with_version(BEEF_V2)
84 }
85
86 fn new_with_version(version: u32) -> Self {
87 Beef {
88 version,
89 bumps: Vec::new(),
90 transactions: HashMap::new(),
91 }
92 }
93
94 pub fn from_hex(hex_str: &str) -> Result<Self, SpvError> {
96 let bytes = hex::decode(hex_str)?;
97 Self::from_bytes(&bytes)
98 }
99
100 pub fn from_bytes(data: &[u8]) -> Result<Self, SpvError> {
102 if data.len() < 4 {
103 return Err(SpvError::InvalidBeef("data too short".to_string()));
104 }
105
106 let prefix = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
108 let reader_data = if prefix == ATOMIC_BEEF {
109 if data.len() < 36 {
110 return Err(SpvError::InvalidBeef("atomic BEEF too short".to_string()));
111 }
112 &data[36..]
113 } else {
114 data
115 };
116
117 let mut reader = BsvReader::new(reader_data);
118
119 let version = reader
120 .read_u32_le()
121 .map_err(|e| SpvError::InvalidBeef(format!("reading version: {}", e)))?;
122
123 if version != BEEF_V1 && version != BEEF_V2 {
124 return Err(SpvError::InvalidBeef(format!(
125 "invalid BEEF version. expected {} or {}, received {}",
126 BEEF_V1, BEEF_V2, version
127 )));
128 }
129
130 let bumps = Self::read_bumps(&mut reader)?;
131
132 if version == BEEF_V1 {
133 let txs = Self::read_all_transactions_v1(&mut reader, &bumps)?;
134 let mut beef_txs = HashMap::new();
135 for (tx, bump_idx) in txs {
136 let txid = tx_id_hash(&tx);
137 beef_txs.insert(
138 txid,
139 BeefTx {
140 data_format: if bump_idx.is_some() {
141 DataFormat::RawTxAndBumpIndex
142 } else {
143 DataFormat::RawTx
144 },
145 known_txid: None,
146 transaction: Some(tx),
147 bump_index: bump_idx.unwrap_or(0),
148 },
149 );
150 }
151 Ok(Beef {
152 version,
153 bumps,
154 transactions: beef_txs,
155 })
156 } else {
157 let txs = Self::read_beef_txs(&mut reader, &bumps)?;
158 Ok(Beef {
159 version,
160 bumps,
161 transactions: txs,
162 })
163 }
164 }
165
166 fn read_bumps(reader: &mut BsvReader) -> Result<Vec<MerklePath>, SpvError> {
167 let n = reader
168 .read_varint()
169 .map_err(|e| SpvError::InvalidBeef(format!("reading bump count: {}", e)))?;
170 let mut bumps = Vec::with_capacity(n.value() as usize);
171 for _ in 0..n.value() {
172 bumps.push(MerklePath::from_reader(reader)?);
173 }
174 Ok(bumps)
175 }
176
177 fn read_beef_txs(
178 reader: &mut BsvReader,
179 _bumps: &[MerklePath],
180 ) -> Result<HashMap<Hash, BeefTx>, SpvError> {
181 let n = reader
182 .read_varint()
183 .map_err(|e| SpvError::InvalidBeef(format!("reading tx count: {}", e)))?;
184
185 let mut txs = HashMap::new();
186 for _ in 0..n.value() {
187 let format_byte = reader
188 .read_u8()
189 .map_err(|e| SpvError::InvalidBeef(format!("reading format byte: {}", e)))?;
190 let data_format = DataFormat::try_from(format_byte)?;
191
192 if data_format == DataFormat::TxIDOnly {
193 let hash_bytes = reader
194 .read_bytes(32)
195 .map_err(|e| SpvError::InvalidBeef(format!("reading txid: {}", e)))?;
196 let txid = Hash::from_bytes(hash_bytes)
197 .map_err(|e| SpvError::InvalidBeef(format!("invalid txid: {}", e)))?;
198 txs.insert(
199 txid,
200 BeefTx {
201 data_format,
202 known_txid: Some(txid),
203 transaction: None,
204 bump_index: 0,
205 },
206 );
207 } else {
208 let bump_index = if data_format == DataFormat::RawTxAndBumpIndex {
209 let idx = reader
210 .read_varint()
211 .map_err(|e| SpvError::InvalidBeef(format!("reading bump index: {}", e)))?;
212 idx.value() as usize
213 } else {
214 0
215 };
216
217 let tx = Transaction::read_from(reader)
218 .map_err(|e| SpvError::InvalidBeef(format!("reading transaction: {}", e)))?;
219
220 let txid = tx_id_hash(&tx);
221
222 txs.insert(
228 txid,
229 BeefTx {
230 data_format,
231 known_txid: None,
232 transaction: Some(tx),
233 bump_index,
234 },
235 );
236 }
237 }
238
239 Ok(txs)
240 }
241
242 fn read_all_transactions_v1(
244 reader: &mut BsvReader,
245 bumps: &[MerklePath],
246 ) -> Result<Vec<(Transaction, Option<usize>)>, SpvError> {
247 let n = reader
248 .read_varint()
249 .map_err(|e| SpvError::InvalidBeef(format!("reading tx count: {}", e)))?;
250
251 let mut txs = Vec::new();
252
253 for _ in 0..n.value() {
254 let tx = Transaction::read_from(reader)
255 .map_err(|e| SpvError::InvalidBeef(format!("reading transaction: {}", e)))?;
256
257 let has_bump = reader
258 .read_u8()
259 .map_err(|e| SpvError::InvalidBeef(format!("reading has_bump: {}", e)))?;
260
261 let bump_idx = if has_bump != 0 {
262 let path_index = reader
263 .read_varint()
264 .map_err(|e| SpvError::InvalidBeef(format!("reading path index: {}", e)))?;
265 let idx = path_index.value() as usize;
266 if idx < bumps.len() {
267 Some(idx)
268 } else {
269 None
270 }
271 } else {
272 None
273 };
274
275 txs.push((tx, bump_idx));
276 }
277
278 Ok(txs)
279 }
280
281 pub fn to_bytes(&self) -> Result<Vec<u8>, SpvError> {
283 let mut visited: HashMap<Hash, ()> = HashMap::new();
285 let mut ordered_tx_bytes: Vec<Vec<u8>> = Vec::new();
286
287 fn collect_tx(
289 beef: &Beef,
290 tx: &BeefTx,
291 visited: &mut HashMap<Hash, ()>,
292 ordered: &mut Vec<Vec<u8>>,
293 ) -> Result<(), SpvError> {
294 let txid = if tx.data_format == DataFormat::TxIDOnly {
295 tx.known_txid
296 .ok_or_else(|| SpvError::InvalidBeef("txid is nil".to_string()))?
297 } else {
298 let t = tx
299 .transaction
300 .as_ref()
301 .ok_or_else(|| SpvError::InvalidBeef("transaction is nil".to_string()))?;
302 tx_id_hash(t)
303 };
304
305 if visited.contains_key(&txid) {
306 return Ok(());
307 }
308
309 if tx.data_format == DataFormat::TxIDOnly {
310 let mut bytes = vec![tx.data_format as u8];
311 bytes.extend_from_slice(txid.as_bytes());
312 ordered.push(bytes);
313 } else {
314 let t = tx.transaction.as_ref().unwrap();
315 for input in &t.inputs {
317 let source_txid = Hash::from_bytes(&input.source_txid).unwrap_or_default();
318 if let Some(parent) = beef.transactions.get(&source_txid) {
319 collect_tx(beef, parent, visited, ordered)?;
320 }
321 }
322
323 let raw_bytes = t.to_bytes();
324 let mut tx_bytes = Vec::new();
325 tx_bytes.push(tx.data_format as u8);
326 if tx.data_format == DataFormat::RawTxAndBumpIndex {
327 tx_bytes.extend_from_slice(&VarInt(tx.bump_index as u64).to_bytes());
328 }
329 tx_bytes.extend_from_slice(&raw_bytes);
330 ordered.push(tx_bytes);
331 }
332
333 visited.insert(txid, ());
334 Ok(())
335 }
336
337 for tx in self.transactions.values() {
338 collect_tx(self, tx, &mut visited, &mut ordered_tx_bytes)?;
339 }
340
341 let mut writer = BsvWriter::new();
343
344 writer.write_u32_le(self.version);
346
347 writer.write_varint(VarInt(self.bumps.len() as u64));
349 for bump in &self.bumps {
350 writer.write_bytes(&bump.to_bytes());
351 }
352
353 writer.write_varint(VarInt(self.transactions.len() as u64));
355 for tx_bytes in &ordered_tx_bytes {
356 writer.write_bytes(tx_bytes);
357 }
358
359 Ok(writer.into_bytes())
360 }
361
362 pub fn to_hex(&self) -> Result<String, SpvError> {
364 Ok(hex::encode(self.to_bytes()?))
365 }
366
367 pub fn find_transaction(&self, txid: &str) -> Option<&Transaction> {
369 let hash = Hash::from_hex(txid).ok()?;
370 self.transactions.get(&hash)?.transaction.as_ref()
371 }
372
373 pub fn find_bump(&self, txid: &str) -> Option<&MerklePath> {
375 let hash = Hash::from_hex(txid).ok()?;
376 self.find_bump_by_hash(&hash)
377 }
378
379 pub fn find_bump_by_hash(&self, txid: &Hash) -> Option<&MerklePath> {
381 for bump in &self.bumps {
382 if !bump.path.is_empty() {
383 for leaf in &bump.path[0] {
384 if let Some(ref h) = leaf.hash {
385 if h == txid {
386 return Some(bump);
387 }
388 }
389 }
390 }
391 }
392 None
393 }
394
395 pub fn merge_bump(&mut self, bump: &MerklePath) -> Result<usize, SpvError> {
397 for (i, existing) in self.bumps.iter_mut().enumerate() {
399 if existing.block_height == bump.block_height {
400 let root_a = existing.compute_root(None);
401 let root_b = bump.compute_root(None);
402 if let (Ok(ra), Ok(rb)) = (&root_a, &root_b) {
403 if ra == rb {
404 existing.combine(bump)?;
405 return Ok(i);
406 }
407 }
408 }
409 }
410
411 let idx = self.bumps.len();
412 self.bumps.push(bump.clone());
413 Ok(idx)
414 }
415
416 pub fn is_valid(&self, allow_txid_only: bool) -> bool {
418 let mut proven: HashMap<Hash, bool> = HashMap::new();
420
421 for (txid, beef_tx) in &self.transactions {
422 if beef_tx.data_format == DataFormat::RawTxAndBumpIndex
423 && beef_tx.bump_index < self.bumps.len()
424 {
425 let bump = &self.bumps[beef_tx.bump_index];
426 if !bump.path.is_empty() {
427 for leaf in &bump.path[0] {
428 if let Some(ref h) = leaf.hash {
429 if h == txid {
430 proven.insert(*txid, true);
431 break;
432 }
433 }
434 }
435 }
436 }
437 }
438
439 let mut changed = true;
441 while changed {
442 changed = false;
443 for (txid, beef_tx) in &self.transactions {
444 if proven.contains_key(txid) {
445 continue;
446 }
447 if beef_tx.data_format == DataFormat::TxIDOnly {
448 if !allow_txid_only {
449 return false;
450 }
451 continue;
452 }
453 if let Some(ref tx) = beef_tx.transaction {
454 let all_inputs_proven = tx.inputs.iter().all(|input| {
455 let src_txid = Hash::from_bytes(&input.source_txid).unwrap_or_default();
456 proven.contains_key(&src_txid)
457 });
458 if all_inputs_proven {
459 proven.insert(*txid, true);
460 changed = true;
461 }
462 }
463 }
464 }
465
466 for (txid, beef_tx) in &self.transactions {
468 if beef_tx.data_format == DataFormat::TxIDOnly {
469 if !allow_txid_only {
470 return false;
471 }
472 continue;
473 }
474 if !proven.contains_key(txid) {
475 return false;
476 }
477 }
478
479 let mut roots: HashMap<u32, String> = HashMap::new();
481 for bump in &self.bumps {
482 if bump.path.is_empty() {
483 continue;
484 }
485 for leaf in &bump.path[0] {
486 if leaf.txid == Some(true) {
487 if let Some(ref h) = leaf.hash {
488 if let Ok(root) = bump.compute_root(Some(h)) {
489 let root_str = root.to_string();
490 if let Some(existing) = roots.get(&bump.block_height) {
491 if existing != &root_str {
492 return false;
493 }
494 }
495 roots.insert(bump.block_height, root_str);
496 }
497 }
498 }
499 }
500 }
501
502 true
503 }
504
505 pub fn verify(
507 &self,
508 chain_tracker: &dyn crate::chain_tracker::ChainTracker,
509 allow_txid_only: bool,
510 ) -> Result<bool, SpvError> {
511 if !self.is_valid(allow_txid_only) {
512 return Ok(false);
513 }
514
515 let mut roots: HashMap<u32, Hash> = HashMap::new();
517 for bump in &self.bumps {
518 if bump.path.is_empty() {
519 continue;
520 }
521 for leaf in &bump.path[0] {
522 if let Some(ref h) = leaf.hash {
523 if let Ok(root) = bump.compute_root(Some(h)) {
524 roots.entry(bump.block_height).or_insert(root);
525 }
526 }
527 }
528 }
529
530 for (height, root) in &roots {
531 if !chain_tracker.is_valid_root_for_height(root, *height)? {
532 return Ok(false);
533 }
534 }
535
536 Ok(true)
537 }
538}
539
540impl Default for Beef {
541 fn default() -> Self {
542 Self::new()
543 }
544}
545
546fn tx_id_hash(tx: &Transaction) -> Hash {
548 let id = tx.tx_id();
549 Hash::new(id)
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555
556 const BRC62_HEX: &str = "0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331020100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000001000100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000000";
557
558 const BEEF_SET: &str = "0200beef03fef1550d001102fd20c2009591fd79f7fb1fbd24c2fdc4911da930e1d7386f0216b6446b85eea29f978f1bfd21c202ac2a05abdae46fc2555c36a76035dedbf9fac4fc349eabffbd9d62ba440ffcb101fd116100cabeb714ea9a3f15a5e4f6138f6dd6b75bab32d8b40d178a0514e6e1e1b372f701fd8930007e04df7216a1d29bb8caabd1f78014b1b4f336eb6aee76bcf1797456ddc86b7501fd451800796afe5b113d8933f5eef2d180e72dc4b644fd76fb1243dfb791d9863702573701fd230c007a6edc003e02c429391cbf426816885731cb8054410599884eed508917a2f57c01fd100600eaa540de74506ed6abcb48e38cc544c53d373269271a7e6cf2143b7cc85d7ea401fd0903001e31aa04628b99d6cfa3e21fb4a7e773487ebc86a504e511eaff3f2176267b9401fd85010031e0d053497f85228b02879f69c4c7b43fb5abc3e0e47ea49a63853b117c9b5001c30083339d5a5b97ad77b74d3538678bb20ea7e61f8b02c24a625933eb496bebd3480160008ee445baec1613d591344a9915d77652f508e6442cd394626a3ff308bcb151f1013100f3f68f2a72e47bb41377e9e429daa496cd220bdcf702a36a209f9feba58d5552011900a01c52f4099bc7bdfea772ab03739bf009d72f24f68b5c4f8cc71a8c4da80804010d00c2ce2d5bfb9cbab9983ae1c871974f23a32c585d9b8440acc4ef5203c1d6c05401070072c7fc59a1717e90633f10d322e0f63272ae97c017d1efae04e4090abeeafac3010200a7aa5fa5576d1de6dd0e32d769592bc247be7bbd0b3e36e2d579fa1ec7d6ebce010000090cba670bea2e0d5c36e979e4cf9f79ad0874d734fb782fec2496d4c554e321010100d963646680643df73c34d7fa16f173595cf32a9ed6f64d2c8ee88a8af6b7bf52fedf590d001202fe66130200023275c6dde10d32d61af52b412b1e3956b5cd085605cd521778f11d53849fdb0cfe6713020000cd5e2298cf4d809c698c8adeeab66718e6b75b3d528bce74e6e01b984c736df901feb209010000736013454e087c89d813c99a043c9029cf2d427815c6a98ba3641c384ae52c4701fdd884007f742824bddca1582e4ded866d9609d9473397f8b86625376be74684f7fb947f01fd6d4200eb7f54ce4f920a3e4c7f96ef6b2d199c519df1b1286415581187ca608f3e47b801fd372100fa6c1c8cba3d3d5d030cd98eb91498cdffe70f0dad1000e123157d5dac22e22a01fd9a1000104c0294e478fbcac4e2325403afd86370c86043f295978b809004b2687a6c9a01fd4c08009ef5a5eaf16cab45a239c43852296ab323ca21faf256ab9768dd0a2f39970ec201fd2704006161cbd1755b66815eb69613b574920e9e836c8c3772aa2260ad3639848d520b01fd1202005e04b5afc0ea8d29dc22b611536832a2a2e7c860bbf4227ce0bdcc8a0e66284601fd0801009719f5f90e3937f3921045d202522fe315da1331acc3cce472c4b084d0debe65018500d79a1c3d45a3c41bf6526a9adbac2676159d2f3c753d7d3b6dba1dc3cbdd3c520143006b88b582d985bffc511556e471a6a20cfda2d41837245329f714214e009a3e48012000c1840dbdfc3014f1e912882b971c030fd21c0b023c01fe6fd7470d6d9bb2ab86011100f9c3de08d38588e225a5ee5334a3c03771a0b51318ca388dd1b5826951604d750109006e2b2e926c86214620d306a59522eee438a79157e9360cb76ee14a868fccc482010500d5c43ea372c432861db73ba0a6897fa29855e542a6ed910626dfb8954d94fa47010300d7863bafb5ca841ca0b13736fced1d492f0f741cb0a2beab1cafa517c878ae2c010000174ccda0879c20b85fa26d423deb0b34c5f2787127e244ccacfae39b5ba8fea7feeb590d001602fe46b3060002fa6ae8371111956f74412e3b1effcbd4fcb278124b6365b34c8cc20a5287bafffe47b306000011883eed76bdc7e7fb79efe23e3c50aa825ade46d79895de1a246e3d69a5b8cf01fea2590300009c92d7f67ac06e4bce0de4f18f438056f25138ee1a0cf61ed3a6d7f32261339b01fed0ac01000006178026214d61dc19c91cb5c08481f2f3daf03392c359de424cbd5d7135c5cf01fd69d6000174f6863438909d648fea32cdd65cbf457ab717f9be327d5d4352dbf157671e01fd356b0059536ea55010906b7071e36f78b20faaaede46a7f27ba4916dc1655836c73de701fd9b3500dee845c02c827dbcd862de359f5e6ad0ecca59213d9eb01896374d9efb7af9fd01fdcc1a00b22861b84b4537dfdaa8eb51957a51007af7836677ad14074601de6cd6c2871c01fd670d00591e76e7b07b26a6d7e940ec4f84497d9f3c7be111b15c336b24d83227db0c1001fdb20600f142d0ff9b2ddb7c21d8913f02adc7abc51fcdd5253154339450b87b59859aa601fd580300ce0307ff2027d405b8afa8a5c8834e9cc8bd073c4f463c3657562bbdb7843fe601fdad010027a3ce3a9829a3df0d9074099a6a3d76c81600a6a9c50f6cf857fb823c1a783901d700cca7689680c528f0a93fd9c980577016b37ce67ce75b1d728c4fa23008b1652b016a00b74bd3ab6c94f1216a803849afc254f37eea378c89167ff0686223db82767e3a013400434d5f48f733bb69fc5f0bd8238ffaec8d002951e6a1b52484fcc05819078372011b0053fef8153f4aed8aa8bdebeae0a6c1aa7712b84887fb565bcd9232fdd60fb0c0010c00009d9f21a9bc9e9d8c99aac9a1df47ffe02334fcb8bc8f3797d64c2564b3bf44010700838a284a4ee33c455b303e1eb23428b35d264b35c4f4b42bd6c68f1a7279f38801020042820e1ab5dbb77b0a6f266167b453f672d007d0c6eddc6229ce57c941f46c670100002c0da37e0453e7d01c810d2280a84792086b1fe1bc232e76ef6783f76c57757601010048746ad4d10a562bb53d2ed29438c9dfd0a6cacb78429277072e789d4d8dd8c101010091a52bf4a100e96dba15cbff933df60fcb26d95d6dd9b55fd5e450d5895e4526010100c202dcbdece72a45a1657ff7dbd979b031b1c8b839bc9a3b958683226644b736030100020000000140f6726035b03b90c1f770f0280444eeb041c45d026a8f4baaf00530bdc473a5020000006b483045022100ccdf467aa46d9570c4778f4e68491cc51dff4b815803d2406b6e8772d800f5ad02200ff8f11a59d207c734e9c68154dcef4023d75c37e661ab866b1d3e3ea77e6bda4121021cf99b6763736f48e6e063f99a43bfa82f15111ba0e0f9776280e6bd75d23af9ffffffff0377082800000000001976a91491b21f8856b862ff291ca0ac2ec924ba2419113788ac75330100000000001976a9144b5b285395052a61328b58c6594dd66aa6003d4988acf229f503000000001976a9148efcb6c55f5c299d48d0c74762dd811345c9093b88ac0000000001010200000001bcfe1adc5e99edb82c6a48f44cbae19bc0e5d31f9c8e4b3a92d6befb1cb2e510020000006a4730440220211655b505edd6fe9196aba77477dac5c9f638fe204243c09f1188a19164ac7f022035fb8640750515ca85df8197dec87a76db5c578f05b8ae645e30d8f70d429a324121028bf1be8161c50f98289df3ecd3185ed2273e9d448840232cf2f077f05e789c29ffffffff03d8000400000000001976a9144f427ee5f3099f0ac571f6b723a628e7b08fb64c88ac75330100000000001976a914f7cad87036406e5d3aef5d4a4d65887c76f9466788ac27db1004000000001976a9143219d1b6bd74f932dcb39a5f3b48cfde2b61cc0088ac0000000001020100000002e646efa607ff14299bc0b0cfaa65e035feb493cc440cb8abb8eb6225f8d4c1c4000000006b483045022100b410c4f82655f56fc8de4a622d3e4a8c662198de5ca8963989d70b85734986f502204fe884d99aa6ffd44bb01396b9f63bebcb7222b76e6e26c2bd60837ff555f1f8412103fda4ece7b0c9150872f8ef5241164b36a230fd9657bc43ca083d9e78bc0bcba6ffffffff3275c6dde10d32d61af52b412b1e3956b5cd085605cd521778f11d53849fdb0c000000006a473044022057f9d55ace1945866be0f83431867c58eda32d73ae3fdabed2d3424ebbe493530220553e286ae67bcaf49b0ea1d3163f41b1b3c91702a054e100c1e71ca4927f6dd8412103fda4ece7b0c9150872f8ef5241164b36a230fd9657bc43ca083d9e78bc0bcba6ffffffff04400d0300000000001976a9140e8338fa60e5391d54e99c734640e72461922d9988aca0860100000000001976a9140602787cc457f68c43581224fda6b9555aaab58e88ac10270000000000001976a91402cfbfc3931c7c1cf712574e80e75b1c2df14b2088acd5120000000000001976a914bd3dbab46060873e17ca754b0db0da4552c9a09388ac00000000";
559
560 #[test]
561 fn test_new_empty_beef_v1() {
562 let v1 = Beef::new_v1();
563 let bytes = v1.to_bytes().unwrap();
564 assert_eq!("0100beef0000", hex::encode(&bytes));
565 }
566
567 #[test]
568 fn test_new_empty_beef_v2() {
569 let v2 = Beef::new_v2();
570 let bytes = v2.to_bytes().unwrap();
571 assert_eq!("0200beef0000", hex::encode(&bytes));
572 }
573
574 #[test]
575 fn test_parse_beef_v1() {
576 let beef = Beef::from_hex(BRC62_HEX).unwrap();
577 assert_eq!(beef.version, BEEF_V1);
578 assert!(beef.is_valid(false));
579 }
580
581 #[test]
582 fn test_parse_beef_v2() {
583 let beef = Beef::from_hex(BEEF_SET).unwrap();
584 assert_eq!(beef.version, BEEF_V2);
585 assert_eq!(beef.bumps.len(), 3);
586 assert_eq!(beef.transactions.len(), 3);
587 }
588
589 #[test]
590 fn test_find_transaction() {
591 let beef = Beef::from_hex(BEEF_SET).unwrap();
592 let tx = beef
593 .find_transaction("b1fc0f44ba629dbdffab9e34fcc4faf9dbde3560a7365c55c26fe4daab052aac");
594 assert!(!beef.transactions.is_empty());
598 }
599
600 #[test]
601 fn test_beef_v2_roundtrip() {
602 let beef = Beef::from_hex(BEEF_SET).unwrap();
604 let bytes = beef.to_bytes().unwrap();
605 let beef2 = Beef::from_bytes(&bytes).unwrap();
606 assert_eq!(beef.version, beef2.version);
607 assert_eq!(beef.bumps.len(), beef2.bumps.len());
608 assert_eq!(beef.transactions.len(), beef2.transactions.len());
609 }
610
611 #[test]
612 fn test_invalid_beef() {
613 assert!(Beef::from_bytes(&[0xFF, 0xFF, 0xFF, 0xFF]).is_err());
614 assert!(Beef::from_bytes(&[]).is_err());
615 }
616
617 #[test]
618 fn test_beef_is_valid() {
619 let beef = Beef::from_hex(BEEF_SET).unwrap();
620 assert!(beef.is_valid(true));
621 }
622}