use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::dag::DAGNode;
use crate::hash::Hash;
use crate::seal::SealRef;
use crate::state::{GlobalState, Metadata, StateAssignment, StateRef};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Transition {
pub transition_id: u16,
pub owned_inputs: Vec<StateRef>,
pub owned_outputs: Vec<StateAssignment>,
pub global_updates: Vec<GlobalState>,
pub metadata: Vec<Metadata>,
pub validation_script: Vec<u8>,
pub signatures: Vec<Vec<u8>>,
}
impl Transition {
pub fn new(
transition_id: u16,
owned_inputs: Vec<StateRef>,
owned_outputs: Vec<StateAssignment>,
global_updates: Vec<GlobalState>,
metadata: Vec<Metadata>,
validation_script: Vec<u8>,
signatures: Vec<Vec<u8>>,
) -> Self {
Self {
transition_id,
owned_inputs,
owned_outputs,
global_updates,
metadata,
validation_script,
signatures,
}
}
pub fn hash(&self) -> Hash {
let mut hasher = Sha256::new();
hasher.update(b"CSV-TRANSITION-v1");
hasher.update(&self.transition_id.to_le_bytes());
hasher.update(&(self.owned_inputs.len() as u64).to_le_bytes());
for input in &self.owned_inputs {
hasher.update(&input.type_id.to_le_bytes());
hasher.update(input.commitment.as_bytes());
hasher.update(&input.output_index.to_le_bytes());
}
hasher.update(&(self.owned_outputs.len() as u64).to_le_bytes());
for output in &self.owned_outputs {
hasher.update(&output.type_id.to_le_bytes());
hasher.update(&output.seal.to_vec());
hasher.update(&output.data);
}
hasher.update(&(self.global_updates.len() as u64).to_le_bytes());
for update in &self.global_updates {
hasher.update(&update.type_id.to_le_bytes());
hasher.update(&update.data);
}
hasher.update(&(self.metadata.len() as u64).to_le_bytes());
for meta in &self.metadata {
hasher.update(meta.key.as_bytes());
hasher.update(&meta.value);
}
hasher.update(&(self.validation_script.len() as u64).to_le_bytes());
hasher.update(&self.validation_script);
hasher.update(&(self.signatures.len() as u64).to_le_bytes());
for sig in &self.signatures {
hasher.update(sig);
}
let result = hasher.finalize();
let mut array = [0u8; 32];
array.copy_from_slice(&result);
Hash::new(array)
}
pub fn consumed_seals(&self) -> Vec<SealRef> {
Vec::new()
}
pub fn assigned_seals(&self) -> Vec<SealRef> {
self.owned_outputs.iter().map(|o| o.seal.clone()).collect()
}
pub fn is_genesis_like(&self) -> bool {
self.owned_inputs.is_empty()
}
pub fn is_destructive(&self) -> bool {
self.owned_outputs.is_empty() && self.global_updates.is_empty()
}
pub fn to_dag_node(&self) -> DAGNode {
DAGNode::new(
self.hash(),
self.validation_script.clone(),
self.signatures.clone(),
self.metadata
.iter()
.flat_map(|m| m.value.clone())
.collect::<Vec<_>>()
.chunks(32)
.map(|c| c.to_vec())
.collect(),
Vec::new(), )
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_transition() -> Transition {
Transition::new(
1, vec![
StateRef::new(10, Hash::new([1u8; 32]), 0), ],
vec![
StateAssignment::new(
10,
SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
600u64.to_le_bytes().to_vec(),
), StateAssignment::new(
10,
SealRef::new(vec![0xBB; 16], Some(2)).unwrap(),
400u64.to_le_bytes().to_vec(),
), ],
vec![
GlobalState::new(1, vec![100, 200]), ],
vec![Metadata::from_string("memo", "payment for services")],
vec![0x01, 0x02, 0x03], vec![vec![0xAB; 64]], )
}
#[test]
fn test_transition_creation() {
let t = test_transition();
assert_eq!(t.transition_id, 1);
assert_eq!(t.owned_inputs.len(), 1);
assert_eq!(t.owned_outputs.len(), 2);
assert_eq!(t.global_updates.len(), 1);
assert_eq!(t.metadata.len(), 1);
}
#[test]
fn test_transition_hash() {
let t = test_transition();
let hash = t.hash();
assert_eq!(hash.as_bytes().len(), 32);
}
#[test]
fn test_transition_hash_deterministic() {
let t1 = test_transition();
let t2 = test_transition();
assert_eq!(t1.hash(), t2.hash());
}
#[test]
fn test_transition_hash_differs_by_inputs() {
let mut t1 = test_transition();
let t2 = test_transition();
t1.owned_inputs
.push(StateRef::new(10, Hash::new([99u8; 32]), 1));
assert_ne!(t1.hash(), t2.hash());
}
#[test]
fn test_transition_hash_differs_by_outputs() {
let mut t1 = test_transition();
let t2 = test_transition();
t1.owned_outputs.push(StateAssignment::new(
10,
SealRef::new(vec![0xCC; 16], Some(3)).unwrap(),
vec![100],
));
assert_ne!(t1.hash(), t2.hash());
}
#[test]
fn test_transition_hash_differs_by_script() {
let mut t1 = test_transition();
let t2 = test_transition();
t1.validation_script = vec![0xFF, 0xFF, 0xFF];
assert_ne!(t1.hash(), t2.hash());
}
#[test]
fn test_transition_hash_differs_by_signatures() {
let mut t1 = test_transition();
let t2 = test_transition();
t1.signatures.push(vec![0xCD; 64]);
assert_ne!(t1.hash(), t2.hash());
}
#[test]
fn test_transition_hash_bytecode_order() {
let t1 = Transition::new(
1,
vec![],
vec![],
vec![],
vec![],
vec![0x01, 0x02, 0x03],
vec![],
);
let t2 = Transition::new(
1,
vec![],
vec![],
vec![],
vec![],
vec![0x03, 0x02, 0x01],
vec![],
);
assert_ne!(t1.hash(), t2.hash());
}
#[test]
fn test_assigned_seals() {
let t = test_transition();
let seals = t.assigned_seals();
assert_eq!(seals.len(), 2);
assert_eq!(seals[0].nonce, Some(1));
assert_eq!(seals[1].nonce, Some(2));
}
#[test]
fn test_is_genesis_like() {
let genesis_like = Transition::new(
0,
vec![], vec![StateAssignment::new(
10,
SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
1000u64.to_le_bytes().to_vec(),
)],
vec![],
vec![],
vec![0x01],
vec![],
);
assert!(genesis_like.is_genesis_like());
let normal = test_transition();
assert!(!normal.is_genesis_like());
}
#[test]
fn test_is_destructive() {
let destructive = Transition::new(
2, vec![StateRef::new(10, Hash::new([1u8; 32]), 0)],
vec![], vec![], vec![],
vec![0x01],
vec![],
);
assert!(destructive.is_destructive());
let normal = test_transition();
assert!(!normal.is_destructive());
}
#[test]
fn test_empty_transition() {
let t = Transition::new(0, vec![], vec![], vec![], vec![], vec![], vec![]);
assert!(t.is_genesis_like());
assert!(t.is_destructive());
assert_eq!(t.hash().as_bytes().len(), 32);
}
#[test]
fn test_transition_serialization_roundtrip() {
let t = test_transition();
let bytes = bincode::serialize(&t).unwrap();
let restored: Transition = bincode::deserialize(&bytes).unwrap();
assert_eq!(t, restored);
assert_eq!(t.hash(), restored.hash());
}
#[test]
fn test_to_dag_node() {
let t = test_transition();
let node = t.to_dag_node();
assert_eq!(node.node_id, t.hash());
assert_eq!(node.bytecode, t.validation_script);
assert_eq!(node.signatures, t.signatures);
}
}