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

refactor(core): Guarantee system accounts existence for programs #3961

Merged
merged 28 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2b589db
Ensure system accounts always exist for programs; ditch the concept o…
ekovalev Apr 20, 2024
8cb4cca
Add tests
ekovalev May 21, 2024
7f70522
Add migration
ekovalev May 24, 2024
d596b2d
Fix tests
ekovalev May 24, 2024
7185719
Create an account for a program in raw upload for benchmarks
ekovalev May 26, 2024
1216cb1
Fix insufficient value issue in benchmarks
ekovalev May 26, 2024
fa5be4f
Fix benchmarking tests
ekovalev May 27, 2024
45774fb
Fix post_update version check in try-runtime tests
ekovalev May 28, 2024
e196360
Add test to validate value handling in gr_create_program syscall
ekovalev May 29, 2024
69df4f0
Tidy-up
ekovalev May 29, 2024
74ee616
Merge branch 'master' into ek-programs-accounts-management
ekovalev May 30, 2024
c514731
Use ED lock instead of direct inc_consumers to ensure account existence
ekovalev Jun 8, 2024
cba022f
Merge branch 'master' into ek-programs-accounts-management
ekovalev Jun 8, 2024
1ca29e4
Catch up with the changes in master
ekovalev Jun 8, 2024
e45101a
Prettify tests
ekovalev Jun 10, 2024
5dad8f1
Address suggestions
ekovalev Jun 12, 2024
e72324a
Merge branch 'master' into ek-programs-accounts-management
ekovalev Jun 12, 2024
97b66e5
Add integration test
ekovalev Jun 18, 2024
4d8cf02
Add a reference to an issue for future improvement
ekovalev Jun 18, 2024
c9ede2e
Merge branch 'master' into ek-programs-accounts-management
ekovalev Jun 19, 2024
e39fc3a
Merge branch 'master' into ek-programs-accounts-management
ekovalev Jun 20, 2024
6303154
Merge branch 'master' into ek-programs-accounts-management
ekovalev Jun 20, 2024
62ef1ba
Add balances api integration test
ekovalev Jun 20, 2024
973b756
Fix tests
ekovalev Jun 21, 2024
a6b03bb
Merge branch 'master' into ek-programs-accounts-management
ekovalev Jun 21, 2024
449e7e4
Typo
ekovalev Jun 21, 2024
e2cc6e8
Bump pallet storage version
ekovalev Jun 21, 2024
e9b64ec
Merge branch 'master' into ek-programs-accounts-management
ekovalev Jun 23, 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
20 changes: 20 additions & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ members = [
"examples/delayed-reservation-sender",
"examples/compose",
"examples/constructor",
"examples/create-program-reentrance",
"examples/delayed-sender",
"examples/distributor",
"examples/fungible-token",
Expand Down Expand Up @@ -69,6 +70,7 @@ members = [
"examples/sync-duplicate",
"examples/syscalls",
"examples/syscall-error",
"examples/value-sender",
"examples/vec",
"examples/wait",
"examples/wait-timeout",
Expand Down Expand Up @@ -408,6 +410,7 @@ 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-create-program-reentrance = { path = "examples/create-program-reentrance" }
demo-delayed-sender = { path = "examples/delayed-sender" }
demo-distributor = { path = "examples/distributor" }
demo-futures-unordered = { path = "examples/futures-unordered", features = ["debug"] }
Expand Down Expand Up @@ -439,6 +442,7 @@ demo-signal-entry = { path = "examples/signal-entry", default-features = false }
demo-state-rollback = { path = "examples/state-rollback" }
demo-sync-duplicate = { path = "examples/sync-duplicate" }
demo-vec = { path = "examples/vec" }
demo-value-sender = { path = "examples/value-sender" }
demo-wait = { path = "examples/wait" }
demo-waiter = { path = "examples/waiter", default-features = false }
demo-wait-timeout = { path = "examples/wait-timeout" }
Expand Down
22 changes: 21 additions & 1 deletion common/src/pallet_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ use frame_system::limits::BlockWeights;

#[macro_export]
macro_rules! impl_pallet_balances {
($( $tokens:tt )*) => {
#[allow(dead_code)]
type BalancesConfigDustRemoval = ();

mod pallet_tests_balances_config_impl {
use super::*;

$crate::impl_pallet_balances_inner!($( $tokens )*);
}
};
}

#[macro_export]
macro_rules! impl_pallet_balances_inner {
($runtime:ty) => {
impl pallet_balances::Config for $runtime {
type MaxLocks = ();
Expand All @@ -36,13 +50,19 @@ macro_rules! impl_pallet_balances {
type RuntimeHoldReason = RuntimeHoldReason;
type ReserveIdentifier = [u8; 8];
type Balance = Balance;
type DustRemoval = ();
type DustRemoval = BalancesConfigDustRemoval;
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
}
};

($runtime:ty, DustRemoval = $dust_removal:ty $(, $( $rest:tt )*)?) => {
type BalancesConfigDustRemoval = $dust_removal;

$crate::impl_pallet_balances_inner!($runtime $(, $( $rest )*)?);
};
}

pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
Expand Down
9 changes: 8 additions & 1 deletion core-processor/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ pub enum JournalNote {
},
/// Store programs requested by user to be initialized later
StoreNewPrograms {
/// Current program id.
program_id: ProgramId,
/// Code hash used to create new programs with ids in `candidates` field
code_id: CodeId,
/// Collection of program candidate ids and their init message ids.
Expand Down Expand Up @@ -386,7 +388,12 @@ pub trait JournalHandler {
/// Store new programs in storage.
///
/// Program ids are ids of _potential_ (planned to be initialized) programs.
fn store_new_programs(&mut self, code_id: CodeId, candidates: Vec<(MessageId, ProgramId)>);
fn store_new_programs(
&mut self,
program_id: ProgramId,
code_id: CodeId,
candidates: Vec<(MessageId, ProgramId)>,
);
/// Stop processing queue.
///
/// Pushes StoredDispatch back to the top of the queue and decreases gas allowance.
Expand Down
121 changes: 107 additions & 14 deletions core-processor/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,8 +511,6 @@ impl<'a, LP: LazyPagesInterface> ExtMutator<'a, LP> {
packet: &T,
check_gas_limit: bool,
) -> Result<(), FallibleExtError> {
self.ext.check_message_value(packet.value())?;

let gas_limit = if check_gas_limit {
self.check_gas_limit(packet.gas_limit())?
} else {
Expand Down Expand Up @@ -722,16 +720,6 @@ impl<LP: LazyPagesInterface> Ext<LP> {
Ok(result)
}

fn check_message_value(&self, message_value: u128) -> Result<(), FallibleExtError> {
let existential_deposit = self.context.existential_deposit;
// Sending value should apply the range {0} ∪ [existential_deposit; +inf)
if message_value != 0 && message_value < existential_deposit {
Err(MessageError::InsufficientValue.into())
} else {
Ok(())
}
}

fn check_gas_limit(&self, gas_limit: Option<GasLimit>) -> Result<GasLimit, FallibleExtError> {
let mailbox_threshold = self.context.mailbox_threshold;
let gas_limit = gas_limit.unwrap_or(0);
Expand Down Expand Up @@ -1013,7 +1001,6 @@ impl<LP: LazyPagesInterface> Externalities for Ext<LP> {
) -> Result<MessageId, Self::FallibleError> {
self.with_changes(|mutator| {
mutator.check_forbidden_destination(msg.destination())?;
mutator.check_message_value(msg.value())?;
// TODO: unify logic around different source of gas (may be origin msg,
// or reservation) in order to implement #1828.
mutator.check_reservation_gas_limit_for_delayed_sending(&id, delay)?;
Expand Down Expand Up @@ -1063,7 +1050,6 @@ impl<LP: LazyPagesInterface> Externalities for Ext<LP> {
self.with_changes(|mutator| {
mutator
.check_forbidden_destination(mutator.context.message_context.reply_destination())?;
mutator.check_message_value(msg.value())?;
// TODO: gasful sending (#1828)
mutator.charge_message_value(msg.value())?;
mutator.charge_sending_fee(0)?;
Expand Down Expand Up @@ -1372,13 +1358,17 @@ impl<LP: LazyPagesInterface> Externalities for Ext<LP> {
packet: InitPacket,
delay: u32,
) -> Result<(MessageId, ProgramId), Self::FallibleError> {
let ed = self.context.existential_deposit;
self.with_changes(|mutator| {
// We don't check for forbidden destination here, since dest is always unique and almost impossible to match SYSTEM_ID
mutator.safe_gasfull_sends(&packet, delay)?;
mutator.charge_expiring_resources(&packet, true)?;
mutator.charge_sending_fee(delay)?;
mutator.charge_for_dispatch_stash_hold(delay)?;

// Charge ED to value_counter
mutator.charge_message_value(ed)?;

let code_hash = packet.code_id();

// Send a message for program creation
Expand Down Expand Up @@ -1516,6 +1506,18 @@ mod tests {

self
}

fn with_existential_deposit(mut self, ed: u128) -> Self {
self.0.existential_deposit = ed;

self
}

fn with_value(mut self, value: u128) -> Self {
self.0.value_counter = ValueCounter::new(value);

self
}
}

// Invariant: Refund never occurs in `free` call.
Expand Down Expand Up @@ -2173,4 +2175,95 @@ mod tests {
gas - msg_gas - 2 * ext.context.mailbox_threshold
);
}

#[test]
// This function tests:
//
// - `create_program` fails due to lack of value to pay for ED
// - `create_program` is successful
fn test_create_program() {
let mut ext = Ext::new(
ProcessorContextBuilder::new()
.with_message_context(MessageContextBuilder::new().build())
.with_existential_deposit(500)
.with_value(0)
.build(),
);

let data = InitPacket::default();

let msg = ext.create_program(data.clone(), 0);
assert_eq!(
msg.unwrap_err(),
FallibleExtError::Core(FallibleExtErrorCore::Execution(
FallibleExecutionError::NotEnoughValue
))
);

let mut ext = Ext::new(
ProcessorContextBuilder::new()
.with_gas(GasCounter::new(u64::MAX))
.with_message_context(MessageContextBuilder::new().build())
.with_existential_deposit(500)
.with_value(1500)
.build(),
);

let msg = ext.create_program(data.clone(), 0);
assert!(msg.is_ok());
}

#[test]
// This function tests:
//
// - `send_commit` with value greater than the ED
// - `send_commit` with value below the ED
fn test_send_commit_with_value() {
let mut ext = Ext::new(
ProcessorContextBuilder::new()
.with_message_context(
MessageContextBuilder::new()
.with_outgoing_limit(u32::MAX)
.build(),
)
.with_existential_deposit(500)
.with_value(0)
.build(),
);

let data = HandlePacket::new(ProgramId::default(), Payload::default(), 1000);

let handle = ext.send_init().expect("No outgoing limit");

let msg = ext.send_commit(handle, data.clone(), 0);
assert_eq!(
msg.unwrap_err(),
FallibleExtError::Core(FallibleExtErrorCore::Execution(
FallibleExecutionError::NotEnoughValue
))
);

let mut ext = Ext::new(
ProcessorContextBuilder::new()
.with_message_context(
MessageContextBuilder::new()
.with_outgoing_limit(u32::MAX)
.build(),
)
.with_existential_deposit(500)
.with_value(5000)
.build(),
);

let handle = ext.send_init().expect("No outgoing limit");
// Sending value greater than ED is ok
let msg = ext.send_commit(handle, data.clone(), 0);
assert!(msg.is_ok());

let data = HandlePacket::new(ProgramId::default(), Payload::default(), 100);
let handle = ext.send_init().expect("No outgoing limit");
let msg = ext.send_commit(handle, data, 0);
// Sending value below ED is also fine
assert!(msg.is_ok());
}
}
3 changes: 2 additions & 1 deletion core-processor/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ pub fn handle_journal(
}
JournalNote::SendValue { from, to, value } => handler.send_value(from, to, value),
JournalNote::StoreNewPrograms {
program_id,
code_id,
candidates,
} => handler.store_new_programs(code_id, candidates),
} => handler.store_new_programs(program_id, code_id, candidates),
JournalNote::StopProcessing {
dispatch,
gas_burned,
Expand Down
1 change: 1 addition & 0 deletions core-processor/src/processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ pub fn process_success(
// Must be handled before handling generated dispatches.
for (code_id, candidates) in program_candidates {
journal.push(JournalNote::StoreNewPrograms {
program_id,
code_id,
candidates,
});
Expand Down
21 changes: 21 additions & 0 deletions examples/create-program-reentrance/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "demo-create-program-reentrance"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true

[dependencies]
gstd.workspace = true
hex.workspace = true

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

[features]
debug = ["gstd/debug"]
wasm-wrapper = []
std = ["wasm-wrapper"]
default = ["std"]
25 changes: 25 additions & 0 deletions examples/create-program-reentrance/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// This file is part of Gear.

// Copyright (C) 2021-2024 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 gear_wasm_builder::WasmBuilder;

fn main() {
WasmBuilder::new()
.exclude_features(vec!["std", "wasm-wrapper"])
.build();
}
Loading
Loading