use petgraph::dot::Dot;
use petgraph::graph::{DiGraph, NodeIndex};
use serde::{Deserialize, Serialize};
use sn_transfers::{is_genesis_spend, CashNoteRedemption, NanoTokens, SignedSpend, SpendAddress};
use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
use super::dag_error::{DagError, SpendFault};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpendDag {
dag: DiGraph<SpendAddress, NanoTokens>,
spends: BTreeMap<SpendAddress, DagEntry>,
source: SpendAddress,
faults: BTreeMap<SpendAddress, BTreeSet<SpendFault>>,
}
type DagIndex = usize;
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
enum DagEntry {
Utxo(DagIndex),
DoubleSpend(Vec<(SignedSpend, DagIndex)>),
Spend(Box<SignedSpend>, DagIndex),
}
impl DagEntry {
fn indexes(&self) -> Vec<DagIndex> {
match self {
DagEntry::Utxo(idx) => vec![*idx],
DagEntry::DoubleSpend(spends) => spends.iter().map(|(_, idx)| *idx).collect(),
DagEntry::Spend(_, idx) => vec![*idx],
}
}
fn spends(&self) -> Vec<&SignedSpend> {
match self {
DagEntry::Spend(spend, _) => vec![&**spend],
DagEntry::DoubleSpend(spends) => spends.iter().map(|(s, _)| s).collect(),
DagEntry::Utxo(_) => vec![],
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum SpendDagGet {
SpendNotFound,
SpendIsAnUtxo,
DoubleSpend(Vec<SignedSpend>),
Spend(Box<SignedSpend>),
}
impl SpendDag {
pub fn new(source: SpendAddress) -> Self {
Self {
dag: DiGraph::new(),
spends: BTreeMap::new(),
source,
faults: BTreeMap::new(),
}
}
pub fn source(&self) -> SpendAddress {
self.source
}
pub fn load_from_file<P: AsRef<Path>>(path: P) -> crate::Result<Self> {
let bytes = std::fs::read(path)?;
let dag: SpendDag = rmp_serde::from_slice(&bytes)?;
Ok(dag)
}
pub fn dump_to_file<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {
let bytes = rmp_serde::to_vec(&self)?;
std::fs::write(path, bytes)?;
Ok(())
}
pub fn insert(&mut self, spend_addr: SpendAddress, spend: SignedSpend) -> bool {
let existing_entry = self.spends.get(&spend_addr).cloned();
let new_node_idx = match existing_entry {
None => {
let node_idx = self.dag.add_node(spend_addr);
self.spends.insert(
spend_addr,
DagEntry::Spend(Box::new(spend.clone()), node_idx.index()),
);
node_idx
}
Some(DagEntry::Utxo(idx)) => {
self.spends
.insert(spend_addr, DagEntry::Spend(Box::new(spend.clone()), idx));
NodeIndex::new(idx)
}
Some(DagEntry::Spend(s, idx)) => {
let existing_spend = *s.clone();
if existing_spend == spend {
return false;
}
let node_idx = self.dag.add_node(spend_addr);
let double_spend = DagEntry::DoubleSpend(vec![
(existing_spend.clone(), idx),
(spend.clone(), node_idx.index()),
]);
self.spends.insert(spend_addr, double_spend);
node_idx
}
Some(DagEntry::DoubleSpend(vec_s)) => {
if vec_s.iter().any(|(s, _idx)| s == &spend) {
return false;
}
let node_idx = self.dag.add_node(spend_addr);
let mut vec_s = vec_s.clone();
vec_s.push((spend.clone(), node_idx.index()));
self.spends.insert(spend_addr, DagEntry::DoubleSpend(vec_s));
node_idx
}
};
for descendant in spend.spend.spent_tx.outputs.iter() {
let descendant_addr = SpendAddress::from_unique_pubkey(&descendant.unique_pubkey);
let spends_at_addr = self.spends.entry(descendant_addr).or_insert_with(|| {
let node_idx = self.dag.add_node(descendant_addr);
DagEntry::Utxo(node_idx.index())
});
for idx in spends_at_addr.indexes() {
let descendant_idx = NodeIndex::new(idx);
self.dag
.update_edge(new_node_idx, descendant_idx, descendant.amount);
}
}
if spend_addr == self.source {
return true;
}
let spend_amount = spend.token();
for ancestor in spend.spend.parent_tx.inputs.iter() {
let ancestor_addr = SpendAddress::from_unique_pubkey(&ancestor.unique_pubkey);
let spends_at_addr = self.spends.entry(ancestor_addr).or_insert_with(|| {
let node_idx = self.dag.add_node(ancestor_addr);
DagEntry::Utxo(node_idx.index())
});
for idx in spends_at_addr.indexes() {
let ancestor_idx = NodeIndex::new(idx);
self.dag
.update_edge(ancestor_idx, new_node_idx, *spend_amount);
}
}
true
}
pub fn get_utxos(&self) -> BTreeSet<SpendAddress> {
let mut leaves = BTreeSet::new();
for node_index in self.dag.node_indices() {
if !self
.dag
.neighbors_directed(node_index, petgraph::Direction::Outgoing)
.any(|_| true)
{
let utxo_addr = self.dag[node_index];
leaves.insert(utxo_addr);
}
}
leaves
}
pub fn dump_dot_format(&self) -> String {
format!("{:?}", Dot::with_config(&self.dag, &[]))
}
pub fn merge(&mut self, sub_dag: SpendDag) {
for (addr, spends) in sub_dag.spends {
match spends {
DagEntry::Utxo(_) => continue,
DagEntry::DoubleSpend(spends) => {
for (spend, _) in spends {
self.insert(addr, spend);
}
}
DagEntry::Spend(spend, _) => {
self.insert(addr, *spend);
}
}
}
self.faults.extend(sub_dag.faults);
}
pub fn get_spend(&self, addr: &SpendAddress) -> SpendDagGet {
match self.spends.get(addr) {
None => SpendDagGet::SpendNotFound,
Some(DagEntry::Utxo(_)) => SpendDagGet::SpendIsAnUtxo,
Some(DagEntry::DoubleSpend(spends)) => {
SpendDagGet::DoubleSpend(spends.iter().map(|(s, _)| s.clone()).collect())
}
Some(DagEntry::Spend(spend, _)) => SpendDagGet::Spend(spend.clone()),
}
}
pub fn get_spend_faults(&self, addr: &SpendAddress) -> BTreeSet<SpendFault> {
self.faults.get(addr).cloned().unwrap_or_default()
}
pub fn get_spend_indexes(&self, addr: &SpendAddress) -> Vec<usize> {
self.spends
.get(addr)
.map(|spends| spends.indexes())
.unwrap_or_default()
}
pub fn all_spends(&self) -> Vec<&SignedSpend> {
self.spends
.values()
.flat_map(|entry| entry.spends())
.collect()
}
pub fn all_royalties(&self) -> crate::Result<Vec<CashNoteRedemption>> {
let spends = self.all_spends();
let mut royalties = Vec::new();
for s in spends {
for derivation_idx in s.spend.network_royalties.iter() {
let spend_addr = SpendAddress::from_unique_pubkey(&s.spend.unique_pubkey);
royalties.push(CashNoteRedemption::new(*derivation_idx, spend_addr));
}
}
Ok(royalties)
}
fn get_ancestor_spends(
&self,
spend: &SignedSpend,
) -> Result<BTreeSet<SignedSpend>, SpendFault> {
let addr = spend.address();
let mut ancestors = BTreeSet::new();
for input in spend.spend.parent_tx.inputs.iter() {
let ancestor_addr = SpendAddress::from_unique_pubkey(&input.unique_pubkey);
match self.spends.get(&ancestor_addr) {
Some(DagEntry::Spend(ancestor_spend, _)) => {
ancestors.insert(*ancestor_spend.clone());
}
Some(DagEntry::Utxo(_)) => {
warn!("InvalidAncestry: SpendIsAnUtxo ancestor {ancestor_addr:?} for spend {spend:?}");
return Err(SpendFault::InvalidAncestry {
addr,
invalid_ancestor: ancestor_addr,
});
}
Some(DagEntry::DoubleSpend(_)) => {
warn!("InvalidAncestry: DoubleSpend ancestor {ancestor_addr:?} for spend {spend:?}");
return Err(SpendFault::InvalidAncestry {
addr,
invalid_ancestor: ancestor_addr,
});
}
None => {
return Err(SpendFault::MissingAncestry {
addr,
invalid_ancestor: ancestor_addr,
})
}
}
}
Ok(ancestors)
}
fn all_descendants(&self, addr: &SpendAddress) -> Result<BTreeSet<&SpendAddress>, DagError> {
let mut descendants = BTreeSet::new();
let mut to_traverse = BTreeSet::from_iter(vec![addr]);
while let Some(current_addr) = to_traverse.pop_first() {
let indexes = match self.spends.get(current_addr) {
Some(entry) => entry.indexes(),
None => {
warn!("Incoherent DAG, missing descendant spend when expecting one at: {current_addr:?}");
return Err(DagError::IncoherentDag(
*current_addr,
format!("Missing descendant spend in DAG at: {current_addr:?}"),
));
}
};
let descendants_via_dag: BTreeSet<&SpendAddress> = indexes
.into_iter()
.flat_map(|idx| {
self.dag
.neighbors_directed(NodeIndex::new(idx), petgraph::Direction::Outgoing)
.map(|i| &self.dag[i])
})
.collect();
let descendants_via_tx: BTreeSet<SpendAddress> = self
.spends
.get(current_addr)
.map(|entry| entry.spends())
.unwrap_or_default()
.into_iter()
.flat_map(|s| s.spend.spent_tx.outputs.to_vec())
.map(|o| SpendAddress::from_unique_pubkey(&o.unique_pubkey))
.collect();
if descendants_via_dag != descendants_via_tx.iter().collect() {
warn!("Incoherent DAG at: {current_addr:?}");
return Err(DagError::IncoherentDag(
*current_addr,
format!("descendants via DAG: {descendants_via_dag:?} do not match descendants via TX: {descendants_via_tx:?}")
));
}
let not_transversed = descendants_via_dag.difference(&descendants);
to_traverse.extend(not_transversed);
descendants.extend(descendants_via_dag.iter().cloned());
}
Ok(descendants)
}
fn find_orphans(&self, source: &SpendAddress) -> Result<BTreeSet<SpendFault>, DagError> {
let mut recorded_faults = BTreeSet::new();
let all_addresses: BTreeSet<&SpendAddress> = self.spends.keys().collect();
let all_descendants = self.all_descendants(source)?;
let parents: BTreeSet<_> = self
.get_spend_indexes(source)
.into_iter()
.flat_map(|idx| {
self.dag
.neighbors_directed(NodeIndex::new(idx), petgraph::Direction::Incoming)
})
.map(|parent_idx| &self.dag[parent_idx])
.collect();
let non_orphans =
BTreeSet::from_iter(all_descendants.into_iter().chain(parents).chain([source]));
let orphans: BTreeSet<&SpendAddress> =
all_addresses.difference(&non_orphans).cloned().collect();
for orphan in orphans {
let src = *source;
let addr = *orphan;
debug!("Found orphan: {orphan:?} of {src:?}");
recorded_faults.insert(SpendFault::OrphanSpend { addr, src });
}
Ok(recorded_faults)
}
pub fn record_faults(&mut self, source: &SpendAddress) -> Result<(), DagError> {
let faults = self.verify(source)?;
for f in faults {
self.faults.entry(f.spend_address()).or_default().insert(f);
}
Ok(())
}
pub fn verify(&self, source: &SpendAddress) -> Result<BTreeSet<SpendFault>, DagError> {
info!("Verifying DAG starting off: {source:?}");
let mut recorded_faults = BTreeSet::new();
if petgraph::algo::is_cyclic_directed(&self.dag) {
warn!("DAG is cyclic");
return Err(DagError::DagContainsCycle(*source));
}
debug!("Verifying DAG source: {source:?}");
match self.spends.get(source) {
None => {
debug!("DAG does not contain its source: {source:?}");
return Err(DagError::MissingSource(*source));
}
Some(DagEntry::DoubleSpend(_)) => {
debug!("DAG source is a double spend: {source:?}");
recorded_faults.insert(SpendFault::DoubleSpend(*source));
}
_ => (),
}
debug!("Looking for orphans of {source:?}");
recorded_faults.extend(self.find_orphans(source)?);
for (addr, _) in self.spends.iter() {
debug!("Verifying transaction at: {addr:?}");
let spends = self
.spends
.get(addr)
.map(|s| s.spends())
.unwrap_or_default();
if spends.len() > 1 {
debug!("Found a double spend entry in DAG: {source:?}");
recorded_faults.insert(SpendFault::DoubleSpend(*addr));
continue;
}
if addr == source {
debug!("Skip transaction verification for source at: {addr:?}");
continue;
}
for s in spends {
recorded_faults.extend(self.verify_parent_tx(s)?);
}
}
info!(
"Found {} faults: {recorded_faults:#?}",
recorded_faults.len()
);
Ok(recorded_faults)
}
fn verify_parent_tx(&self, spend: &SignedSpend) -> Result<BTreeSet<SpendFault>, DagError> {
let addr = spend.address();
let mut recorded_faults = BTreeSet::new();
debug!(
"Verifying transaction {} at: {addr:?}",
spend.spend.parent_tx.hash().to_hex()
);
if is_genesis_spend(spend) {
debug!("Skip transaction verification for Genesis at: {addr:?}");
return Ok(recorded_faults);
}
let ancestor_spends = match self.get_ancestor_spends(spend) {
Ok(a) => a,
Err(e) => {
debug!("Failed to get ancestor spends of: {addr:?} {e}");
recorded_faults.insert(e);
return Ok(recorded_faults);
}
};
match spend
.spend
.parent_tx
.verify_against_inputs_spent(&ancestor_spends)
{
Ok(_) => (),
Err(e) => {
recorded_faults.insert(SpendFault::InvalidTransaction(addr, format!("{e}")));
let poison = format!("ancestor transaction was poisoned at: {addr:?}: {e}");
let descendants_faults = self.poison_all_descendants(spend, poison)?;
recorded_faults.extend(descendants_faults);
}
}
Ok(recorded_faults)
}
fn poison_all_descendants(
&self,
spend: &SignedSpend,
poison: String,
) -> Result<BTreeSet<SpendFault>, DagError> {
let mut recorded_faults = BTreeSet::new();
let spent_tx = spend.spent_tx();
let direct_descendants = spent_tx
.outputs
.iter()
.map(|o| SpendAddress::from_unique_pubkey(&o.unique_pubkey));
let all_descendants = direct_descendants
.map(|addr| self.all_descendants(&addr))
.collect::<Result<BTreeSet<_>, _>>()?
.into_iter()
.flatten()
.collect::<BTreeSet<&SpendAddress>>();
for d in all_descendants {
recorded_faults.insert(SpendFault::PoisonedAncestry(*d, poison.clone()));
}
Ok(recorded_faults)
}
}
#[cfg(test)]
mod tests {
use xor_name::XorName;
use super::*;
#[test]
fn test_spend_dag_serialisation() {
let mut rng = rand::thread_rng();
let dummy_source = SpendAddress::new(XorName::random(&mut rng));
let dag = SpendDag::new(dummy_source);
let serialized_data = rmp_serde::to_vec(&dag).expect("Serialization failed");
let deserialized_instance: SpendDag =
rmp_serde::from_slice(&serialized_data).expect("Deserialization failed");
let reserialized_data =
rmp_serde::to_vec(&deserialized_instance).expect("Serialization failed");
assert_eq!(reserialized_data, serialized_data);
}
}