diff --git a/packages/dpns-contract/schema/v1/dpns-contract-documents.json b/packages/dpns-contract/schema/v1/dpns-contract-documents.json index 973d7fe9366..723d522d91b 100644 --- a/packages/dpns-contract/schema/v1/dpns-contract-documents.json +++ b/packages/dpns-contract/schema/v1/dpns-contract-documents.json @@ -124,6 +124,9 @@ "records", "subdomainRules" ], + "transient": [ + "preorderSalt" + ], "additionalProperties": false, "$comment": "In order to register a domain you need to create a preorder. The preorder step is needed to prevent man-in-the-middle attacks. normalizedLabel + '.' + normalizedParentDomain must not be longer than 253 chars length as defined by RFC 1035. Domain documents are immutable: modification and deletion are restricted" }, diff --git a/packages/platform-test-suite/test/e2e/dpns.spec.js b/packages/platform-test-suite/test/e2e/dpns.spec.js index dd3dc06c1b7..53c88a5e51c 100644 --- a/packages/platform-test-suite/test/e2e/dpns.spec.js +++ b/packages/platform-test-suite/test/e2e/dpns.spec.js @@ -220,14 +220,28 @@ describe('DPNS', () => { const rawDocument = documents[0].toObject(); delete rawDocument.$createdAt; + delete rawDocument.$createdAtCoreBlockHeight; + delete rawDocument.$createdAtBlockHeight; delete rawDocument.$updatedAt; + delete rawDocument.$updatedAtCoreBlockHeight; + delete rawDocument.$updatedAtBlockHeight; delete rawDocument.$transferredAt; + delete rawDocument.$transferredAtCoreBlockHeight; + delete rawDocument.$transferredAtBlockHeight; + delete rawDocument.preorderSalt; const rawRegisteredDomain = registeredDomain.toObject(); delete rawRegisteredDomain.$createdAt; + delete rawRegisteredDomain.$createdAtCoreBlockHeight; + delete rawRegisteredDomain.$createdAtBlockHeight; delete rawRegisteredDomain.$updatedAt; + delete rawRegisteredDomain.$updatedAtCoreBlockHeight; + delete rawRegisteredDomain.$updatedAtBlockHeight; delete rawRegisteredDomain.$transferredAt; + delete rawRegisteredDomain.$transferredAtCoreBlockHeight; + delete rawRegisteredDomain.$transferredAtBlockHeight; + delete rawRegisteredDomain.preorderSalt; expect(rawDocument).to.deep.equal(rawRegisteredDomain); }); @@ -238,14 +252,28 @@ describe('DPNS', () => { const rawDocument = document.toObject(); delete rawDocument.$createdAt; + delete rawDocument.$createdAtCoreBlockHeight; + delete rawDocument.$createdAtBlockHeight; delete rawDocument.$updatedAt; + delete rawDocument.$updatedAtCoreBlockHeight; + delete rawDocument.$updatedAtBlockHeight; delete rawDocument.$transferredAt; + delete rawDocument.$transferredAtCoreBlockHeight; + delete rawDocument.$transferredAtBlockHeight; + delete rawDocument.preorderSalt; const rawRegisteredDomain = registeredDomain.toObject(); delete rawRegisteredDomain.$createdAt; + delete rawRegisteredDomain.$createdAtCoreBlockHeight; + delete rawRegisteredDomain.$createdAtBlockHeight; delete rawRegisteredDomain.$updatedAt; + delete rawRegisteredDomain.$updatedAtCoreBlockHeight; + delete rawRegisteredDomain.$updatedAtBlockHeight; delete rawRegisteredDomain.$transferredAt; + delete rawRegisteredDomain.$transferredAtCoreBlockHeight; + delete rawRegisteredDomain.$transferredAtBlockHeight; + delete rawRegisteredDomain.preorderSalt; expect(rawDocument).to.deep.equal(rawRegisteredDomain); }); @@ -259,14 +287,28 @@ describe('DPNS', () => { const rawDocument = document.toObject(); delete rawDocument.$createdAt; + delete rawDocument.$createdAtCoreBlockHeight; + delete rawDocument.$createdAtBlockHeight; delete rawDocument.$updatedAt; + delete rawDocument.$updatedAtCoreBlockHeight; + delete rawDocument.$updatedAtBlockHeight; delete rawDocument.$transferredAt; + delete rawDocument.$transferredAtCoreBlockHeight; + delete rawDocument.$transferredAtBlockHeight; + delete rawDocument.preorderSalt; const rawRegisteredDomain = registeredDomain.toObject(); delete rawRegisteredDomain.$createdAt; + delete rawRegisteredDomain.$createdAtCoreBlockHeight; + delete rawRegisteredDomain.$createdAtBlockHeight; delete rawRegisteredDomain.$updatedAt; + delete rawRegisteredDomain.$updatedAtCoreBlockHeight; + delete rawRegisteredDomain.$updatedAtBlockHeight; delete rawRegisteredDomain.$transferredAt; + delete rawRegisteredDomain.$transferredAtCoreBlockHeight; + delete rawRegisteredDomain.$transferredAtBlockHeight; + delete rawRegisteredDomain.preorderSalt; expect(rawDocument).to.deep.equal(rawRegisteredDomain); }); diff --git a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json index 5afb60c77bb..80d20a3f775 100644 --- a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json +++ b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json @@ -506,6 +506,12 @@ "minProperties": 1, "maxProperties": 100 }, + "transient": { + "type": "array", + "items": { + "type": "string" + } + }, "additionalProperties": { "type": "boolean", "const": false diff --git a/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs b/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs index 004044bed5a..377b852e25d 100644 --- a/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs @@ -77,6 +77,12 @@ impl DocumentTypeV0Getters for DocumentType { } } + fn transient_fields(&self) -> &BTreeSet { + match self { + DocumentType::V0(v0) => v0.transient_fields(), + } + } + fn documents_keep_history(&self) -> bool { match self { DocumentType::V0(v0) => v0.documents_keep_history(), @@ -205,6 +211,12 @@ impl<'a> DocumentTypeV0Getters for DocumentTypeRef<'a> { } } + fn transient_fields(&self) -> &BTreeSet { + match self { + DocumentTypeRef::V0(v0) => v0.transient_fields(), + } + } + fn documents_keep_history(&self) -> bool { match self { DocumentTypeRef::V0(v0) => v0.documents_keep_history(), @@ -333,6 +345,12 @@ impl<'a> DocumentTypeV0Getters for DocumentTypeMutRef<'a> { } } + fn transient_fields(&self) -> &BTreeSet { + match self { + DocumentTypeMutRef::V0(v0) => v0.transient_fields(), + } + } + fn documents_keep_history(&self) -> bool { match self { DocumentTypeMutRef::V0(v0) => v0.documents_keep_history(), diff --git a/packages/rs-dpp/src/data_contract/document_type/accessors/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/accessors/v0/mod.rs index 77c4aaf8966..ad134b558cb 100644 --- a/packages/rs-dpp/src/data_contract/document_type/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/accessors/v0/mod.rs @@ -44,6 +44,10 @@ pub trait DocumentTypeV0Getters { /// Returns the required fields of the document type. fn required_fields(&self) -> &BTreeSet; + /// Returns the transient fields of the document type. + /// Transient fields are fields that should be stripped from the document before storage. + fn transient_fields(&self) -> &BTreeSet; + /// Returns the documents keep history flag of the document type. fn documents_keep_history(&self) -> bool; diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs index cab211ddc31..82019a53e77 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs @@ -251,6 +251,13 @@ impl DocumentTypeV0 { property_names::REQUIRED, ); + let transient_fields = Value::inner_recursive_optional_array_of_strings( + schema_map, + "".to_string(), + property_names::PROPERTIES, + property_names::TRANSIENT, + ); + // Based on the property name, determine the type for (property_key, property_value) in property_values { // TODO: It's very inefficient. It must be done in one iteration and flattened properties @@ -258,6 +265,7 @@ impl DocumentTypeV0 { insert_values( &mut flattened_document_properties, &required_fields, + &transient_fields, None, property_key.clone(), property_value, @@ -268,6 +276,7 @@ impl DocumentTypeV0 { insert_values_nested( &mut document_properties, &required_fields, + &transient_fields, property_key, property_value, &root_schema, @@ -558,6 +567,7 @@ impl DocumentTypeV0 { identifier_paths, binary_paths, required_fields, + transient_fields, documents_keep_history, documents_mutable, documents_can_be_deleted, @@ -577,6 +587,7 @@ impl DocumentTypeV0 { fn insert_values( document_properties: &mut IndexMap, known_required: &BTreeSet, + known_transient: &BTreeSet, prefix: Option, property_key: String, property_value: &Value, @@ -602,6 +613,7 @@ fn insert_values( let type_value = inner_properties.get_str(property_names::TYPE)?; let is_required = known_required.contains(&prefixed_property_key); + let is_transient = known_transient.contains(&prefixed_property_key); let field_type: DocumentPropertyType; match type_value { @@ -645,6 +657,7 @@ fn insert_values( DocumentProperty { property_type: field_type, required: is_required, + transient: is_transient, }, ); } @@ -686,6 +699,7 @@ fn insert_values( DocumentProperty { property_type: field_type, required: is_required, + transient: is_transient, }, ); } @@ -698,6 +712,7 @@ fn insert_values( DocumentProperty { property_type: field_type, required: is_required, + transient: is_transient, }, ); } @@ -709,6 +724,7 @@ fn insert_values( fn insert_values_nested( document_properties: &mut IndexMap, known_required: &BTreeSet, + known_transient: &BTreeSet, property_key: String, property_value: &Value, root_schema: &Value, @@ -725,6 +741,8 @@ fn insert_values_nested( let is_required = known_required.contains(&property_key); + let is_transient = known_transient.contains(&property_key); + let field_type = match type_value { "integer" => DocumentPropertyType::I64, "number" => DocumentPropertyType::F64, @@ -800,6 +818,17 @@ fn insert_values_nested( }) .collect(); + let stripped_transient: BTreeSet = known_transient + .iter() + .filter_map(|key| { + if key.starts_with(&property_key) && key.len() > property_key.len() { + Some(key[property_key.len() + 1..].to_string()) + } else { + None + } + }) + .collect(); + for (object_property_key, object_property_value) in properties.iter() { let object_property_string = object_property_key .as_text() @@ -811,6 +840,7 @@ fn insert_values_nested( insert_values_nested( &mut nested_properties, &stripped_required, + &stripped_transient, object_property_string, object_property_value, root_schema, @@ -822,6 +852,7 @@ fn insert_values_nested( DocumentProperty { property_type: DocumentPropertyType::Object(nested_properties), required: is_required, + transient: is_transient, }, ); return Ok(()); @@ -834,6 +865,7 @@ fn insert_values_nested( DocumentProperty { property_type: field_type, required: is_required, + transient: is_transient, }, ); diff --git a/packages/rs-dpp/src/data_contract/document_type/mod.rs b/packages/rs-dpp/src/data_contract/document_type/mod.rs index 182bfe881a8..89143a89a5e 100644 --- a/packages/rs-dpp/src/data_contract/document_type/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/mod.rs @@ -47,6 +47,7 @@ mod property_names { pub const PROPERTIES: &str = "properties"; pub const POSITION: &str = "position"; pub const REQUIRED: &str = "required"; + pub const TRANSIENT: &str = "transient"; pub const TYPE: &str = "type"; pub const REF: &str = "$ref"; pub const CREATED_AT: &str = "$createdAt"; diff --git a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs index 63a3f390d8d..927fb561a1a 100644 --- a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs @@ -26,6 +26,7 @@ pub mod array; pub struct DocumentProperty { pub property_type: DocumentPropertyType, pub required: bool, + pub transient: bool, } #[derive(Debug, PartialEq, Clone, Serialize)] diff --git a/packages/rs-dpp/src/data_contract/document_type/v0/accessors.rs b/packages/rs-dpp/src/data_contract/document_type/v0/accessors.rs index 90f8fb61976..39306888028 100644 --- a/packages/rs-dpp/src/data_contract/document_type/v0/accessors.rs +++ b/packages/rs-dpp/src/data_contract/document_type/v0/accessors.rs @@ -61,6 +61,9 @@ impl DocumentTypeV0Getters for DocumentTypeV0 { fn required_fields(&self) -> &BTreeSet { &self.required_fields } + fn transient_fields(&self) -> &BTreeSet { + &self.transient_fields + } fn documents_keep_history(&self) -> bool { self.documents_keep_history diff --git a/packages/rs-dpp/src/data_contract/document_type/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/v0/mod.rs index 6cc38eb9285..9951c2b638b 100644 --- a/packages/rs-dpp/src/data_contract/document_type/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/v0/mod.rs @@ -43,6 +43,8 @@ pub struct DocumentTypeV0 { pub(in crate::data_contract) binary_paths: BTreeSet, /// The required fields on the document type pub(in crate::data_contract) required_fields: BTreeSet, + /// The transient fields on the document type + pub(in crate::data_contract) transient_fields: BTreeSet, /// Should documents keep history? pub(in crate::data_contract) documents_keep_history: bool, /// Are documents mutable? diff --git a/packages/rs-dpp/src/data_contract/document_type/v0/random_document_type.rs b/packages/rs-dpp/src/data_contract/document_type/v0/random_document_type.rs index 78d9c08f2e9..767df9dba91 100644 --- a/packages/rs-dpp/src/data_contract/document_type/v0/random_document_type.rs +++ b/packages/rs-dpp/src/data_contract/document_type/v0/random_document_type.rs @@ -196,6 +196,7 @@ impl DocumentTypeV0 { DocumentProperty { property_type: document_type, required, + transient: false, } }; @@ -432,6 +433,7 @@ impl DocumentTypeV0 { identifier_paths, binary_paths, required_fields, + transient_fields: Default::default(), documents_keep_history, documents_mutable, documents_can_be_deleted, @@ -520,6 +522,7 @@ impl DocumentTypeV0 { DocumentProperty { property_type: document_type, required, + transient: false, } }; @@ -605,6 +608,7 @@ impl DocumentTypeV0 { identifier_paths, binary_paths, required_fields, + transient_fields: Default::default(), documents_keep_history, documents_mutable, documents_can_be_deleted, diff --git a/packages/rs-dpp/src/document/v0/serialize.rs b/packages/rs-dpp/src/document/v0/serialize.rs index a26879d9ff9..0083216a12f 100644 --- a/packages/rs-dpp/src/document/v0/serialize.rs +++ b/packages/rs-dpp/src/document/v0/serialize.rs @@ -212,7 +212,7 @@ impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { .try_for_each(|(field_name, property)| { if let Some(value) = self.properties.get(field_name) { if value.is_null() { - if property.required { + if property.required && !property.transient { Err(ProtocolError::DataContractError( DataContractError::MissingRequiredKey( "a required field is not present".to_string(), @@ -225,7 +225,7 @@ impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { Ok(()) } } else { - if !property.required { + if !property.required || property.transient { // dbg!("we added 1", field_name); buffer.push(1); } @@ -236,7 +236,7 @@ impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { buffer.extend(value.as_slice()); Ok(()) } - } else if property.required { + } else if property.required && !property.transient { Err(ProtocolError::DataContractError( DataContractError::MissingRequiredKey(format!( "a required field {field_name} is not present" @@ -441,7 +441,7 @@ impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { Ok(()) } } else { - if !property.required { + if !property.required || property.transient { // dbg!("we added 1", field_name); buffer.push(1); } @@ -452,7 +452,7 @@ impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { buffer.extend(value.as_slice()); Ok(()) } - } else if property.required { + } else if property.required && !property.transient { Err(ProtocolError::DataContractError( DataContractError::MissingRequiredKey(format!( "a required field {field_name} is not present" @@ -644,7 +644,7 @@ impl DocumentPlatformDeserializationMethodsV0 for DocumentV0 { .iter() .filter_map(|(key, property)| { if finished_buffer { - return if property.required { + return if property.required && !property.transient { Some(Err(DataContractError::CorruptedSerialization( "required field after finished buffer".to_string(), ))) @@ -654,7 +654,7 @@ impl DocumentPlatformDeserializationMethodsV0 for DocumentV0 { } let read_value = property .property_type - .read_optionally_from(&mut buf, property.required); + .read_optionally_from(&mut buf, property.required & !property.transient); match read_value { Ok(read_value) => { diff --git a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v0/mod.rs index cab90f7575b..42bca824834 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v0/mod.rs @@ -294,7 +294,7 @@ mod tests { assert_eq!( hex::encode(root_hash), - "edc1f51a2725e13e02f0a5dd9aad1b3de07872c9cd03e83388a10b0016a27f86" + "6cf0ca2eea48431b1df389a2d360fa1e9889c34f05248a7159b4e45c818fb80d" ) } } diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs index 8ce28932b4b..651b4fa9c67 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -1194,7 +1194,7 @@ mod tests { .unwrap() .unwrap() ), - "5d71ec297867cf1e9b6655451385b0c72b56d582abc1576885a78e5754ac5f14".to_string() + "2f5fd65cc5392f9be3a8ceab38f2e284a0c1ccd87b744d3d392ac83a97d0f4b9".to_string() ) } @@ -1915,7 +1915,7 @@ mod tests { .unwrap() .unwrap() ), - "36116eb13b2dfada15853aa54f9c81ebb2590aa99d512c12604c4bd5d622c49a".to_string() + "85114fce944f2583751d38cb427571e00fc957f2fd6f37a355298e697370c143".to_string() ) } @@ -2050,7 +2050,7 @@ mod tests { .unwrap() .unwrap() ), - "ee6682a2acd816d3277c55b5886199029ed33c13dc902ea8efab3ba7f20271e2".to_string() + "b563c5583d8aae38d0cd841c956c44b029705b915af89416d20a3a20f54bfc39".to_string() ) } diff --git a/packages/rs-drive-abci/tests/strategy_tests/voting_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/voting_tests.rs index 0244fe3f132..9e1f71fa528 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/voting_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/voting_tests.rs @@ -292,12 +292,12 @@ mod tests { assert_eq!( first_contender.document.as_ref().map(hex::encode), - Some("00177f2479090a0286a67d6a1f67b563b51518edd6eea0461829f7d630fd65708d29124be7e86f97e959894a67a9cc078c3e0106d4bfcfbf34bc403a4f099925b401000700000187690895980000018769089598000001876908959800077175616e74756d077175616e74756d00046461736861e49b7e1e1286c69aefdb8b33243524d4c6b48b2e5782b834ae77b0c033eff5210129124be7e86f97e959894a67a9cc078c3e0106d4bfcfbf34bc403a4f099925b40101".to_string()) + Some("00177f2479090a0286a67d6a1f67b563b51518edd6eea0461829f7d630fd65708d29124be7e86f97e959894a67a9cc078c3e0106d4bfcfbf34bc403a4f099925b401000700000187690895980000018769089598000001876908959800077175616e74756d077175616e74756d00046461736800210129124be7e86f97e959894a67a9cc078c3e0106d4bfcfbf34bc403a4f099925b40101".to_string()) ); assert_eq!( second_contender.document.as_ref().map(hex::encode), - Some("00490e212593a1d3cc6ae17bf107ab9cb465175e7877fcf7d085ed2fce27be11d68b8948a6801501bbe0431e3d994dcf71cf5a2a0939fe51b0e600076199aba4fb01000700000187690895980000018769089598000001876908959800077175616e74756d077175616e74756d00046461736894be348633f0cdceeb5548e4816963ad9e2ff06432d56d7f521ad902b8e5ac8121018b8948a6801501bbe0431e3d994dcf71cf5a2a0939fe51b0e600076199aba4fb0100".to_string()) + Some("00490e212593a1d3cc6ae17bf107ab9cb465175e7877fcf7d085ed2fce27be11d68b8948a6801501bbe0431e3d994dcf71cf5a2a0939fe51b0e600076199aba4fb01000700000187690895980000018769089598000001876908959800077175616e74756d077175616e74756d0004646173680021018b8948a6801501bbe0431e3d994dcf71cf5a2a0939fe51b0e600076199aba4fb0100".to_string()) ); assert_eq!(first_contender.identifier, identity2_id.to_vec()); diff --git a/packages/rs-drive/src/state_transition_action/document/documents_batch/document_transition/document_create_transition_action/v0/mod.rs b/packages/rs-drive/src/state_transition_action/document/documents_batch/document_transition/document_create_transition_action/v0/mod.rs index 10ffaaebb92..e61fbb73c9f 100644 --- a/packages/rs-drive/src/state_transition_action/document/documents_batch/document_transition/document_create_transition_action/v0/mod.rs +++ b/packages/rs-drive/src/state_transition_action/document/documents_batch/document_transition/document_create_transition_action/v0/mod.rs @@ -133,6 +133,8 @@ impl DocumentFromCreateTransitionActionV0 for Document { .. } = v0; + let mut data = data.clone(); + match base { DocumentBaseTransitionAction::V0(base_v0) => { let DocumentBaseTransitionActionV0 { @@ -148,6 +150,12 @@ impl DocumentFromCreateTransitionActionV0 for Document { let required_fields = document_type.required_fields(); + let transient_fields = document_type.transient_fields(); + + if !transient_fields.is_empty() { + data.retain(|key, _| !transient_fields.contains(key)); + } + let is_created_at_required = required_fields.contains(CREATED_AT); let is_updated_at_required = required_fields.contains(UPDATED_AT); let is_transferred_at_required = required_fields.contains(TRANSFERRED_AT); @@ -174,7 +182,7 @@ impl DocumentFromCreateTransitionActionV0 for Document { 0 => Ok(DocumentV0 { id: *id, owner_id, - properties: data.clone(), + properties: data, revision: document_type.initial_revision(), created_at: if is_created_at_required { Some(block_info.time_ms) @@ -242,7 +250,7 @@ impl DocumentFromCreateTransitionActionV0 for Document { let DocumentCreateTransitionActionV0 { base, block_info, - data, + mut data, .. } = v0; @@ -261,6 +269,12 @@ impl DocumentFromCreateTransitionActionV0 for Document { let required_fields = document_type.required_fields(); + let transient_fields = document_type.transient_fields(); + + if !transient_fields.is_empty() { + data.retain(|key, _| !transient_fields.contains(key)); + } + let is_created_at_required = required_fields.contains(CREATED_AT); let is_updated_at_required = required_fields.contains(UPDATED_AT); let is_transferred_at_required = required_fields.contains(TRANSFERRED_AT);