1use crate::serialization::PlatformDeserializableUntrusted;
2use crate::state_transition::*;
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 pub fn deserialize_untagged_untrusted_exact(
22 state_transition_type: StateTransitionType,
23 bytes: &[u8],
24 ) -> Result<Self, ProtocolError> {
25 if bytes.len() >= STATE_TRANSITION_MAX_ENCODED_BYTES {
28 return Err(ProtocolError::MaxEncodedBytesReachedError {
29 max_size_kbytes: STATE_TRANSITION_MAX_ENCODED_BYTES,
30 size_hit: bytes.len(),
31 });
32 }
33 let state_transition: Self = match state_transition_type {
34 StateTransitionType::DataContractCreate => {
35 DataContractCreateTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
36 }
37 StateTransitionType::DataContractUpdate => {
38 DataContractUpdateTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
39 }
40 StateTransitionType::Batch => {
41 BatchTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
42 }
43 StateTransitionType::IdentityCreate => {
44 IdentityCreateTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
45 }
46 StateTransitionType::IdentityTopUp => {
47 IdentityTopUpTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
48 }
49 StateTransitionType::IdentityCreditWithdrawal => {
50 IdentityCreditWithdrawalTransition::deserialize_from_bytes_untrusted_exact(bytes)?
51 .into()
52 }
53 StateTransitionType::IdentityUpdate => {
54 IdentityUpdateTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
55 }
56 StateTransitionType::IdentityCreditTransfer => {
57 IdentityCreditTransferTransition::deserialize_from_bytes_untrusted_exact(bytes)?
58 .into()
59 }
60 StateTransitionType::MasternodeVote => {
61 MasternodeVoteTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
62 }
63 StateTransitionType::IdentityCreditTransferToAddresses => {
64 IdentityCreditTransferToAddressesTransition::deserialize_from_bytes_untrusted_exact(
65 bytes,
66 )?
67 .into()
68 }
69 StateTransitionType::IdentityCreateFromAddresses => {
70 IdentityCreateFromAddressesTransition::deserialize_from_bytes_untrusted_exact(
71 bytes,
72 )?
73 .into()
74 }
75 StateTransitionType::IdentityTopUpFromAddresses => {
76 IdentityTopUpFromAddressesTransition::deserialize_from_bytes_untrusted_exact(bytes)?
77 .into()
78 }
79 StateTransitionType::AddressFundsTransfer => {
80 AddressFundsTransferTransition::deserialize_from_bytes_untrusted_exact(bytes)?
81 .into()
82 }
83 StateTransitionType::AddressFundingFromAssetLock => {
84 AddressFundingFromAssetLockTransition::deserialize_from_bytes_untrusted_exact(
85 bytes,
86 )?
87 .into()
88 }
89 StateTransitionType::AddressCreditWithdrawal => {
90 AddressCreditWithdrawalTransition::deserialize_from_bytes_untrusted_exact(bytes)?
91 .into()
92 }
93 StateTransitionType::Shield => {
94 ShieldTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
95 }
96 StateTransitionType::ShieldedTransfer => {
97 ShieldedTransferTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
98 }
99 StateTransitionType::Unshield => {
100 UnshieldTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
101 }
102 StateTransitionType::ShieldFromAssetLock => {
103 ShieldFromAssetLockTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
104 }
105 StateTransitionType::ShieldedWithdrawal => {
106 ShieldedWithdrawalTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
107 }
108 StateTransitionType::IdentityCreateFromShieldedPool => {
109 IdentityCreateFromShieldedPoolTransition::deserialize_from_bytes_untrusted_exact(
110 bytes,
111 )?
112 .into()
113 }
114 StateTransitionType::ShieldFromIdentity => {
115 ShieldFromIdentityTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
116 }
117 StateTransitionType::IdentityTopUpFromShieldedPool => {
118 IdentityTopUpFromShieldedPoolTransition::deserialize_from_bytes_untrusted_exact(
119 bytes,
120 )?
121 .into()
122 }
123 StateTransitionType::TokenShieldedTransferWithShieldedFee => {
124 TokenShieldedTransferWithShieldedFeeTransition::deserialize_from_bytes_untrusted_exact(
125 bytes,
126 )?
127 .into()
128 }
129 StateTransitionType::TokenUnshieldWithShieldedFee => {
130 TokenUnshieldWithShieldedFeeTransition::deserialize_from_bytes_untrusted_exact(
131 bytes,
132 )?
133 .into()
134 }
135 StateTransitionType::TokenPurchaseFromShieldedPool => {
136 TokenPurchaseFromShieldedPoolTransition::deserialize_from_bytes_untrusted_exact(
137 bytes,
138 )?
139 .into()
140 }
141 StateTransitionType::IdentityKeyLimitsUpdate => {
142 IdentityKeyLimitsUpdateTransition::deserialize_from_bytes_untrusted_exact(bytes)?
143 .into()
144 }
145 StateTransitionType::ContractUserModeration => {
146 ContractUserModerationTransition::deserialize_from_bytes_untrusted_exact(bytes)?
147 .into()
148 }
149 StateTransitionType::ContractFeeClaim => {
150 ContractFeeClaimTransition::deserialize_from_bytes_untrusted_exact(bytes)?.into()
151 }
152 };
153 if state_transition.state_transition_type() != state_transition_type {
155 return Err(ProtocolError::CorruptedCodeExecution(format!(
156 "untagged {state_transition_type} bytes decoded as {}",
157 state_transition.state_transition_type()
158 )));
159 }
160 Ok(state_transition)
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use hex::ToHex;
167 use base64::engine::general_purpose::STANDARD;
168 use base64::Engine;
169 use platform_value::string_encoding::Encoding;
170 use platform_value::{Identifier, Value};
171 use crate::bls::native_bls::NativeBlsModule;
172 use crate::data_contract::accessors::v0::DataContractV0Getters;
173 use crate::identity::state_transition::AssetLockProved;
174 use crate::identity::accessors::IdentityGettersV0;
175 use crate::identity::core_script::CoreScript;
176 use crate::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
177 use crate::identity::Identity;
178 use crate::prelude::AssetLockProof;
179 use crate::serialization::PlatformMessageSignable;
180 use crate::serialization::Signable;
181 use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
182 use crate::state_transition::data_contract_create_transition::DataContractCreateTransition;
183 use crate::state_transition::data_contract_update_transition::{
184 DataContractUpdateTransition, DataContractUpdateTransitionV0,
185 };
186 use crate::state_transition::batch_transition::batched_transition::document_create_transition::{
187 DocumentCreateTransition, DocumentCreateTransitionV0,
188 };
189 use crate::state_transition::batch_transition::batched_transition::document_transition::{
190 DocumentTransition, DocumentTransitionV0Methods,
191 };
192 use crate::state_transition::batch_transition::batched_transition::document_transition_action_type::DocumentTransitionActionType;
193 use crate::state_transition::batch_transition::batched_transition::BatchedTransition;
194 use crate::state_transition::batch_transition::{
195 BatchTransition, BatchTransitionV1,
196 };
197 use crate::state_transition::batch_transition::document_base_transition::v0::DocumentBaseTransitionV0;
198 use crate::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
199 use crate::state_transition::identity_create_transition::accessors::IdentityCreateTransitionAccessorsV0;
200 use crate::state_transition::identity_create_transition::v0::IdentityCreateTransitionV0;
201 use crate::state_transition::identity_create_transition::IdentityCreateTransition;
202 use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0;
203 use crate::state_transition::identity_topup_transition::v0::IdentityTopUpTransitionV0;
204 use crate::state_transition::identity_update_transition::v0::IdentityUpdateTransitionV0;
205 use crate::state_transition::identity_update_transition::IdentityUpdateTransition;
206 use crate::state_transition::StateTransitionType;
207 use crate::state_transition::STATE_TRANSITION_MAX_ENCODED_BYTES;
208 use crate::state_transition::public_key_in_creation::accessors::IdentityPublicKeyInCreationV0Setters;
209 use crate::state_transition::StateTransition;
210 use crate::tests::fixtures::{
211 get_data_contract_fixture, get_batched_transitions_fixture,
212 get_extended_documents_fixture_with_owner_id_from_contract,
213 raw_instant_asset_lock_proof_fixture,
214 };
215 use crate::version::PlatformVersion;
216 use crate::withdrawal::Pooling;
217 use crate::ProtocolError;
218 use platform_version::version::LATEST_PLATFORM_VERSION;
219 use platform_version::TryIntoPlatformVersioned;
220 use rand::rngs::StdRng;
221 use rand::SeedableRng;
222 use std::collections::BTreeMap;
223
224 #[test]
225 fn should_build_identity_create_transition_from_mainnet_asset_lock_transaction() {
228 const EXPECTED_STATE_TRANSITION_HASH: &str =
229 "6CDCC15AC4EC68DBB414EE0DA692DFE363A996A0F285423BEFC3A29F87948A0D";
230 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==";
231 const EXPECTED_IDENTITY_ADDRESS: &str = "5tf2QotaJw8kRNpQEa8TXtRQ6FLxwUrY4Mtee2JF2nco";
232 const EXPECTED_CORE_TRANSACTION_HASH: &str =
233 "5529726cc14d856a363745c68ba914339c318a9b78a99bb4b4145b23d7630732";
234 let raw_transaction = STANDARD
235 .decode(RAW_TRANSACTION_BASE64)
236 .expect("base64 transaction should decode");
237 let state_transition = StateTransition::deserialize_from_bytes_untrusted(&raw_transaction)
238 .expect("State transition deserializes correctly");
239
240 assert_eq!(
241 &state_transition
242 .transaction_id()
243 .expect("expected transaction id")
244 .encode_hex_upper::<String>(),
245 EXPECTED_STATE_TRANSITION_HASH
246 );
247
248 let StateTransition::IdentityCreate(identity_create_transition) = state_transition else {
249 panic!("expected identity create transition");
250 };
251
252 let asset_lock_proof = identity_create_transition.asset_lock_proof();
255 let AssetLockProof::Chain(chain_proof) = asset_lock_proof else {
256 panic!("expected chain asset lock proof for this mainnet transaction");
257 };
258
259 assert_eq!(
261 &chain_proof.out_point.txid.to_string().to_lowercase(),
262 EXPECTED_CORE_TRANSACTION_HASH
263 );
264
265 let identity_address = identity_create_transition
266 .identity_id()
267 .to_string(Encoding::Base58);
268
269 assert_eq!(identity_address, EXPECTED_IDENTITY_ADDRESS);
270 }
271
272 #[test]
273 #[cfg(feature = "random-identities")]
274 fn identity_create_transition_ser_de() {
275 let platform_version = LATEST_PLATFORM_VERSION;
276 let identity = Identity::random_identity(5, Some(5), platform_version)
277 .expect("expected a random identity");
278 let asset_lock_proof = raw_instant_asset_lock_proof_fixture(None, None);
279
280 let identity_create_transition = IdentityCreateTransition::V0(
281 IdentityCreateTransitionV0::try_from_identity(
282 &identity,
283 AssetLockProof::Instant(asset_lock_proof),
284 platform_version,
285 )
286 .expect("expected to make an identity create transition"),
287 );
288
289 let state_transition: StateTransition = identity_create_transition.into();
290 let bytes = state_transition
291 .serialize_to_bytes()
292 .expect("expected to serialize");
293 let recovered_state_transition = StateTransition::deserialize_from_bytes_untrusted(&bytes)
294 .expect("expected to deserialize state transition");
295 assert_eq!(state_transition, recovered_state_transition);
296 }
297
298 #[test]
299 #[cfg(feature = "random-identities")]
300 fn identity_topup_transition_ser_de() {
301 let platform_version = PlatformVersion::latest();
302 let identity = Identity::random_identity(5, Some(5), platform_version)
303 .expect("expected a random identity");
304 let asset_lock_proof = raw_instant_asset_lock_proof_fixture(None, None);
305
306 let identity_topup_transition = IdentityTopUpTransitionV0 {
307 asset_lock_proof: AssetLockProof::Instant(asset_lock_proof),
308 identity_id: identity.id(),
309 user_fee_increase: 0,
310 signature: [1u8; 65].to_vec().into(),
311 };
312 let state_transition: StateTransition = identity_topup_transition.into();
313 let bytes = state_transition
314 .serialize_to_bytes()
315 .expect("expected to serialize");
316 let recovered_state_transition = StateTransition::deserialize_from_bytes_untrusted(&bytes)
317 .expect("expected to deserialize state transition");
318 assert_eq!(state_transition, recovered_state_transition);
319 }
320
321 #[test]
322 #[cfg(feature = "random-identities")]
323 fn identity_update_transition_add_keys_ser_de() {
324 let mut rng = StdRng::seed_from_u64(5);
325 let (identity, mut keys): (Identity, BTreeMap<_, _>) =
326 Identity::random_identity_with_main_keys_with_private_key(
327 5,
328 &mut rng,
329 LATEST_PLATFORM_VERSION,
330 )
331 .expect("expected to get identity");
332 let bls = NativeBlsModule;
333 let add_public_keys_in_creation = identity
334 .public_keys()
335 .values()
336 .map(|public_key| public_key.into())
337 .collect();
338 let mut identity_update_transition = IdentityUpdateTransitionV0 {
339 signature: Default::default(),
340 signature_public_key_id: 0,
341 identity_id: identity.id(),
342 revision: 1,
343 nonce: 1,
344 add_public_keys: add_public_keys_in_creation,
345 disable_public_keys: vec![],
346 user_fee_increase: 0,
347 };
348
349 let key_signable_bytes = identity_update_transition
350 .signable_bytes()
351 .expect("expected to get signable bytes");
352
353 identity_update_transition
354 .add_public_keys
355 .iter_mut()
356 .zip(identity.public_keys().clone().into_values())
357 .try_for_each(|(public_key_with_witness, public_key)| {
358 if public_key.key_type().is_unique_key_type() {
359 let private_key = keys
360 .get(&public_key)
361 .expect("expected to have the private key");
362 let signature = key_signable_bytes
363 .as_slice()
364 .sign_by_private_key(private_key, public_key.key_type(), &bls)?
365 .into();
366 public_key_with_witness.set_signature(signature);
367 }
368
369 Ok::<(), ProtocolError>(())
370 })
371 .expect("expected to update keys");
372
373 let (public_key, private_key) = keys.pop_first().unwrap();
374
375 let mut state_transition: StateTransition = identity_update_transition.into();
376
377 state_transition
378 .sign_by_private_key(private_key.as_slice(), public_key.key_type(), &bls)
379 .expect("expected to sign IdentityUpdateTransition");
380 let bytes = state_transition
381 .serialize_to_bytes()
382 .expect("expected to serialize");
383 let recovered_state_transition = StateTransition::deserialize_from_bytes_untrusted(&bytes)
384 .expect("expected to deserialize state transition");
385 assert_eq!(state_transition, recovered_state_transition);
386 }
387
388 #[test]
389 #[cfg(feature = "state-transition-signing")]
390 fn identity_update_transition_disable_keys_ser_de() {
391 let mut rng = StdRng::seed_from_u64(5);
392 let (identity, mut keys): (Identity, BTreeMap<_, _>) =
393 Identity::random_identity_with_main_keys_with_private_key(
394 5,
395 &mut rng,
396 LATEST_PLATFORM_VERSION,
397 )
398 .expect("expected to get identity");
399 let bls = NativeBlsModule;
400 let add_public_keys_in_creation = identity
401 .public_keys()
402 .values()
403 .map(|public_key| public_key.into())
404 .collect();
405 let mut identity_update_transition = IdentityUpdateTransitionV0 {
406 signature: Default::default(),
407 signature_public_key_id: 0,
408 identity_id: identity.id(),
409 revision: 1,
410 nonce: 1,
411 add_public_keys: add_public_keys_in_creation,
412 disable_public_keys: vec![3, 4, 5],
413 user_fee_increase: 0,
414 };
415
416 let key_signable_bytes = identity_update_transition
417 .signable_bytes()
418 .expect("expected to get signable bytes");
419
420 identity_update_transition
421 .add_public_keys
422 .iter_mut()
423 .zip(identity.public_keys().clone().into_values())
424 .try_for_each(|(public_key_with_witness, public_key)| {
425 if public_key.key_type().is_unique_key_type() {
426 let private_key = keys
427 .get(&public_key)
428 .expect("expected to have the private key");
429 let signature = key_signable_bytes
430 .as_slice()
431 .sign_by_private_key(private_key, public_key.key_type(), &bls)?
432 .into();
433 public_key_with_witness.set_signature(signature);
434 }
435
436 Ok::<(), ProtocolError>(())
437 })
438 .expect("expected to update keys");
439
440 let (public_key, private_key) = keys.pop_first().unwrap();
441
442 let mut state_transition: StateTransition = identity_update_transition.into();
443
444 state_transition
445 .sign_by_private_key(private_key.as_slice(), public_key.key_type(), &bls)
446 .expect("expected to sign IdentityUpdateTransition");
447 let bytes = state_transition
448 .serialize_to_bytes()
449 .expect("expected to serialize");
450 let recovered_state_transition = StateTransition::deserialize_from_bytes_untrusted(&bytes)
451 .expect("expected to deserialize state transition");
452 assert_eq!(state_transition, recovered_state_transition);
453 }
454
455 #[test]
456 #[cfg(feature = "random-identities")]
457 fn identity_credit_withdrawal_transition_ser_de() {
458 let platform_version = PlatformVersion::latest();
459 let identity = Identity::random_identity(5, Some(5), platform_version)
460 .expect("expected a random identity");
461 let identity_credit_withdrawal_transition = IdentityCreditWithdrawalTransitionV0 {
462 identity_id: identity.id(),
463 amount: 5000000,
464 core_fee_per_byte: 34,
465 pooling: Pooling::Standard,
466 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
467 nonce: 1,
468 user_fee_increase: 0,
469 signature_public_key_id: 0,
470 signature: [1u8; 65].to_vec().into(),
471 };
472 let state_transition: StateTransition = identity_credit_withdrawal_transition.into();
473 let bytes = state_transition
474 .serialize_to_bytes()
475 .expect("expected to serialize");
476 let recovered_state_transition = StateTransition::deserialize_from_bytes_untrusted(&bytes)
477 .expect("expected to deserialize state transition");
478 assert_eq!(state_transition, recovered_state_transition);
479 }
480
481 #[test]
482 #[cfg(feature = "random-identities")]
483 fn data_contract_create_ser_de() {
484 let platform_version = LATEST_PLATFORM_VERSION;
485 let identity = Identity::random_identity(5, Some(5), platform_version)
486 .expect("expected a random identity");
487 let created_data_contract = get_data_contract_fixture(
488 Some(identity.id()),
489 0,
490 LATEST_PLATFORM_VERSION.protocol_version,
491 );
492 let data_contract_create_transition: DataContractCreateTransition = created_data_contract
493 .try_into_platform_versioned(platform_version)
494 .expect("expected to transform into a DataContractCreateTransition");
495 let state_transition: StateTransition = data_contract_create_transition.into();
496 let bytes = state_transition
497 .serialize_to_bytes()
498 .expect("expected to serialize");
499 let recovered_state_transition = StateTransition::deserialize_from_bytes_untrusted(&bytes)
500 .expect("expected to deserialize state transition");
501 assert_eq!(state_transition, recovered_state_transition);
502 }
503
504 #[test]
505 #[cfg(feature = "random-identities")]
506 fn data_contract_update_ser_de() {
507 let platform_version = PlatformVersion::latest();
508 let identity = Identity::random_identity(5, Some(5), platform_version)
509 .expect("expected a random identity");
510 let created_data_contract =
511 get_data_contract_fixture(Some(identity.id()), 0, platform_version.protocol_version);
512 let data_contract_update_transition =
513 DataContractUpdateTransition::V0(DataContractUpdateTransitionV0 {
514 identity_contract_nonce: 1,
515 data_contract: created_data_contract
516 .data_contract_owned()
517 .try_into_platform_versioned(platform_version)
518 .expect("expected a data contract"),
519 user_fee_increase: 0,
520 signature_public_key_id: 0,
521 signature: [1u8; 65].to_vec().into(),
522 });
523 let state_transition: StateTransition = data_contract_update_transition.into();
524 let bytes = state_transition
525 .serialize_to_bytes()
526 .expect("expected to serialize");
527 let recovered_state_transition = StateTransition::deserialize_from_bytes_untrusted(&bytes)
528 .expect("expected to deserialize state transition");
529 assert_eq!(state_transition, recovered_state_transition);
530 }
531
532 #[test]
533 fn document_batch_transition_10_created_documents_ser_de() {
534 let platform_version = PlatformVersion::latest();
535
536 let mut nonces = BTreeMap::new();
537 let data_contract = get_data_contract_fixture(None, 0, platform_version.protocol_version)
538 .data_contract_owned();
539 let documents = get_extended_documents_fixture_with_owner_id_from_contract(
540 &data_contract,
541 platform_version.protocol_version,
542 )
543 .unwrap();
544 let documents = documents
545 .iter()
546 .map(|extended_document| {
547 let document = extended_document.document().clone();
548 let data_contract = extended_document.data_contract();
549 (
550 document,
551 data_contract
552 .document_type_for_name(extended_document.document_type_name())
553 .unwrap(),
554 *extended_document.entropy(),
555 None,
556 )
557 })
558 .collect::<Vec<_>>();
559 let transitions = get_batched_transitions_fixture(
560 [(DocumentTransitionActionType::Create, documents)],
561 &mut nonces,
562 );
563 let documents_batch_transition: BatchTransition = BatchTransitionV1 {
564 owner_id: data_contract.owner_id(),
565 transitions,
566 ..Default::default()
567 }
568 .into();
569 let state_transition: StateTransition = documents_batch_transition.into();
570 let bytes = state_transition
571 .serialize_to_bytes()
572 .expect("expected to serialize");
573 let recovered_state_transition = StateTransition::deserialize_from_bytes_untrusted(&bytes)
574 .expect("expected to deserialize state transition");
575 assert_eq!(state_transition, recovered_state_transition);
576 }
577
578 const DEEP_VALUE_TEST_STACK_SIZE: usize = 16 * 1024 * 1024;
585
586 fn on_deep_value_stack(test: impl FnOnce() + Send + 'static) {
587 std::thread::Builder::new()
588 .stack_size(DEEP_VALUE_TEST_STACK_SIZE)
589 .spawn(test)
590 .expect("the deep value test thread should spawn")
591 .join()
592 .unwrap_or_else(|panic| std::panic::resume_unwind(panic));
593 }
594
595 #[test]
596 fn document_batch_rejects_excessive_value_depth_during_decode() {
597 on_deep_value_stack(|| {
598 let nested = (0..300).fold(Value::Null, |value, _| Value::Array(vec![value]));
599 let document_transition = DocumentTransition::Create(DocumentCreateTransition::V0(
600 DocumentCreateTransitionV0 {
601 base: DocumentBaseTransition::V0(DocumentBaseTransitionV0 {
602 id: Identifier::default(),
603 identity_contract_nonce: 1,
604 document_type_name: "test".to_string(),
605 data_contract_id: Identifier::default(),
606 }),
607 entropy: [0; 32],
608 data: BTreeMap::from([("nested".to_string(), nested)]),
609 prefunded_voting_balance: None,
610 },
611 ));
612 assert_eq!(
613 document_transition.first_data_depth_exceeding(256),
614 Some(257)
615 );
616
617 let state_transition = StateTransition::Batch(BatchTransition::V1(BatchTransitionV1 {
618 transitions: vec![BatchedTransition::Document(document_transition)],
619 ..Default::default()
620 }));
621 let bytes = state_transition
622 .serialize_to_bytes()
623 .expect("the state transition should encode below the byte limit");
624 assert!(
625 bytes.len() as u64
626 <= PlatformVersion::latest()
627 .system_limits
628 .max_state_transition_size
629 );
630
631 std::mem::forget(state_transition);
634
635 let error = StateTransition::deserialize_from_bytes_untrusted_in_version(
636 &bytes,
637 PlatformVersion::latest(),
638 )
639 .expect_err("excessive nesting must be rejected during decode");
640 assert!(error
641 .to_string()
642 .contains("value nesting depth 257 exceeds maximum 256"));
643 });
644 }
645
646 #[test]
647 fn document_batch_value_depth_limits_align_between_decode_and_validation() {
648 on_deep_value_stack(|| {
649 let max_depth = PlatformVersion::latest()
653 .system_limits
654 .max_document_value_depth
655 .expect("latest protocol should enforce document value depth")
656 as usize;
657 let nested = (1..max_depth).fold(Value::Array(vec![Value::Null]), |value, _| {
658 Value::Array(vec![value])
659 });
660 let document_transition = DocumentTransition::Create(DocumentCreateTransition::V0(
661 DocumentCreateTransitionV0 {
662 base: DocumentBaseTransition::V0(DocumentBaseTransitionV0 {
663 id: Identifier::default(),
664 identity_contract_nonce: 1,
665 document_type_name: "test".to_string(),
666 data_contract_id: Identifier::default(),
667 }),
668 entropy: [0; 32],
669 data: BTreeMap::from([("nested".to_string(), nested)]),
670 prefunded_voting_balance: None,
671 },
672 ));
673 assert_eq!(
674 document_transition.first_data_depth_exceeding(max_depth),
675 None
676 );
677 assert_eq!(
678 document_transition.first_data_depth_exceeding(max_depth - 1),
679 Some(max_depth)
680 );
681
682 let state_transition = StateTransition::Batch(BatchTransition::V1(BatchTransitionV1 {
683 transitions: vec![BatchedTransition::Document(document_transition)],
684 ..Default::default()
685 }));
686 let bytes = state_transition
687 .serialize_to_bytes()
688 .expect("the state transition should encode below the byte limit");
689
690 let recovered = StateTransition::deserialize_from_bytes_untrusted_in_version(
691 &bytes,
692 PlatformVersion::latest(),
693 )
694 .expect("a value at the decoder ceiling must decode");
695 assert_eq!(state_transition, recovered);
696 });
697 }
698
699 #[test]
700 fn deserialize_empty_bytes_should_fail() {
701 let result = StateTransition::deserialize_from_bytes_untrusted(&[]);
702 assert!(
703 result.is_err(),
704 "deserialization of empty bytes should fail"
705 );
706 }
707
708 #[test]
709 fn deserialize_single_byte_should_fail() {
710 let result = StateTransition::deserialize_from_bytes_untrusted(&[0xFF]);
711 assert!(
712 result.is_err(),
713 "deserialization of a single 0xFF byte should fail"
714 );
715 }
716
717 #[test]
718 #[cfg(feature = "random-identities")]
719 fn deserialize_truncated_bytes_should_fail() {
720 let platform_version = PlatformVersion::latest();
721 let identity = Identity::random_identity(5, Some(5), platform_version)
722 .expect("expected a random identity");
723 let transition = IdentityCreditWithdrawalTransitionV0 {
724 identity_id: identity.id(),
725 amount: 5000000,
726 core_fee_per_byte: 34,
727 pooling: Pooling::Standard,
728 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
729 nonce: 1,
730 user_fee_increase: 0,
731 signature_public_key_id: 0,
732 signature: [1u8; 65].to_vec().into(),
733 };
734 let state_transition: StateTransition = transition.into();
735 let bytes = state_transition
736 .serialize_to_bytes()
737 .expect("expected to serialize");
738
739 let half = &bytes[..bytes.len() / 2];
741 assert!(
742 StateTransition::deserialize_from_bytes_untrusted(half).is_err(),
743 "deserialization of truncated-to-half bytes should fail"
744 );
745
746 let minus_one = &bytes[..bytes.len() - 1];
748 assert!(
749 StateTransition::deserialize_from_bytes_untrusted(minus_one).is_err(),
750 "deserialization of bytes missing last byte should fail"
751 );
752
753 let first_only = &bytes[..1];
755 assert!(
756 StateTransition::deserialize_from_bytes_untrusted(first_only).is_err(),
757 "deserialization of only the first byte should fail"
758 );
759 }
760
761 #[test]
762 #[cfg(feature = "random-identities")]
763 fn deserialize_corrupted_bytes_should_not_panic() {
764 let platform_version = PlatformVersion::latest();
765 let identity = Identity::random_identity(5, Some(5), platform_version)
766 .expect("expected a random identity");
767 let transition = IdentityCreditWithdrawalTransitionV0 {
768 identity_id: identity.id(),
769 amount: 5000000,
770 core_fee_per_byte: 34,
771 pooling: Pooling::Standard,
772 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
773 nonce: 1,
774 user_fee_increase: 0,
775 signature_public_key_id: 0,
776 signature: [1u8; 65].to_vec().into(),
777 };
778 let state_transition: StateTransition = transition.into();
779 let mut bytes = state_transition
780 .serialize_to_bytes()
781 .expect("expected to serialize");
782
783 let mid = bytes.len() / 2;
785 bytes[mid] ^= 0xFF;
786
787 let result = StateTransition::deserialize_from_bytes_untrusted(&bytes);
789 if let Ok(recovered) = result {
790 assert_ne!(
791 state_transition, recovered,
792 "corrupted bytes should not deserialize to the original value"
793 );
794 }
795 }
796
797 fn craft_oversized_vec_payload(fake_len: u64) -> Vec<u8> {
803 let config = bincode::config::standard()
804 .with_big_endian()
805 .with_no_limit();
806 let mut buf = Vec::new();
808 buf.extend_from_slice(&bincode::encode_to_vec(5u32, config).unwrap());
810 buf.push(0);
812 buf.extend_from_slice(&[0u8; 32]);
814 buf.extend_from_slice(&bincode::encode_to_vec(1000u64, config).unwrap());
816 buf.extend_from_slice(&bincode::encode_to_vec(1u32, config).unwrap());
818 buf.extend_from_slice(&bincode::encode_to_vec(0u32, config).unwrap());
820 buf.extend_from_slice(&bincode::encode_to_vec(fake_len, config).unwrap());
822 buf
824 }
825
826 #[test]
827 fn deserialize_crafted_huge_vec_length_does_not_oom() {
828 let payload = craft_oversized_vec_payload(8_000_000_000);
831 let result = StateTransition::deserialize_from_bytes_untrusted(&payload);
832 assert!(
834 result.is_err(),
835 "crafted payload with 8GB vec length must be rejected, not cause OOM"
836 );
837 }
838
839 #[test]
840 fn deserialize_crafted_vec_exceeding_limit_is_rejected() {
841 let payload = craft_oversized_vec_payload(200_000);
847 let result = StateTransition::deserialize_from_bytes_untrusted(&payload);
848 assert!(
849 result.is_err(),
850 "Vec length exceeding byte budget must be rejected"
851 );
852 }
853
854 #[test]
855 fn deserialize_no_limit_does_not_enforce_byte_budget() {
856 let payload = craft_oversized_vec_payload(200_000);
860 let result = StateTransition::deserialize_from_bytes_untrusted_no_limit(&payload);
861 assert!(result.is_err());
862 if let ProtocolError::MaxEncodedBytesReachedError { .. } = result.unwrap_err() {
864 panic!("deserialize_from_bytes_untrusted_no_limit should NOT enforce byte budget");
865 }
866 }
867
868 #[test]
869 fn deserialize_many_empty_list() {
870 let result = StateTransition::deserialize_many_untrusted(&[]);
871 assert_eq!(result.unwrap(), vec![]);
872 }
873
874 #[test]
875 fn deserialize_many_with_invalid_entry() {
876 let result = StateTransition::deserialize_many_untrusted(&[vec![0xFF]]);
877 assert!(
878 result.is_err(),
879 "deserialize_many with invalid entry should fail"
880 );
881 }
882
883 #[test]
884 #[cfg(feature = "random-identities")]
885 fn deserialize_many_with_valid_entries() {
886 let platform_version = PlatformVersion::latest();
887 let identity = Identity::random_identity(5, Some(5), platform_version)
888 .expect("expected a random identity");
889
890 let make_transition = |amount: u64, nonce: u64| -> StateTransition {
891 let t = IdentityCreditWithdrawalTransitionV0 {
892 identity_id: identity.id(),
893 amount,
894 core_fee_per_byte: 34,
895 pooling: Pooling::Standard,
896 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
897 nonce,
898 user_fee_increase: 0,
899 signature_public_key_id: 0,
900 signature: [1u8; 65].to_vec().into(),
901 };
902 t.into()
903 };
904
905 let st1 = make_transition(1000000, 1);
906 let st2 = make_transition(2000000, 2);
907 let st3 = make_transition(3000000, 3);
908
909 let raw: Vec<Vec<u8>> = vec![
910 st1.serialize_to_bytes().unwrap(),
911 st2.serialize_to_bytes().unwrap(),
912 st3.serialize_to_bytes().unwrap(),
913 ];
914
915 let recovered =
916 StateTransition::deserialize_many_untrusted(&raw).expect("should deserialize all");
917 assert_eq!(recovered.len(), 3);
918 assert_eq!(recovered[0], st1);
919 assert_eq!(recovered[1], st2);
920 assert_eq!(recovered[2], st3);
921 }
922
923 #[test]
924 #[cfg(feature = "random-identities")]
925 fn exact_decode_refuses_trailing_bytes() {
926 let platform_version = PlatformVersion::latest();
927 let identity = Identity::random_identity(5, Some(5), platform_version)
928 .expect("expected a random identity");
929 let st: StateTransition = IdentityCreditWithdrawalTransitionV0 {
930 identity_id: identity.id(),
931 amount: 1000000,
932 core_fee_per_byte: 34,
933 pooling: Pooling::Standard,
934 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
935 nonce: 1,
936 user_fee_increase: 0,
937 signature_public_key_id: 0,
938 signature: [1u8; 65].to_vec().into(),
939 }
940 .into();
941 let bytes = st.serialize_to_bytes().unwrap();
942
943 assert_eq!(
944 StateTransition::deserialize_from_bytes_untrusted_exact(&bytes).unwrap(),
945 st
946 );
947
948 let mut padded = bytes.clone();
949 padded.push(0);
950 assert_eq!(
952 StateTransition::deserialize_from_bytes_untrusted(&padded).unwrap(),
953 st
954 );
955 assert!(matches!(
956 StateTransition::deserialize_from_bytes_untrusted_exact(&padded),
957 Err(ProtocolError::PlatformDeserializationError(message))
958 if message.contains("1 bytes left over")
959 ));
960 }
961
962 #[test]
965 fn exact_decode_refuses_a_transition_followed_by_another() {
966 let update: StateTransition = IdentityUpdateTransitionV0 {
967 identity_id: Identifier::from([0x21; 32]),
968 revision: 1,
969 nonce: 1,
970 disable_public_keys: vec![1],
971 ..Default::default()
972 }
973 .into();
974 let withdrawal: StateTransition = IdentityCreditWithdrawalTransitionV0 {
975 identity_id: Identifier::from([0x21; 32]),
976 amount: 1000000,
977 core_fee_per_byte: 34,
978 pooling: Pooling::Standard,
979 output_script: CoreScript::from_bytes((0..23).collect::<Vec<u8>>()),
980 nonce: 2,
981 user_fee_increase: 0,
982 signature_public_key_id: 0,
983 signature: Default::default(),
984 }
985 .into();
986
987 let mut both = update.serialize_to_bytes().unwrap();
988 both.extend_from_slice(&withdrawal.serialize_to_bytes().unwrap());
989
990 assert_eq!(
991 StateTransition::deserialize_from_bytes_untrusted(&both).unwrap(),
992 update
993 );
994 assert!(matches!(
995 StateTransition::deserialize_from_bytes_untrusted_exact(&both),
996 Err(ProtocolError::PlatformDeserializationError(message)) if message.contains("left over")
997 ));
998 }
999
1000 #[test]
1001 #[cfg(feature = "random-identities")]
1002 fn untagged_decode_matches_the_tagged_transition() {
1003 let platform_version = PlatformVersion::latest();
1004 let identity = Identity::random_identity(5, Some(5), platform_version)
1005 .expect("expected a random identity");
1006 let update = IdentityUpdateTransitionV0 {
1007 signature: [7u8; 65].to_vec().into(),
1008 signature_public_key_id: 0,
1009 identity_id: identity.id(),
1010 revision: 1,
1011 nonce: 1,
1012 add_public_keys: identity
1013 .public_keys()
1014 .values()
1015 .map(|public_key| public_key.into())
1016 .collect(),
1017 disable_public_keys: vec![],
1018 user_fee_increase: 0,
1019 };
1020 let inner_bytes = IdentityUpdateTransition::from(update.clone())
1021 .serialize_to_bytes()
1022 .unwrap();
1023 let st: StateTransition = update.into();
1024
1025 let decoded = StateTransition::deserialize_untagged_untrusted_exact(
1026 StateTransitionType::IdentityUpdate,
1027 &inner_bytes,
1028 )
1029 .expect("untagged identity update decodes");
1030 assert_eq!(decoded, st);
1031 assert_eq!(
1032 &decoded.serialize_to_bytes().unwrap()[1..],
1033 &inner_bytes[..]
1034 );
1035
1036 assert!(StateTransition::deserialize_untagged_untrusted_exact(
1037 StateTransitionType::Batch,
1038 &inner_bytes,
1039 )
1040 .is_err());
1041
1042 let mut padded = inner_bytes.clone();
1043 padded.push(0);
1044 assert!(StateTransition::deserialize_untagged_untrusted_exact(
1045 StateTransitionType::IdentityUpdate,
1046 &padded,
1047 )
1048 .is_err());
1049 }
1050
1051 #[test]
1053 fn untagged_decode_honours_the_state_transition_budget() {
1054 let st: StateTransition = IdentityCreditWithdrawalTransitionV0 {
1055 identity_id: Identifier::from([0x21; 32]),
1056 amount: 1000000,
1057 core_fee_per_byte: 34,
1058 pooling: Pooling::Standard,
1059 output_script: CoreScript::from_bytes(vec![0; STATE_TRANSITION_MAX_ENCODED_BYTES]),
1060 nonce: 1,
1061 user_fee_increase: 0,
1062 signature_public_key_id: 0,
1063 signature: Default::default(),
1064 }
1065 .into();
1066 let tagged = bincode::encode_to_vec(&st, bincode::config::standard().with_big_endian())
1067 .expect("encodes");
1068
1069 match StateTransition::deserialize_untagged_untrusted_exact(
1070 StateTransitionType::IdentityCreditWithdrawal,
1071 &tagged[1..],
1072 ) {
1073 Err(ProtocolError::MaxEncodedBytesReachedError {
1074 max_size_kbytes, ..
1075 }) => assert_eq!(max_size_kbytes, STATE_TRANSITION_MAX_ENCODED_BYTES),
1076 other => panic!("expected the budget error, got {other:?}"),
1077 }
1078 }
1079}