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