From cd1527d3ea54a7bcf0d51a036cd453859050c35b Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Fri, 17 Jan 2025 19:39:37 +0700 Subject: [PATCH] fix(dpp)!: wrapping overflow issue (#2430) --- .../methods/estimated_size/v0/mod.rs | 35 +++- .../document_type/methods/max_size/v0/mod.rs | 32 ++- .../document_type/methods/mod.rs | 4 +- .../document_type/property/mod.rs | 195 ++++++++++-------- .../v0/mod.rs | 2 +- .../v0/mod.rs | 2 +- .../v0/mod.rs | 2 +- .../v0/mod.rs | 2 +- .../v0/mod.rs | 2 +- .../v0/mod.rs | 2 +- .../util/object_size_info/document_info.rs | 21 +- 11 files changed, 182 insertions(+), 117 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/document_type/methods/estimated_size/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/methods/estimated_size/v0/mod.rs index fcb8bfb81ad..55e6065f9ea 100644 --- a/packages/rs-dpp/src/data_contract/document_type/methods/estimated_size/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/methods/estimated_size/v0/mod.rs @@ -1,19 +1,32 @@ use crate::data_contract::document_type::v0::DocumentTypeV0; - +use crate::ProtocolError; +use platform_version::version::PlatformVersion; // If another document type (like V1) ever were to exist we would need to implement estimated_size_v0 again impl DocumentTypeV0 { /// The estimated size uses the middle ceil size of all attributes - pub(in crate::data_contract::document_type) fn estimated_size_v0(&self) -> u16 { - let mut iter = self - .flattened_properties - .iter() - .filter_map(|(_, document_property)| { - document_property.property_type.middle_byte_size_ceil() - }); - let first = Some(iter.next().unwrap_or_default()); + pub(in crate::data_contract::document_type) fn estimated_size_v0( + &self, + platform_version: &PlatformVersion, + ) -> Result { + let mut total_size = 0u16; + + for (_, document_property) in self.flattened_properties.iter() { + // This call now returns a Result, ProtocolError>. + let maybe_size = document_property + .property_type + .middle_byte_size_ceil(platform_version)?; + + if let Some(size) = maybe_size { + total_size = match total_size.checked_add(size) { + Some(new_total) => new_total, + None => { + return Ok(u16::MAX); + } + }; + } + } - iter.fold(first, |acc, item| acc.and_then(|acc| acc.checked_add(item))) - .unwrap_or(u16::MAX) + Ok(total_size) } } diff --git a/packages/rs-dpp/src/data_contract/document_type/methods/max_size/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/methods/max_size/v0/mod.rs index ce8ffbd4394..346a7b5cf98 100644 --- a/packages/rs-dpp/src/data_contract/document_type/methods/max_size/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/methods/max_size/v0/mod.rs @@ -1,16 +1,30 @@ use crate::data_contract::document_type::v0::DocumentTypeV0; - +use crate::ProtocolError; +use platform_version::version::PlatformVersion; // If another document type (like V1) ever were to exist we would need to implement max_size_v0 again impl DocumentTypeV0 { - pub(in crate::data_contract::document_type) fn max_size_v0(&self) -> u16 { - let mut iter = self - .flattened_properties - .iter() - .filter_map(|(_, document_property)| document_property.property_type.max_byte_size()); - let first = Some(iter.next().unwrap_or_default()); + pub(in crate::data_contract::document_type) fn max_size_v0( + &self, + platform_version: &PlatformVersion, + ) -> Result { + let mut total_size = 0u16; + + for (_, document_property) in self.flattened_properties.iter() { + let maybe_size = document_property + .property_type + .max_byte_size(platform_version)?; + + if let Some(size) = maybe_size { + total_size = match total_size.checked_add(size) { + Some(new_total) => new_total, + None => { + return Ok(u16::MAX); + } + }; + } + } - iter.fold(first, |acc, item| acc.and_then(|acc| acc.checked_add(item))) - .unwrap_or(u16::MAX) + Ok(total_size) } } diff --git a/packages/rs-dpp/src/data_contract/document_type/methods/mod.rs b/packages/rs-dpp/src/data_contract/document_type/methods/mod.rs index 1c05a453046..d158336994f 100644 --- a/packages/rs-dpp/src/data_contract/document_type/methods/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/methods/mod.rs @@ -222,7 +222,7 @@ impl DocumentTypeV0Methods for DocumentTypeV0 { .methods .max_size { - 0 => Ok(self.max_size_v0()), + 0 => self.max_size_v0(platform_version), version => Err(ProtocolError::UnknownVersionMismatch { method: "max_size".to_string(), known_versions: vec![0], @@ -239,7 +239,7 @@ impl DocumentTypeV0Methods for DocumentTypeV0 { .methods .estimated_size { - 0 => Ok(self.estimated_size_v0()), + 0 => self.estimated_size_v0(platform_version), version => Err(ProtocolError::UnknownVersionMismatch { method: "estimated_size".to_string(), known_versions: vec![0], 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 927fb561a1a..393fb89980f 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 @@ -12,6 +12,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use indexmap::IndexMap; use integer_encoding::{VarInt, VarIntReader}; use platform_value::{Identifier, Value}; +use platform_version::version::PlatformVersion; use rand::distributions::{Alphanumeric, Standard}; use rand::rngs::StdRng; use rand::Rng; @@ -159,69 +160,93 @@ impl DocumentPropertyType { } } - pub fn min_byte_size(&self) -> Option { + pub fn min_byte_size( + &self, + platform_version: &PlatformVersion, + ) -> Result, ProtocolError> { match self { - DocumentPropertyType::U128 => Some(16), - DocumentPropertyType::I128 => Some(16), - DocumentPropertyType::U64 => Some(8), - DocumentPropertyType::I64 => Some(8), - DocumentPropertyType::U32 => Some(4), - DocumentPropertyType::I32 => Some(4), - DocumentPropertyType::U16 => Some(2), - DocumentPropertyType::I16 => Some(2), - DocumentPropertyType::U8 => Some(1), - DocumentPropertyType::I8 => Some(1), - DocumentPropertyType::F64 => Some(8), + DocumentPropertyType::U128 => Ok(Some(16)), + DocumentPropertyType::I128 => Ok(Some(16)), + DocumentPropertyType::U64 => Ok(Some(8)), + DocumentPropertyType::I64 => Ok(Some(8)), + DocumentPropertyType::U32 => Ok(Some(4)), + DocumentPropertyType::I32 => Ok(Some(4)), + DocumentPropertyType::U16 => Ok(Some(2)), + DocumentPropertyType::I16 => Ok(Some(2)), + DocumentPropertyType::U8 => Ok(Some(1)), + DocumentPropertyType::I8 => Ok(Some(1)), + DocumentPropertyType::F64 => Ok(Some(8)), DocumentPropertyType::String(sizes) => match sizes.min_length { - None => Some(0), - Some(size) => Some(size * 4), + None => Ok(Some(0)), + Some(size) => { + if platform_version.protocol_version > 8 { + match size.checked_mul(4) { + Some(mul) => Ok(Some(mul)), + None => Err(ProtocolError::Overflow("min_byte_size overflow")), + } + } else { + Ok(Some(size.wrapping_mul(4))) + } + } }, DocumentPropertyType::ByteArray(sizes) => match sizes.min_size { - None => Some(0), - Some(size) => Some(size), + None => Ok(Some(0)), + Some(size) => Ok(Some(size)), }, - DocumentPropertyType::Boolean => Some(1), - DocumentPropertyType::Date => Some(8), + DocumentPropertyType::Boolean => Ok(Some(1)), + DocumentPropertyType::Date => Ok(Some(8)), DocumentPropertyType::Object(sub_fields) => sub_fields .iter() - .map(|(_, sub_field)| sub_field.property_type.min_byte_size()) + .map(|(_, sub_field)| sub_field.property_type.min_byte_size(platform_version)) .sum(), - DocumentPropertyType::Array(_) => None, - DocumentPropertyType::VariableTypeArray(_) => None, - DocumentPropertyType::Identifier => Some(32), + DocumentPropertyType::Array(_) => Ok(None), + DocumentPropertyType::VariableTypeArray(_) => Ok(None), + DocumentPropertyType::Identifier => Ok(Some(32)), } } - pub fn max_byte_size(&self) -> Option { + pub fn max_byte_size( + &self, + platform_version: &PlatformVersion, + ) -> Result, ProtocolError> { match self { - DocumentPropertyType::U128 => Some(16), - DocumentPropertyType::I128 => Some(16), - DocumentPropertyType::U64 => Some(8), - DocumentPropertyType::I64 => Some(8), - DocumentPropertyType::U32 => Some(4), - DocumentPropertyType::I32 => Some(4), - DocumentPropertyType::U16 => Some(2), - DocumentPropertyType::I16 => Some(2), - DocumentPropertyType::U8 => Some(1), - DocumentPropertyType::I8 => Some(1), - DocumentPropertyType::F64 => Some(8), + DocumentPropertyType::U128 => Ok(Some(16)), + DocumentPropertyType::I128 => Ok(Some(16)), + DocumentPropertyType::U64 => Ok(Some(8)), + DocumentPropertyType::I64 => Ok(Some(8)), + DocumentPropertyType::U32 => Ok(Some(4)), + DocumentPropertyType::I32 => Ok(Some(4)), + DocumentPropertyType::U16 => Ok(Some(2)), + DocumentPropertyType::I16 => Ok(Some(2)), + DocumentPropertyType::U8 => Ok(Some(1)), + DocumentPropertyType::I8 => Ok(Some(1)), + DocumentPropertyType::F64 => Ok(Some(8)), DocumentPropertyType::String(sizes) => match sizes.max_length { - None => Some(u16::MAX), - Some(size) => Some(size * 4), + None => Ok(Some(u16::MAX)), + Some(size) => { + if platform_version.protocol_version > 8 { + match size.checked_mul(4) { + Some(mul) => Ok(Some(mul)), + None => Err(ProtocolError::Overflow("max_byte_size overflow")), + } + } else { + Ok(Some(size.wrapping_mul(4))) + } + } }, DocumentPropertyType::ByteArray(sizes) => match sizes.max_size { - None => Some(u16::MAX), - Some(size) => Some(size), + None => Ok(Some(u16::MAX)), + Some(size) => Ok(Some(size)), }, - DocumentPropertyType::Boolean => Some(1), - DocumentPropertyType::Date => Some(8), + DocumentPropertyType::Boolean => Ok(Some(1)), + DocumentPropertyType::Date => Ok(Some(8)), DocumentPropertyType::Object(sub_fields) => sub_fields .iter() - .map(|(_, sub_field)| sub_field.property_type.max_byte_size()) + .map(|(_, sub_field)| sub_field.property_type.max_byte_size(platform_version)) .sum(), - DocumentPropertyType::Array(_) => None, - DocumentPropertyType::VariableTypeArray(_) => None, - DocumentPropertyType::Identifier => Some(32), + DocumentPropertyType::Array(_) => Ok(None), + DocumentPropertyType::VariableTypeArray(_) => Ok(None), + DocumentPropertyType::Identifier => Ok(Some(32)), } } @@ -259,60 +284,66 @@ impl DocumentPropertyType { } /// The middle size rounded down halfway between min and max size - pub fn middle_size(&self) -> Option { - match self { - DocumentPropertyType::Array(_) | DocumentPropertyType::VariableTypeArray(_) => { - return None - } - _ => {} + pub fn middle_size(&self, platform_version: &PlatformVersion) -> Option { + let min_size = self.min_size()?; + let max_size = self.max_size()?; + if platform_version.protocol_version > 8 { + Some(((min_size as u32 + max_size as u32) / 2) as u16) + } else { + Some(min_size.wrapping_add(max_size) / 2) } - let min_size = self.min_size().unwrap(); - let max_size = self.max_size().unwrap(); - Some((min_size + max_size) / 2) } /// The middle size rounded up halfway between min and max size - pub fn middle_size_ceil(&self) -> Option { - match self { - DocumentPropertyType::Array(_) | DocumentPropertyType::VariableTypeArray(_) => { - return None - } - _ => {} + pub fn middle_size_ceil(&self, platform_version: &PlatformVersion) -> Option { + let min_size = self.min_size()?; + let max_size = self.max_size()?; + if platform_version.protocol_version > 8 { + Some(((min_size as u32 + max_size as u32 + 1) / 2) as u16) + } else { + Some(min_size.wrapping_add(max_size).wrapping_add(1) / 2) } - let min_size = self.min_size().unwrap(); - let max_size = self.max_size().unwrap(); - Some((min_size + max_size + 1) / 2) } /// The middle size rounded down halfway between min and max byte size - pub fn middle_byte_size(&self) -> Option { - match self { - DocumentPropertyType::Array(_) | DocumentPropertyType::VariableTypeArray(_) => { - return None - } - _ => {} + pub fn middle_byte_size( + &self, + platform_version: &PlatformVersion, + ) -> Result, ProtocolError> { + let Some(min_size) = self.min_byte_size(platform_version)? else { + return Ok(None); + }; + let Some(max_size) = self.max_byte_size(platform_version)? else { + return Ok(None); + }; + if platform_version.protocol_version > 8 { + Ok(Some(((min_size as u32 + max_size as u32) / 2) as u16)) + } else { + Ok(Some(min_size.wrapping_add(max_size) / 2)) } - let min_size = self.min_byte_size().unwrap(); - let max_size = self.max_byte_size().unwrap(); - Some((min_size + max_size) / 2) } /// The middle size rounded up halfway between min and max byte size - pub fn middle_byte_size_ceil(&self) -> Option { - match self { - DocumentPropertyType::Array(_) | DocumentPropertyType::VariableTypeArray(_) => { - return None - } - _ => {} + pub fn middle_byte_size_ceil( + &self, + platform_version: &PlatformVersion, + ) -> Result, ProtocolError> { + let Some(min_size) = self.min_byte_size(platform_version)? else { + return Ok(None); + }; + let Some(max_size) = self.max_byte_size(platform_version)? else { + return Ok(None); + }; + if platform_version.protocol_version > 8 { + Ok(Some(((min_size as u32 + max_size as u32 + 1) / 2) as u16)) + } else { + Ok(Some(min_size.wrapping_add(max_size).wrapping_add(1) / 2)) } - let min_size = self.min_byte_size().unwrap() as u32; - let max_size = self.max_byte_size().unwrap() as u32; - Some(((min_size + max_size + 1) / 2) as u16) } pub fn random_size(&self, rng: &mut StdRng) -> u16 { - let min_size = self.min_size().unwrap(); - let max_size = self.max_size().unwrap(); + let min_size = self.min_size().unwrap_or_default(); + let max_size = self.max_size().unwrap_or_default(); rng.gen_range(min_size..=max_size) } diff --git a/packages/rs-drive/src/drive/document/delete/remove_indices_for_index_level_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/delete/remove_indices_for_index_level_for_contract_operations/v0/mod.rs index 6ad033a0f41..f0ce3394b8b 100644 --- a/packages/rs-drive/src/drive/document/delete/remove_indices_for_index_level_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/delete/remove_indices_for_index_level_for_contract_operations/v0/mod.rs @@ -105,7 +105,7 @@ impl Drive { let document_top_field_estimated_size = document_and_contract_info .owned_document_info .document_info - .get_estimated_size_for_document_type(name, document_type)?; + .get_estimated_size_for_document_type(name, document_type, platform_version)?; if document_top_field_estimated_size > u8::MAX as u16 { return Err(Error::Fee(FeeError::Overflow( diff --git a/packages/rs-drive/src/drive/document/delete/remove_indices_for_top_index_level_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/delete/remove_indices_for_top_index_level_for_contract_operations/v0/mod.rs index 332e9214519..c8ad3727088 100644 --- a/packages/rs-drive/src/drive/document/delete/remove_indices_for_top_index_level_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/delete/remove_indices_for_top_index_level_for_contract_operations/v0/mod.rs @@ -107,7 +107,7 @@ impl Drive { let document_top_field_estimated_size = document_and_contract_info .owned_document_info .document_info - .get_estimated_size_for_document_type(name, document_type)?; + .get_estimated_size_for_document_type(name, document_type, platform_version)?; if document_top_field_estimated_size > u8::MAX as u16 { return Err(Error::Fee(FeeError::Overflow( diff --git a/packages/rs-drive/src/drive/document/insert/add_indices_for_index_level_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/insert/add_indices_for_index_level_for_contract_operations/v0/mod.rs index 2725cc80b1c..918b8497424 100644 --- a/packages/rs-drive/src/drive/document/insert/add_indices_for_index_level_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/insert/add_indices_for_index_level_for_contract_operations/v0/mod.rs @@ -125,7 +125,7 @@ impl Drive { let document_top_field_estimated_size = document_and_contract_info .owned_document_info .document_info - .get_estimated_size_for_document_type(name, document_type)?; + .get_estimated_size_for_document_type(name, document_type, platform_version)?; if document_top_field_estimated_size > u8::MAX as u16 { return Err(Error::Fee(FeeError::Overflow( diff --git a/packages/rs-drive/src/drive/document/insert/add_indices_for_top_index_level_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/insert/add_indices_for_top_index_level_for_contract_operations/v0/mod.rs index c21146a1832..54e347ab4a5 100644 --- a/packages/rs-drive/src/drive/document/insert/add_indices_for_top_index_level_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/insert/add_indices_for_top_index_level_for_contract_operations/v0/mod.rs @@ -134,7 +134,7 @@ impl Drive { let document_top_field_estimated_size = document_and_contract_info .owned_document_info .document_info - .get_estimated_size_for_document_type(name, document_type)?; + .get_estimated_size_for_document_type(name, document_type, platform_version)?; if document_top_field_estimated_size > u8::MAX as u16 { return Err(Error::Fee(FeeError::Overflow( diff --git a/packages/rs-drive/src/drive/document/insert_contested/add_contested_indices_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/insert_contested/add_contested_indices_for_contract_operations/v0/mod.rs index 1881419fdd2..1d5dbad0b79 100644 --- a/packages/rs-drive/src/drive/document/insert_contested/add_contested_indices_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/insert_contested/add_contested_indices_for_contract_operations/v0/mod.rs @@ -111,7 +111,7 @@ impl Drive { let document_top_field_estimated_size = document_and_contract_info .owned_document_info .document_info - .get_estimated_size_for_document_type(name, document_type)?; + .get_estimated_size_for_document_type(name, document_type, platform_version)?; if document_top_field_estimated_size > u8::MAX as u16 { return Err(Error::Fee(FeeError::Overflow( diff --git a/packages/rs-drive/src/drive/document/insert_contested/add_contested_indices_for_index_level_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/insert_contested/add_contested_indices_for_index_level_for_contract_operations/v0/mod.rs index a989dd3997b..f48e5e46c01 100644 --- a/packages/rs-drive/src/drive/document/insert_contested/add_contested_indices_for_index_level_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/insert_contested/add_contested_indices_for_index_level_for_contract_operations/v0/mod.rs @@ -123,7 +123,7 @@ impl Drive { let document_top_field_estimated_size = document_and_contract_info .owned_document_info .document_info - .get_estimated_size_for_document_type(name, document_type)?; + .get_estimated_size_for_document_type(name, document_type, platform_version)?; if document_top_field_estimated_size > u8::MAX as u16 { return Err(Error::Fee(FeeError::Overflow( diff --git a/packages/rs-drive/src/util/object_size_info/document_info.rs b/packages/rs-drive/src/util/object_size_info/document_info.rs index e2ad23c4f5a..e671b293a88 100644 --- a/packages/rs-drive/src/util/object_size_info/document_info.rs +++ b/packages/rs-drive/src/util/object_size_info/document_info.rs @@ -48,6 +48,7 @@ pub trait DocumentInfoV0Methods { &self, key_path: &str, document_type: DocumentTypeRef, + platform_version: &PlatformVersion, ) -> Result; /// Gets the raw path for the given document type fn get_raw_for_document_type( @@ -111,6 +112,7 @@ impl<'a> DocumentInfoV0Methods for DocumentInfo<'a> { &self, key_path: &str, document_type: DocumentTypeRef, + platform_version: &PlatformVersion, ) -> Result { match key_path { "$ownerId" | "$id" => Ok(DEFAULT_HASH_SIZE_U16), @@ -128,11 +130,14 @@ impl<'a> DocumentInfoV0Methods for DocumentInfo<'a> { key_path ))) })?; - let estimated_size = property.property_type.middle_byte_size_ceil().ok_or({ - Error::Drive(DriveError::CorruptedCodeExecution( - "document type must have a max size", - )) - })?; + let estimated_size = property + .property_type + .middle_byte_size_ceil(platform_version)? + .ok_or({ + Error::Drive(DriveError::CorruptedCodeExecution( + "document type must have a max size", + )) + })?; Ok(estimated_size) } } @@ -217,8 +222,10 @@ impl<'a> DocumentInfoV0Methods for DocumentInfo<'a> { )) })?; - let estimated_middle_size = - property.property_type.middle_byte_size_ceil().ok_or({ + let estimated_middle_size = property + .property_type + .middle_byte_size_ceil(platform_version)? + .ok_or({ Error::Drive(DriveError::CorruptedCodeExecution( "document type must have a max size", ))