use chia_wallet_sdk::driver::{get_merkle_tree, Datastore, NftStateLayer, SpendContext};
use chia_wallet_sdk::prelude::{ToTreeHash, TreeHash};
use chia_wallet_sdk::types::puzzles::{DelegationLayerArgs, DL_METADATA_UPDATER_PUZZLE_HASH};
use crate::metadata::DigDataStoreMetadata;
use crate::types::LineageProof;
use crate::MerkleResult;
pub fn child_lineage_proof(store: &Datastore<DigDataStoreMetadata>) -> MerkleResult<LineageProof> {
let mut ctx = SpendContext::new();
Ok(LineageProof {
parent_parent_coin_info: store.coin.parent_coin_info,
parent_inner_puzzle_hash: parent_inner_puzzle_hash(store, &mut ctx)?.into(),
parent_amount: store.coin.amount,
})
}
fn parent_inner_puzzle_hash(
store: &Datastore<DigDataStoreMetadata>,
ctx: &mut SpendContext,
) -> MerkleResult<TreeHash> {
let metadata_ptr = ctx.alloc(&store.info.metadata)?;
let metadata_hash = ctx.tree_hash(metadata_ptr);
let inner_puzzle_hash = if store.info.delegated_puzzles.is_empty() {
store.info.owner_puzzle_hash.into()
} else {
DelegationLayerArgs::curry_tree_hash(
store.info.launcher_id,
store.info.owner_puzzle_hash,
get_merkle_tree(ctx, store.info.delegated_puzzles.clone())?.root(),
)
};
Ok(NftStateLayer::new(
metadata_hash,
DL_METADATA_UPDATER_PUZZLE_HASH.into(),
inner_puzzle_hash,
)
.tree_hash())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mint::mint_datastore;
use crate::types::{Bytes32, Owner, Proof};
use chia_puzzle_types::standard::StandardArgs;
use chia_wallet_sdk::test::Simulator;
#[test]
fn child_lineage_proof_matches_the_parsed_on_chain_proof() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let built = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
Bytes32::new([0x5a; 32]),
None,
None,
None,
None,
None,
owner_ph,
vec![],
0,
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store1 = built.child.expect("mint yields a child (eve store)");
let standalone = child_lineage_proof(&store1)?;
let built2 = crate::update::update_root(
&store1,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x77; 32]),
..Default::default()
},
)?;
let store2 = built2.child.clone().expect("update yields a child");
let parsed = match store2.proof {
Proof::Lineage(lp) => lp,
Proof::Eve(_) => panic!("store2 is not an eve coin"),
};
assert_eq!(
standalone.parent_inner_puzzle_hash, parsed.parent_inner_puzzle_hash,
"child_lineage_proof's parent_inner_puzzle_hash must equal the SDK-parsed on-chain value"
);
assert_eq!(
standalone.parent_parent_coin_info,
parsed.parent_parent_coin_info
);
assert_eq!(standalone.parent_amount, parsed.parent_amount);
Ok(())
}
#[test]
fn child_lineage_proof_produces_a_consensus_valid_child_spend() -> anyhow::Result<()> {
use crate::types::Datastore;
let mut sim = Simulator::new();
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let built = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
Bytes32::new([0x5a; 32]),
None,
None,
None,
None,
None,
owner_ph,
vec![],
0,
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store1 = built.child.expect("mint yields a child (eve store)");
let built2 = crate::update::update_root(
&store1,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x77; 32]),
..Default::default()
},
)?;
sim.spend_coins(built2.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store2 = built2.child.expect("update yields a child");
let store2_via_clp = Datastore::new(
store2.coin,
Proof::Lineage(child_lineage_proof(&store1)?),
store2.info.clone(),
);
let built3 = crate::update::update_root(
&store2_via_clp,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x88; 32]),
..Default::default()
},
)?;
sim.spend_coins(built3.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
Ok(())
}
#[test]
fn child_lineage_proof_matches_the_parsed_on_chain_proof_for_a_delegated_store(
) -> anyhow::Result<()> {
use crate::types::DelegatedPuzzle;
let mut sim = Simulator::new();
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let delegated_puzzles = vec![
DelegatedPuzzle::Admin(chia_wallet_sdk::prelude::TreeHash::new([0x11; 32])),
DelegatedPuzzle::Writer(chia_wallet_sdk::prelude::TreeHash::new([0x22; 32])),
];
let built = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
Bytes32::new([0x5a; 32]),
None,
None,
None,
None,
None,
owner_ph,
delegated_puzzles.clone(),
0,
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store1 = built.child.expect("mint yields a child (eve store)");
assert!(
!store1.info.delegated_puzzles.is_empty(),
"test precondition: the store carries a delegation set"
);
let standalone = child_lineage_proof(&store1)?;
let built2 = crate::update::update_root(
&store1,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x77; 32]),
..Default::default()
},
)?;
let store2 = built2.child.clone().expect("update yields a child");
let parsed = match store2.proof {
Proof::Lineage(lp) => lp,
Proof::Eve(_) => panic!("store2 is not an eve coin"),
};
assert_eq!(
standalone.parent_inner_puzzle_hash, parsed.parent_inner_puzzle_hash,
"delegated store: parent_inner_puzzle_hash must equal the SDK-parsed on-chain value"
);
assert_eq!(
standalone.parent_parent_coin_info,
parsed.parent_parent_coin_info
);
assert_eq!(standalone.parent_amount, parsed.parent_amount);
Ok(())
}
#[test]
fn child_lineage_proof_produces_a_consensus_valid_child_spend_for_a_delegated_store(
) -> anyhow::Result<()> {
use crate::types::{Datastore, DelegatedPuzzle};
let mut sim = Simulator::new();
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let delegated_puzzles = vec![
DelegatedPuzzle::Admin(chia_wallet_sdk::prelude::TreeHash::new([0x11; 32])),
DelegatedPuzzle::Writer(chia_wallet_sdk::prelude::TreeHash::new([0x22; 32])),
];
let built = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
Bytes32::new([0x5a; 32]),
None,
None,
None,
None,
None,
owner_ph,
delegated_puzzles,
0,
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store1 = built.child.expect("mint yields a child (eve store)");
let built2 = crate::update::update_root(
&store1,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x77; 32]),
..Default::default()
},
)?;
sim.spend_coins(built2.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store2 = built2.child.expect("update yields a child");
let store2_via_clp = Datastore::new(
store2.coin,
Proof::Lineage(child_lineage_proof(&store1)?),
store2.info.clone(),
);
let built3 = crate::update::update_root(
&store2_via_clp,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x88; 32]),
..Default::default()
},
)?;
sim.spend_coins(built3.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
Ok(())
}
#[test]
fn lineage_proof_binds_to_the_store_coin() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let built = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
Bytes32::new([0x5a; 32]),
None,
None,
None,
None,
None,
owner_ph,
vec![],
0,
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store = built.child.expect("mint yields a child");
let proof = child_lineage_proof(&store)?;
assert_eq!(proof.parent_amount, store.coin.amount);
assert_eq!(
proof.parent_parent_coin_info, store.coin.parent_coin_info,
"the proof references the store coin's parent"
);
Ok(())
}
#[test]
fn lineage_proof_lets_a_child_spend_validate() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let built = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
Bytes32::new([0x5a; 32]),
None,
None,
None,
None,
None,
owner_ph,
vec![],
0,
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store = built.child.expect("mint yields a child");
let child = crate::update::update_root(
&store,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x77; 32]),
..Default::default()
},
)?;
sim.spend_coins(child.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
Ok(())
}
}