Skip to content

Commit

Permalink
bugfix(runtime): Correct charging on delayed sendings from reservatio…
Browse files Browse the repository at this point in the history
…ns (#3496)
  • Loading branch information
breathx authored Nov 20, 2023
1 parent 1f186ef commit 9fcabda
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 138 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ members = [
"examples/autoreply",
"examples/calc-hash",
"examples/custom",
"examples/delayed-reservation-sender",
"examples/compose",
"examples/constructor",
"examples/delayed-sender",
Expand Down Expand Up @@ -384,6 +385,7 @@ demo-calc-hash = { path = "examples/calc-hash" }
demo-calc-hash-in-one-block = { path = "examples/calc-hash/in-one-block" }
demo-calc-hash-over-blocks = { path = "examples/calc-hash/over-blocks" }
demo-custom = { path = "examples/custom" }
demo-delayed-reservation-sender = { path = "examples/delayed-reservation-sender" }
demo-compose = { path = "examples/compose" }
demo-constructor = { path = "examples/constructor", default-features = false }
demo-delayed-sender = { path = "examples/delayed-sender" }
Expand Down
45 changes: 42 additions & 3 deletions core-processor/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,46 @@ impl Ext {
}
}

// TODO: gasful sending (#1828).
/// Check reservation gas for gasless sending.
/// Gas of reservation for sending should be [mailbox_threshold + delay price; +inf).
fn check_reservation_gas_limit(
&mut self,
reservation_id: &ReservationId,
delay: u32,
) -> Result<(), FallibleExtError> {
let limit = self
.context
.gas_reserver
.limit_of(reservation_id)
.ok_or(ReservationError::InvalidReservationId)?;

// Almost unreachable since reservation couldn't be created
// with gas less than mailbox_threshold.
//
// TODO: review this place once more in #1828.
let limit = limit
.checked_sub(self.context.mailbox_threshold)
.ok_or(MessageError::InsufficientGasLimit)?;

// Checking that reservation could be charged for
// dispatch stash with given delay.
if delay != 0 {
// Take delay and get cost of block.
// reserve = wait_cost * (delay + reserve_for).
let cost_per_block = self.context.dispatch_hold_cost;
let waiting_reserve = (self.context.reserve_for as u64)
.saturating_add(delay as u64)
.saturating_mul(cost_per_block);

if limit < waiting_reserve {
return Err(MessageError::InsufficientGasForDelayedSending.into());
}
}

Ok(())
}

fn reduce_gas(&mut self, gas_limit: GasLimit) -> Result<(), FallibleExtError> {
if self.context.gas_counter.reduce(gas_limit) != ChargeResult::Enough {
Err(FallibleExecutionError::NotEnoughGas.into())
Expand Down Expand Up @@ -804,13 +844,11 @@ impl Externalities for Ext {
) -> Result<MessageId, Self::FallibleError> {
self.check_forbidden_destination(msg.destination())?;
self.check_message_value(msg.value())?;
self.check_gas_limit(msg.gas_limit())?;
self.check_reservation_gas_limit(&id, delay)?;
// TODO: gasful sending (#1828)
self.charge_message_value(msg.value())?;
self.charge_sending_fee(delay)?;

self.charge_for_dispatch_stash_hold(delay)?;

self.context.gas_reserver.mark_used(id)?;

let msg_id = self
Expand Down Expand Up @@ -843,6 +881,7 @@ impl Externalities for Ext {
) -> Result<MessageId, Self::FallibleError> {
self.check_forbidden_destination(self.context.message_context.reply_destination())?;
self.check_message_value(msg.value())?;
self.check_reservation_gas_limit(&id, 0)?;
// TODO: gasful sending (#1828)
self.charge_message_value(msg.value())?;
self.charge_sending_fee(0)?;
Expand Down
9 changes: 9 additions & 0 deletions core/src/reservation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ impl GasReserver {
}
}

/// Returns amount of gas in reservation, if exists.
pub fn limit_of(&self, reservation_id: &ReservationId) -> Option<u64> {
self.states.get(reservation_id).and_then(|v| match v {
GasReservationState::Exists { amount, .. }
| GasReservationState::Created { amount, .. } => Some(*amount),
_ => None,
})
}

/// Reserves gas.
///
/// Creates a new reservation and returns its id.
Expand Down
19 changes: 19 additions & 0 deletions examples/delayed-reservation-sender/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "demo-delayed-reservation-sender"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true

[dependencies]
gstd.workspace = true

[build-dependencies]
gear-wasm-builder.workspace = true

[features]
debug = ["gstd/debug"]
default = ["std"]
std = []
21 changes: 21 additions & 0 deletions examples/delayed-reservation-sender/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// This file is part of Gear.

// Copyright (C) 2021-2023 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

fn main() {
gear_wasm_builder::build();
}
50 changes: 50 additions & 0 deletions examples/delayed-reservation-sender/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// This file is part of Gear.

// Copyright (C) 2021-2023 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#![no_std]

use gstd::codec::{Decode, Encode};

#[cfg(feature = "std")]
mod code {
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
}

#[cfg(feature = "std")]
pub use code::WASM_BINARY_OPT as WASM_BINARY;

pub const SENDING_EXPECT: &str = "Failed to send delayed message from reservation";

#[derive(Encode, Decode, Debug, Clone, Copy)]
#[codec(crate = gstd::codec)]
pub enum ReservationSendingShowcase {
ToSourceInPlace {
reservation_amount: u64,
reservation_delay: u32,
sending_delay: u32,
},
ToSourceAfterWait {
reservation_amount: u64,
reservation_delay: u32,
wait_for: u32,
sending_delay: u32,
},
}

#[cfg(not(feature = "std"))]
mod wasm;
75 changes: 75 additions & 0 deletions examples/delayed-reservation-sender/src/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// This file is part of Gear.

// Copyright (C) 2023 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::{ReservationSendingShowcase, SENDING_EXPECT};
use gstd::{exec, msg, prelude::*, ReservationId};

static mut CALLED_BEFORE: bool = false;
static mut RESERVATION_ID: Option<ReservationId> = None;

#[no_mangle]
extern "C" fn handle() {
let showcase = msg::load().expect("Failed to load request");

match showcase {
ReservationSendingShowcase::ToSourceInPlace {
reservation_amount,
reservation_delay,
sending_delay,
} => {
let reservation_id = ReservationId::reserve(reservation_amount, reservation_delay)
.expect("Failed to reserve gas");

msg::send_bytes_delayed_from_reservation(
reservation_id,
msg::source(),
[],
0,
sending_delay,
)
.expect(SENDING_EXPECT);
}
ReservationSendingShowcase::ToSourceAfterWait {
reservation_amount,
reservation_delay,
wait_for,
sending_delay,
} => {
if unsafe { !CALLED_BEFORE } {
let reservation_id = ReservationId::reserve(reservation_amount, reservation_delay)
.expect("Failed to reserve gas");

unsafe {
CALLED_BEFORE = true;
RESERVATION_ID = Some(reservation_id);
}

exec::wait_for(wait_for);
}

msg::send_bytes_delayed_from_reservation(
unsafe { RESERVATION_ID.expect("Unset") },
msg::source(),
[],
0,
sending_delay,
)
.expect(SENDING_EXPECT);
}
}
}
1 change: 1 addition & 0 deletions pallets/gear/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ demo-out-of-memory.workspace = true
demo-ping.workspace = true
demo-sync-duplicate.workspace = true
demo-custom.workspace = true
demo-delayed-reservation-sender.workspace = true
test-syscalls.workspace = true
page_size.workspace = true
frame-support-test = { workspace = true, features = ["std"] }
Expand Down
2 changes: 1 addition & 1 deletion pallets/gear/src/benchmarking/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ where
program.gas_reservation_map.insert(
ReservationId::from(x as u64),
GasReservationSlot {
amount: 1_000,
amount: 100_000,
start: 1,
finish: 100,
},
Expand Down
30 changes: 9 additions & 21 deletions pallets/gear/src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,8 +535,7 @@ where
let hold_builder = HoldBoundBuilder::<T>::new(StorageType::DispatchStash);

// Calculating correct gas amount for delay.
let bn_delay = delay.saturated_into::<BlockNumberFor<T>>();
let delay_hold = hold_builder.clone().duration(bn_delay);
let delay_hold = hold_builder.duration(delay.saturated_into());
let gas_for_delay = delay_hold.lock_amount();

let interval_finish = if to_user {
Expand Down Expand Up @@ -591,22 +590,18 @@ where
Self::remove_gas_reservation_with_task(dispatch.source(), reservation_id);
}

// Calculating correct hold bound to lock gas.
let maximal_hold = hold_builder.maximum_for_message(dispatch.id());
let hold = delay_hold.min(maximal_hold);

// Locking funds for holding.
let lock_id = hold.lock_id().unwrap_or_else(|| {
let lock_id = delay_hold.lock_id().unwrap_or_else(|| {
unreachable!("DispatchStash storage is guaranteed to have an associated lock id")
});
GasHandlerOf::<T>::lock(dispatch.id(), lock_id, hold.lock_amount())
GasHandlerOf::<T>::lock(dispatch.id(), lock_id, delay_hold.lock_amount())
.unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e));

if hold.expected_duration().is_zero() {
if delay_hold.expected_duration().is_zero() {
unreachable!("Hold duration cannot be zero");
}

hold.expected()
delay_hold.expected()
} else {
match (dispatch.gas_limit(), reservation) {
(Some(gas_limit), None) => Self::split_with_value(
Expand All @@ -629,25 +624,18 @@ where
}
}

// `HoldBound` builder.
let hold_builder = HoldBoundBuilder::<T>::new(StorageType::DispatchStash);

// Calculating correct hold bound to lock gas.
let maximal_hold = hold_builder.maximum_for_message(dispatch.id());
let hold = delay_hold.min(maximal_hold);

// Locking funds for holding.
let lock_id = hold.lock_id().unwrap_or_else(|| {
let lock_id = delay_hold.lock_id().unwrap_or_else(|| {
unreachable!("DispatchStash storage is guaranteed to have an associated lock id")
});
GasHandlerOf::<T>::lock(dispatch.id(), lock_id, hold.lock_amount())
GasHandlerOf::<T>::lock(dispatch.id(), lock_id, delay_hold.lock_amount())
.unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e));

if hold.expected_duration().is_zero() {
if delay_hold.expected_duration().is_zero() {
unreachable!("Hold duration cannot be zero");
}

hold.expected()
delay_hold.expected()
};

if !dispatch.value().is_zero() {
Expand Down
Loading

0 comments on commit 9fcabda

Please sign in to comment.