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!(pallet-gear): append outgoing bytes limit to prevent runtime allocator overflow #3743

Merged
merged 31 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
eea8a3d
initial (with garbage in ping)
grishasobol Feb 19, 2024
ab24fd4
add tests
grishasobol Feb 20, 2024
9a361cf
add outgoing_bytes_limit to ContextSettings
grishasobol Feb 20, 2024
de694f3
make MessageContext::new result Option<Self>
grishasobol Feb 20, 2024
82cff6a
return normal ping
grishasobol Feb 20, 2024
a7a7c82
add OutgoingBytesLimit (and pass pre-commit)
grishasobol Feb 20, 2024
4742398
add test in gear-core and comments
grishasobol Feb 20, 2024
0e80eb4
fix bug, add more tests
grishasobol Feb 21, 2024
43787be
fix comment
grishasobol Feb 21, 2024
9349dba
change < limit to <= limit
grishasobol Feb 21, 2024
53e7f86
fix test
grishasobol Feb 21, 2024
38ad0e9
temprorary change system error to execution error
grishasobol Feb 26, 2024
dfdd07d
Merge branch 'master' into gsobol-outgoing-bytes-limit
grishasobol Feb 26, 2024
ebc9594
add issue number
grishasobol Feb 26, 2024
92d80a0
Merge branch 'master' into gsobol-outgoing-bytes-limit
grishasobol Feb 26, 2024
7ccea36
fix after master merge
grishasobol Feb 26, 2024
55732d5
add new comments
grishasobol Feb 27, 2024
52cfdfc
Merge branch 'master' into gsobol-outgoing-bytes-limit
grishasobol Feb 27, 2024
1ce634c
fix after master merge
grishasobol Feb 27, 2024
9b74768
fix(pallet-gear-builtin): Fix the issue of bi-replying (#3758)
breathx Feb 27, 2024
2c3f85f
Merge branch 'master' into gsobol-outgoing-bytes-limit
grishasobol Feb 28, 2024
ec51756
add todo comment
grishasobol Feb 28, 2024
b78669d
review fixes - change unwrap to expect in message context tests
grishasobol Feb 29, 2024
0d97362
commented MessageStoreOutgoingBytesOverflow
grishasobol Feb 29, 2024
ea083c9
Merge branch 'master' into gsobol-outgoing-bytes-limit
grishasobol Mar 1, 2024
e4c2f4a
review fix: change `then_some` to `if`
grishasobol Mar 2, 2024
afc51c8
change errors returns order
grishasobol Mar 5, 2024
56d90d7
Merge branch 'master' into gsobol-outgoing-bytes-limit
grishasobol Mar 5, 2024
06ec57a
Empty-Commit
grishasobol Mar 5, 2024
6317363
Merge branch 'master' into gsobol-outgoing-bytes-limit
grishasobol Mar 5, 2024
7787528
fix comment
grishasobol Mar 5, 2024
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
6 changes: 6 additions & 0 deletions core-errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ pub enum MessageError {
)]
IncorrectMessageForReplyDeposit = 310,

/// The error occurs when program tries to send messages
/// with total size bigger than allowed.
#[display(fmt = "Outgoing messages bytes limit exceeded")]
OutgoingMessagesBytesLimitExceeded = 311,

// TODO: remove after delay refactoring is done
/// An error occurs in attempt to charge gas for dispatch stash hold.
#[display(fmt = "Not enough gas to hold dispatch message")]
Expand Down Expand Up @@ -262,6 +267,7 @@ impl ExtError {
308 => Some(MessageError::InsufficientGasLimit.into()),
309 => Some(MessageError::DuplicateReplyDeposit.into()),
310 => Some(MessageError::IncorrectMessageForReplyDeposit.into()),
311 => Some(MessageError::OutgoingMessagesBytesLimitExceeded.into()),
399 => Some(MessageError::InsufficientGasForDelayedSending.into()),
//
500 => Some(ReservationError::InvalidReservationId.into()),
Expand Down
12 changes: 12 additions & 0 deletions core-processor/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,13 @@ pub enum ActorExecutionErrorReplyReason {
/// Trap explanation
#[display(fmt = "{_0}")]
Trap(TrapExplanation),
// TODO: move this to SystemExecutionError after runtime upgrade,
// if wait-list does not contain messages with total outgoing bytes more than `OutgoingBytesLimit` #3751.
/// Message is not supported now
#[display(
fmt = "Message is not supported: outgoing bytes limit is exceeded after runtime-upgrade"
)]
UnsupportedMessage,
}

