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: {}",
34 b
35 ))),
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
45pub struct BeefTx {
46 pub tx: Option<Transaction>,
48 pub txid: String,
50 pub bump_index: Option<usize>,
52 pub input_txids: Vec<String>,
54}
55
56impl BeefTx {
57 pub fn from_tx(tx: Transaction, bump_index: Option<usize>) -> Result<Self, TransactionError> {
59 let txid = tx.id()?;
60 let input_txids = if bump_index.is_some() {
61 Vec::new()
62 } else {
63 Self::collect_input_txids(&tx)
64 };
65 Ok(BeefTx {
66 tx: Some(tx),
67 txid,
68 bump_index,
69 input_txids,
70 })
71 }
72
73 pub fn from_txid(txid: String) -> Self {
75 BeefTx {
76 tx: None,
77 txid,
78 bump_index: None,
79 input_txids: Vec::new(),
80 }
81 }
82
83 pub fn is_txid_only(&self) -> bool {
85 self.tx.is_none()
86 }
87
88 pub fn has_proof(&self) -> bool {
90 self.bump_index.is_some()
91 }
92
93 fn collect_input_txids(tx: &Transaction) -> Vec<String> {
95 let mut txids = Vec::new();
96 for input in &tx.inputs {
97 if let Some(ref stxid) = input.source_txid {
98 if !txids.contains(stxid) {
99 txids.push(stxid.clone());
100 }
101 }
102 }
103 txids
104 }
105
106 pub fn from_binary_v1(reader: &mut impl Read) -> Result<Self, TransactionError> {
110 let tx = Transaction::from_binary(reader)?;
111 let mut has_bump_buf = [0u8; 1];
112 reader.read_exact(&mut has_bump_buf)?;
113 let bump_index = if has_bump_buf[0] != 0 {
114 Some(
115 read_varint(reader).map_err(|e| TransactionError::InvalidFormat(e.to_string()))?
116 as usize,
117 )
118 } else {
119 None
120 };
121 Self::from_tx(tx, bump_index)
122 }
123
124 pub fn from_binary_v2(reader: &mut impl Read) -> Result<Self, TransactionError> {
128 let mut format_buf = [0u8; 1];
129 reader.read_exact(&mut format_buf)?;
130 let format = TxDataFormat::from_byte(format_buf[0])?;
131
132 match format {
133 TxDataFormat::TxidOnly => {
134 let mut txid_bytes = [0u8; 32];
136 reader.read_exact(&mut txid_bytes)?;
137 txid_bytes.reverse();
138 let txid = to_hex(&txid_bytes);
139 Ok(BeefTx::from_txid(txid))
140 }
141 TxDataFormat::RawTxAndBumpIndex => {
142 let bump_index = read_varint(reader)
143 .map_err(|e| TransactionError::InvalidFormat(e.to_string()))?
144 as usize;
145 let tx = Transaction::from_binary(reader)?;
146 Self::from_tx(tx, Some(bump_index))
147 }
148 TxDataFormat::RawTx => {
149 let tx = Transaction::from_binary(reader)?;
150 Self::from_tx(tx, None)
151 }
152 }
153 }
154
155 pub fn to_binary_v1(&self, writer: &mut impl Write) -> Result<(), TransactionError> {
157 if let Some(ref tx) = self.tx {
158 tx.to_binary(writer)?;
159 } else {
160 return Err(TransactionError::BeefError(
161 "cannot serialize txid-only BeefTx in V1 format".to_string(),
162 ));
163 }
164
165 if let Some(bump_index) = self.bump_index {
166 writer.write_all(&[1u8])?; write_varint(writer, bump_index as u64)?;
168 } else {
169 writer.write_all(&[0u8])?; }
171 Ok(())
172 }
173
174 pub fn to_binary_v2(&self, writer: &mut impl Write) -> Result<(), TransactionError> {
176 if self.is_txid_only() {
177 writer.write_all(&[TxDataFormat::TxidOnly as u8])?;
178 let mut txid_bytes =
179 from_hex(&self.txid).map_err(|e| TransactionError::InvalidFormat(e.to_string()))?;
180 txid_bytes.reverse(); writer.write_all(&txid_bytes)?;
182 } else if let Some(bump_index) = self.bump_index {
183 writer.write_all(&[TxDataFormat::RawTxAndBumpIndex as u8])?;
184 write_varint(writer, bump_index as u64)?;
185 self.tx
186 .as_ref()
187 .ok_or_else(|| {
188 TransactionError::InvalidFormat("BeefTx has bump_index but no tx".to_string())
189 })?
190 .to_binary(writer)?;
191 } else {
192 writer.write_all(&[TxDataFormat::RawTx as u8])?;
193 self.tx
194 .as_ref()
195 .ok_or_else(|| {
196 TransactionError::InvalidFormat("BeefTx has no tx data".to_string())
197 })?
198 .to_binary(writer)?;
199 }
200 Ok(())
201 }
202}