use std::collections::BTreeSet;
use chia_protocol::{Bytes32, Coin, CoinSpend, Program};
use chia_puzzle_types::singleton::SingletonArgs;
use chia_puzzle_types::Proof;
use chia_puzzles::SINGLETON_LAUNCHER_HASH;
use chia_wallet_sdk::driver::{Did, DidInfo, Layer, Puzzle, SingletonLayer};
use chia_wallet_sdk::prelude::{Allocator, NodePtr};
use chia_wallet_sdk::types::{run_puzzle, Condition};
use clvm_traits::{FromClvm, ToClvm};
use clvm_utils::TreeHash;
use crate::error::{DidError, DidResult};
pub const MAX_LINEAGE_DEPTH: usize = 100_000;
pub trait ChainSource {
type Error: core::fmt::Display;
fn resolve_singleton_lineage(
&self,
launcher_id: Bytes32,
) -> Result<Option<SingletonLineage>, Self::Error>;
fn parent_spend(&self, coin_id: Bytes32) -> Result<Option<CoinSpend>, Self::Error>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SingletonLineage {
tip: Bytes32,
members: BTreeSet<Bytes32>,
}
impl SingletonLineage {
pub fn new(tip: Bytes32, members: impl IntoIterator<Item = Bytes32>) -> Self {
let mut members: BTreeSet<Bytes32> = members.into_iter().collect();
members.insert(tip);
Self { tip, members }
}
pub fn single(tip: Bytes32) -> Self {
Self::new(tip, [tip])
}
pub fn tip(&self) -> Bytes32 {
self.tip
}
pub fn contains(&self, coin_id: Bytes32) -> bool {
self.members.contains(&coin_id)
}
pub fn len(&self) -> usize {
self.members.len()
}
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct DidTip {
pub coin: Coin,
pub info: DidInfo,
pub proof: Proof,
}
impl DidTip {
pub fn did(&self) -> Did {
Did::new(self.coin, self.proof, self.info)
}
}
#[derive(Debug)]
pub(crate) struct AuthenticatedLineage {
pub(crate) launcher_id: Bytes32,
pub(crate) launcher_coin: Coin,
pub(crate) trail: Vec<Bytes32>,
}
pub(crate) fn authenticate_singleton<S: ChainSource>(
coin_id: Bytes32,
source: &S,
) -> DidResult<AuthenticatedLineage> {
authenticate_singleton_bounded(coin_id, source, MAX_LINEAGE_DEPTH)
}
pub(crate) fn authenticate_singleton_bounded<S: ChainSource>(
coin_id: Bytes32,
source: &S,
max_depth: usize,
) -> DidResult<AuthenticatedLineage> {
let mut allocator = Allocator::new();
let mut trail = vec![coin_id];
let mut current = coin_id;
let mut expected_launcher: Option<Bytes32> = None;
for _hop in 0..max_depth {
let spend = source
.parent_spend(current)
.map_err(chain_error)?
.ok_or(DidError::NotASingleton)?;
let parent = spend.coin;
let (parent_puzzle, parent_solution) = parse_spend(&mut allocator, &spend)?;
if parent.puzzle_hash == SINGLETON_LAUNCHER_HASH.into() {
let launcher_id = parent.coin_id();
if let Some(expected) = expected_launcher {
require(expected == launcher_id)?;
}
require(launcher_creates(
&mut allocator,
parent,
parent_puzzle,
parent_solution,
current,
)?)?;
return Ok(AuthenticatedLineage {
launcher_id,
launcher_coin: parent,
trail,
});
}
let layer = SingletonLayer::<Puzzle>::parse_puzzle(&allocator, parent_puzzle)
.map_err(DidError::Driver)?
.ok_or(DidError::NotASingleton)?;
if let Some(expected) = expected_launcher {
require(expected == layer.launcher_id)?;
}
expected_launcher = Some(layer.launcher_id);
let successor = singleton_successor(&mut allocator, parent, &layer, parent_solution)?
.ok_or(DidError::NotASingleton)?;
require(successor.coin_id() == current)?;
trail.push(parent.coin_id());
current = parent.coin_id();
}
Err(DidError::LineageTooDeep)
}
fn singleton_successor(
allocator: &mut Allocator,
parent: Coin,
layer: &SingletonLayer<Puzzle>,
parent_solution: NodePtr,
) -> DidResult<Option<Coin>> {
let solution = SingletonLayer::<Puzzle>::parse_solution(allocator, parent_solution)
.map_err(DidError::Driver)?;
let output = run_puzzle(allocator, layer.inner_puzzle.ptr(), solution.inner_solution)
.map_err(|error| DidError::Parse(error.to_string()))?;
let conditions = Vec::<Condition>::from_clvm(allocator, output)
.map_err(|e| DidError::Parse(e.to_string()))?;
let Some(create_coin) = conditions
.into_iter()
.filter_map(Condition::into_create_coin)
.find(|create_coin| create_coin.amount % 2 == 1)
else {
return Ok(None);
};
let inner_hash: TreeHash = create_coin.puzzle_hash.into();
let full_puzzle_hash = SingletonArgs::curry_tree_hash(layer.launcher_id, inner_hash);
Ok(Some(Coin::new(
parent.coin_id(),
full_puzzle_hash.into(),
create_coin.amount,
)))
}
fn launcher_creates(
allocator: &mut Allocator,
launcher: Coin,
launcher_puzzle: Puzzle,
launcher_solution: NodePtr,
eve_id: Bytes32,
) -> DidResult<bool> {
let output = run_puzzle(allocator, launcher_puzzle.ptr(), launcher_solution)
.map_err(|error| DidError::Parse(error.to_string()))?;
let conditions = Vec::<Condition>::from_clvm(allocator, output)
.map_err(|e| DidError::Parse(e.to_string()))?;
Ok(conditions
.into_iter()
.filter_map(Condition::into_create_coin)
.any(|create_coin| {
Coin::new(
launcher.coin_id(),
create_coin.puzzle_hash,
create_coin.amount,
)
.coin_id()
== eve_id
}))
}
pub fn walk_did_lineage_to_tip<S: ChainSource>(
source: &S,
launcher_id: Bytes32,
) -> DidResult<Option<DidTip>> {
let Some(lineage) = source
.resolve_singleton_lineage(launcher_id)
.map_err(chain_error)?
else {
return Ok(None);
};
let tip_id = lineage.tip();
let spend = source
.parent_spend(tip_id)
.map_err(chain_error)?
.ok_or(DidError::NoIdentitySingleton)?;
let parent = spend.coin;
let mut allocator = Allocator::new();
let (parent_puzzle, parent_solution) = parse_spend(&mut allocator, &spend)?;
let layer = SingletonLayer::<Puzzle>::parse_puzzle(&allocator, parent_puzzle)
.map_err(DidError::Driver)?
.ok_or(DidError::NotDid)?;
let tip_coin = singleton_successor(&mut allocator, parent, &layer, parent_solution)?
.filter(|coin| coin.coin_id() == tip_id)
.ok_or(DidError::NotDid)?;
let did = Did::parse_child(
&mut allocator,
parent,
parent_puzzle,
parent_solution,
tip_coin,
)
.map_err(DidError::Driver)?
.ok_or(DidError::NotDid)?;
Ok(Some(DidTip {
coin: did.coin,
info: did.info,
proof: did.proof,
}))
}
fn parse_spend(allocator: &mut Allocator, spend: &CoinSpend) -> DidResult<(Puzzle, NodePtr)> {
let puzzle_ptr = alloc_program(allocator, &spend.puzzle_reveal)?;
let solution_ptr = alloc_program(allocator, &spend.solution)?;
Ok((Puzzle::parse(allocator, puzzle_ptr), solution_ptr))
}
fn alloc_program(allocator: &mut Allocator, program: &Program) -> DidResult<NodePtr> {
program
.to_clvm(allocator)
.map_err(|error| DidError::Parse(error.to_string()))
}
fn require(condition: bool) -> DidResult<()> {
condition.then_some(()).ok_or(DidError::NotASingleton)
}
fn chain_error<E: core::fmt::Display>(error: E) -> DidError {
DidError::Chain(error.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lineage_membership_includes_tip_and_ancestors() {
let launcher = Bytes32::new([1u8; 32]);
let cn = Bytes32::new([2u8; 32]);
let tip = Bytes32::new([3u8; 32]);
let lineage = SingletonLineage::new(tip, [launcher, cn]);
assert!(lineage.contains(launcher));
assert!(lineage.contains(cn));
assert!(lineage.contains(tip));
assert!(!lineage.contains(Bytes32::new([9u8; 32])));
assert_eq!(lineage.tip(), tip);
assert_eq!(lineage.len(), 3);
assert!(!lineage.is_empty());
}
#[test]
fn single_lineage_is_tip_only() {
let tip = Bytes32::new([7u8; 32]);
let lineage = SingletonLineage::single(tip);
assert_eq!(lineage.len(), 1);
assert!(lineage.contains(tip));
}
}