Skip to main content

dpp/identity/state_transition/asset_lock_proof/
mod.rs

1use std::convert::{TryFrom, TryInto};
2
3use dashcore::{OutPoint, Transaction};
4
5use serde::{Deserialize, Deserializer, Serialize};
6
7use bincode::{Decode, Encode};
8
9pub use instant::*;
10use platform_value::Value;
11#[cfg(feature = "validation")]
12use platform_version::version::PlatformVersion;
13use serde::de::Error;
14
15use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
16use crate::prelude::Identifier;
17#[cfg(feature = "validation")]
18use crate::validation::SimpleConsensusValidationResult;
19use crate::{ProtocolError, SerdeParsingError};
20
21pub mod chain;
22pub mod instant;
23pub mod validate_asset_lock_transaction_structure;
24
25// TODO: Serialization with bincode
26// TODO: Consider use Box for InstantAssetLockProof
27#[derive(Clone, Debug, Eq, PartialEq, Serialize, Encode, Decode)]
28#[serde(untagged)]
29#[allow(clippy::large_enum_variant)]
30pub enum AssetLockProof {
31    Instant(#[bincode(with_serde)] InstantAssetLockProof),
32    Chain(#[bincode(with_serde)] ChainAssetLockProof),
33}
34
35#[derive(Deserialize)]
36#[serde(untagged)]
37enum RawAssetLockProof {
38    Instant(RawInstantLockProof),
39    Chain(ChainAssetLockProof),
40}
41
42impl TryFrom<RawAssetLockProof> for AssetLockProof {
43    type Error = ProtocolError;
44
45    fn try_from(value: RawAssetLockProof) -> Result<Self, Self::Error> {
46        match value {
47            RawAssetLockProof::Instant(raw_instant_lock) => {
48                let instant_lock = raw_instant_lock.try_into()?;
49
50                Ok(AssetLockProof::Instant(instant_lock))
51            }
52            RawAssetLockProof::Chain(chain) => Ok(AssetLockProof::Chain(chain)),
53        }
54    }
55}
56
57impl<'de> Deserialize<'de> for AssetLockProof {
58    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
59    where
60        D: Deserializer<'de>,
61    {
62        // Try to parse into IS Lock
63        // let maybe_is_lock = RawInstantLock::deserialize(&deserializer);
64        //
65        // if let Ok(raw_instant_lock) = maybe_is_lock {
66        //     let instant_lock = raw_instant_lock.try_into()
67        //         .map_err(|e: ProtocolError| D::Error::custom(e.to_string()))?;
68        //
69        //     return Ok(AssetLockProof::Instant(instant_lock))
70        // };
71        //
72        //
73        // ChainAssetLockProof::deserialize(deserializer)
74        //     .map(|chain| AssetLockProof::Chain(chain))
75        // // Try to parse into chain lock
76
77        let raw = RawAssetLockProof::deserialize(deserializer)?;
78        raw.try_into().map_err(|e: ProtocolError| {
79            D::Error::custom(format!(
80                "expected to be able to deserialize asset lock proof: {}",
81                e
82            ))
83        })
84    }
85}
86
87impl Default for AssetLockProof {
88    fn default() -> Self {
89        Self::Instant(InstantAssetLockProof::default())
90    }
91}
92
93impl AsRef<AssetLockProof> for AssetLockProof {
94    fn as_ref(&self) -> &AssetLockProof {
95        self
96    }
97}
98//
99// impl Serialize for AssetLockProof {
100//     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101//     where
102//         S: Serializer,
103//     {
104//         match self {
105//             AssetLockProof::Instant(instant_proof) => instant_proof.serialize(serializer),
106//             AssetLockProof::Chain(chain) => chain.serialize(serializer),
107//         }
108//     }
109// }
110//
111// impl<'de> Deserialize<'de> for AssetLockProof {
112//     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113//     where
114//         D: Deserializer<'de>,
115//     {
116//         let value = platform_value::Value::deserialize(deserializer)?;
117//
118//         let proof_type_int: u8 = value
119//             .get_integer("type")
120//             .map_err(|e| D::Error::custom(e.to_string()))?;
121//         let proof_type = AssetLockProofType::try_from(proof_type_int)
122//             .map_err(|e| D::Error::custom(e.to_string()))?;
123//
124//         match proof_type {
125//             AssetLockProofType::Instant => Ok(Self::Instant(
126//                 platform_value::from_value(value).map_err(|e| D::Error::custom(e.to_string()))?,
127//             )),
128//             AssetLockProofType::Chain => Ok(Self::Chain(
129//                 platform_value::from_value(value).map_err(|e| D::Error::custom(e.to_string()))?,
130//             )),
131//         }
132//     }
133// }
134
135pub enum AssetLockProofType {
136    Instant = 0,
137    Chain = 1,
138}
139
140impl TryFrom<u8> for AssetLockProofType {
141    type Error = SerdeParsingError;
142
143    fn try_from(value: u8) -> Result<Self, Self::Error> {
144        match value {
145            0 => Ok(Self::Instant),
146            1 => Ok(Self::Chain),
147            _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
148        }
149    }
150}
151
152impl TryFrom<u64> for AssetLockProofType {
153    type Error = SerdeParsingError;
154
155    fn try_from(value: u64) -> Result<Self, Self::Error> {
156        match value {
157            0 => Ok(Self::Instant),
158            1 => Ok(Self::Chain),
159            _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
160        }
161    }
162}
163
164// TODO: Versioning
165impl AssetLockProof {
166    pub fn type_from_raw_value(value: &Value) -> Option<AssetLockProofType> {
167        let proof_type_res = value.get_integer::<u8>("type");
168
169        match proof_type_res {
170            Ok(proof_type_int) => {
171                let proof_type = AssetLockProofType::try_from(proof_type_int);
172                proof_type.ok()
173            }
174            Err(_) => None,
175        }
176    }
177
178    pub fn create_identifier(&self) -> Result<Identifier, ProtocolError> {
179        match self {
180            AssetLockProof::Instant(instant_proof) => instant_proof.create_identifier(),
181            AssetLockProof::Chain(chain_proof) => Ok(chain_proof.create_identifier()),
182        }
183    }
184
185    pub fn output_index(&self) -> u32 {
186        match self {
187            AssetLockProof::Instant(proof) => proof.output_index(),
188            AssetLockProof::Chain(proof) => proof.out_point.vout,
189        }
190    }
191
192    pub fn out_point(&self) -> Option<OutPoint> {
193        match self {
194            AssetLockProof::Instant(proof) => proof.out_point(),
195            AssetLockProof::Chain(proof) => Some(proof.out_point),
196        }
197    }
198
199    pub fn transaction(&self) -> Option<&Transaction> {
200        match self {
201            AssetLockProof::Instant(is_lock) => Some(is_lock.transaction()),
202            AssetLockProof::Chain(_chain_lock) => None,
203        }
204    }
205
206    pub fn to_raw_object(&self) -> Result<Value, ProtocolError> {
207        match self {
208            AssetLockProof::Instant(is) => {
209                platform_value::to_value(is).map_err(ProtocolError::ValueError)
210            }
211            AssetLockProof::Chain(cl) => {
212                platform_value::to_value(cl).map_err(ProtocolError::ValueError)
213            }
214        }
215    }
216
217    /// Validate the structure of the asset lock proof
218    #[cfg(feature = "validation")]
219    pub fn validate_structure(
220        &self,
221        platform_version: &PlatformVersion,
222    ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
223        match self {
224            AssetLockProof::Instant(proof) => proof.validate_structure(platform_version),
225            AssetLockProof::Chain(_) => Ok(SimpleConsensusValidationResult::default()),
226        }
227    }
228}
229
230impl TryFrom<&Value> for AssetLockProof {
231    type Error = ProtocolError;
232
233    fn try_from(value: &Value) -> Result<Self, Self::Error> {
234        //this is a complete hack for the moment
235        //todo: replace with
236        //  from_value(value.clone()).map_err(ProtocolError::ValueError)
237        let proof_type_int: Option<u8> = value
238            .get_optional_integer("type")
239            .map_err(ProtocolError::ValueError)?;
240        if let Some(proof_type_int) = proof_type_int {
241            let proof_type = AssetLockProofType::try_from(proof_type_int)?;
242
243            match proof_type {
244                AssetLockProofType::Instant => Ok(Self::Instant(value.clone().try_into()?)),
245                AssetLockProofType::Chain => Ok(Self::Chain(value.clone().try_into()?)),
246            }
247        } else {
248            let map = value.as_map().ok_or(ProtocolError::DecodingError(
249                "error decoding asset lock proof".to_string(),
250            ))?;
251            let (key, asset_lock_value) = map.first().ok_or(ProtocolError::DecodingError(
252                "error decoding asset lock proof as it was empty".to_string(),
253            ))?;
254            match key.as_str().ok_or(ProtocolError::DecodingError(
255                "error decoding asset lock proof".to_string(),
256            ))? {
257                "Instant" => Ok(Self::Instant(asset_lock_value.clone().try_into()?)),
258                "Chain" => Ok(Self::Chain(asset_lock_value.clone().try_into()?)),
259                _ => Err(ProtocolError::DecodingError(
260                    "error decoding asset lock proof".to_string(),
261                )),
262            }
263        }
264    }
265}
266
267impl TryFrom<Value> for AssetLockProof {
268    type Error = ProtocolError;
269
270    fn try_from(value: Value) -> Result<Self, Self::Error> {
271        let proof_type_int: Option<u8> = value
272            .get_optional_integer("type")
273            .map_err(ProtocolError::ValueError)?;
274        if let Some(proof_type_int) = proof_type_int {
275            let proof_type = AssetLockProofType::try_from(proof_type_int)?;
276
277            match proof_type {
278                AssetLockProofType::Instant => Ok(Self::Instant(value.try_into()?)),
279                AssetLockProofType::Chain => Ok(Self::Chain(value.try_into()?)),
280            }
281        } else {
282            let map = value.as_map().ok_or(ProtocolError::DecodingError(
283                "error decoding asset lock proof".to_string(),
284            ))?;
285            let (key, asset_lock_value) = map.first().ok_or(ProtocolError::DecodingError(
286                "error decoding asset lock proof as it was empty".to_string(),
287            ))?;
288            match key.as_str().ok_or(ProtocolError::DecodingError(
289                "error decoding asset lock proof".to_string(),
290            ))? {
291                "Instant" => Ok(Self::Instant(asset_lock_value.clone().try_into()?)),
292                "Chain" => Ok(Self::Chain(asset_lock_value.clone().try_into()?)),
293                _ => Err(ProtocolError::DecodingError(
294                    "error decoding asset lock proof".to_string(),
295                )),
296            }
297        }
298    }
299}
300
301impl TryInto<Value> for AssetLockProof {
302    type Error = ProtocolError;
303
304    fn try_into(self) -> Result<Value, Self::Error> {
305        match self {
306            AssetLockProof::Instant(instant_proof) => {
307                platform_value::to_value(instant_proof).map_err(ProtocolError::ValueError)
308            }
309            AssetLockProof::Chain(chain_proof) => {
310                platform_value::to_value(chain_proof).map_err(ProtocolError::ValueError)
311            }
312        }
313    }
314}
315
316impl TryInto<Value> for &AssetLockProof {
317    type Error = ProtocolError;
318
319    fn try_into(self) -> Result<Value, Self::Error> {
320        match self {
321            AssetLockProof::Instant(instant_proof) => {
322                platform_value::to_value(instant_proof).map_err(ProtocolError::ValueError)
323            }
324            AssetLockProof::Chain(chain_proof) => {
325                platform_value::to_value(chain_proof).map_err(ProtocolError::ValueError)
326            }
327        }
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334    use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
335
336    mod asset_lock_proof_type_try_from {
337        use super::*;
338
339        #[test]
340        fn u8_instant_type() {
341            let proof_type = AssetLockProofType::try_from(0u8).expect("should parse type 0");
342            assert!(matches!(proof_type, AssetLockProofType::Instant));
343        }
344
345        #[test]
346        fn u8_chain_type() {
347            let proof_type = AssetLockProofType::try_from(1u8).expect("should parse type 1");
348            assert!(matches!(proof_type, AssetLockProofType::Chain));
349        }
350
351        #[test]
352        fn u8_invalid_type() {
353            let result = AssetLockProofType::try_from(2u8);
354            assert!(result.is_err());
355        }
356
357        #[test]
358        fn u8_max_invalid_type() {
359            let result = AssetLockProofType::try_from(255u8);
360            assert!(result.is_err());
361        }
362
363        #[test]
364        fn u64_instant_type() {
365            let proof_type = AssetLockProofType::try_from(0u64).expect("should parse type 0");
366            assert!(matches!(proof_type, AssetLockProofType::Instant));
367        }
368
369        #[test]
370        fn u64_chain_type() {
371            let proof_type = AssetLockProofType::try_from(1u64).expect("should parse type 1");
372            assert!(matches!(proof_type, AssetLockProofType::Chain));
373        }
374
375        #[test]
376        fn u64_invalid_type() {
377            let result = AssetLockProofType::try_from(2u64);
378            assert!(result.is_err());
379        }
380
381        #[test]
382        fn u64_large_invalid_type() {
383            let result = AssetLockProofType::try_from(u64::MAX);
384            assert!(result.is_err());
385        }
386    }
387
388    mod chain_asset_lock_proof {
389        use super::*;
390
391        fn make_chain_proof() -> ChainAssetLockProof {
392            ChainAssetLockProof::new(100, [0xAB; 36])
393        }
394
395        #[test]
396        fn chain_proof_construction() {
397            let proof = ChainAssetLockProof::new(42, [0x01; 36]);
398            assert_eq!(proof.core_chain_locked_height, 42);
399        }
400
401        #[test]
402        fn chain_proof_create_identifier_deterministic() {
403            let proof = make_chain_proof();
404            let id1 = proof.create_identifier();
405            let id2 = proof.create_identifier();
406            assert_eq!(id1, id2);
407        }
408
409        #[test]
410        fn different_outpoints_produce_different_identifiers() {
411            let proof_a = ChainAssetLockProof::new(100, [0xAA; 36]);
412            let proof_b = ChainAssetLockProof::new(100, [0xBB; 36]);
413            assert_ne!(proof_a.create_identifier(), proof_b.create_identifier());
414        }
415
416        #[test]
417        fn chain_proof_equality() {
418            let a = ChainAssetLockProof::new(10, [0x01; 36]);
419            let b = ChainAssetLockProof::new(10, [0x01; 36]);
420            assert_eq!(a, b);
421        }
422
423        #[test]
424        fn chain_proof_inequality_height() {
425            let a = ChainAssetLockProof::new(10, [0x01; 36]);
426            let b = ChainAssetLockProof::new(20, [0x01; 36]);
427            assert_ne!(a, b);
428        }
429    }
430
431    mod asset_lock_proof_methods {
432        use super::*;
433
434        fn make_chain_lock_proof() -> AssetLockProof {
435            let chain_proof = ChainAssetLockProof::new(50, [0xCC; 36]);
436            AssetLockProof::Chain(chain_proof)
437        }
438
439        #[test]
440        fn default_is_instant() {
441            let proof = AssetLockProof::default();
442            assert!(matches!(proof, AssetLockProof::Instant(_)));
443        }
444
445        #[test]
446        fn as_ref_returns_self() {
447            let proof = make_chain_lock_proof();
448            let reference: &AssetLockProof = proof.as_ref();
449            assert_eq!(&proof, reference);
450        }
451
452        #[test]
453        fn chain_proof_output_index() {
454            let mut out_point_bytes = [0u8; 36];
455            // Set vout (last 4 bytes in little-endian) to 3
456            out_point_bytes[32] = 3;
457            let chain_proof = ChainAssetLockProof::new(50, out_point_bytes);
458            let proof = AssetLockProof::Chain(chain_proof);
459            assert_eq!(proof.output_index(), 3);
460        }
461
462        #[test]
463        fn chain_proof_out_point_is_some() {
464            let proof = make_chain_lock_proof();
465            assert!(proof.out_point().is_some());
466        }
467
468        #[test]
469        fn chain_proof_transaction_is_none() {
470            let proof = make_chain_lock_proof();
471            assert!(proof.transaction().is_none());
472        }
473
474        #[test]
475        fn chain_proof_to_raw_object() {
476            let proof = make_chain_lock_proof();
477            let result = proof.to_raw_object();
478            assert!(result.is_ok());
479        }
480
481        #[test]
482        fn chain_proof_create_identifier() {
483            let proof = make_chain_lock_proof();
484            let id = proof.create_identifier();
485            assert!(id.is_ok());
486        }
487    }
488
489    mod try_from_value {
490        use super::*;
491
492        #[test]
493        fn chain_proof_value_round_trip() {
494            let chain_proof = ChainAssetLockProof::new(100, [0x42; 36]);
495            let proof = AssetLockProof::Chain(chain_proof);
496
497            // Convert to Value
498            let value: Value = (&proof).try_into().expect("should convert to Value");
499
500            // Now try to read type from value
501            let _type_from_value = AssetLockProof::type_from_raw_value(&value);
502            // Chain proofs serialized via serde may or may not have "type" field depending
503            // on the serialization format. The untagged format may not include it.
504            // What matters is that the conversion itself works.
505
506            // Convert from Value back - this tests the TryFrom<Value> path
507            // with the untagged serde format
508            let raw_value = proof.to_raw_object().expect("should convert to raw object");
509            assert!(!raw_value.is_null());
510        }
511
512        #[test]
513        fn type_from_raw_value_returns_none_for_missing_type() {
514            let value = Value::Map(vec![]);
515            let result = AssetLockProof::type_from_raw_value(&value);
516            assert!(result.is_none());
517        }
518
519        #[test]
520        fn try_from_empty_map_fails() {
521            let value = Value::Map(vec![]);
522            let result = AssetLockProof::try_from(&value);
523            assert!(result.is_err());
524        }
525
526        #[test]
527        fn try_from_value_with_unknown_key_fails() {
528            let value = Value::Map(vec![(
529                Value::Text("Unknown".to_string()),
530                Value::Map(vec![]),
531            )]);
532            let result = AssetLockProof::try_from(&value);
533            assert!(result.is_err());
534        }
535    }
536
537    mod try_into_value {
538        use super::*;
539
540        #[test]
541        fn chain_proof_try_into_value() {
542            let chain_proof = ChainAssetLockProof::new(200, [0xDD; 36]);
543            let proof = AssetLockProof::Chain(chain_proof);
544
545            let value: Result<Value, ProtocolError> = proof.try_into();
546            assert!(value.is_ok());
547        }
548
549        #[test]
550        fn chain_proof_ref_try_into_value() {
551            let chain_proof = ChainAssetLockProof::new(200, [0xDD; 36]);
552            let proof = AssetLockProof::Chain(chain_proof);
553
554            let value: Result<Value, ProtocolError> = (&proof).try_into();
555            assert!(value.is_ok());
556        }
557    }
558}