use super::action::DagActionHash;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DagTipSet {
tips: HashSet<DagActionHash>,
}
impl DagTipSet {
pub fn new() -> Self {
Self {
tips: HashSet::new(),
}
}
pub fn from_raw(hashes: Vec<[u8; 32]>) -> Self {
Self {
tips: hashes.into_iter().map(DagActionHash).collect(),
}
}
pub fn advance(&mut self, action_hash: DagActionHash, parent_hashes: &[DagActionHash]) {
for parent in parent_hashes {
self.tips.remove(parent);
}
self.tips.insert(action_hash);
}
pub fn current(&self) -> Vec<DagActionHash> {
self.tips.iter().copied().collect()
}
pub fn len(&self) -> usize {
self.tips.len()
}
pub fn is_empty(&self) -> bool {
self.tips.is_empty()
}
pub fn contains(&self, hash: &DagActionHash) -> bool {
self.tips.contains(hash)
}
pub fn to_raw(&self) -> Vec<[u8; 32]> {
self.tips.iter().map(|h| h.0).collect()
}
}
impl Default for DagTipSet {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_tip_set() {
let tips = DagTipSet::new();
assert!(tips.is_empty());
assert_eq!(tips.len(), 0);
}
#[test]
fn test_linear_chain() {
let mut tips = DagTipSet::new();
let genesis = DagActionHash([1; 32]);
tips.advance(genesis, &[]);
assert_eq!(tips.len(), 1);
assert!(tips.contains(&genesis));
let a2 = DagActionHash([2; 32]);
tips.advance(a2, &[genesis]);
assert_eq!(tips.len(), 1);
assert!(!tips.contains(&genesis));
assert!(tips.contains(&a2));
}
#[test]
fn test_concurrent_branches() {
let mut tips = DagTipSet::new();
let genesis = DagActionHash([1; 32]);
tips.advance(genesis, &[]);
let b1 = DagActionHash([2; 32]);
let b2 = DagActionHash([3; 32]);
tips.advance(b1, &[genesis]);
tips.advance(b2, &[genesis]);
assert_eq!(tips.len(), 2);
assert!(tips.contains(&b1));
assert!(tips.contains(&b2));
}
#[test]
fn test_merge() {
let mut tips = DagTipSet::new();
let genesis = DagActionHash([1; 32]);
tips.advance(genesis, &[]);
let b1 = DagActionHash([2; 32]);
let b2 = DagActionHash([3; 32]);
tips.advance(b1, &[genesis]);
tips.advance(b2, &[genesis]);
assert_eq!(tips.len(), 2);
let merge = DagActionHash([4; 32]);
tips.advance(merge, &[b1, b2]);
assert_eq!(tips.len(), 1);
assert!(tips.contains(&merge));
}
#[test]
fn test_raw_roundtrip() {
let mut tips = DagTipSet::new();
tips.advance(DagActionHash([10; 32]), &[]);
tips.advance(DagActionHash([20; 32]), &[]);
let raw = tips.to_raw();
let restored = DagTipSet::from_raw(raw);
assert_eq!(restored.len(), 2);
}
}