use std::collections::BTreeSet;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use crate::canonical;
use crate::content_id::ContentId;
use crate::error::ContentError;
use crate::trait_def::ContentAddressable;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound(serialize = "T: Serialize", deserialize = "T: DeserializeOwned"))]
pub struct MerkleNode<T> {
pub payload: T,
pub parents: BTreeSet<ContentId>,
}
impl<T> MerkleNode<T> {
pub fn new(payload: T, parents: impl IntoIterator<Item = ContentId>) -> Self {
Self {
payload,
parents: parents.into_iter().collect(),
}
}
pub fn genesis(payload: T) -> Self {
Self {
payload,
parents: BTreeSet::new(),
}
}
pub fn payload(&self) -> &T {
&self.payload
}
pub fn parents(&self) -> &BTreeSet<ContentId> {
&self.parents
}
}
impl<T: Serialize> MerkleNode<T> {
pub fn id(&self) -> Result<ContentId, ContentError> {
self.content_id()
}
}
impl<T: Serialize> ContentAddressable for MerkleNode<T> {
fn canonical_form(&self) -> Result<Vec<u8>, ContentError> {
canonical::to_canonical_dagcbor(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Event {
author: String,
body: String,
}
fn event(author: &str, body: &str) -> Event {
Event {
author: author.to_string(),
body: body.to_string(),
}
}
fn id_for(payload: &str) -> ContentId {
MerkleNode::genesis(payload.to_string()).id().unwrap()
}
#[test]
fn content_id_is_deterministic() {
let p = id_for("parent");
let a = MerkleNode::new(event("alice", "hi"), [p]);
let b = MerkleNode::new(event("alice", "hi"), [p]);
assert_eq!(
a.id().unwrap(),
b.id().unwrap(),
"equal nodes must produce equal ids"
);
assert_eq!(
a.canonical_form().unwrap(),
b.canonical_form().unwrap(),
"equal nodes must produce equal canonical bytes"
);
}
#[test]
fn different_payload_changes_id() {
let p = id_for("parent");
let a = MerkleNode::new(event("alice", "hi"), [p]);
let b = MerkleNode::new(event("alice", "bye"), [p]);
assert_ne!(
a.id().unwrap(),
b.id().unwrap(),
"a different payload must change the node id"
);
}
#[test]
fn different_parents_change_id() {
let payload = event("alice", "hi");
let p1 = id_for("p1");
let p2 = id_for("p2");
let one = MerkleNode::new(payload.clone(), [p1]);
let other = MerkleNode::new(payload.clone(), [p2]);
assert_ne!(
one.id().unwrap(),
other.id().unwrap(),
"changing a parent id must change the node id"
);
let with_two = MerkleNode::new(payload.clone(), [p1, p2]);
assert_ne!(
one.id().unwrap(),
with_two.id().unwrap(),
"adding a parent must change the node id"
);
}
#[test]
fn genesis_differs_from_node_with_parent() {
let payload = event("alice", "hi");
let g = MerkleNode::genesis(payload.clone());
assert!(g.parents().is_empty(), "genesis node has no parents");
let parent = id_for("parent");
let child = MerkleNode::new(payload.clone(), [parent]);
assert_ne!(
g.id().unwrap(),
child.id().unwrap(),
"a genesis node must differ from a same-payload node with a parent"
);
let g2 = MerkleNode::new(payload, std::iter::empty());
assert_eq!(
g.id().unwrap(),
g2.id().unwrap(),
"new(payload, []) must equal genesis(payload)"
);
}
#[test]
fn parents_encode_as_tag42_links() {
let p1 = id_for("p1");
let p2 = id_for("p2");
let node = MerkleNode::new(event("alice", "hi"), [p1, p2]);
let bytes = node.canonical_form().unwrap();
let tag_count = bytes.windows(2).filter(|w| *w == [0xd8, 0x2a]).count();
assert_eq!(
tag_count, 2,
"each of the two parents must be a dag-cbor tag-42 link"
);
let g = MerkleNode::genesis(event("alice", "hi"));
let g_bytes = g.canonical_form().unwrap();
let g_count = g_bytes.windows(2).filter(|w| *w == [0xd8, 0x2a]).count();
assert_eq!(g_count, 0, "a genesis node carries no tag-42 links");
}
#[test]
fn btreeset_ordering_makes_id_insertion_order_independent() {
let p1 = id_for("p1");
let p2 = id_for("p2");
let p3 = id_for("p3");
let forward = MerkleNode::new(event("alice", "hi"), [p1, p2, p3]);
let reverse = MerkleNode::new(event("alice", "hi"), [p3, p2, p1]);
assert_eq!(
forward.id().unwrap(),
reverse.id().unwrap(),
"parent insertion order must not affect the node id"
);
assert_eq!(
forward.canonical_form().unwrap(),
reverse.canonical_form().unwrap(),
"parent insertion order must not affect the canonical bytes"
);
let dup = MerkleNode::new(event("alice", "hi"), [p1, p1, p2, p3, p2]);
assert_eq!(
forward.id().unwrap(),
dup.id().unwrap(),
"duplicate parents must collapse in the BTreeSet"
);
}
#[test]
fn small_dag_b_references_a() {
let a = MerkleNode::genesis(event("alice", "first"));
let a_id = a.id().unwrap();
let b = MerkleNode::new(event("bob", "reply"), [a_id]);
let b_id_1 = b.id().unwrap();
let b_id_2 = b.id().unwrap();
assert_eq!(b_id_1, b_id_2, "B's id is stable");
assert!(
b.parents().contains(&a_id),
"A's id must be present in B's parent links"
);
assert_eq!(b.parents().len(), 1, "B has exactly one parent link (A)");
}
#[test]
fn roundtrip_and_verify() {
let parent = id_for("parent");
let node = MerkleNode::new(event("alice", "hi"), [parent]);
let id = node.id().unwrap();
assert!(
node.verify(&id).unwrap(),
"a node must verify against its own id"
);
let bytes = node.canonical_form().unwrap();
let back: MerkleNode<Event> = canonical::from_canonical_dagcbor_checked(&bytes).unwrap();
assert_eq!(node, back, "a node must survive a dag-cbor roundtrip");
assert!(
back.verify(&id).unwrap(),
"the round-tripped node must still verify against the original id"
);
}
}