Skip to main content

dpp/data_contract/v1/serialization/
mod.rs

1use crate::data_contract::document_type::class_methods::consensus_or_protocol_required_fields_error;
2use crate::data_contract::document_type::validate_required_since_within_contract_version;
3use crate::data_contract::document_type::DocumentType;
4use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0;
5use crate::data_contract::serialized_version::DataContractInSerializationFormat;
6use crate::data_contract::{DataContract, DataContractV1};
7use crate::version::{PlatformVersion, PlatformVersionCurrentVersion};
8use crate::ProtocolError;
9use std::collections::BTreeMap;
10
11use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1;
12use crate::validation::operations::ProtocolValidationOperation;
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14
15impl Serialize for DataContractV1 {
16    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
17    where
18        S: Serializer,
19    {
20        let data_contract: DataContract = self.clone().into();
21        let serialization_format = DataContractInSerializationFormatV1::from(data_contract);
22        serialization_format.serialize(serializer)
23    }
24}
25
26impl<'de> Deserialize<'de> for DataContractV1 {
27    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
28    where
29        D: Deserializer<'de>,
30    {
31        let serialization_format = DataContractInSerializationFormatV1::deserialize(deserializer)?;
32        let current_version = PlatformVersion::get_version_or_current_or_latest(None)
33            .map_err(|e| serde::de::Error::custom(e.to_string()))?;
34        // when deserializing from json/platform_value/cbor we always want to validate (as this is not coming from the state)
35        DataContractV1::try_from_platform_versioned_v1(
36            serialization_format,
37            true,
38            &mut vec![],
39            current_version,
40        )
41        .map_err(serde::de::Error::custom)
42    }
43}
44
45impl DataContractV1 {
46    pub(in crate::data_contract) fn try_from_platform_versioned(
47        value: DataContractInSerializationFormat,
48        full_validation: bool,
49        validation_operations: &mut Vec<ProtocolValidationOperation>,
50        platform_version: &PlatformVersion,
51    ) -> Result<Self, ProtocolError> {
52        match value {
53            DataContractInSerializationFormat::V0(serialization_format_v0) => {
54                let data_contract = DataContractV1::try_from_platform_versioned_v0(
55                    serialization_format_v0,
56                    full_validation,
57                    validation_operations,
58                    platform_version,
59                )?;
60
61                Ok(data_contract)
62            }
63            DataContractInSerializationFormat::V1(serialization_format_v1) => {
64                let data_contract = DataContractV1::try_from_platform_versioned_v1(
65                    serialization_format_v1,
66                    full_validation,
67                    validation_operations,
68                    platform_version,
69                )?;
70
71                Ok(data_contract)
72            }
73        }
74    }
75
76    pub(in crate::data_contract) fn try_from_platform_versioned_v0(
77        data_contract_data: DataContractInSerializationFormatV0,
78        full_validation: bool,
79        validation_operations: &mut Vec<ProtocolValidationOperation>,
80        platform_version: &PlatformVersion,
81    ) -> Result<Self, ProtocolError> {
82        let DataContractInSerializationFormatV0 {
83            id,
84            config,
85            version,
86            owner_id,
87            document_schemas,
88            schema_defs,
89        } = data_contract_data;
90
91        let document_types = DocumentType::create_document_types_from_document_schemas(
92            id,
93            1,
94            config.version(),
95            document_schemas,
96            schema_defs.as_ref(),
97            &BTreeMap::new(),
98            &config,
99            full_validation,
100            false,
101            validation_operations,
102            platform_version,
103        )?;
104
105        validate_required_since_within_contract_version(&document_types, version)
106            .map_err(consensus_or_protocol_required_fields_error)?;
107
108        let data_contract = DataContractV1 {
109            id,
110            version,
111            owner_id,
112            document_types,
113            config,
114            schema_defs,
115            created_at: None,
116            updated_at: None,
117            created_at_block_height: None,
118            updated_at_block_height: None,
119            created_at_epoch: None,
120            updated_at_epoch: None,
121            groups: Default::default(),
122            tokens: Default::default(),
123            keywords: Default::default(),
124            description: None,
125        };
126
127        Ok(data_contract)
128    }
129
130    pub(in crate::data_contract) fn try_from_platform_versioned_v1(
131        data_contract_data: DataContractInSerializationFormatV1,
132        full_validation: bool,
133        validation_operations: &mut Vec<ProtocolValidationOperation>,
134        platform_version: &PlatformVersion,
135    ) -> Result<Self, ProtocolError> {
136        let DataContractInSerializationFormatV1 {
137            id,
138            config,
139            version,
140            owner_id,
141            document_schemas,
142            schema_defs,
143            created_at,
144            updated_at,
145            created_at_block_height,
146            updated_at_block_height,
147            created_at_epoch,
148            updated_at_epoch,
149            groups,
150            tokens,
151            keywords,
152            description,
153        } = data_contract_data;
154
155        let document_types = DocumentType::create_document_types_from_document_schemas(
156            id,
157            1,
158            config.version(),
159            document_schemas,
160            schema_defs.as_ref(),
161            &tokens,
162            &config,
163            full_validation,
164            !tokens.is_empty(),
165            validation_operations,
166            platform_version,
167        )?;
168
169        validate_required_since_within_contract_version(&document_types, version)
170            .map_err(consensus_or_protocol_required_fields_error)?;
171
172        let data_contract = DataContractV1 {
173            id,
174            version,
175            owner_id,
176            document_types,
177            config,
178            schema_defs,
179            created_at,
180            updated_at,
181            created_at_block_height,
182            updated_at_block_height,
183            created_at_epoch,
184            updated_at_epoch,
185            groups,
186            tokens,
187            keywords: keywords
188                .into_iter()
189                .map(|keyword| keyword.to_lowercase())
190                .collect(),
191            description,
192        };
193
194        Ok(data_contract)
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use crate::data_contract::DataContract;
201    use crate::identity::accessors::IdentityGettersV0;
202    use crate::identity::Identity;
203    use crate::serialization::{
204        PlatformDeserializableWithPotentialValidationFromVersionedStructureUntrusted,
205        PlatformSerializableWithPlatformVersion,
206    };
207    use crate::tests::fixtures::get_data_contract_fixture;
208    use crate::version::PlatformVersion;
209    use platform_version::version::LATEST_PLATFORM_VERSION;
210
211    #[test]
212    #[cfg(feature = "random-identities")]
213    fn data_contract_ser_de() {
214        // V1 of the contract is first present in protocol version 7
215        let platform_version = PlatformVersion::get(7).expect("expected protocol version 7");
216        let identity = Identity::random_identity(5, Some(5), platform_version)
217            .expect("expected a random identity");
218        let contract =
219            get_data_contract_fixture(Some(identity.id()), 0, platform_version.protocol_version)
220                .data_contract_owned();
221        let bytes = contract
222            .serialize_to_bytes_with_platform_version(LATEST_PLATFORM_VERSION)
223            .expect("expected to serialize");
224        let recovered_contract =
225            DataContract::versioned_deserialize_untrusted(&bytes, false, platform_version)
226                .expect("expected to deserialize state transition");
227        assert_eq!(contract, recovered_contract);
228    }
229
230    #[test]
231    #[cfg(feature = "random-identities")]
232    fn data_contract_v1_serde_json_roundtrip() {
233        use crate::data_contract::accessors::v0::DataContractV0Getters;
234        use crate::data_contract::DataContractV1;
235
236        // V1 contracts are first produced at platform version 9
237        let platform_version = PlatformVersion::get(9).expect("expected protocol version 9");
238        let identity = Identity::random_identity(5, Some(5), platform_version)
239            .expect("expected a random identity");
240        let contract =
241            get_data_contract_fixture(Some(identity.id()), 0, platform_version.protocol_version)
242                .data_contract_owned();
243        let v1 = contract.into_v1().expect("expected V1 contract");
244
245        let json = serde_json::to_string(&v1).expect("expected to serialize to JSON");
246        let recovered: DataContractV1 =
247            serde_json::from_str(&json).expect("expected to deserialize from JSON");
248
249        // Schema normalization during deserialization means full equality may differ;
250        // verify stable identity fields to confirm a successful roundtrip.
251        assert_eq!(v1.id(), recovered.id());
252        assert_eq!(v1.owner_id(), recovered.owner_id());
253        assert_eq!(v1.version(), recovered.version());
254    }
255
256    /// A `secret` document type whose `encryptedMessage` carries `encryptedFor`
257    /// when `encrypted` is set, and the same type without the keyword otherwise.
258    #[cfg(feature = "random-identities")]
259    fn secret_schema(encrypted: bool) -> platform_value::Value {
260        use platform_value::platform_value;
261
262        let mut encrypted_message = platform_value!({
263            "type": "array",
264            "byteArray": true,
265            "minItems": 32,
266            "maxItems": 1040,
267            "position": 3
268        });
269        if encrypted {
270            encrypted_message
271                .insert(
272                    "encryptedFor".to_string(),
273                    platform_value!({
274                        "recipient": "recipientId",
275                        "recipientKey": "recipientKeyId",
276                        "senderKey": "senderKeyId",
277                        "scheme": "ecdh-secp256k1-aes256-cbc"
278                    }),
279                )
280                .expect("expected to insert encryptedFor");
281        }
282        platform_value!({
283            "type": "object",
284            "properties": {
285                "recipientId": {
286                    "type": "array",
287                    "byteArray": true,
288                    "minItems": 32,
289                    "maxItems": 32,
290                    "contentMediaType": "application/x.dash.dpp.identifier",
291                    "position": 0
292                },
293                "recipientKeyId": { "type": "integer", "minimum": 0, "maximum": 4294967295_u64, "position": 1 },
294                "senderKeyId": { "type": "integer", "minimum": 0, "maximum": 4294967295_u64, "position": 2 },
295                "encryptedMessage": encrypted_message
296            },
297            "required": ["recipientId", "recipientKeyId", "senderKeyId", "encryptedMessage"],
298            "additionalProperties": false
299        })
300    }
301
302    /// The declaration lives in the document schema the contract serializes, so
303    /// a contract carrying it round-trips like any other and comes back with
304    /// the declaration parsed; one without it serializes exactly as before.
305    #[test]
306    #[cfg(feature = "random-identities")]
307    fn should_round_trip_a_contract_with_and_without_encrypted_for_through_platform_serialization()
308    {
309        use crate::data_contract::accessors::v0::DataContractV0Getters;
310        use crate::data_contract::document_type::accessors::DocumentTypeV0Getters;
311        use crate::data_contract::document_type::{
312            EncryptedFor, EncryptedForRecipient, EncryptionScheme,
313        };
314        use crate::data_contract::schema::DataContractSchemaMethodsV0;
315
316        let platform_version = PlatformVersion::latest();
317        let identity = Identity::random_identity(5, Some(5), platform_version)
318            .expect("expected a random identity");
319
320        for encrypted in [true, false] {
321            let mut contract = get_data_contract_fixture(
322                Some(identity.id()),
323                0,
324                platform_version.protocol_version,
325            )
326            .data_contract_owned();
327            contract
328                .set_document_schema(
329                    "secret",
330                    secret_schema(encrypted),
331                    true,
332                    &mut Vec::new(),
333                    platform_version,
334                )
335                .expect("expected to add the secret document type");
336
337            let bytes = contract
338                .serialize_to_bytes_with_platform_version(platform_version)
339                .expect("expected to serialize");
340            let recovered =
341                DataContract::versioned_deserialize_untrusted(&bytes, true, platform_version)
342                    .expect("expected to deserialize");
343            assert_eq!(contract, recovered);
344
345            let secret_type = recovered
346                .document_type_for_name("secret")
347                .expect("expected the secret document type");
348            let encrypted_properties = secret_type.encrypted_properties();
349            if encrypted {
350                assert_eq!(
351                    encrypted_properties,
352                    vec![(
353                        &"encryptedMessage".to_string(),
354                        &EncryptedFor {
355                            recipient: EncryptedForRecipient::Property("recipientId".to_string()),
356                            recipient_key: "recipientKeyId".to_string(),
357                            sender_key: "senderKeyId".to_string(),
358                            scheme: EncryptionScheme::EcdhSecp256k1Aes256Cbc,
359                        }
360                    )]
361                );
362            } else {
363                assert!(encrypted_properties.is_empty());
364            }
365        }
366    }
367}