Skip to content

Commit

Permalink
fix(dpp)!: wrapping overflow issue (#2430)
Browse files Browse the repository at this point in the history
  • Loading branch information
QuantumExplorer authored Jan 17, 2025
1 parent fd7ee85 commit cd1527d
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 117 deletions.
Original file line number Diff line number Diff line change
@@ -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<u16, ProtocolError> {
let mut total_size = 0u16;

for (_, document_property) in self.flattened_properties.iter() {
// This call now returns a Result<Option<u16>, 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)
}
}
Original file line number Diff line number Diff line change
@@ -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<u16, ProtocolError> {
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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],
Expand Down
195 changes: 113 additions & 82 deletions packages/rs-dpp/src/data_contract/document_type/property/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -159,69 +160,93 @@ impl DocumentPropertyType {
}
}

pub fn min_byte_size(&self) -> Option<u16> {
pub fn min_byte_size(
&self,
platform_version: &PlatformVersion,
) -> Result<Option<u16>, 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<u16> {
pub fn max_byte_size(
&self,
platform_version: &PlatformVersion,
) -> Result<Option<u16>, 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)),
}
}

Expand Down Expand Up @@ -259,60 +284,66 @@ impl DocumentPropertyType {
}

/// The middle size rounded down halfway between min and max size
pub fn middle_size(&self) -> Option<u16> {
match self {
DocumentPropertyType::Array(_) | DocumentPropertyType::VariableTypeArray(_) => {
return None
}
_ => {}
pub fn middle_size(&self, platform_version: &PlatformVersion) -> Option<u16> {
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<u16> {
match self {
DocumentPropertyType::Array(_) | DocumentPropertyType::VariableTypeArray(_) => {
return None
}
_ => {}
pub fn middle_size_ceil(&self, platform_version: &PlatformVersion) -> Option<u16> {
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<u16> {
match self {
DocumentPropertyType::Array(_) | DocumentPropertyType::VariableTypeArray(_) => {
return None
}
_ => {}
pub fn middle_byte_size(
&self,
platform_version: &PlatformVersion,
) -> Result<Option<u16>, 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<u16> {
match self {
DocumentPropertyType::Array(_) | DocumentPropertyType::VariableTypeArray(_) => {
return None
}
_ => {}
pub fn middle_byte_size_ceil(
&self,
platform_version: &PlatformVersion,
) -> Result<Option<u16>, 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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading

0 comments on commit cd1527d

Please sign in to comment.