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