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_serialization::PLATFORM_BINCODE_CONFIG;
11use platform_value::Value;
12#[cfg(feature = "validation")]
13use platform_version::version::PlatformVersion;
14use serde::de::Error;
15
16use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
17use crate::prelude::Identifier;
18#[cfg(feature = "json-conversion")]
19use crate::serialization::JsonConvertible;
20#[cfg(feature = "value-conversion")]
21use crate::serialization::ValueConvertible;
22#[cfg(feature = "validation")]
23use crate::validation::SimpleConsensusValidationResult;
24use crate::{ProtocolError, SerdeParsingError};
25
26pub mod chain;
27pub mod instant;
28pub mod validate_asset_lock_transaction_structure;
29
30#[derive(Clone, Debug, Eq, PartialEq, Serialize, Encode, Decode, DecodeUntrusted)]
43#[serde(tag = "$type", rename_all = "camelCase")]
44#[allow(clippy::large_enum_variant)]
45pub enum AssetLockProof {
46 Instant(#[bincode(with_serde)] InstantAssetLockProof),
47 Chain(#[bincode(with_serde)] ChainAssetLockProof),
48}
49
50#[derive(Deserialize)]
54#[serde(tag = "$type", rename_all = "camelCase")]
55enum RawAssetLockProof {
56 Instant(RawInstantLockProof),
57 Chain(ChainAssetLockProof),
58}
59
60impl TryFrom<RawAssetLockProof> for AssetLockProof {
61 type Error = ProtocolError;
62
63 fn try_from(value: RawAssetLockProof) -> Result<Self, Self::Error> {
64 match value {
65 RawAssetLockProof::Instant(raw_instant_lock) => {
66 let instant_lock = raw_instant_lock.try_into()?;
67
68 Ok(AssetLockProof::Instant(instant_lock))
69 }
70 RawAssetLockProof::Chain(chain) => Ok(AssetLockProof::Chain(chain)),
71 }
72 }
73}
74
75impl<'de> Deserialize<'de> for AssetLockProof {
76 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77 where
78 D: Deserializer<'de>,
79 {
80 let raw = RawAssetLockProof::deserialize(deserializer)?;
81 raw.try_into().map_err(|e: ProtocolError| {
82 D::Error::custom(format!(
83 "expected to be able to deserialize asset lock proof: {}",
84 e
85 ))
86 })
87 }
88}
89
90impl Default for AssetLockProof {
91 fn default() -> Self {
92 Self::Instant(InstantAssetLockProof::default())
93 }
94}
95
96#[cfg(feature = "json-conversion")]
97impl JsonConvertible for AssetLockProof {}
98
99#[cfg(feature = "value-conversion")]
100impl ValueConvertible for AssetLockProof {}
101
102impl AsRef<AssetLockProof> for AssetLockProof {
103 fn as_ref(&self) -> &AssetLockProof {
104 self
105 }
106}
107
108#[cfg(all(
109 test,
110 feature = "json-conversion",
111 feature = "value-conversion",
112 feature = "serde-conversion"
113))]
114mod json_convertible_tests {
115 use super::*;
116 use dashcore::OutPoint;
117 use platform_value::platform_value;
118 use serde_json::json;
119 use std::str::FromStr;
120
121 fn fixture() -> AssetLockProof {
126 let out_point = OutPoint::from_str(
127 "0000000000000000000000000000000000000000000000000000000000000001:1",
128 )
129 .expect("outpoint");
130 AssetLockProof::Chain(ChainAssetLockProof {
131 core_chain_locked_height: 12_345,
132 out_point,
133 })
134 }
135
136 #[test]
137 fn json_round_trip_with_full_wire_shape() {
138 use crate::serialization::JsonConvertible;
139 let original = fixture();
140 let json = original.to_json().expect("to_json");
141 assert_eq!(
151 json,
152 json!({
153 "$type": "chain",
154 "coreChainLockedHeight": 12_345,
155 "outPoint": "0000000000000000000000000000000000000000000000000000000000000001:1",
156 })
157 );
158 let recovered = AssetLockProof::from_json(json).expect("from_json");
159 assert_eq!(original, recovered);
160 }
161
162 #[test]
163 fn value_round_trip_with_full_wire_shape() {
164 use crate::serialization::ValueConvertible;
165 let original = fixture();
166 let value = original.to_object().expect("to_object");
167 let mut txid_bytes = [0u8; 32];
172 txid_bytes[0] = 1;
173 assert_eq!(
174 value,
175 platform_value!({
176 "$type": "chain",
177 "coreChainLockedHeight": 12_345u32,
178 "outPoint": {
179 "txid": platform_value::Value::Bytes32(txid_bytes),
180 "vout": 1u32,
181 },
182 })
183 );
184 let recovered = AssetLockProof::from_object(value).expect("from_object");
185 assert_eq!(original, recovered);
186 }
187}
188pub enum AssetLockProofType {
189 Instant = 0,
190 Chain = 1,
191}
192
193impl TryFrom<u8> for AssetLockProofType {
194 type Error = SerdeParsingError;
195
196 fn try_from(value: u8) -> Result<Self, Self::Error> {
197 match value {
198 0 => Ok(Self::Instant),
199 1 => Ok(Self::Chain),
200 _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
201 }
202 }
203}
204
205impl TryFrom<u64> for AssetLockProofType {
206 type Error = SerdeParsingError;
207
208 fn try_from(value: u64) -> Result<Self, Self::Error> {
209 match value {
210 0 => Ok(Self::Instant),
211 1 => Ok(Self::Chain),
212 _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
213 }
214 }
215}
216
217impl AssetLockProof {
219 pub fn serialized_len_in_state_transition(&self) -> Result<usize, ProtocolError> {
226 let mut writer = bincode::enc::write::SizeWriter::default();
227 bincode::encode_into_writer(self, &mut writer, PLATFORM_BINCODE_CONFIG)
228 .map_err(|e| ProtocolError::EncodingError(e.to_string()))?;
229 Ok(writer.bytes_written)
230 }
231
232 pub fn type_from_raw_value(value: &Value) -> Option<AssetLockProofType> {
233 let proof_type_res = value.get_integer::<u8>("type");
234
235 match proof_type_res {
236 Ok(proof_type_int) => {
237 let proof_type = AssetLockProofType::try_from(proof_type_int);
238 proof_type.ok()
239 }
240 Err(_) => None,
241 }
242 }
243
244 pub fn create_identifier(&self) -> Result<Identifier, ProtocolError> {
245 match self {
246 AssetLockProof::Instant(instant_proof) => instant_proof.create_identifier(),
247 AssetLockProof::Chain(chain_proof) => Ok(chain_proof.create_identifier()),
248 }
249 }
250
251 pub fn output_index(&self) -> u32 {
252 match self {
253 AssetLockProof::Instant(proof) => proof.output_index(),
254 AssetLockProof::Chain(proof) => proof.out_point.vout,
255 }
256 }
257
258 pub fn out_point(&self) -> Option<OutPoint> {
259 match self {
260 AssetLockProof::Instant(proof) => proof.out_point(),
261 AssetLockProof::Chain(proof) => Some(proof.out_point),
262 }
263 }
264
265 pub fn transaction(&self) -> Option<&Transaction> {
266 match self {
267 AssetLockProof::Instant(is_lock) => Some(is_lock.transaction()),
268 AssetLockProof::Chain(_chain_lock) => None,
269 }
270 }
271
272 #[cfg(feature = "validation")]
274 pub fn validate_structure(
275 &self,
276 platform_version: &PlatformVersion,
277 ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
278 match self {
279 AssetLockProof::Instant(proof) => proof.validate_structure(platform_version),
280 AssetLockProof::Chain(_) => Ok(SimpleConsensusValidationResult::default()),
281 }
282 }
283}
284
285impl TryFrom<&Value> for AssetLockProof {
296 type Error = ProtocolError;
297
298 fn try_from(value: &Value) -> Result<Self, Self::Error> {
299 platform_value::from_value(value.clone()).map_err(ProtocolError::ValueError)
300 }
301}
302
303impl TryFrom<Value> for AssetLockProof {
304 type Error = ProtocolError;
305
306 fn try_from(value: Value) -> Result<Self, Self::Error> {
307 platform_value::from_value(value).map_err(ProtocolError::ValueError)
308 }
309}
310
311#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
324 use dashcore::{OutPoint, Txid};
325 use std::str::FromStr;
326
327 #[cfg(feature = "state-transitions")]
332 #[test]
333 fn should_measure_the_proof_as_a_serialized_transition_carries_it() {
334 use crate::serialization::PlatformSerializable;
335 use crate::state_transition::identity_topup_transition::v0::IdentityTopUpTransitionV0;
336 use crate::state_transition::identity_topup_transition::IdentityTopUpTransition;
337 use crate::state_transition::StateTransition;
338 use dashcore::{InstantLock, ScriptBuf, TxIn};
339
340 let input: Vec<TxIn> = (0..5u8)
341 .map(|i| TxIn {
342 previous_output: OutPoint::from([i; 36]),
343 script_sig: ScriptBuf::from_bytes(vec![0u8; 107]),
344 sequence: u32::MAX,
345 witness: Default::default(),
346 })
347 .collect();
348 let instant = AssetLockProof::Instant(InstantAssetLockProof::new(
349 InstantLock {
350 inputs: input.iter().map(|i| i.previous_output).collect(),
351 ..InstantLock::default()
352 },
353 Transaction {
354 version: 3,
355 lock_time: 0,
356 input,
357 output: Vec::new(),
358 special_transaction_payload: None,
359 },
360 0,
361 ));
362 let chain = AssetLockProof::Chain(ChainAssetLockProof {
363 core_chain_locked_height: u32::MAX,
364 out_point: OutPoint::from([7u8; 36]),
365 });
366 let transition_len = |proof: &AssetLockProof| {
367 StateTransition::IdentityTopUp(IdentityTopUpTransition::V0(IdentityTopUpTransitionV0 {
368 asset_lock_proof: proof.clone(),
369 identity_id: Identifier::default(),
370 user_fee_increase: 0,
371 signature: Default::default(),
372 }))
373 .serialize_to_bytes()
374 .expect("serialize")
375 .len()
376 };
377
378 let instant_len = instant.serialized_len_in_state_transition().unwrap();
379 let chain_len = chain.serialized_len_in_state_transition().unwrap();
380 assert!(instant_len > chain_len);
381 assert_eq!(
382 transition_len(&instant) - transition_len(&chain),
383 instant_len - chain_len
384 );
385 }
386
387 #[test]
392 fn chain_variant_serializes_with_internal_tag() {
393 let txid =
394 Txid::from_str("e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d")
395 .unwrap();
396 let proof = AssetLockProof::Chain(ChainAssetLockProof {
397 core_chain_locked_height: 11,
398 out_point: OutPoint { txid, vout: 1 },
399 });
400
401 let json = serde_json::to_value(&proof).expect("serialize");
402
403 assert_eq!(json["$type"], "chain");
404 assert_eq!(json["coreChainLockedHeight"], 11);
405 assert!(
406 json.get("data").is_none(),
407 "should not have a `data` wrapper, got: {}",
408 json
409 );
410
411 let restored: AssetLockProof = serde_json::from_value(json).expect("deserialize");
413 assert_eq!(proof, restored);
414 }
415
416 mod asset_lock_proof_type_try_from {
417 use super::*;
418
419 #[test]
420 fn u8_instant_type() {
421 let proof_type = AssetLockProofType::try_from(0u8).expect("should parse type 0");
422 assert!(matches!(proof_type, AssetLockProofType::Instant));
423 }
424
425 #[test]
426 fn u8_chain_type() {
427 let proof_type = AssetLockProofType::try_from(1u8).expect("should parse type 1");
428 assert!(matches!(proof_type, AssetLockProofType::Chain));
429 }
430
431 #[test]
432 fn u8_invalid_type() {
433 let result = AssetLockProofType::try_from(2u8);
434 assert!(result.is_err());
435 }
436
437 #[test]
438 fn u8_max_invalid_type() {
439 let result = AssetLockProofType::try_from(255u8);
440 assert!(result.is_err());
441 }
442
443 #[test]
444 fn u64_instant_type() {
445 let proof_type = AssetLockProofType::try_from(0u64).expect("should parse type 0");
446 assert!(matches!(proof_type, AssetLockProofType::Instant));
447 }
448
449 #[test]
450 fn u64_chain_type() {
451 let proof_type = AssetLockProofType::try_from(1u64).expect("should parse type 1");
452 assert!(matches!(proof_type, AssetLockProofType::Chain));
453 }
454
455 #[test]
456 fn u64_invalid_type() {
457 let result = AssetLockProofType::try_from(2u64);
458 assert!(result.is_err());
459 }
460
461 #[test]
462 fn u64_large_invalid_type() {
463 let result = AssetLockProofType::try_from(u64::MAX);
464 assert!(result.is_err());
465 }
466 }
467
468 mod chain_asset_lock_proof {
469 use super::*;
470
471 fn make_chain_proof() -> ChainAssetLockProof {
472 ChainAssetLockProof::new(100, [0xAB; 36])
473 }
474
475 #[test]
476 fn chain_proof_construction() {
477 let proof = ChainAssetLockProof::new(42, [0x01; 36]);
478 assert_eq!(proof.core_chain_locked_height, 42);
479 }
480
481 #[test]
482 fn chain_proof_create_identifier_deterministic() {
483 let proof = make_chain_proof();
484 let id1 = proof.create_identifier();
485 let id2 = proof.create_identifier();
486 assert_eq!(id1, id2);
487 }
488
489 #[test]
490 fn different_outpoints_produce_different_identifiers() {
491 let proof_a = ChainAssetLockProof::new(100, [0xAA; 36]);
492 let proof_b = ChainAssetLockProof::new(100, [0xBB; 36]);
493 assert_ne!(proof_a.create_identifier(), proof_b.create_identifier());
494 }
495
496 #[test]
497 fn chain_proof_equality() {
498 let a = ChainAssetLockProof::new(10, [0x01; 36]);
499 let b = ChainAssetLockProof::new(10, [0x01; 36]);
500 assert_eq!(a, b);
501 }
502
503 #[test]
504 fn chain_proof_inequality_height() {
505 let a = ChainAssetLockProof::new(10, [0x01; 36]);
506 let b = ChainAssetLockProof::new(20, [0x01; 36]);
507 assert_ne!(a, b);
508 }
509 }
510
511 mod asset_lock_proof_methods {
512 use super::*;
513
514 fn make_chain_lock_proof() -> AssetLockProof {
515 let chain_proof = ChainAssetLockProof::new(50, [0xCC; 36]);
516 AssetLockProof::Chain(chain_proof)
517 }
518
519 #[test]
520 fn default_is_instant() {
521 let proof = AssetLockProof::default();
522 assert!(matches!(proof, AssetLockProof::Instant(_)));
523 }
524
525 #[test]
526 fn as_ref_returns_self() {
527 let proof = make_chain_lock_proof();
528 let reference: &AssetLockProof = proof.as_ref();
529 assert_eq!(&proof, reference);
530 }
531
532 #[test]
533 fn chain_proof_output_index() {
534 let mut out_point_bytes = [0u8; 36];
535 out_point_bytes[32] = 3;
537 let chain_proof = ChainAssetLockProof::new(50, out_point_bytes);
538 let proof = AssetLockProof::Chain(chain_proof);
539 assert_eq!(proof.output_index(), 3);
540 }
541
542 #[test]
543 fn chain_proof_out_point_is_some() {
544 let proof = make_chain_lock_proof();
545 assert!(proof.out_point().is_some());
546 }
547
548 #[test]
549 fn chain_proof_transaction_is_none() {
550 let proof = make_chain_lock_proof();
551 assert!(proof.transaction().is_none());
552 }
553
554 #[test]
555 fn chain_proof_to_object_canonical() {
556 use crate::serialization::ValueConvertible;
561 let proof = make_chain_lock_proof();
562 let result = proof.to_object();
563 assert!(result.is_ok());
564 }
565
566 #[test]
567 fn chain_proof_create_identifier() {
568 let proof = make_chain_lock_proof();
569 let id = proof.create_identifier();
570 assert!(id.is_ok());
571 }
572 }
573
574 mod try_from_value {
575 use super::*;
576
577 #[test]
578 fn chain_proof_value_round_trip() {
579 use crate::serialization::ValueConvertible;
584 let chain_proof = ChainAssetLockProof::new(100, [0x42; 36]);
585 let proof = AssetLockProof::Chain(chain_proof);
586
587 let value = proof.to_object().expect("to_object");
588 let map = value.to_map_ref().expect("map");
593 assert_eq!(
594 map.iter()
595 .find_map(|(k, v)| (k.as_text() == Some("$type")).then(|| v.as_text())),
596 Some(Some("chain"))
597 );
598
599 let recovered =
600 AssetLockProof::from_object(value).expect("from_object should round-trip");
601 assert_eq!(proof, recovered);
602 }
603
604 #[test]
605 fn type_from_raw_value_returns_none_for_missing_type() {
606 let value = Value::Map(vec![]);
607 let result = AssetLockProof::type_from_raw_value(&value);
608 assert!(result.is_none());
609 }
610
611 #[test]
612 fn try_from_empty_map_fails() {
613 let value = Value::Map(vec![]);
614 let result = AssetLockProof::try_from(&value);
615 assert!(result.is_err());
616 }
617
618 #[test]
619 fn try_from_value_with_unknown_key_fails() {
620 let value = Value::Map(vec![(
621 Value::Text("Unknown".to_string()),
622 Value::Map(vec![]),
623 )]);
624 let result = AssetLockProof::try_from(&value);
625 assert!(result.is_err());
626 }
627 }
628
629 }