use std::collections::BTreeSet;
use crate::{GetQuorum, Network};
use sn_protocol::{
error::{Error, Result},
storage::{try_deserialize_record, RecordHeader, RecordKind, SpendAddress},
NetworkAddress, PrettyPrintRecordKey,
};
use sn_transfers::{CashNote, DerivationIndex, SignedSpend, Transaction, UniquePubkey};
use sn_transfers::{LocalWallet, Transfer};
use tokio::task::JoinSet;
impl Network {
pub async fn get_spend(&self, address: SpendAddress, re_attempt: bool) -> Result<SignedSpend> {
let key = NetworkAddress::from_cash_note_address(address).to_record_key();
let record = self
.get_record_from_network(key, None, GetQuorum::All, re_attempt)
.await
.map_err(|_| Error::SpendNotFound(address))?;
debug!(
"Got record from the network, {:?}",
PrettyPrintRecordKey::from(record.key.clone())
);
let header =
RecordHeader::from_record(&record).map_err(|_| Error::SpendNotFound(address))?;
if let RecordKind::Spend = header.kind {
match try_deserialize_record::<Vec<SignedSpend>>(&record)
.map_err(|_| Error::SpendNotFound(address))?
.as_slice()
{
[one, two, ..] => {
error!("Found double spend for {address:?}");
Err(Error::DoubleSpendAttempt(
Box::new(one.to_owned()),
Box::new(two.to_owned()),
))
}
[one] => {
trace!("Spend get for address: {address:?} successful");
Ok(one.clone())
}
_ => {
trace!("Found no spend for {address:?}");
Err(Error::SpendNotFound(address))
}
}
} else {
error!("RecordKind mismatch while trying to retrieve a Vec<SignedSpend>");
Err(Error::RecordKindMismatch(RecordKind::Spend))
}
}
pub async fn verify_and_unpack_transfer(
&self,
transfer: Transfer,
wallet: &LocalWallet,
) -> Result<Vec<CashNote>> {
trace!("Decyphering Transfer");
let cashnote_redemptions = wallet
.unwrap_transfer(transfer)
.map_err(|_| Error::FailedToDecypherTransfer)?;
let main_pubkey = wallet.address();
trace!("Getting parent Tx for validation");
let parent_addrs: BTreeSet<SpendAddress> = cashnote_redemptions
.iter()
.map(|u| u.parent_spend)
.collect();
let mut tasks = JoinSet::new();
for addr in parent_addrs.clone() {
let self_clone = self.clone();
let _ = tasks.spawn(async move { self_clone.get_spend(addr, true).await });
}
let mut parent_spends = BTreeSet::new();
while let Some(result) = tasks.join_next().await {
let signed_spend = result
.map_err(|_| Error::FailedToGetTransferParentSpend)?
.map_err(|e| Error::InvalidTransfer(format!("{e}")))?;
let _ = parent_spends.insert(signed_spend.clone());
}
let parent_txs: BTreeSet<Transaction> =
parent_spends.iter().map(|s| s.spent_tx()).collect();
trace!("Getting parent spends for validation");
let already_collected_parents = &parent_addrs;
let other_parent_cash_note_addr: BTreeSet<SpendAddress> = parent_txs
.clone()
.into_iter()
.flat_map(|tx| tx.inputs)
.map(|i| SpendAddress::from_unique_pubkey(&i.unique_pubkey()))
.filter(|addr| !already_collected_parents.contains(addr))
.collect();
let mut tasks = JoinSet::new();
for addr in other_parent_cash_note_addr {
let self_clone = self.clone();
let _ = tasks.spawn(async move { self_clone.get_spend(addr, true).await });
}
while let Some(result) = tasks.join_next().await {
let signed_spend = result
.map_err(|_| Error::FailedToGetTransferParentSpend)?
.map_err(|e| Error::InvalidTransfer(format!("{e}")))?;
let _ = parent_spends.insert(signed_spend.clone());
}
let our_output_unique_pubkeys: Vec<(UniquePubkey, DerivationIndex)> = cashnote_redemptions
.iter()
.map(|u| (wallet.derive_key(&u.derivation_index), u.derivation_index))
.map(|(k, d)| (k.unique_pubkey(), d))
.collect();
let mut our_output_cash_notes = Vec::new();
for (id, derivation_index) in our_output_unique_pubkeys.into_iter() {
let src_tx = parent_txs
.iter()
.find(|tx| tx.outputs.iter().any(|o| o.unique_pubkey() == &id))
.ok_or(Error::InvalidTransfer(
"None of the CashNoteRedemptions are refered to in upstream Txs".to_string(),
))?
.clone();
let signed_spends: BTreeSet<SignedSpend> = parent_spends
.iter()
.filter(|s| s.spent_tx_hash() == src_tx.hash())
.cloned()
.collect();
let cash_note = CashNote {
id,
src_tx,
signed_spends,
main_pubkey,
derivation_index,
};
our_output_cash_notes.push(cash_note);
}
trace!("Validating parent spends");
for tx in parent_txs {
let input_spends = parent_spends
.iter()
.filter(|s| s.spent_tx_hash() == tx.hash())
.cloned()
.collect();
tx.verify_against_inputs_spent(&input_spends)
.map_err(|e| Error::InvalidTransfer(format!("Payment parent Tx invalid: {e}")))?;
}
Ok(our_output_cash_notes)
}
}