1use std::io::Read;
38
39use bsv_rs::transaction::beef_stream::{display_hex, Hash32, Step, StreamError};
40use bsv_rs::transaction::{
41 verify_stream_structure, Beef, BeefDecoder, BeefStream, Element, Headers, MerklePath,
42 Transaction, Verdict,
43};
44
45pub use bsv_rs::transaction::beef_stream::StreamError as DoorError;
46pub use bsv_rs::transaction::{Kind, Reason, Refusal};
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct BeefLimits {
54 pub max_txs: usize,
55 pub max_bumps: usize,
56 pub max_bytes: usize,
57}
58
59pub const SUBMIT_BODY_MAX_BYTES: usize = 10_000_000;
62pub const ATOMIC_HEADER_BYTES: usize = 36;
64const MAX_SUBMISSION_TXS: usize = 512;
66const MAX_SUBMISSION_BUMPS: usize = 512;
68
69pub const SUBMIT_BEEF_LIMITS: BeefLimits = BeefLimits {
71 max_txs: MAX_SUBMISSION_TXS,
72 max_bumps: MAX_SUBMISSION_BUMPS,
73 max_bytes: SUBMIT_BODY_MAX_BYTES,
74};
75pub const ENGINE_BEEF_LIMITS: BeefLimits = BeefLimits {
77 max_bytes: SUBMIT_BODY_MAX_BYTES + ATOMIC_HEADER_BYTES,
78 ..SUBMIT_BEEF_LIMITS
79};
80pub const EF_BEEF_LIMITS: BeefLimits = ENGINE_BEEF_LIMITS;
82pub const STORED_BEEF_LIMITS: BeefLimits = ENGINE_BEEF_LIMITS;
84pub const PEER_BEEF_LIMITS: BeefLimits = ENGINE_BEEF_LIMITS;
86pub const DISCOVERY_BEEF_LIMITS: BeefLimits = ENGINE_BEEF_LIMITS;
88pub const APP_BEEF_LIMITS: BeefLimits = STORED_BEEF_LIMITS;
90pub const QUEUE_BEEF_LIMITS: BeefLimits = BeefLimits {
93 max_bytes: ENGINE_BEEF_LIMITS.max_bytes,
94 ..SUBMIT_BEEF_LIMITS
95};
96pub const DEAD_LETTER_BEEF_LIMITS: BeefLimits = QUEUE_BEEF_LIMITS;
98pub const CENSUS_BEEF_LIMITS: BeefLimits = BeefLimits {
101 max_bytes: 2 * 1024 * 1024,
102 ..SUBMIT_BEEF_LIMITS
103};
104
105pub const COURIER_PROOF_MAX_BYTES: usize = (8 * 1024 - 2) / 2;
110pub const PUSH_PROOF_MAX_BYTES: usize = COURIER_PROOF_MAX_BYTES;
113pub const STORED_PROOF_MAX_BYTES: usize = COURIER_PROOF_MAX_BYTES;
116pub const PEER_PROOF_MAX_BYTES: usize = COURIER_PROOF_MAX_BYTES;
119
120#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct BeefRead {
123 pub version: u32,
125 pub subject: Option<String>,
127 pub bumps: u64,
129 pub txs: u64,
131 pub last_txid: Option<String>,
133 pub bytes: u64,
135}
136
137#[derive(Default)]
139struct Tally {
140 bumps: u64,
141 txs: u64,
142 last_txid: Option<[u8; 32]>,
143}
144
145impl Tally {
146 fn see(&mut self, element: &Element) {
147 match element {
148 Element::Bump(_) => self.bumps += 1,
149 Element::Tx { txid, .. } | Element::TxidOnly { txid, .. } => {
150 self.txs += 1;
151 self.last_txid = Some(*txid);
152 }
153 }
154 }
155
156 fn read(self, version: Option<u32>, subject: Option<[u8; 32]>, bytes: u64) -> BeefRead {
157 BeefRead {
158 version: version.unwrap_or_default(),
159 subject: subject.as_ref().map(display_hex),
160 bumps: self.bumps,
161 txs: self.txs,
162 last_txid: self.last_txid.as_ref().map(display_hex),
163 bytes,
164 }
165 }
166}
167
168pub fn fold_beef<R: Read>(
174 source: R,
175 mut visit: impl FnMut(Element),
176) -> Result<BeefRead, StreamError> {
177 let mut stream = BeefStream::new(source);
178 let mut tally = Tally::default();
179 while let Some(element) = stream.next_element()? {
180 tally.see(&element);
181 visit(element);
182 }
183 Ok(tally.read(stream.version(), stream.subject(), stream.offset()))
184}
185
186pub fn fold_held(bytes: &[u8], mut visit: impl FnMut(Element)) -> Result<BeefRead, Refusal> {
189 let mut decoder = BeefDecoder::new();
190 let mut tally = Tally::default();
191 let mut input = bytes;
192 loop {
193 match decoder.next(&mut input)? {
194 Step::Element(element) => {
195 tally.see(&element);
196 visit(element);
197 }
198 Step::NeedMore => {
199 decoder.finish()?;
200 break;
201 }
202 Step::Done => break,
203 }
204 }
205 Ok(tally.read(decoder.version(), decoder.subject(), decoder.offset()))
206}
207
208pub fn read_beef(bytes: &[u8]) -> Result<BeefRead, Refusal> {
211 fold_held(bytes, |_| {})
212}
213
214fn refused(refusal: &Refusal) -> bsv_rs::Error {
215 bsv_rs::Error::BeefError(refusal.to_string())
216}
217
218struct EveryRoot;
221
222impl Headers for EveryRoot {
223 fn carries(&self, _height: u64, _root: &Hash32) -> bool {
224 true
225 }
226}
227
228pub fn structure_roots(bytes: &[u8]) -> Result<Vec<(u64, String)>, Refusal> {
237 match verify_stream_structure(bytes, EveryRoot, None) {
238 Ok(Verdict::Valid { roots, .. }) => Ok(roots
239 .iter()
240 .map(|(height, root)| (*height, display_hex(root)))
241 .collect()),
242 Ok(Verdict::Invalid { offset, reason, .. }) => Err(Refusal { offset, reason }),
243 Ok(Verdict::Unverifiable { .. }) => {
246 unreachable!("the structure-only reader requested mining context")
247 }
248 Ok(Verdict::SpendRefused { .. }) | Err(_) => {
250 unreachable!("the structure check of held bytes ran a script or failed to read")
251 }
252 }
253}
254
255pub fn parse_beef(bytes: &[u8], _door: &BeefLimits) -> bsv_rs::Result<Beef> {
260 read_beef(bytes).map_err(|refusal| refused(&refusal))?;
261 Beef::from_binary(bytes)
262}
263
264pub fn transaction_from_beef(
267 bytes: &[u8],
268 txid: Option<&str>,
269 door: &BeefLimits,
270) -> bsv_rs::Result<Transaction> {
271 let parsed = parse_beef(bytes, door)?;
272 let target = txid
273 .map(str::to_string)
274 .or_else(|| parsed.atomic_txid.clone())
275 .or_else(|| parsed.txs.last().map(bsv_rs::transaction::BeefTx::txid))
276 .ok_or_else(|| bsv_rs::Error::TransactionError("No transactions in BEEF".into()))?;
277 parsed.find_atomic_transaction(&target).ok_or_else(|| {
278 bsv_rs::Error::TransactionError(format!("Transaction {target} not found in BEEF"))
279 })
280}
281
282fn own_bump_index(bytes: &[u8], txid: &str) -> Result<Option<u64>, Refusal> {
286 let mut found = None;
287 fold_held(bytes, |element| match element {
288 Element::Tx {
289 txid: id,
290 bump_index,
291 ..
292 } if display_hex(&id) == txid => found = Some(bump_index),
293 Element::TxidOnly { txid: id, .. } if display_hex(&id) == txid => found = Some(None),
294 _ => {}
295 })?;
296 Ok(found.flatten())
297}
298
299pub fn has_proof(bytes: &[u8], txid: &str) -> bool {
303 matches!(own_bump_index(bytes, txid), Ok(Some(_)))
304}
305
306pub fn own_proof(bytes: &[u8], txid: &str) -> Result<Option<MerklePath>, Refusal> {
313 let Some(wanted) = own_bump_index(bytes, txid)? else {
314 return Ok(None);
315 };
316 let (mut seen, mut own) = (0u64, None);
317 fold_held(bytes, |element| {
318 if let Element::Bump(bump) = element {
319 if seen == wanted {
320 own = bump.to_merkle_path().ok();
321 }
322 seen += 1;
323 }
324 })?;
325 Ok(own)
326}
327
328pub fn own_bump(bytes: &[u8], txid: &str) -> Option<MerklePath> {
330 own_proof(bytes, txid).ok().flatten()
331}
332
333pub fn merkle_path_from_hex(hex: &str, max_bytes: usize) -> bsv_rs::Result<MerklePath> {
337 check_size(hex.len(), max_bytes.saturating_mul(2), "merkle proof hex")?;
338 proof_from_hex(hex)
339}
340
341pub fn proof_from_hex(hex: &str) -> bsv_rs::Result<MerklePath> {
344 let bytes = bsv_rs::primitives::from_hex(hex)?;
345 MerklePath::from_binary(&bytes)
346}
347
348pub fn is_limit_breach(_e: &bsv_rs::Error) -> bool {
351 false
352}
353
354pub fn check_size(size: usize, max_bytes: usize, kind: &str) -> bsv_rs::Result<()> {
357 if size > max_bytes {
358 return Err(bsv_rs::Error::BeefError(format!(
359 "{kind} of {size} bytes is over max_bytes {max_bytes}"
360 )));
361 }
362 Ok(())
363}
364
365#[cfg(test)]
366#[path = "../tests/support/beef_doors.rs"]
367mod door_shapes;
368
369#[cfg(test)]
370mod door_pin {
371 use super::door_shapes as shapes;
372 use super::*;
373
374 fn tree_height_65() -> Vec<u8> {
376 let mut bin = vec![0x02, 0x00, 0xbe, 0xef, 0x01];
377 bin.extend_from_slice(&[0xfe, 0x00, 0x35, 0x0c, 0x00]);
378 bin.push(65);
379 bin
380 }
381
382 #[test]
383 fn a_refusal_names_the_offset_and_the_kind() {
384 let refusal = read_beef(&tree_height_65()).unwrap_err();
385 assert_eq!(
386 (refusal.offset, refusal.kind()),
387 (10, Kind::TreeHeightOver64)
388 );
389 let refusal = read_beef(&[0xde, 0xad, 0xbe, 0xef]).unwrap_err();
390 assert_eq!((refusal.offset, refusal.kind()), (0, Kind::BadVersion));
391 let e = parse_beef(&tree_height_65(), &SUBMIT_BEEF_LIMITS).unwrap_err();
392 assert_eq!(
393 e.to_string()
394 .matches("invalid BEEF at byte 10: TreeHeightOver64")
395 .count(),
396 1,
397 "{e}"
398 );
399 }
400
401 #[test]
402 fn a_claimed_count_is_refused_for_the_bytes_never_as_a_count() {
403 let bin = [0x02, 0x00, 0xbe, 0xef, 0xfd, 0x2c, 0x01];
405 let refusal = read_beef(&bin).unwrap_err();
406 assert_eq!((refusal.offset, refusal.kind()), (7, Kind::BadVarint));
408 let e = parse_beef(&bin, &SUBMIT_BEEF_LIMITS).unwrap_err();
409 assert!(!e.to_string().contains("max_"), "{e}");
410 assert!(!is_limit_breach(&e));
411 }
412
413 struct Drip<'a>(&'a [u8]);
415 impl Read for Drip<'_> {
416 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
417 let n = self.0.len().min(buf.len()).min(7);
418 buf[..n].copy_from_slice(&self.0[..n]);
419 self.0 = &self.0[n..];
420 Ok(n)
421 }
422 }
423
424 struct Fails<'a>(&'a [u8]);
426 impl Read for Fails<'_> {
427 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
428 if self.0.is_empty() {
429 return Err(std::io::Error::other("the object body broke"));
430 }
431 let n = self.0.len().min(buf.len());
432 buf[..n].copy_from_slice(&self.0[..n]);
433 self.0 = &self.0[n..];
434 Ok(n)
435 }
436 }
437
438 #[test]
439 fn a_source_is_read_as_the_held_bytes_are_one_element_in_hand() {
440 for (bytes, _) in [
441 shapes::body(1),
442 shapes::transactions(2_048),
443 shapes::bumps(2_048),
444 shapes::sized_body(SUBMIT_BODY_MAX_BYTES + 1),
445 ] {
446 let held = read_beef(&bytes).unwrap();
447 assert_eq!(held.bytes, bytes.len() as u64);
448 let (mut elements, mut largest) = (0u64, 0u64);
449 let streamed = fold_beef(Drip(&bytes), |element| {
450 elements += 1;
451 largest = largest.max(element.wire_len());
452 })
453 .unwrap();
454 assert_eq!(streamed, held);
455 assert_eq!(elements, held.bumps + held.txs);
456 assert!(largest < held.bytes);
458 }
459 let (bytes, _) = shapes::body(1);
461 let cut = &bytes[..bytes.len() - 3];
462 assert!(matches!(
463 fold_beef(Fails(cut), |_| {}),
464 Err(StreamError::Io(_))
465 ));
466 assert_eq!(read_beef(cut).unwrap_err().kind(), Kind::Truncated);
467 }
468
469 #[test]
471 fn the_own_proof_is_the_in_memory_index_s() {
472 let (stub_only, _) = shapes::transactions(3);
473 for (bytes, id) in [
474 shapes::body(1),
475 shapes::transactions(600),
476 shapes::bumps(600),
477 ] {
478 let beef = Beef::from_binary(&bytes).unwrap();
479 let entry = beef.find_txid(&id).unwrap();
480 let in_memory = entry.bump_index().and_then(|i| beef.bumps.get(i));
481 assert!(has_proof(&bytes, &id));
482 assert_eq!(
483 own_bump(&bytes, &id).map(|b| b.to_hex()),
484 in_memory.map(MerklePath::to_hex)
485 );
486 let stub = format!("{:064x}", 1);
488 assert!(!has_proof(&stub_only, &stub));
489 assert!(matches!(own_proof(&stub_only, &stub), Ok(None)));
490 assert!(matches!(own_proof(&bytes, &"ee".repeat(32)), Ok(None)));
491 }
492 for (bytes, offset, _) in shapes::invalid() {
494 let id = "11".repeat(32);
495 assert_eq!(own_proof(&bytes, &id).unwrap_err().offset, offset as u64);
496 assert!(!has_proof(&bytes, &id) && own_bump(&bytes, &id).is_none());
497 }
498 }
499
500 #[test]
502 fn the_own_proof_follows_the_bump_index_not_the_earliest_carrier() {
503 let (bytes, id) = shapes::body(1);
504 let mut beef = Beef::from_binary(&bytes).unwrap();
505 beef.bumps
506 .push(MerklePath::from_coinbase_txid(&id, 900_000));
507 beef.find_txid_mut(&id).unwrap().set_bump_index(Some(1));
508 let moved = beef.to_binary();
509 assert_eq!(own_bump(&moved, &id).unwrap().block_height, 900_000);
510 assert_eq!(beef.find_bump(&id).unwrap().block_height, 800_000);
511 }
512
513 #[test]
516 fn a_proof_over_the_wire_bound_is_read_where_no_wire_bounds_it() {
517 let (at, over) = shapes::proof_boundaries();
518 assert_eq!(over.to_binary().len(), COURIER_PROOF_MAX_BYTES + 1);
519 assert_eq!(
520 proof_from_hex(&over.to_hex()).unwrap().to_hex(),
521 over.to_hex()
522 );
523 assert_eq!(proof_from_hex(&at.to_hex()).unwrap().to_hex(), at.to_hex());
524 assert!(proof_from_hex("zz").is_err());
525 for bound in [COURIER_PROOF_MAX_BYTES, PUSH_PROOF_MAX_BYTES] {
526 assert!(merkle_path_from_hex(&at.to_hex(), bound).is_ok());
527 assert!(merkle_path_from_hex(&over.to_hex(), bound).is_err());
528 }
529 }
530
531 #[test]
535 fn the_structure_check_answers_the_roots_or_the_refusal() {
536 let (bytes, id) = shapes::body(1);
537 let beef = Beef::from_binary(&bytes).unwrap();
538 let root = beef
539 .find_bump(&id)
540 .unwrap()
541 .compute_root(Some(&id))
542 .unwrap();
543 assert_eq!(structure_roots(&bytes).unwrap(), vec![(800_000, root)]);
544 for (bytes, offset, _) in shapes::no_input() {
545 let refusal = structure_roots(&bytes).unwrap_err();
546 assert_eq!(
547 (refusal.offset, refusal.kind()),
548 (offset as u64, Kind::NoInputs)
549 );
550 }
551 for (bytes, offset, kind) in shapes::invalid() {
552 let refusal = structure_roots(&bytes).unwrap_err();
553 assert_eq!(refusal.offset, offset as u64, "{kind}");
554 }
555 let parent = Transaction::from_binary(beef.txs[0].raw_tx().unwrap()).unwrap();
559 let mut child = Transaction::new();
560 let mut input = bsv_rs::transaction::TransactionInput::new(id.clone(), 0);
561 input.unlocking_script = Some(bsv_rs::script::UnlockingScript::new());
562 child.inputs.push(input);
563 child.outputs.push(parent.outputs[0].clone());
564 let mut wire = vec![0x01, 0x00, 0xbe, 0xef, 0x01];
565 wire.extend_from_slice(&beef.bumps[0].to_binary());
566 wire.push(0x02);
567 let child_at = wire.len();
568 wire.extend_from_slice(&child.to_binary());
569 wire.push(0x00);
570 wire.extend_from_slice(&parent.to_binary());
571 wire.extend_from_slice(&[0x01, 0x00]);
572 assert!(Beef::from_binary(&wire).unwrap().is_valid(false));
573 let refusal = structure_roots(&wire).unwrap_err();
574 assert_eq!(
575 (refusal.offset, refusal.kind()),
576 (child_at as u64 + 5, Kind::InputNamesNoElement)
577 );
578 }
579
580 #[test]
581 fn an_empty_beef_is_read() {
582 let read = read_beef(&Beef::new().to_binary()).unwrap();
583 assert_eq!((read.bumps, read.txs, read.bytes), (0, 0, 6));
584 assert_eq!(read.subject, None);
585 }
586}