1use crate::serialization::PlatformDeserializable;
2use crate::state_transition::StateTransition;
3use crate::ProtocolError;
4
5impl StateTransition {
6 pub fn deserialize_many(raw_state_transitions: &[Vec<u8>]) -> Result<Vec<Self>, ProtocolError> {
7 raw_state_transitions
8 .iter()
9 .map(|raw_state_transition| Self::deserialize_from_bytes(raw_state_transition))
10 .collect()
11 }
12}
13
14#[cfg(test)]
15mod tests {
16 use hex::ToHex;
17 use base64::engine::general_purpose::STANDARD;
18 use base64::Engine;
19 use platform_value::string_encoding::Encoding;
20 use platform_value::{Identifier, Value};
21 use crate::bls::native_bls::NativeBlsModule;
22 use crate::data_contract::accessors::v0::DataContractV0Getters;
23 use crate::identity::state_transition::AssetLockProved;
24 use crate::identity::accessors::IdentityGettersV0;
25 use crate::identity::core_script::CoreScript;
26 use crate::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
27 use crate::identity::Identity;
28 use crate::prelude::AssetLockProof;
29 use crate::serialization::PlatformMessageSignable;
30 use crate::serialization::Signable;
31 use crate::serialization::{PlatformDeserializable, PlatformSerializable};
32 use crate::state_transition::data_contract_create_transition::DataContractCreateTransition;
33 use crate::state_transition::data_contract_update_transition::{
34 DataContractUpdateTransition, DataContractUpdateTransitionV0,
35 };
36 use crate::state_transition::batch_transition::batched_transition::document_create_transition::{
37 DocumentCreateTransition, DocumentCreateTransitionV0,
38 };
39 use crate::state_transition::batch_transition::batched_transition::document_transition::{
40 DocumentTransition, DocumentTransitionV0Methods,
41 };
42 use crate::state_transition::batch_transition::batched_transition::document_transition_action_type::DocumentTransitionActionType;
43 use crate::state_transition::batch_transition::batched_transition::BatchedTransition;
44 use crate::state_transition::batch_transition::{
45 BatchTransition, BatchTransitionV1,
46 };
47 use crate::state_transition::batch_transition::document_base_transition::v0::DocumentBaseTransitionV0;
48 use crate::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
49 use crate::state_transition::identity_create_transition::accessors::IdentityCreateTransitionAccessorsV0;
50 use crate::state_transition::identity_create_transition::v0::IdentityCreateTransitionV0;
51 use crate::state_transition::identity_create_transition::IdentityCreateTransition;
52 use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0;
53 use crate::state_transition::identity_topup_transition::v0::IdentityTopUpTransitionV0;
54 use crate::state_transition::identity_update_transition::v0::IdentityUpdateTransitionV0;
55 use crate::state_transition::public_key_in_creation::accessors::IdentityPublicKeyInCreationV0Setters;
56 use crate::state_transition::StateTransition;
57 use crate::tests::fixtures::{
58 get_data_contract_fixture, get_batched_transitions_fixture,
59 get_extended_documents_fixture_with_owner_id_from_contract,
60 raw_instant_asset_lock_proof_fixture,
61 };
62 use crate::version::PlatformVersion;
63 use crate::withdrawal::Pooling;
64 use crate::ProtocolError;
65 use platform_version::version::LATEST_PLATFORM_VERSION;
66 use platform_version::TryIntoPlatformVersioned;
67 use rand::rngs::StdRng;
68 use rand::SeedableRng;
69 use std::collections::BTreeMap;
70
71 #[test]
72 fn should_build_identity_create_transition_from_mainnet_asset_lock_transaction() {
75 const EXPECTED_STATE_TRANSITION_HASH: &str =
76 "6CDCC15AC4EC68DBB414EE0DA692DFE363A996A0F285423BEFC3A29F87948A0D";
77 const RAW_TRANSACTION_BASE64: &str = "AwADAAAAAAAAACEDeLqSkwVyfHvYThgegiZUvPu0+dU4kyd3PJKigGLC1spBH+wrzjjA/ZGZdQmUzpQyOiC3GyP2eBp8ga9cNlnIOkptMzAtfXPA2daH3xTqt25JQ+fZ6UKB3ypzTK3fOXaAATgAAQAAAgAAIQPoVeBC6iyS0jFV0Dly5WV0SEl6uDciQqqi4EATeUJutEEfAd6+/HbUM4FLS6+lNc6AH8vaD9lViiYny4GPsl/AlBxdr0WjJxxU/B0cNVH8kRMo+W6a+1iSN+NZS7MTyzmTHwACAAEDAAAhA6S0TKbm1a/xyrYMG+Y2odspJ1roL1TcoK9h552yE1VCQSA+KpHiQ8lDBseXI/1ZCMxEvu0qopdjDojaQ4FzaZMgUGfPBeXSfMbQGksLMNseKRBLob/g0DHJWqZAxSDOuAwZAfwAIQxGIDIHY9cjWxS0tJupeJuKMZwzFKmLxkU3NmqFTcFscilVAABBH9R3vwbfA3q5XJG4m4z87OAA1uG8wup915wGGKAxdEObXPSqIvPBWrHlGTf/Uymanc2cDH1uKdsniJyoORwauPBIqlz61/Kf9HDnubX4GoHRYdnb4WzE+Tdh+L39a2dN2A==";
78 const EXPECTED_IDENTITY_ADDRESS: &str = "5tf2QotaJw8kRNpQEa8TXtRQ6FLxwUrY4Mtee2JF2nco";
79 const EXPECTED_CORE_TRANSACTION_HASH: &str =
80 "5529726cc14d856a363745c68ba914339c318a9b78a99bb4b4145b23d7630732";
81 let raw_transaction = STANDARD
82 .decode(RAW_TRANSACTION_BASE64)
83 .expect("base64 transaction should decode");
84 let state_transition = StateTransition::deserialize_from_bytes(&raw_transaction)
85 .expect("State transition deserializes correctly");
86
87 assert_eq!(
88 &state_transition
89 .transaction_id()
90 .expect("expected transaction id")
91 .encode_hex_upper::<String>(),
92 EXPECTED_STATE_TRANSITION_HASH
93 );
94
95 let StateTransition::IdentityCreate(identity_create_transition) = state_transition else {
96 panic!("expected identity create transition");
97 };
98
99 let asset_lock_proof = identity_create_transition.asset_lock_proof();
102 let AssetLockProof::Chain(chain_proof) = asset_lock_proof else {
103 panic!("expected chain asset lock proof for this mainnet transaction");
104 };
105
106 assert_eq!(
108 &chain_proof.out_point.txid.to_string().to_lowercase(),
109 EXPECTED_CORE_TRANSACTION_HASH
110 );
111
112 let identity_address = identity_create_transition
113 .identity_id()
114 .to_string(Encoding::Base58);
115
116 assert_eq!(identity_address, EXPECTED_IDENTITY_ADDRESS);
117 }
118
119 #[test]
120 #[cfg(feature = "random-identities")]
121 fn identity_create_transition_ser_de() {
122 let platform_version = LATEST_PLATFORM_VERSION;
123 let identity = Identity::random_identity(5, Some(5), platform_version)
124 .expect("expected a random identity");
125 let asset_lock_proof = raw_instant_asset_lock_proof_fixture(None, None);
126
127 let identity_create_transition = IdentityCreateTransition::V0(
128 IdentityCreateTransitionV0::try_from_identity(
129 &identity,
130 AssetLockProof::Instant(asset_lock_proof),
131 platform_version,
132 )
133 .expect("expected to make an identity create transition"),
134 );
135
136 let state_transition: StateTransition = identity_create_transition.into();
137 let bytes = state_transition
138 .serialize_to_bytes()
139 .expect("expected to serialize");
140 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
141 .expect("expected to deserialize state transition");
142 assert_eq!(state_transition, recovered_state_transition);
143 }
144
145 #[test]
146 #[cfg(feature = "random-identities")]
147 fn identity_topup_transition_ser_de() {
148 let platform_version = PlatformVersion::latest();
149 let identity = Identity::random_identity(5, Some(5), platform_version)
150 .expect("expected a random identity");
151 let asset_lock_proof = raw_instant_asset_lock_proof_fixture(None, None);
152
153 let identity_topup_transition = IdentityTopUpTransitionV0 {
154 asset_lock_proof: AssetLockProof::Instant(asset_lock_proof),
155 identity_id: identity.id(),
156 user_fee_increase: 0,
157 signature: [1u8; 65].to_vec().into(),
158 };
159 let state_transition: StateTransition = identity_topup_transition.into();
160 let bytes = state_transition
161 .serialize_to_bytes()
162 .expect("expected to serialize");
163 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
164 .expect("expected to deserialize state transition");
165 assert_eq!(state_transition, recovered_state_transition);
166 }
167
168 #[test]
169 #[cfg(feature = "random-identities")]
170 fn identity_update_transition_add_keys_ser_de() {
171 let mut rng = StdRng::seed_from_u64(5);
172 let (identity, mut keys): (Identity, BTreeMap<_, _>) =
173 Identity::random_identity_with_main_keys_with_private_key(
174 5,
175 &mut rng,
176 LATEST_PLATFORM_VERSION,
177 )
178 .expect("expected to get identity");
179 let bls = NativeBlsModule;
180 let add_public_keys_in_creation = identity
181 .public_keys()
182 .values()
183 .map(|public_key| public_key.into())
184 .collect();
185 let mut identity_update_transition = IdentityUpdateTransitionV0 {
186 signature: Default::default(),
187 signature_public_key_id: 0,
188 identity_id: identity.id(),
189 revision: 1,
190 nonce: 1,
191 add_public_keys: add_public_keys_in_creation,
192 disable_public_keys: vec![],
193 user_fee_increase: 0,
194 };
195
196 let key_signable_bytes = identity_update_transition
197 .signable_bytes()
198 .expect("expected to get signable bytes");
199
200 identity_update_transition
201 .add_public_keys
202 .iter_mut()
203 .zip(identity.public_keys().clone().into_values())
204 .try_for_each(|(public_key_with_witness, public_key)| {
205 if public_key.key_type().is_unique_key_type() {
206 let private_key = keys
207 .get(&public_key)
208 .expect("expected to have the private key");
209 let signature = key_signable_bytes
210 .as_slice()
211 .sign_by_private_key(private_key, public_key.key_type(), &bls)?
212 .into();
213 public_key_with_witness.set_signature(signature);
214 }
215
216 Ok::<(), ProtocolError>(())
217 })
218 .expect("expected to update keys");
219
220 let (public_key, private_key) = keys.pop_first().unwrap();
221
222 let mut state_transition: StateTransition = identity_update_transition.into();
223
224 state_transition
225 .sign_by_private_key(private_key.as_slice(), public_key.key_type(), &bls)
226 .expect("expected to sign IdentityUpdateTransition");
227 let bytes = state_transition
228 .serialize_to_bytes()
229 .expect("expected to serialize");
230 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
231 .expect("expected to deserialize state transition");
232 assert_eq!(state_transition, recovered_state_transition);
233 }
234
235 #[test]
236 #[cfg(feature = "state-transition-signing")]
237 fn identity_update_transition_disable_keys_ser_de() {
238 let mut rng = StdRng::seed_from_u64(5);
239 let (identity, mut keys): (Identity, BTreeMap<_, _>) =
240 Identity::random_identity_with_main_keys_with_private_key(
241 5,
242 &mut rng,
243 LATEST_PLATFORM_VERSION,
244 )
245 .expect("expected to get identity");
246 let bls = NativeBlsModule;
247 let add_public_keys_in_creation = identity
248 .public_keys()
249 .values()
250 .map(|public_key| public_key.into())
251 .collect();
252 let mut identity_update_transition = IdentityUpdateTransitionV0 {
253 signature: Default::default(),
254 signature_public_key_id: 0,
255 identity_id: identity.id(),
256 revision: 1,
257 nonce: 1,
258 add_public_keys: add_public_keys_in_creation,
259 disable_public_keys: vec![3, 4, 5],
260 user_fee_increase: 0,
261 };
262
263 let key_signable_bytes = identity_update_transition
264 .signable_bytes()
265 .expect("expected to get signable bytes");
266
267 identity_update_transition
268 .add_public_keys
269 .iter_mut()
270 .zip(identity.public_keys().clone().into_values())
271 .try_for_each(|(public_key_with_witness, public_key)| {
272 if public_key.key_type().is_unique_key_type() {
273 let private_key = keys
274 .get(&public_key)
275 .expect("expected to have the private key");
276 let signature = key_signable_bytes
277 .as_slice()
278 .sign_by_private_key(private_key, public_key.key_type(), &bls)?
279 .into();
280 public_key_with_witness.set_signature(signature);
281 }
282
283 Ok::<(), ProtocolError>(())
284 })
285 .expect("expected to update keys");
286
287 let (public_key, private_key) = keys.pop_first().unwrap();
288
289 let mut state_transition: StateTransition = identity_update_transition.into();
290
291 state_transition
292 .sign_by_private_key(private_key.as_slice(), public_key.key_type(), &bls)
293 .expect("expected to sign IdentityUpdateTransition");
294 let bytes = state_transition
295 .serialize_to_bytes()
296 .expect("expected to serialize");
297 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
298 .expect("expected to deserialize state transition");
299 assert_eq!(state_transition, recovered_state_transition);
300 }
301
302 #[test]
303 #[cfg(feature = "random-identities")]
304 fn identity_credit_withdrawal_transition_ser_de() {
305 let platform_version = PlatformVersion::latest();
306 let identity = Identity::random_identity(5, Some(5), platform_version)
307 .expect("expected a random identity");
308 let identity_credit_withdrawal_transition = IdentityCreditWithdrawalTransitionV0 {
309 identity_id: identity.id(),
310 amount: 5000000,
311 core_fee_per_byte: 34,
312 pooling: Pooling::Standard,
313 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
314 nonce: 1,
315 user_fee_increase: 0,
316 signature_public_key_id: 0,
317 signature: [1u8; 65].to_vec().into(),
318 };
319 let state_transition: StateTransition = identity_credit_withdrawal_transition.into();
320 let bytes = state_transition
321 .serialize_to_bytes()
322 .expect("expected to serialize");
323 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
324 .expect("expected to deserialize state transition");
325 assert_eq!(state_transition, recovered_state_transition);
326 }
327
328 #[test]
329 #[cfg(feature = "random-identities")]
330 fn data_contract_create_ser_de() {
331 let platform_version = LATEST_PLATFORM_VERSION;
332 let identity = Identity::random_identity(5, Some(5), platform_version)
333 .expect("expected a random identity");
334 let created_data_contract = get_data_contract_fixture(
335 Some(identity.id()),
336 0,
337 LATEST_PLATFORM_VERSION.protocol_version,
338 );
339 let data_contract_create_transition: DataContractCreateTransition = created_data_contract
340 .try_into_platform_versioned(platform_version)
341 .expect("expected to transform into a DataContractCreateTransition");
342 let state_transition: StateTransition = data_contract_create_transition.into();
343 let bytes = state_transition
344 .serialize_to_bytes()
345 .expect("expected to serialize");
346 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
347 .expect("expected to deserialize state transition");
348 assert_eq!(state_transition, recovered_state_transition);
349 }
350
351 #[test]
352 #[cfg(feature = "random-identities")]
353 fn data_contract_update_ser_de() {
354 let platform_version = PlatformVersion::latest();
355 let identity = Identity::random_identity(5, Some(5), platform_version)
356 .expect("expected a random identity");
357 let created_data_contract =
358 get_data_contract_fixture(Some(identity.id()), 0, platform_version.protocol_version);
359 let data_contract_update_transition =
360 DataContractUpdateTransition::V0(DataContractUpdateTransitionV0 {
361 identity_contract_nonce: 1,
362 data_contract: created_data_contract
363 .data_contract_owned()
364 .try_into_platform_versioned(platform_version)
365 .expect("expected a data contract"),
366 user_fee_increase: 0,
367 signature_public_key_id: 0,
368 signature: [1u8; 65].to_vec().into(),
369 });
370 let state_transition: StateTransition = data_contract_update_transition.into();
371 let bytes = state_transition
372 .serialize_to_bytes()
373 .expect("expected to serialize");
374 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
375 .expect("expected to deserialize state transition");
376 assert_eq!(state_transition, recovered_state_transition);
377 }
378
379 #[test]
380 fn document_batch_transition_10_created_documents_ser_de() {
381 let platform_version = PlatformVersion::latest();
382
383 let mut nonces = BTreeMap::new();
384 let data_contract = get_data_contract_fixture(None, 0, platform_version.protocol_version)
385 .data_contract_owned();
386 let documents = get_extended_documents_fixture_with_owner_id_from_contract(
387 &data_contract,
388 platform_version.protocol_version,
389 )
390 .unwrap();
391 let documents = documents
392 .iter()
393 .map(|extended_document| {
394 let document = extended_document.document().clone();
395 let data_contract = extended_document.data_contract();
396 (
397 document,
398 data_contract
399 .document_type_for_name(extended_document.document_type_name())
400 .unwrap(),
401 *extended_document.entropy(),
402 None,
403 )
404 })
405 .collect::<Vec<_>>();
406 let transitions = get_batched_transitions_fixture(
407 [(DocumentTransitionActionType::Create, documents)],
408 &mut nonces,
409 );
410 let documents_batch_transition: BatchTransition = BatchTransitionV1 {
411 owner_id: data_contract.owner_id(),
412 transitions,
413 ..Default::default()
414 }
415 .into();
416 let state_transition: StateTransition = documents_batch_transition.into();
417 let bytes = state_transition
418 .serialize_to_bytes()
419 .expect("expected to serialize");
420 let recovered_state_transition = StateTransition::deserialize_from_bytes(&bytes)
421 .expect("expected to deserialize state transition");
422 assert_eq!(state_transition, recovered_state_transition);
423 }
424
425 #[test]
426 fn document_batch_rejects_excessive_value_depth_during_decode() {
427 let nested = (0..300).fold(Value::Null, |value, _| Value::Array(vec![value]));
428 let document_transition =
429 DocumentTransition::Create(DocumentCreateTransition::V0(DocumentCreateTransitionV0 {
430 base: DocumentBaseTransition::V0(DocumentBaseTransitionV0 {
431 id: Identifier::default(),
432 identity_contract_nonce: 1,
433 document_type_name: "test".to_string(),
434 data_contract_id: Identifier::default(),
435 }),
436 entropy: [0; 32],
437 data: BTreeMap::from([("nested".to_string(), nested)]),
438 prefunded_voting_balance: None,
439 }));
440 assert_eq!(
441 document_transition.first_data_depth_exceeding(256),
442 Some(257)
443 );
444
445 let state_transition = StateTransition::Batch(BatchTransition::V1(BatchTransitionV1 {
446 transitions: vec![BatchedTransition::Document(document_transition)],
447 ..Default::default()
448 }));
449 let bytes = state_transition
450 .serialize_to_bytes()
451 .expect("the state transition should encode below the byte limit");
452 assert!(
453 bytes.len() as u64
454 <= PlatformVersion::latest()
455 .system_limits
456 .max_state_transition_size
457 );
458
459 std::mem::forget(state_transition);
462
463 let error =
464 StateTransition::deserialize_from_bytes_in_version(&bytes, PlatformVersion::latest())
465 .expect_err("excessive nesting must be rejected during decode");
466 assert!(error
467 .to_string()
468 .contains("value nesting depth 257 exceeds maximum 256"));
469 }
470
471 #[test]
472 fn document_batch_value_depth_limits_align_between_decode_and_validation() {
473 let max_depth = PlatformVersion::latest()
477 .system_limits
478 .max_document_value_depth
479 .expect("latest protocol should enforce document value depth")
480 as usize;
481 let nested = (1..max_depth).fold(Value::Array(vec![Value::Null]), |value, _| {
482 Value::Array(vec![value])
483 });
484 let document_transition =
485 DocumentTransition::Create(DocumentCreateTransition::V0(DocumentCreateTransitionV0 {
486 base: DocumentBaseTransition::V0(DocumentBaseTransitionV0 {
487 id: Identifier::default(),
488 identity_contract_nonce: 1,
489 document_type_name: "test".to_string(),
490 data_contract_id: Identifier::default(),
491 }),
492 entropy: [0; 32],
493 data: BTreeMap::from([("nested".to_string(), nested)]),
494 prefunded_voting_balance: None,
495 }));
496 assert_eq!(
497 document_transition.first_data_depth_exceeding(max_depth),
498 None
499 );
500 assert_eq!(
501 document_transition.first_data_depth_exceeding(max_depth - 1),
502 Some(max_depth)
503 );
504
505 let state_transition = StateTransition::Batch(BatchTransition::V1(BatchTransitionV1 {
506 transitions: vec![BatchedTransition::Document(document_transition)],
507 ..Default::default()
508 }));
509 let bytes = state_transition
510 .serialize_to_bytes()
511 .expect("the state transition should encode below the byte limit");
512
513 let recovered =
514 StateTransition::deserialize_from_bytes_in_version(&bytes, PlatformVersion::latest())
515 .expect("a value at the decoder ceiling must decode");
516 assert_eq!(state_transition, recovered);
517 }
518
519 #[test]
520 fn deserialize_empty_bytes_should_fail() {
521 let result = StateTransition::deserialize_from_bytes(&[]);
522 assert!(
523 result.is_err(),
524 "deserialization of empty bytes should fail"
525 );
526 }
527
528 #[test]
529 fn deserialize_single_byte_should_fail() {
530 let result = StateTransition::deserialize_from_bytes(&[0xFF]);
531 assert!(
532 result.is_err(),
533 "deserialization of a single 0xFF byte should fail"
534 );
535 }
536
537 #[test]
538 #[cfg(feature = "random-identities")]
539 fn deserialize_truncated_bytes_should_fail() {
540 let platform_version = PlatformVersion::latest();
541 let identity = Identity::random_identity(5, Some(5), platform_version)
542 .expect("expected a random identity");
543 let transition = IdentityCreditWithdrawalTransitionV0 {
544 identity_id: identity.id(),
545 amount: 5000000,
546 core_fee_per_byte: 34,
547 pooling: Pooling::Standard,
548 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
549 nonce: 1,
550 user_fee_increase: 0,
551 signature_public_key_id: 0,
552 signature: [1u8; 65].to_vec().into(),
553 };
554 let state_transition: StateTransition = transition.into();
555 let bytes = state_transition
556 .serialize_to_bytes()
557 .expect("expected to serialize");
558
559 let half = &bytes[..bytes.len() / 2];
561 assert!(
562 StateTransition::deserialize_from_bytes(half).is_err(),
563 "deserialization of truncated-to-half bytes should fail"
564 );
565
566 let minus_one = &bytes[..bytes.len() - 1];
568 assert!(
569 StateTransition::deserialize_from_bytes(minus_one).is_err(),
570 "deserialization of bytes missing last byte should fail"
571 );
572
573 let first_only = &bytes[..1];
575 assert!(
576 StateTransition::deserialize_from_bytes(first_only).is_err(),
577 "deserialization of only the first byte should fail"
578 );
579 }
580
581 #[test]
582 #[cfg(feature = "random-identities")]
583 fn deserialize_corrupted_bytes_should_not_panic() {
584 let platform_version = PlatformVersion::latest();
585 let identity = Identity::random_identity(5, Some(5), platform_version)
586 .expect("expected a random identity");
587 let transition = IdentityCreditWithdrawalTransitionV0 {
588 identity_id: identity.id(),
589 amount: 5000000,
590 core_fee_per_byte: 34,
591 pooling: Pooling::Standard,
592 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
593 nonce: 1,
594 user_fee_increase: 0,
595 signature_public_key_id: 0,
596 signature: [1u8; 65].to_vec().into(),
597 };
598 let state_transition: StateTransition = transition.into();
599 let mut bytes = state_transition
600 .serialize_to_bytes()
601 .expect("expected to serialize");
602
603 let mid = bytes.len() / 2;
605 bytes[mid] ^= 0xFF;
606
607 let result = StateTransition::deserialize_from_bytes(&bytes);
609 if let Ok(recovered) = result {
610 assert_ne!(
611 state_transition, recovered,
612 "corrupted bytes should not deserialize to the original value"
613 );
614 }
615 }
616
617 fn craft_oversized_vec_payload(fake_len: u64) -> Vec<u8> {
623 let config = bincode::config::standard()
624 .with_big_endian()
625 .with_no_limit();
626 let mut buf = Vec::new();
628 buf.extend_from_slice(&bincode::encode_to_vec(5u32, config).unwrap());
630 buf.push(0);
632 buf.extend_from_slice(&[0u8; 32]);
634 buf.extend_from_slice(&bincode::encode_to_vec(1000u64, config).unwrap());
636 buf.extend_from_slice(&bincode::encode_to_vec(1u32, config).unwrap());
638 buf.extend_from_slice(&bincode::encode_to_vec(0u32, config).unwrap());
640 buf.extend_from_slice(&bincode::encode_to_vec(fake_len, config).unwrap());
642 buf
644 }
645
646 #[test]
647 fn deserialize_crafted_huge_vec_length_does_not_oom() {
648 let payload = craft_oversized_vec_payload(8_000_000_000);
651 let result = StateTransition::deserialize_from_bytes(&payload);
652 assert!(
654 result.is_err(),
655 "crafted payload with 8GB vec length must be rejected, not cause OOM"
656 );
657 }
658
659 #[test]
660 fn deserialize_crafted_vec_exceeding_limit_is_rejected() {
661 let payload = craft_oversized_vec_payload(200_000);
667 let result = StateTransition::deserialize_from_bytes(&payload);
668 assert!(
669 result.is_err(),
670 "Vec length exceeding byte budget must be rejected"
671 );
672 }
673
674 #[test]
675 fn deserialize_no_limit_does_not_enforce_byte_budget() {
676 let payload = craft_oversized_vec_payload(200_000);
680 let result = StateTransition::deserialize_from_bytes_no_limit(&payload);
681 assert!(result.is_err());
682 if let ProtocolError::MaxEncodedBytesReachedError { .. } = result.unwrap_err() {
684 panic!("deserialize_from_bytes_no_limit should NOT enforce byte budget");
685 }
686 }
687
688 #[test]
689 fn deserialize_many_empty_list() {
690 let result = StateTransition::deserialize_many(&[]);
691 assert_eq!(result.unwrap(), vec![]);
692 }
693
694 #[test]
695 fn deserialize_many_with_invalid_entry() {
696 let result = StateTransition::deserialize_many(&[vec![0xFF]]);
697 assert!(
698 result.is_err(),
699 "deserialize_many with invalid entry should fail"
700 );
701 }
702
703 #[test]
704 #[cfg(feature = "random-identities")]
705 fn deserialize_many_with_valid_entries() {
706 let platform_version = PlatformVersion::latest();
707 let identity = Identity::random_identity(5, Some(5), platform_version)
708 .expect("expected a random identity");
709
710 let make_transition = |amount: u64, nonce: u64| -> StateTransition {
711 let t = IdentityCreditWithdrawalTransitionV0 {
712 identity_id: identity.id(),
713 amount,
714 core_fee_per_byte: 34,
715 pooling: Pooling::Standard,
716 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
717 nonce,
718 user_fee_increase: 0,
719 signature_public_key_id: 0,
720 signature: [1u8; 65].to_vec().into(),
721 };
722 t.into()
723 };
724
725 let st1 = make_transition(1000000, 1);
726 let st2 = make_transition(2000000, 2);
727 let st3 = make_transition(3000000, 3);
728
729 let raw: Vec<Vec<u8>> = vec![
730 st1.serialize_to_bytes().unwrap(),
731 st2.serialize_to_bytes().unwrap(),
732 st3.serialize_to_bytes().unwrap(),
733 ];
734
735 let recovered = StateTransition::deserialize_many(&raw).expect("should deserialize all");
736 assert_eq!(recovered.len(), 3);
737 assert_eq!(recovered[0], st1);
738 assert_eq!(recovered[1], st2);
739 assert_eq!(recovered[2], st3);
740 }
741}