Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: possible overflow issues #877

Merged
merged 6 commits into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::prelude::TimestampMillis;
use crate::{prelude::TimestampMillis, NonConsensusError};

use super::validation_result::TimeWindowValidationResult;

Expand All @@ -8,16 +8,24 @@ pub const BLOCK_TIME_WINDOW_MILLIS: u64 = BLOCK_TIME_WINDOW_MINUTES * 60 * 1000;
pub fn validate_time_in_block_time_window(
last_block_header_time_millis: TimestampMillis,
time_to_check_millis: TimestampMillis,
) -> TimeWindowValidationResult {
let time_window_start = last_block_header_time_millis - BLOCK_TIME_WINDOW_MILLIS;
let time_window_end = last_block_header_time_millis + BLOCK_TIME_WINDOW_MILLIS;
) -> Result<TimeWindowValidationResult, NonConsensusError> {
let time_window_start = last_block_header_time_millis
.checked_sub(BLOCK_TIME_WINDOW_MILLIS)
.ok_or(NonConsensusError::Overflow(
"calculation of start window failed",
))?;
let time_window_end = last_block_header_time_millis
.checked_add(BLOCK_TIME_WINDOW_MILLIS)
.ok_or(NonConsensusError::Overflow(
"calculation of end window failed",
))?;

let valid =
time_to_check_millis >= time_window_start && time_to_check_millis <= time_window_end;

TimeWindowValidationResult {
Ok(TimeWindowValidationResult {
time_window_start,
time_window_end,
valid,
}
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,17 @@ where
}
};

let new_version = new_data_contract_object.get_integer(contract_property_names::VERSION)?;
let new_version: u32 =
new_data_contract_object.get_integer(contract_property_names::VERSION)?;
let old_version = existing_data_contract.version;
if (new_version - old_version) != 1 {

if (new_version
.checked_sub(old_version)
.ok_or(ProtocolError::Overflow(
"comparing protocol versions failed",
))?)
!= 1
{
validation_result.add_error(BasicError::InvalidDataContractVersionError(
InvalidDataContractVersionError::new(old_version + 1, new_version),
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::document::state_transition::documents_batch_transition::{
};
use crate::document::Document;
use crate::validation::{AsyncDataValidator, SimpleValidationResult};
use crate::NonConsensusError;
use crate::{
block_time_window::validate_time_in_block_time_window::validate_time_in_block_time_window,
consensus::ConsensusError,
Expand Down Expand Up @@ -255,11 +256,11 @@ fn validate_transition(
result.merge(validation_result);

let validation_result =
check_created_inside_time_window(transition, last_header_block_time_millis);
check_created_inside_time_window(transition, last_header_block_time_millis)?;
result.merge(validation_result);

let validation_result =
check_updated_inside_time_window(transition, last_header_block_time_millis);
check_updated_inside_time_window(transition, last_header_block_time_millis)?;
result.merge(validation_result);

let validation_result =
Expand All @@ -280,7 +281,7 @@ fn validate_transition(
DocumentTransitionAction::ReplaceAction(DocumentReplaceTransitionAction::default()),
);
let validation_result =
check_updated_inside_time_window(transition, last_header_block_time_millis);
check_updated_inside_time_window(transition, last_header_block_time_millis)?;
result.merge(validation_result);

let validation_result = check_revision(transition, fetched_documents);
Expand Down Expand Up @@ -454,14 +455,14 @@ fn check_if_timestamps_are_equal(
fn check_created_inside_time_window(
document_transition: &DocumentTransition,
last_block_ts_millis: TimestampMillis,
) -> SimpleValidationResult {
) -> Result<SimpleValidationResult, NonConsensusError> {
let mut result = SimpleValidationResult::default();
let created_at = match document_transition.get_created_at() {
Some(t) => t,
None => return result,
None => return Ok(result),
};

let window_validation = validate_time_in_block_time_window(last_block_ts_millis, created_at);
let window_validation = validate_time_in_block_time_window(last_block_ts_millis, created_at)?;
if !window_validation.is_valid() {
result.add_error(ConsensusError::StateError(
StateError::DocumentTimestampWindowViolationError(
Expand All @@ -475,20 +476,20 @@ fn check_created_inside_time_window(
),
));
}
result
Ok(result)
}

fn check_updated_inside_time_window(
document_transition: &DocumentTransition,
last_block_ts_millis: TimestampMillis,
) -> SimpleValidationResult {
) -> Result<SimpleValidationResult, ProtocolError> {
let mut result = SimpleValidationResult::default();
let updated_at = match document_transition.get_updated_at() {
Some(t) => t,
None => return result,
None => return Ok(result),
};

let window_validation = validate_time_in_block_time_window(last_block_ts_millis, updated_at);
let window_validation = validate_time_in_block_time_window(last_block_ts_millis, updated_at)?;
if !window_validation.is_valid() {
result.add_error(ConsensusError::StateError(
StateError::DocumentTimestampWindowViolationError(
Expand All @@ -502,5 +503,5 @@ fn check_updated_inside_time_window(
),
));
}
result
Ok(result)
}
4 changes: 4 additions & 0 deletions packages/rs-dpp/src/errors/non_consensus_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ pub enum NonConsensusError {

#[error(transparent)]
Error(#[from] anyhow::Error),

/// Error
#[error("overflow error: {0}")]
Overflow(&'static str),
}

pub mod object_names {
Expand Down
20 changes: 13 additions & 7 deletions packages/rs-dpp/src/identity/credits_converter.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
use crate::{state_transition::fee::Credits, ProtocolError};

pub const RATIO: u64 = 1000;

pub fn convert_satoshi_to_credits(amount: u64) -> u64 {
amount * RATIO
pub fn convert_satoshi_to_credits(amount: u64) -> Result<Credits, ProtocolError> {
amount.checked_mul(RATIO).ok_or(ProtocolError::Overflow(
"converting satoshi to credits failed",
))
}

pub fn convert_credits_to_satoshi(amount: u64) -> u64 {
amount / RATIO
pub fn convert_credits_to_satoshi(amount: Credits) -> Result<u64, ProtocolError> {
amount.checked_div(RATIO).ok_or(ProtocolError::Overflow(
"converting credits to satoshi failed",
))
}

#[cfg(test)]
Expand All @@ -15,22 +21,22 @@ mod test {
#[test]
fn test_should_convert_satoshi_to_credits() {
let amount = 42;
let converted = convert_satoshi_to_credits(amount);
let converted = convert_satoshi_to_credits(amount).unwrap();

assert_eq!(converted, amount * RATIO);
}

#[test]
fn test_should_convert_credits_to_satoshi() {
let amount = 10000;
let converted = convert_credits_to_satoshi(amount);
let converted = convert_credits_to_satoshi(amount).unwrap();
assert_eq!(converted, amount / RATIO);
}

#[test]
fn test_convert_to_0_satoshi_if_amount_lower_than_ratio() {
let amount = RATIO - 1;
let converted = convert_credits_to_satoshi(amount);
let converted = convert_credits_to_satoshi(amount).unwrap();
assert_eq!(converted, 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ where
)
.await?;

let credits_amount = convert_satoshi_to_credits(output.value);
let credits_amount = convert_satoshi_to_credits(output.value)?;

let identity = Identity {
protocol_version: state_transition.get_protocol_version(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ where
)
.await?;

let mut credits_amount = convert_satoshi_to_credits(output.value);
let mut credits_amount = convert_satoshi_to_credits(output.value)?;

let out_point = state_transition
.get_asset_lock_proof()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ where
let mut identity = stored_identity.clone();

// Check revision
if identity.get_revision() != (state_transition.get_revision() - 1) {
if identity.get_revision()
!= (state_transition.get_revision().checked_sub(1).ok_or(
NonConsensusError::Overflow("unable subtract 1 from revision"),
)?)
{
validation_result.add_error(StateError::InvalidIdentityRevisionError(
InvalidIdentityRevisionError::new(
state_transition.get_identity_id().to_owned(),
Expand Down Expand Up @@ -135,7 +139,7 @@ where
},
)?;
let window_validation_result =
validate_time_in_block_time_window(last_block_header_time, disabled_at_ms);
validate_time_in_block_time_window(last_block_header_time, disabled_at_ms)?;

if !window_validation_result.is_valid() {
validation_result.add_error(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
use crate::NonConsensusError;

use super::{
operations::{Operation, OperationLike},
DummyFeesResult, Refunds,
Credits, DummyFeesResult, Refunds,
};

pub fn calculate_operation_fees(operations: &[Operation]) -> DummyFeesResult {
let mut storage_fee = 0;
let mut processing_fee = 0;
pub fn calculate_operation_fees(
operations: &[Operation],
) -> Result<DummyFeesResult, NonConsensusError> {
let mut storage_fee: Credits = 0;
let mut processing_fee: Credits = 0;
let mut fee_refunds: Vec<Refunds> = Vec::new();

for operation in operations {
storage_fee += operation.get_storage_cost();
processing_fee += operation.get_processing_cost();
storage_fee = storage_fee
.checked_add(operation.get_storage_cost()?)
.ok_or(NonConsensusError::Overflow("storage cost is too big"))?;
processing_fee = processing_fee
.checked_add(operation.get_processing_cost()?)
.ok_or(NonConsensusError::Overflow("processing cost is too big"))?;

// Merge refunds
if let Some(operation_refunds) = operation.get_refunds() {
Expand All @@ -30,16 +38,19 @@ pub fn calculate_operation_fees(operations: &[Operation]) -> DummyFeesResult {
.credits_per_epoch
.entry(epoch_index.to_string())
.or_default();
*epoch += credits

*epoch = epoch
.checked_add(*credits)
.ok_or(NonConsensusError::Overflow("credits per epoch are too big"))?
}
}
}
}
}

DummyFeesResult {
Ok(DummyFeesResult {
storage: storage_fee,
processing: processing_fee,
fee_refunds,
}
})
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use crate::state_transition::{
fee::calculate_state_transition_fee_from_operations_factory::calculate_state_transition_fee_from_operations,
StateTransition, StateTransitionLike,
use crate::{
state_transition::{
fee::calculate_state_transition_fee_from_operations_factory::calculate_state_transition_fee_from_operations,
StateTransition, StateTransitionLike,
},
NonConsensusError,
};

use super::FeeResult;

pub fn calculate_state_transition_fee(state_transition: &StateTransition) -> FeeResult {
pub fn calculate_state_transition_fee(
state_transition: &StateTransition,
) -> Result<FeeResult, NonConsensusError> {
let execution_context = state_transition.get_execution_context();

calculate_state_transition_fee_from_operations(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::prelude::Identifier;
use crate::{prelude::Identifier, NonConsensusError};

use super::{
calculate_operation_fees::calculate_operation_fees, constants::DEFAULT_USER_TIP,
Expand All @@ -8,7 +8,7 @@ use super::{
pub fn calculate_state_transition_fee_from_operations(
operations: &[Operation],
identity_id: &Identifier,
) -> FeeResult {
) -> Result<FeeResult, NonConsensusError> {
calculate_state_transition_fee_from_operations_with_custom_calculator(
operations,
identity_id,
Expand All @@ -19,9 +19,9 @@ pub fn calculate_state_transition_fee_from_operations(
fn calculate_state_transition_fee_from_operations_with_custom_calculator(
operations: &[Operation],
identity_id: &Identifier,
calculate_operation_fees_fn: impl FnOnce(&[Operation]) -> DummyFeesResult,
) -> FeeResult {
let calculated_fees = calculate_operation_fees_fn(operations);
calculate_operation_fees_fn: impl FnOnce(&[Operation]) -> Result<DummyFeesResult, NonConsensusError>,
) -> Result<FeeResult, NonConsensusError> {
let calculated_fees = calculate_operation_fees_fn(operations)?;

let storage_fee = calculated_fees.storage;
let processing_fee = calculated_fees.processing;
Expand All @@ -43,14 +43,14 @@ fn calculate_state_transition_fee_from_operations_with_custom_calculator(
let required_amount = (storage_fee - total_refunds) + DEFAULT_USER_TIP;
let desired_amount = (storage_fee + processing_fee - total_refunds) + DEFAULT_USER_TIP;

FeeResult {
Ok(FeeResult {
storage_fee,
processing_fee,
fee_refunds,
total_refunds,
required_amount,
desired_amount,
}
})
}

#[cfg(test)]
Expand All @@ -62,6 +62,7 @@ mod test {
operations::Operation, Credits, DummyFeesResult, FeeResult, Refunds,
},
tests::utils::generate_random_identifier_struct,
NonConsensusError,
};

use super::calculate_state_transition_fee_from_operations_with_custom_calculator;
Expand All @@ -84,19 +85,20 @@ mod test {
credits_per_epoch,
};

let mock = |_operations: &[Operation]| -> DummyFeesResult {
DummyFeesResult {
let mock = |_operations: &[Operation]| -> Result<DummyFeesResult, NonConsensusError> {
Ok(DummyFeesResult {
storage: storage_fee,
processing: processing_fee,
fee_refunds: vec![refunds.clone()],
}
})
};

let result = calculate_state_transition_fee_from_operations_with_custom_calculator(
&[],
&identifier,
mock,
);
)
.expect("result should be returned");
let expected = FeeResult {
storage_fee,
processing_fee,
Expand Down
Loading