use crate::{close_group_majority, driver::GetRecordCfg, Error, GetRecordError, Network, Result};
use libp2p::kad::{Quorum, Record};
use sn_protocol::{
storage::{try_deserialize_record, RecordHeader, RecordKind, SpendAddress},
NetworkAddress, PrettyPrintRecordKey,
};
use sn_transfers::{
CashNote, CashNoteRedemption, DerivationIndex, HotWallet, MainPubkey, SignedSpend, Transaction,
Transfer, UniquePubkey,
};
use std::collections::BTreeSet;
use tokio::task::JoinSet;
fn parse_signed_spends(address: &SpendAddress, record: &Record) -> Result<SignedSpend> {
match get_singed_spends_from_record(record)?.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::NoSpendFoundInsideRecord(*address))
}
}
}
impl Network {
pub async fn try_get_spend(&self, address: SpendAddress) -> Result<SignedSpend> {
let key = NetworkAddress::from_spend_address(address).to_record_key();
let get_cfg = GetRecordCfg {
get_quorum: Quorum::Majority,
re_attempt: false,
target_record: None,
expected_holders: Default::default(),
};
let record = match self.get_record_from_network(key.clone(), &get_cfg).await {
Ok(record) => record,
Err(err) => return Err(err),
};
debug!(
"Got record from the network, {:?}",
PrettyPrintRecordKey::from(&record.key)
);
parse_signed_spends(&address, &record)
}
pub async fn get_spend(&self, address: SpendAddress) -> Result<SignedSpend> {
let key = NetworkAddress::from_spend_address(address).to_record_key();
let mut get_cfg = GetRecordCfg {
get_quorum: Quorum::All,
re_attempt: true,
target_record: None,
expected_holders: Default::default(),
};
let record = match self.get_record_from_network(key.clone(), &get_cfg).await {
Ok(record) => record,
Err(Error::GetRecordError(GetRecordError::NotEnoughCopies {
record,
expected,
got,
})) => {
if got >= close_group_majority() {
debug!("At least a majority nodes hold the spend {address:?}, so trying to get it again.");
get_cfg.re_attempt = true;
self.get_record_from_network(key, &get_cfg).await?
} else {
return Err(Error::GetRecordError(GetRecordError::NotEnoughCopies {
record,
expected,
got,
}));
}
}
Err(err) => return Err(err),
};
debug!(
"Got record from the network, {:?}",
PrettyPrintRecordKey::from(&record.key)
);
parse_signed_spends(&address, &record)
}
pub async fn verify_and_unpack_transfer(
&self,
transfer: &Transfer,
wallet: &HotWallet,
) -> Result<Vec<CashNote>> {
trace!("Decyphering Transfer");
let cashnote_redemptions = wallet.unwrap_transfer(transfer)?;
self.verify_cash_notes_redemptions(wallet.address(), &cashnote_redemptions)
.await
}
pub async fn verify_cash_notes_redemptions(
&self,
main_pubkey: MainPubkey,
cashnote_redemptions: &[CashNoteRedemption],
) -> Result<Vec<CashNote>> {
trace!(
"Getting parent Tx for validation from {:?}",
cashnote_redemptions.len()
);
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).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();
let our_output_unique_pubkeys: Vec<(UniquePubkey, DerivationIndex)> = cashnote_redemptions
.iter()
.map(|u| {
let unique_pubkey = main_pubkey.new_unique_pubkey(&u.derivation_index);
(unique_pubkey, u.derivation_index)
})
.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 tx_inputs_keys: Vec<_> = tx.inputs.iter().map(|i| i.unique_pubkey()).collect();
let mut tasks = JoinSet::new();
for input_key in tx_inputs_keys {
if parent_spends.iter().any(|s| s.unique_pubkey() == input_key) {
continue;
}
let self_clone = self.clone();
let addr = SpendAddress::from_unique_pubkey(input_key);
let _ = tasks.spawn(async move { self_clone.get_spend(addr).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 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}", tx.hash()))
})?;
}
Ok(our_output_cash_notes)
}
}
pub fn get_singed_spends_from_record(record: &Record) -> Result<Vec<SignedSpend>> {
let header = RecordHeader::from_record(record)?;
if let RecordKind::Spend = header.kind {
let spends = try_deserialize_record::<Vec<SignedSpend>>(record)?;
Ok(spends)
} else {
error!("RecordKind mismatch while trying to retrieve a Vec<SignedSpend>");
Err(Error::RecordKindMismatch(RecordKind::Spend))
}
}