bsv/transaction/
beef_tx.rs1use std::io::{Read, Write};
7
8use crate::primitives::utils::{from_hex, to_hex};
9use crate::transaction::error::TransactionError;
10use crate::transaction::transaction::Transaction;
11use crate::transaction::{read_varint, write_varint};
12
13#[repr(u8)]
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum TxDataFormat {
17 RawTx = 0,
19 RawTxAndBumpIndex = 1,
21 TxidOnly = 2,
23}
24
25impl TxDataFormat {
26 pub fn from_byte(b: u8) -> Result<Self, TransactionError> {
28 match b {
29 0 => Ok(TxDataFormat::RawTx),
30 1 => Ok(TxDataFormat::RawTxAndBumpIndex),
31 2 => Ok(TxDataFormat::TxidOnly),
32 _ => Err(TransactionError::InvalidFormat(format!(
33 "unknown TxDataFormat byte: {b}"
34 ))),
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
44pub struct BeefTx {
45 pub tx: Option<Transaction>,
47 pub txid: String,
49 pub bump_index: Option<usize>,
51 pub input_txids: Vec<String>,
53}
54
55impl BeefTx {
56 pub fn from_tx(tx: Transaction, bump_index: Option<usize>) -> Result<Self, TransactionError> {
58 let txid = tx.id()?;
59 let input_txids = if bump_index.is_some() {
60 Vec::new()
61 } else {
62 Self::collect_input_txids(&tx)
63 };
64 Ok(BeefTx {
65 tx: Some(tx),
66 txid,
67 bump_index,
68 input_txids,
69 })
70 }
71
72 pub fn from_txid(txid: String) -> Self {
74 BeefTx {
75 tx: None,
76 txid,
77 bump_index: None,
78 input_txids: Vec::new(),
79 }
80 }
81
82 pub fn is_txid_only(&self) -> bool {
84 self.tx.is_none()
85 }
86
87 pub fn has_proof(&self) -> bool {
89 self.bump_index.is_some()
90 }
91
92 fn collect_input_txids(tx: &Transaction) -> Vec<String> {
94 let mut txids = Vec::new();
95 for input in &tx.inputs {
96 if let Some(ref stxid) = input.source_txid {
97 if !txids.contains(stxid) {
98 txids.push(stxid.clone());
99 }
100 }
101 }
102 txids
103 }
104
105 pub fn from_binary_v1(reader: &mut impl Read) -> Result<Self, TransactionError> {
109 let tx = Transaction::from_binary(reader)?;
110 let mut has_bump_buf = [0u8; 1];
111 reader.read_exact(&mut has_bump_buf)?;
112 let bump_index = if has_bump_buf[0] != 0 {
113 Some(
114 read_varint(reader).map_err(|e| TransactionError::InvalidFormat(e.to_string()))?
115 as usize,
116 )
117 } else {
118 None
119 };
120 Self::from_tx(tx, bump_index)
121 }
122
123 pub fn from_binary_v2(reader: &mut impl Read) -> Result<Self, TransactionError> {
127 let mut format_buf = [0u8; 1];
128 reader.read_exact(&mut format_buf)?;
129 let format = TxDataFormat::from_byte(format_buf[0])?;
130
131 match format {
132 TxDataFormat::TxidOnly => {
133 let mut txid_bytes = [0u8; 32];
135 reader.read_exact(&mut txid_bytes)?;
136 txid_bytes.reverse();
137 let txid = to_hex(&txid_bytes);
138 Ok(BeefTx::from_txid(txid))
139 }
140 TxDataFormat::RawTxAndBumpIndex => {
141 let bump_index = read_varint(reader)
142 .map_err(|e| TransactionError::InvalidFormat(e.to_string()))?
143 as usize;
144 let tx = Transaction::from_binary(reader)?;
145 Self::from_tx(tx, Some(bump_index))
146 }
147 TxDataFormat::RawTx => {
148 let tx = Transaction::from_binary(reader)?;
149 Self::from_tx(tx, None)
150 }
151 }
152 }
153
154 pub fn to_binary_v1(&self, writer: &mut impl Write) -> Result<(), TransactionError> {
156 if let Some(ref tx) = self.tx {
157 tx.to_binary(writer)?;
158 } else {
159 return Err(TransactionError::BeefError(
160 "cannot serialize txid-only BeefTx in V1 format".to_string(),
161 ));
162 }
163
164 if let Some(bump_index) = self.bump_index {
165 writer.write_all(&[1u8])?; write_varint(writer, bump_index as u64)?;
167 } else {
168 writer.write_all(&[0u8])?; }
170 Ok(())
171 }
172
173 pub fn to_binary_v2(&self, writer: &mut impl Write) -> Result<(), TransactionError> {
175 if self.is_txid_only() {
176 writer.write_all(&[TxDataFormat::TxidOnly as u8])?;
177 let mut txid_bytes =
178 from_hex(&self.txid).map_err(|e| TransactionError::InvalidFormat(e.to_string()))?;
179 txid_bytes.reverse(); writer.write_all(&txid_bytes)?;
181 } else if let Some(bump_index) = self.bump_index {
182 writer.write_all(&[TxDataFormat::RawTxAndBumpIndex as u8])?;
183 write_varint(writer, bump_index as u64)?;
184 self.tx
185 .as_ref()
186 .ok_or_else(|| {
187 TransactionError::InvalidFormat("BeefTx has bump_index but no tx".to_string())
188 })?
189 .to_binary(writer)?;
190 } else {
191 writer.write_all(&[TxDataFormat::RawTx as u8])?;
192 self.tx
193 .as_ref()
194 .ok_or_else(|| {
195 TransactionError::InvalidFormat("BeefTx has no tx data".to_string())
196 })?
197 .to_binary(writer)?;
198 }
199 Ok(())
200 }
201}