impl ActorExecutionErrorReplyReason {
Expand All @@ -480,6 +487,7 @@ impl ActorExecutionErrorReplyReason {
TrapExplanation::StackLimitExceeded => SimpleExecutionError::StackLimitExceeded,
TrapExplanation::Unknown => SimpleExecutionError::UnreachableInstruction,
},
Self::UnsupportedMessage => SimpleExecutionError::Unsupported,
}
}
}
Expand All @@ -501,6 +509,10 @@ pub enum SystemExecutionError {
/// Error during `into_ext_info()` call
#[display(fmt = "`into_ext_info()` error: {_0}")]
IntoExtInfo(MemoryError),
// TODO: uncomment when #3751
// /// Incoming dispatch store has too many outgoing messages total bytes.
// #[display(fmt = "Incoming dispatch store has too many outgoing messages total bytes")]
// MessageStoreOutgoingBytesOverflow,
}

/// Actor.
Expand Down
2 changes: 2 additions & 0 deletions core-processor/src/configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ pub struct BlockConfig {
pub existential_deposit: u128,
/// Outgoing limit.
pub outgoing_limit: u32,
/// Outgoing bytes limit.
pub outgoing_bytes_limit: u32,
/// Host function weights.
pub host_fn_weights: HostFnWeights,
/// Forbidden functions.
Expand Down
48 changes: 29 additions & 19 deletions core-processor/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,14 @@ where
AllocationsContext::new(allocations.clone(), static_pages, settings.max_pages);

// Creating message context.
let message_context = MessageContext::new(dispatch.clone(), program_id, msg_ctx_settings);
let Some(message_context) = MessageContext::new(dispatch.clone(), program_id, msg_ctx_settings)
else {
return Err(ActorExecutionError {
gas_amount: gas_counter.to_amount(),
reason: ActorExecutionErrorReplyReason::UnsupportedMessage,
}
.into());
};

// Creating value counter.
//
Expand Down Expand Up @@ -380,30 +387,33 @@ where
0.into()
};

let message_context = MessageContext::new(
IncomingDispatch::new(
DispatchKind::Handle,
IncomingMessage::new(
Default::default(),
Default::default(),
payload
.try_into()
.map_err(|e| format!("Failed to create payload: {e:?}"))?,
gas_limit,
Default::default(),
Default::default(),
),
None,
),
program.id(),
Default::default(),
)
.ok_or("Incorrect message store context: out of outgoing bytes limit")?;

