1use alloc::vec::Vec;
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10
11use crate::dag::DAGNode;
12use crate::hash::Hash;
13use crate::seal::SealRef;
14use crate::state::{GlobalState, Metadata, StateAssignment, StateRef};
15
16#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
21pub struct Transition {
22 pub transition_id: u16,
24 pub owned_inputs: Vec<StateRef>,
26 pub owned_outputs: Vec<StateAssignment>,
28 pub global_updates: Vec<GlobalState>,
30 pub metadata: Vec<Metadata>,
32 pub validation_script: Vec<u8>,
34 pub signatures: Vec<Vec<u8>>,
36}
37
38impl Transition {
39 pub fn new(
41 transition_id: u16,
42 owned_inputs: Vec<StateRef>,
43 owned_outputs: Vec<StateAssignment>,
44 global_updates: Vec<GlobalState>,
45 metadata: Vec<Metadata>,
46 validation_script: Vec<u8>,
47 signatures: Vec<Vec<u8>>,
48 ) -> Self {
49 Self {
50 transition_id,
51 owned_inputs,
52 owned_outputs,
53 global_updates,
54 metadata,
55 validation_script,
56 signatures,
57 }
58 }
59
60 pub fn hash(&self) -> Hash {
62 let mut hasher = Sha256::new();
63
64 hasher.update(b"CSV-TRANSITION-v1");
65 hasher.update(&self.transition_id.to_le_bytes());
66
67 hasher.update(&(self.owned_inputs.len() as u64).to_le_bytes());
69 for input in &self.owned_inputs {
70 hasher.update(&input.type_id.to_le_bytes());
71 hasher.update(input.commitment.as_bytes());
72 hasher.update(&input.output_index.to_le_bytes());
73 }
74
75 hasher.update(&(self.owned_outputs.len() as u64).to_le_bytes());
77 for output in &self.owned_outputs {
78 hasher.update(&output.type_id.to_le_bytes());
79 hasher.update(&output.seal.to_vec());
80 hasher.update(&output.data);
81 }
82
83 hasher.update(&(self.global_updates.len() as u64).to_le_bytes());
85 for update in &self.global_updates {
86 hasher.update(&update.type_id.to_le_bytes());
87 hasher.update(&update.data);
88 }
89
90 hasher.update(&(self.metadata.len() as u64).to_le_bytes());
92 for meta in &self.metadata {
93 hasher.update(meta.key.as_bytes());
94 hasher.update(&meta.value);
95 }
96
97 hasher.update(&(self.validation_script.len() as u64).to_le_bytes());
99 hasher.update(&self.validation_script);
100
101 hasher.update(&(self.signatures.len() as u64).to_le_bytes());
103 for sig in &self.signatures {
104 hasher.update(sig);
105 }
106
107 let result = hasher.finalize();
108 let mut array = [0u8; 32];
109 array.copy_from_slice(&result);
110 Hash::new(array)
111 }
112
113 pub fn consumed_seals(&self) -> Vec<SealRef> {
115 Vec::new()
120 }
121
122 pub fn assigned_seals(&self) -> Vec<SealRef> {
124 self.owned_outputs.iter().map(|o| o.seal.clone()).collect()
125 }
126
127 pub fn is_genesis_like(&self) -> bool {
129 self.owned_inputs.is_empty()
130 }
131
132 pub fn is_destructive(&self) -> bool {
134 self.owned_outputs.is_empty() && self.global_updates.is_empty()
135 }
136
137 pub fn to_dag_node(&self) -> DAGNode {
139 DAGNode::new(
140 self.hash(),
141 self.validation_script.clone(),
142 self.signatures.clone(),
143 self.metadata
144 .iter()
145 .flat_map(|m| m.value.clone())
146 .collect::<Vec<_>>()
147 .chunks(32)
148 .map(|c| c.to_vec())
149 .collect(),
150 Vec::new(), )
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 fn test_transition() -> Transition {
160 Transition::new(
161 1, vec![
163 StateRef::new(10, Hash::new([1u8; 32]), 0), ],
165 vec![
166 StateAssignment::new(
167 10,
168 SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
169 600u64.to_le_bytes().to_vec(),
170 ), StateAssignment::new(
172 10,
173 SealRef::new(vec![0xBB; 16], Some(2)).unwrap(),
174 400u64.to_le_bytes().to_vec(),
175 ), ],
177 vec![
178 GlobalState::new(1, vec![100, 200]), ],
180 vec![Metadata::from_string("memo", "payment for services")],
181 vec![0x01, 0x02, 0x03], vec![vec![0xAB; 64]], )
184 }
185
186 #[test]
187 fn test_transition_creation() {
188 let t = test_transition();
189 assert_eq!(t.transition_id, 1);
190 assert_eq!(t.owned_inputs.len(), 1);
191 assert_eq!(t.owned_outputs.len(), 2);
192 assert_eq!(t.global_updates.len(), 1);
193 assert_eq!(t.metadata.len(), 1);
194 }
195
196 #[test]
197 fn test_transition_hash() {
198 let t = test_transition();
199 let hash = t.hash();
200 assert_eq!(hash.as_bytes().len(), 32);
201 }
202
203 #[test]
204 fn test_transition_hash_deterministic() {
205 let t1 = test_transition();
206 let t2 = test_transition();
207 assert_eq!(t1.hash(), t2.hash());
208 }
209
210 #[test]
211 fn test_transition_hash_differs_by_inputs() {
212 let mut t1 = test_transition();
213 let t2 = test_transition();
214 t1.owned_inputs
215 .push(StateRef::new(10, Hash::new([99u8; 32]), 1));
216 assert_ne!(t1.hash(), t2.hash());
217 }
218
219 #[test]
220 fn test_transition_hash_differs_by_outputs() {
221 let mut t1 = test_transition();
222 let t2 = test_transition();
223 t1.owned_outputs.push(StateAssignment::new(
224 10,
225 SealRef::new(vec![0xCC; 16], Some(3)).unwrap(),
226 vec![100],
227 ));
228 assert_ne!(t1.hash(), t2.hash());
229 }
230
231 #[test]
232 fn test_transition_hash_differs_by_script() {
233 let mut t1 = test_transition();
234 let t2 = test_transition();
235 t1.validation_script = vec![0xFF, 0xFF, 0xFF];
236 assert_ne!(t1.hash(), t2.hash());
237 }
238
239 #[test]
240 fn test_transition_hash_differs_by_signatures() {
241 let mut t1 = test_transition();
242 let t2 = test_transition();
243 t1.signatures.push(vec![0xCD; 64]);
244 assert_ne!(t1.hash(), t2.hash());
245 }
246
247 #[test]
248 fn test_transition_hash_bytecode_order() {
249 let t1 = Transition::new(
250 1,
251 vec![],
252 vec![],
253 vec![],
254 vec![],
255 vec![0x01, 0x02, 0x03],
256 vec![],
257 );
258 let t2 = Transition::new(
259 1,
260 vec![],
261 vec![],
262 vec![],
263 vec![],
264 vec![0x03, 0x02, 0x01],
265 vec![],
266 );
267 assert_ne!(t1.hash(), t2.hash());
268 }
269
270 #[test]
271 fn test_assigned_seals() {
272 let t = test_transition();
273 let seals = t.assigned_seals();
274 assert_eq!(seals.len(), 2);
275 assert_eq!(seals[0].nonce, Some(1));
276 assert_eq!(seals[1].nonce, Some(2));
277 }
278
279 #[test]
280 fn test_is_genesis_like() {
281 let genesis_like = Transition::new(
282 0,
283 vec![], vec![StateAssignment::new(
285 10,
286 SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
287 1000u64.to_le_bytes().to_vec(),
288 )],
289 vec![],
290 vec![],
291 vec![0x01],
292 vec![],
293 );
294 assert!(genesis_like.is_genesis_like());
295
296 let normal = test_transition();
297 assert!(!normal.is_genesis_like());
298 }
299
300 #[test]
301 fn test_is_destructive() {
302 let destructive = Transition::new(
303 2, vec![StateRef::new(10, Hash::new([1u8; 32]), 0)],
305 vec![], vec![], vec![],
308 vec![0x01],
309 vec![],
310 );
311 assert!(destructive.is_destructive());
312
313 let normal = test_transition();
314 assert!(!normal.is_destructive());
315 }
316
317 #[test]
318 fn test_empty_transition() {
319 let t = Transition::new(0, vec![], vec![], vec![], vec![], vec![], vec![]);
320 assert!(t.is_genesis_like());
321 assert!(t.is_destructive());
322 assert_eq!(t.hash().as_bytes().len(), 32);
323 }
324
325 #[test]
326 fn test_transition_serialization_roundtrip() {
327 let t = test_transition();
328 let bytes = bincode::serialize(&t).unwrap();
329 let restored: Transition = bincode::deserialize(&bytes).unwrap();
330 assert_eq!(t, restored);
331 assert_eq!(t.hash(), restored.hash());
332 }
333
334 #[test]
335 fn test_to_dag_node() {
336 let t = test_transition();
337 let node = t.to_dag_node();
338 assert_eq!(node.node_id, t.hash());
339 assert_eq!(node.bytecode, t.validation_script);
340 assert_eq!(node.signatures, t.signatures);
341 }
342}