dpp/identity/state_transition/asset_lock_proof/
mod.rs1use 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#[derive(Clone, Debug, Eq, PartialEq, Serialize, Encode, Decode)]
38#[serde(tag = "$type", rename_all = "camelCase")]
39#[allow(clippy::large_enum_variant)]
40pub enum AssetLockProof {
41 Instant(#[bincode(with_serde)] InstantAssetLockProof),
42 Chain(#[bincode(with_serde)] ChainAssetLockProof),
43}
44
45#[derive(Deserialize)]
49#[serde(tag = "$type", rename_all = "camelCase")]
50enum RawAssetLockProof {
51 Instant(RawInstantLockProof),
52 Chain(ChainAssetLockProof),
53}
54
55impl TryFrom<RawAssetLockProof> for AssetLockProof {
56 type Error = ProtocolError;
57
58 fn try_from(value: RawAssetLockProof) -> Result<Self, Self::Error> {
59 match value {
60 RawAssetLockProof::Instant(raw_instant_lock) => {
61 let instant_lock = raw_instant_lock.try_into()?;
62
63 Ok(AssetLockProof::Instant(instant_lock))
64 }
65 RawAssetLockProof::Chain(chain) => Ok(AssetLockProof::Chain(chain)),
66 }
67 }
68}
69
70impl<'de> Deserialize<'de> for AssetLockProof {
71 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
72 where
73 D: Deserializer<'de>,
74 {
75 let raw = RawAssetLockProof::deserialize(deserializer)?;
76 raw.try_into().map_err(|e: ProtocolError| {
77 D::Error::custom(format!(
78 "expected to be able to deserialize asset lock proof: {}",
79 e
80 ))
81 })
82 }
83}
84
85impl Default for AssetLockProof {
86 fn default() -> Self {
87 Self::Instant(InstantAssetLockProof::default())
88 }
89}
90
91#[cfg(feature = "json-conversion")]
92impl crate::serialization::JsonConvertible for AssetLockProof {}
93
94#[cfg(feature = "value-conversion")]
95impl crate::serialization::ValueConvertible for AssetLockProof {}
96
97impl AsRef<AssetLockProof> for AssetLockProof {
98 fn as_ref(&self) -> &AssetLockProof {
99 self
100 }
101}
102
103#[cfg(all(
104 test,
105 feature = "json-conversion",
106 feature = "value-conversion",
107 feature = "serde-conversion"
108))]
109mod json_convertible_tests {
110 use super::*;
111 use dashcore::OutPoint;
112 use platform_value::platform_value;
113 use serde_json::json;
114 use std::str::FromStr;
115
116 fn fixture() -> AssetLockProof {
121 let out_point = OutPoint::from_str(
122 "0000000000000000000000000000000000000000000000000000000000000001:1",
123 )
124 .expect("outpoint");
125 AssetLockProof::Chain(ChainAssetLockProof {
126 core_chain_locked_height: 12_345,
127 out_point,
128 })
129 }
130
131 #[test]
132 fn json_round_trip_with_full_wire_shape() {
133 use crate::serialization::JsonConvertible;
134 let original = fixture();
135 let json = original.to_json().expect("to_json");
136 assert_eq!(
146 json,
147 json!({
148 "$type": "chain",
149 "coreChainLockedHeight": 12_345,
150 "outPoint": "0000000000000000000000000000000000000000000000000000000000000001:1",
151 })
152 );
153 let recovered = AssetLockProof::from_json(json).expect("from_json");
154 assert_eq!(original, recovered);
155 }
156
157 #[test]
158 fn value_round_trip_with_full_wire_shape() {
159 use crate::serialization::ValueConvertible;
160 let original = fixture();
161 let value = original.to_object().expect("to_object");
162 let mut txid_bytes = [0u8; 32];
167 txid_bytes[0] = 1;
168 assert_eq!(
169 value,
170 platform_value!({
171 "$type": "chain",
172 "coreChainLockedHeight": 12_345u32,
173 "outPoint": {
174 "txid": platform_value::Value::Bytes32(txid_bytes),
175 "vout": 1u32,
176 },
177 })
178 );
179 let recovered = AssetLockProof::from_object(value).expect("from_object");
180 assert_eq!(original, recovered);
181 }
182}
183pub enum AssetLockProofType {
184 Instant = 0,
185 Chain = 1,
186}
187
188impl TryFrom<u8> for AssetLockProofType {
189 type Error = SerdeParsingError;
190
191 fn try_from(value: u8) -> Result<Self, Self::Error> {
192 match value {
193 0 => Ok(Self::Instant),
194 1 => Ok(Self::Chain),
195 _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
196 }
197 }
198}
199
200impl TryFrom<u64> for AssetLockProofType {
201 type Error = SerdeParsingError;
202
203 fn try_from(value: u64) -> Result<Self, Self::Error> {
204 match value {
205 0 => Ok(Self::Instant),
206 1 => Ok(Self::Chain),
207 _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
208 }
209 }
210}
211
212impl AssetLockProof {
214 pub fn type_from_raw_value(value: &Value) -> Option<AssetLockProofType> {
215 let proof_type_res = value.get_integer::<u8>("type");
216
217 match proof_type_res {
218 Ok(proof_type_int) => {
219 let proof_type = AssetLockProofType::try_from(proof_type_int);
220 proof_type.ok()
221 }
222 Err(_) => None,
223 }
224 }
225
226 pub fn create_identifier(&self) -> Result<Identifier, ProtocolError> {
227 match self {
228 AssetLockProof::Instant(instant_proof) => instant_proof.create_identifier(),
229 AssetLockProof::Chain(chain_proof) => Ok(chain_proof.create_identifier()),
230 }
231 }
232
233 pub fn output_index(&self) -> u32 {
234 match self {
235 AssetLockProof::Instant(proof) => proof.output_index(),
236 AssetLockProof::Chain(proof) => proof.out_point.vout,
237 }
238 }
239
240 pub fn out_point(&self) -> Option<OutPoint> {
241 match self {
242 AssetLockProof::Instant(proof) => proof.out_point(),
243 AssetLockProof::Chain(proof) => Some(proof.out_point),
244 }
245 }
246
247 pub fn transaction(&self) -> Option<&Transaction> {
248 match self {
249 AssetLockProof::Instant(is_lock) => Some(is_lock.transaction()),
250 AssetLockProof::Chain(_chain_lock) => None,
251 }
252 }
253
254 #[cfg(feature = "validation")]
256 pub fn validate_structure(
257 &self,
258 platform_version: &PlatformVersion,
259 ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
260 match self {
261 AssetLockProof::Instant(proof) => proof.validate_structure(platform_version),
262 AssetLockProof::Chain(_) => Ok(SimpleConsensusValidationResult::default()),
263 }
264 }
265}
266
267impl TryFrom<&Value> for AssetLockProof {
278 type Error = ProtocolError;
279
280 fn try_from(value: &Value) -> Result<Self, Self::Error> {
281 platform_value::from_value(value.clone()).map_err(ProtocolError::ValueError)
282 }
283}
284
285impl TryFrom<Value> for AssetLockProof {
286 type Error = ProtocolError;
287
288 fn try_from(value: Value) -> Result<Self, Self::Error> {
289 platform_value::from_value(value).map_err(ProtocolError::ValueError)
290 }
291}
292
293#[cfg(test)]
303mod tests {
304 use super::*;
305 use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
306 use dashcore::{OutPoint, Txid};
307 use std::str::FromStr;
308
309 #[test]
314 fn chain_variant_serializes_with_internal_tag() {
315 let txid =
316 Txid::from_str("e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d")
317 .unwrap();
318 let proof = AssetLockProof::Chain(ChainAssetLockProof {
319 core_chain_locked_height: 11,
320 out_point: OutPoint { txid, vout: 1 },
321 });
322
323 let json = serde_json::to_value(&proof).expect("serialize");
324
325 assert_eq!(json["$type"], "chain");
326 assert_eq!(json["coreChainLockedHeight"], 11);
327 assert!(
328 json.get("data").is_none(),
329 "should not have a `data` wrapper, got: {}",
330 json
331 );
332
333 let restored: AssetLockProof = serde_json::from_value(json).expect("deserialize");
335 assert_eq!(proof, restored);
336 }
337
338 mod asset_lock_proof_type_try_from {
339 use super::*;
340
341 #[test]
342 fn u8_instant_type() {
343 let proof_type = AssetLockProofType::try_from(0u8).expect("should parse type 0");
344 assert!(matches!(proof_type, AssetLockProofType::Instant));
345 }
346
347 #[test]
348 fn u8_chain_type() {
349 let proof_type = AssetLockProofType::try_from(1u8).expect("should parse type 1");
350 assert!(matches!(proof_type, AssetLockProofType::Chain));
351 }
352
353 #[test]
354 fn u8_invalid_type() {
355 let result = AssetLockProofType::try_from(2u8);
356 assert!(result.is_err());
357 }
358
359 #[test]
360 fn u8_max_invalid_type() {
361 let result = AssetLockProofType::try_from(255u8);
362 assert!(result.is_err());
363 }
364
365 #[test]
366 fn u64_instant_type() {
367 let proof_type = AssetLockProofType::try_from(0u64).expect("should parse type 0");
368 assert!(matches!(proof_type, AssetLockProofType::Instant));
369 }
370
371 #[test]
372 fn u64_chain_type() {
373 let proof_type = AssetLockProofType::try_from(1u64).expect("should parse type 1");
374 assert!(matches!(proof_type, AssetLockProofType::Chain));
375 }
376
377 #[test]
378 fn u64_invalid_type() {
379 let result = AssetLockProofType::try_from(2u64);
380 assert!(result.is_err());
381 }
382
383 #[test]
384 fn u64_large_invalid_type() {
385 let result = AssetLockProofType::try_from(u64::MAX);
386 assert!(result.is_err());
387 }
388 }
389
390 mod chain_asset_lock_proof {
391 use super::*;
392
393 fn make_chain_proof() -> ChainAssetLockProof {
394 ChainAssetLockProof::new(100, [0xAB; 36])
395 }
396
397 #[test]
398 fn chain_proof_construction() {
399 let proof = ChainAssetLockProof::new(42, [0x01; 36]);
400 assert_eq!(proof.core_chain_locked_height, 42);
401 }
402
403 #[test]
404 fn chain_proof_create_identifier_deterministic() {
405 let proof = make_chain_proof();
406 let id1 = proof.create_identifier();
407 let id2 = proof.create_identifier();
408 assert_eq!(id1, id2);
409 }
410
411 #[test]
412 fn different_outpoints_produce_different_identifiers() {
413 let proof_a = ChainAssetLockProof::new(100, [0xAA; 36]);
414 let proof_b = ChainAssetLockProof::new(100, [0xBB; 36]);
415 assert_ne!(proof_a.create_identifier(), proof_b.create_identifier());
416 }
417
418 #[test]
419 fn chain_proof_equality() {
420 let a = ChainAssetLockProof::new(10, [0x01; 36]);
421 let b = ChainAssetLockProof::new(10, [0x01; 36]);
422 assert_eq!(a, b);
423 }
424
425 #[test]
426 fn chain_proof_inequality_height() {
427 let a = ChainAssetLockProof::new(10, [0x01; 36]);
428 let b = ChainAssetLockProof::new(20, [0x01; 36]);
429 assert_ne!(a, b);
430 }
431 }
432
433 mod asset_lock_proof_methods {
434 use super::*;
435
436 fn make_chain_lock_proof() -> AssetLockProof {
437 let chain_proof = ChainAssetLockProof::new(50, [0xCC; 36]);
438 AssetLockProof::Chain(chain_proof)
439 }
440
441 #[test]
442 fn default_is_instant() {
443 let proof = AssetLockProof::default();
444 assert!(matches!(proof, AssetLockProof::Instant(_)));
445 }
446
447 #[test]
448 fn as_ref_returns_self() {
449 let proof = make_chain_lock_proof();
450 let reference: &AssetLockProof = proof.as_ref();
451 assert_eq!(&proof, reference);
452 }
453
454 #[test]
455 fn chain_proof_output_index() {
456 let mut out_point_bytes = [0u8; 36];
457 out_point_bytes[32] = 3;
459 let chain_proof = ChainAssetLockProof::new(50, out_point_bytes);
460 let proof = AssetLockProof::Chain(chain_proof);
461 assert_eq!(proof.output_index(), 3);
462 }
463
464 #[test]
465 fn chain_proof_out_point_is_some() {
466 let proof = make_chain_lock_proof();
467 assert!(proof.out_point().is_some());
468 }
469
470 #[test]
471 fn chain_proof_transaction_is_none() {
472 let proof = make_chain_lock_proof();
473 assert!(proof.transaction().is_none());
474 }
475
476 #[test]
477 fn chain_proof_to_object_canonical() {
478 use crate::serialization::ValueConvertible;
483 let proof = make_chain_lock_proof();
484 let result = proof.to_object();
485 assert!(result.is_ok());
486 }
487
488 #[test]
489 fn chain_proof_create_identifier() {
490 let proof = make_chain_lock_proof();
491 let id = proof.create_identifier();
492 assert!(id.is_ok());
493 }
494 }
495
496 mod try_from_value {
497 use super::*;
498
499 #[test]
500 fn chain_proof_value_round_trip() {
501 use crate::serialization::ValueConvertible;
506 let chain_proof = ChainAssetLockProof::new(100, [0x42; 36]);
507 let proof = AssetLockProof::Chain(chain_proof);
508
509 let value = proof.to_object().expect("to_object");
510 let map = value.to_map_ref().expect("map");
515 assert_eq!(
516 map.iter()
517 .find_map(|(k, v)| (k.as_text() == Some("$type")).then(|| v.as_text())),
518 Some(Some("chain"))
519 );
520
521 let recovered =
522 AssetLockProof::from_object(value).expect("from_object should round-trip");
523 assert_eq!(proof, recovered);
524 }
525
526 #[test]
527 fn type_from_raw_value_returns_none_for_missing_type() {
528 let value = Value::Map(vec![]);
529 let result = AssetLockProof::type_from_raw_value(&value);
530 assert!(result.is_none());
531 }
532
533 #[test]
534 fn try_from_empty_map_fails() {
535 let value = Value::Map(vec![]);
536 let result = AssetLockProof::try_from(&value);
537 assert!(result.is_err());
538 }
539
540 #[test]
541 fn try_from_value_with_unknown_key_fails() {
542 let value = Value::Map(vec![(
543 Value::Text("Unknown".to_string()),
544 Value::Map(vec![]),
545 )]);
546 let result = AssetLockProof::try_from(&value);
547 assert!(result.is_err());
548 }
549 }
550
551 }