let context = ProcessorContext {
gas_counter: GasCounter::new(gas_limit),
gas_allowance_counter: GasAllowanceCounter::new(gas_limit),
gas_reserver: GasReserver::new(&Default::default(), Default::default(), Default::default()),
value_counter: ValueCounter::new(Default::default()),
allocations_context: AllocationsContext::new(allocations, static_pages, 512.into()),
message_context: MessageContext::new(
IncomingDispatch::new(
DispatchKind::Handle,
IncomingMessage::new(
Default::default(),
Default::default(),
payload
.try_into()
.map_err(|e| format!("Failed to create payload: {e:?}"))?,
gas_limit,
Default::default(),
Default::default(),
),
None,
),
program.id(),
ContextSettings::new(0, 0, 0, 0, 0, 0),
),
message_context,
block_info,
performance_multiplier: gsys::Percent::new(100),
max_pages: 512.into(),
Expand Down
78 changes: 22 additions & 56 deletions core-processor/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ pub struct ProcessorContext {
impl ProcessorContext {
/// Create new mock [`ProcessorContext`] for usage in tests.
pub fn new_mock() -> ProcessorContext {
use gear_core::message::{ContextSettings, IncomingDispatch};
use gear_core::message::IncomingDispatch;

ProcessorContext {
gas_counter: GasCounter::new(0),
Expand All @@ -134,8 +134,9 @@ impl ProcessorContext {
message_context: MessageContext::new(
Default::default(),
Default::default(),
ContextSettings::new(0, 0, 0, 0, 0, 0),
),
Default::default(),
)
.unwrap(),
block_info: Default::default(),
performance_multiplier: gsys::Percent::new(100),
max_pages: 512.into(),
Expand Down Expand Up @@ -626,13 +627,13 @@ impl Ext {

fn charge_sending_fee(&mut self, delay: u32) -> Result<(), ChargeError> {
if delay == 0 {
self.charge_gas_if_enough(self.context.message_context.settings().sending_fee())
self.charge_gas_if_enough(self.context.message_context.settings().sending_fee)
} else {
self.charge_gas_if_enough(
self.context
.message_context
.settings()
.scheduled_sending_fee(),
.scheduled_sending_fee,
)
}
}
Expand Down Expand Up @@ -1023,7 +1024,7 @@ impl Externalities for Ext {
amount: u64,
duration: u32,
) -> Result<ReservationId, Self::FallibleError> {
self.charge_gas_if_enough(self.context.message_context.settings().reservation_fee())?;
self.charge_gas_if_enough(self.context.message_context.settings().reservation_fee)?;

if duration == 0 {
return Err(ReservationError::ZeroReservationDuration.into());
Expand Down Expand Up @@ -1090,7 +1091,7 @@ impl Externalities for Ext {
}

fn wait(&mut self) -> Result<(), Self::UnrecoverableError> {
self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?;
self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee)?;

if self.context.message_context.reply_sent() {
return Err(UnrecoverableWaitError::WaitAfterReply.into());
Expand All @@ -1107,7 +1108,7 @@ impl Externalities for Ext {
}

fn wait_for(&mut self, duration: u32) -> Result<(), Self::UnrecoverableError> {
self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?;
self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee)?;

if self.context.message_context.reply_sent() {
return Err(UnrecoverableWaitError::WaitAfterReply.into());
Expand All @@ -1128,7 +1129,7 @@ impl Externalities for Ext {
}

fn wait_up_to(&mut self, duration: u32) -> Result<bool, Self::UnrecoverableError> {
self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?;
self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee)?;

if self.context.message_context.reply_sent() {
return Err(UnrecoverableWaitError::WaitAfterReply.into());
Expand All @@ -1153,7 +1154,7 @@ impl Externalities for Ext {
}

fn wake(&mut self, waker_id: MessageId, delay: u32) -> Result<(), Self::FallibleError> {
self.charge_gas_if_enough(self.context.message_context.settings().waking_fee())?;
self.charge_gas_if_enough(self.context.message_context.settings().waking_fee)?;

self.context.message_context.wake(waker_id, delay)?;
Ok(())
Expand Down Expand Up @@ -1223,45 +1224,30 @@ mod tests {
struct MessageContextBuilder {
incoming_dispatch: IncomingDispatch,
program_id: ProgramId,
sending_fee: u64,
scheduled_sending_fee: u64,
waiting_fee: u64,
waking_fee: u64,
reservation_fee: u64,
outgoing_limit: u32,
context_settings: ContextSettings,
}

impl MessageContextBuilder {
fn new() -> Self {
Self {
incoming_dispatch: Default::default(),
program_id: Default::default(),
sending_fee: 0,
scheduled_sending_fee: 0,
waiting_fee: 0,
waking_fee: 0,
reservation_fee: 0,
outgoing_limit: 0,
context_settings: ContextSettings::with_outgoing_limits(u32::MAX, u32::MAX),
}
}

fn build(self) -> MessageContext {
MessageContext::new(
self.incoming_dispatch,
self.program_id,
ContextSettings::new(
self.sending_fee,
self.scheduled_sending_fee,
self.waiting_fee,
self.waking_fee,
self.reservation_fee,
self.outgoing_limit,
),
self.context_settings,
)
.unwrap()
}

fn with_outgoing_limit(mut self, outgoing_limit: u32) -> Self {
self.outgoing_limit = outgoing_limit;
self.context_settings.outgoing_limit = outgoing_limit;

self
}
}
Expand Down Expand Up @@ -1461,11 +1447,7 @@ mod tests {
fn test_send_push() {
let mut ext = Ext::new(
ProcessorContextBuilder::new()
.with_message_context(
MessageContextBuilder::new()
.with_outgoing_limit(u32::MAX)
.build(),
)
.with_message_context(MessageContextBuilder::new().build())
.build(),
);

Expand Down Expand Up @@ -1529,11 +1511,7 @@ mod tests {
fn test_send_push_input() {
let mut ext = Ext::new(
ProcessorContextBuilder::new()
.with_message_context(
MessageContextBuilder::new()
.with_outgoing_limit(u32::MAX)
.build(),
)
.with_message_context(MessageContextBuilder::new().build())
.build(),
);

Expand Down Expand Up @@ -1595,11 +1573,7 @@ mod tests {
let mut ext = Ext::new(
ProcessorContextBuilder::new()
.with_gas(GasCounter::new(u64::MAX))
.with_message_context(
MessageContextBuilder::new()
.with_outgoing_limit(u32::MAX)
.build(),
)
.with_message_context(MessageContextBuilder::new().build())
.build(),
);

Expand Down Expand Up @@ -1636,11 +1610,7 @@ mod tests {
let mut ext = Ext::new(
ProcessorContextBuilder::new()
.with_gas(GasCounter::new(u64::MAX))
.with_message_context(
MessageContextBuilder::new()
.with_outgoing_limit(u32::MAX)
.build(),
)
.with_message_context(MessageContextBuilder::new().build())
.build(),
);

Expand Down Expand Up @@ -1691,11 +1661,7 @@ mod tests {
fn test_reply_push_input() {
let mut ext = Ext::new(
ProcessorContextBuilder::new()
.with_message_context(
MessageContextBuilder::new()
.with_outgoing_limit(u32::MAX)
.build(),
)
.with_message_context(MessageContextBuilder::new().build())
.build(),
);

Expand Down
25 changes: 16 additions & 9 deletions core-processor/src/processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ where
page_costs,
existential_deposit,
outgoing_limit,
outgoing_bytes_limit,
host_fn_weights,
forbidden_funcs,
mailbox_threshold,
Expand Down Expand Up @@ -117,14 +118,21 @@ where
//
// Waking fee: double write cost for removal from waitlist
// and further enqueueing.
let msg_ctx_settings = ContextSettings::new(
write_cost.saturating_mul(2),
write_cost.saturating_mul(4),
write_cost.saturating_mul(3),
write_cost.saturating_mul(2),
write_cost.saturating_mul(2),
let msg_ctx_settings = ContextSettings {
sending_fee: write_cost.saturating_mul(2),
scheduled_sending_fee: write_cost.saturating_mul(4),
waiting_fee: write_cost.saturating_mul(3),
waking_fee: write_cost.saturating_mul(2),
reservation_fee: write_cost.saturating_mul(2),
outgoing_limit,
);
outgoing_bytes_limit,
};

// TODO: add tests that system reservation is successfully unreserved after
// actor execution error #3756.

// Get system reservation context in order to use it if actor execution error occurs.
let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);

let exec_result = executor::execute_wasm::<Ext>(
balance,
Expand Down Expand Up @@ -158,12 +166,11 @@ where
process_allowance_exceed(dispatch, program_id, res.gas_amount.burned())
}
}),
// TODO: we must use message reservation context here instead of default #3718
Err(ExecutionError::Actor(e)) => Ok(process_execution_error(
dispatch,
program_id,
e.gas_amount.burned(),
SystemReservationContext::default(),
system_reservation_ctx,
e.reason,
)),
Err(ExecutionError::System(e)) => Err(e),
Expand Down
Loading
Loading