use std::collections::HashSet;
use std::io::Cursor;
use crate::error::{WalletError, WalletResult};
use crate::storage::find_args::*;
use crate::storage::traits::reader::StorageReader;
use crate::storage::traits::wallet_provider::WalletStorageProvider;
use bsv::transaction::beef::{Beef, BEEF_V2};
use bsv::transaction::beef_tx::BeefTx;
use bsv::transaction::merkle_path::MerklePath;
use bsv::transaction::transaction::Transaction as BsvTransaction;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrustSelf {
Known,
KnownAndNew,
No,
}
struct CollectedTx {
raw_tx: Vec<u8>,
txid: String,
merkle_path: Option<Vec<u8>>,
txid_only: bool,
}
pub async fn get_valid_beef_for_txid(
storage: &(dyn WalletStorageProvider + Send + Sync),
txid: &str,
trust_self: TrustSelf,
known_txids: &HashSet<String>,
) -> WalletResult<Option<Vec<u8>>> {
get_valid_beef_for_txid_inner(storage, txid, trust_self, known_txids).await
}
pub async fn get_valid_beef_for_storage_reader<S: StorageReader + ?Sized>(
storage: &S,
txid: &str,
trust_self: TrustSelf,
known_txids: &HashSet<String>,
) -> WalletResult<Option<Vec<u8>>> {
let mut collected: Vec<CollectedTx> = Vec::new();
let mut visited: HashSet<String> = HashSet::new();
collect_tx_recursive_reader(
storage,
txid,
trust_self,
known_txids,
&mut collected,
&mut visited,
)
.await?;
build_beef_from_collected(collected)
}
async fn get_valid_beef_for_txid_inner(
storage: &dyn WalletStorageProvider,
txid: &str,
trust_self: TrustSelf,
known_txids: &HashSet<String>,
) -> WalletResult<Option<Vec<u8>>> {
let mut collected: Vec<CollectedTx> = Vec::new();
let mut visited: HashSet<String> = HashSet::new();
collect_tx_recursive(
storage,
txid,
trust_self,
known_txids,
&mut collected,
&mut visited,
)
.await?;
build_beef_from_collected(collected)
}
async fn collect_tx_recursive(
storage: &dyn WalletStorageProvider,
txid: &str,
trust_self: TrustSelf,
known_txids: &HashSet<String>,
collected: &mut Vec<CollectedTx>,
visited: &mut HashSet<String>,
) -> WalletResult<()> {
if visited.contains(txid) {
return Ok(());
}
visited.insert(txid.to_string());
let proven = storage
.find_proven_txs(&FindProvenTxsArgs {
partial: ProvenTxPartial {
txid: Some(txid.to_string()),
..Default::default()
},
..Default::default()
})
.await?;
if let Some(ptx) = proven.into_iter().next() {
if trust_self == TrustSelf::Known {
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: txid.to_string(),
merkle_path: None,
txid_only: true,
});
} else {
collected.push(CollectedTx {
raw_tx: ptx.raw_tx,
txid: txid.to_string(),
merkle_path: Some(ptx.merkle_path),
txid_only: false,
});
}
return Ok(());
}
let txs = storage
.find_transactions(&FindTransactionsArgs {
partial: TransactionPartial {
txid: Some(txid.to_string()),
..Default::default()
},
..Default::default()
})
.await?;
let tx_record = match txs.into_iter().next() {
Some(t) => t,
None => {
if known_txids.contains(txid) {
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: txid.to_string(),
merkle_path: None,
txid_only: true,
});
}
return Ok(());
}
};
if trust_self == TrustSelf::Known {
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: txid.to_string(),
merkle_path: None,
txid_only: true,
});
return Ok(());
}
let raw_tx = match tx_record.raw_tx {
Some(ref raw) => raw.clone(),
None => {
if known_txids.contains(txid) {
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: txid.to_string(),
merkle_path: None,
txid_only: true,
});
}
return Ok(());
}
};
let bsv_tx = {
let mut cursor = Cursor::new(&raw_tx);
BsvTransaction::from_binary(&mut cursor).map_err(|e| {
WalletError::Internal(format!("Failed to parse raw_tx for {}: {}", txid, e))
})?
};
for input in &bsv_tx.inputs {
if let Some(ref source_txid) = input.source_txid {
if !visited.contains(source_txid.as_str()) {
if known_txids.contains(source_txid.as_str()) {
visited.insert(source_txid.clone());
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: source_txid.clone(),
merkle_path: None,
txid_only: true,
});
} else {
Box::pin(collect_tx_recursive(
storage,
source_txid,
trust_self,
known_txids,
collected,
visited,
))
.await?;
}
}
}
}
collected.push(CollectedTx {
raw_tx,
txid: txid.to_string(),
merkle_path: None,
txid_only: false,
});
Ok(())
}
fn build_beef_from_collected(collected: Vec<CollectedTx>) -> WalletResult<Option<Vec<u8>>> {
if collected.is_empty() {
return Ok(None);
}
let mut beef = Beef::new(BEEF_V2);
for ctx in &collected {
if ctx.txid_only {
beef.txs.push(BeefTx {
tx: None,
txid: ctx.txid.clone(),
bump_index: None,
input_txids: Vec::new(),
});
continue;
}
let bsv_tx = {
let mut cursor = Cursor::new(&ctx.raw_tx);
BsvTransaction::from_binary(&mut cursor).map_err(|e| {
WalletError::Internal(format!("Failed to parse raw_tx for {}: {}", ctx.txid, e))
})?
};
let bump_index = if let Some(ref mp_bytes) = ctx.merkle_path {
let mp = {
let mut cursor = Cursor::new(mp_bytes);
MerklePath::from_binary(&mut cursor).map_err(|e| {
WalletError::Internal(format!(
"Failed to parse merkle_path for {}: {}",
ctx.txid, e
))
})?
};
let idx = beef.bumps.len();
beef.bumps.push(mp);
Some(idx)
} else {
None
};
let beef_tx = BeefTx::from_tx(bsv_tx, bump_index).map_err(|e| {
WalletError::Internal(format!("Failed to create BeefTx for {}: {}", ctx.txid, e))
})?;
beef.txs.push(beef_tx);
}
let mut buf = Vec::new();
beef.to_binary(&mut buf)
.map_err(|e| WalletError::Internal(format!("Failed to serialize BEEF: {}", e)))?;
Ok(Some(buf))
}
async fn collect_tx_recursive_reader<S: StorageReader + ?Sized>(
storage: &S,
txid: &str,
trust_self: TrustSelf,
known_txids: &HashSet<String>,
collected: &mut Vec<CollectedTx>,
visited: &mut HashSet<String>,
) -> WalletResult<()> {
if visited.contains(txid) {
return Ok(());
}
visited.insert(txid.to_string());
let proven = storage
.find_proven_txs(
&FindProvenTxsArgs {
partial: ProvenTxPartial {
txid: Some(txid.to_string()),
..Default::default()
},
..Default::default()
},
None,
)
.await?;
if let Some(ptx) = proven.into_iter().next() {
if trust_self == TrustSelf::Known {
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: txid.to_string(),
merkle_path: None,
txid_only: true,
});
} else {
collected.push(CollectedTx {
raw_tx: ptx.raw_tx,
txid: txid.to_string(),
merkle_path: Some(ptx.merkle_path),
txid_only: false,
});
}
return Ok(());
}
let txs = storage
.find_transactions(
&FindTransactionsArgs {
partial: TransactionPartial {
txid: Some(txid.to_string()),
..Default::default()
},
..Default::default()
},
None,
)
.await?;
let tx_record = match txs.into_iter().next() {
Some(t) => t,
None => {
if known_txids.contains(txid) {
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: txid.to_string(),
merkle_path: None,
txid_only: true,
});
}
return Ok(());
}
};
if trust_self == TrustSelf::Known {
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: txid.to_string(),
merkle_path: None,
txid_only: true,
});
return Ok(());
}
let raw_tx = match tx_record.raw_tx {
Some(ref raw) => raw.clone(),
None => {
if known_txids.contains(txid) {
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: txid.to_string(),
merkle_path: None,
txid_only: true,
});
}
return Ok(());
}
};
let bsv_tx = {
let mut cursor = Cursor::new(&raw_tx);
BsvTransaction::from_binary(&mut cursor).map_err(|e| {
WalletError::Internal(format!("Failed to parse raw_tx for {}: {}", txid, e))
})?
};
for input in &bsv_tx.inputs {
if let Some(ref source_txid) = input.source_txid {
if !visited.contains(source_txid.as_str()) {
if known_txids.contains(source_txid.as_str()) {
visited.insert(source_txid.clone());
collected.push(CollectedTx {
raw_tx: Vec::new(),
txid: source_txid.clone(),
merkle_path: None,
txid_only: true,
});
} else {
Box::pin(collect_tx_recursive_reader(
storage,
source_txid,
trust_self,
known_txids,
collected,
visited,
))
.await?;
}
}
}
}
collected.push(CollectedTx {
raw_tx,
txid: txid.to_string(),
merkle_path: None,
txid_only: false,
});
Ok(())
}