diff --git a/app-libs/sgx-runtime/pallets/notes/src/lib.rs b/app-libs/sgx-runtime/pallets/notes/src/lib.rs index e616d4edd..0ca397566 100644 --- a/app-libs/sgx-runtime/pallets/notes/src/lib.rs +++ b/app-libs/sgx-runtime/pallets/notes/src/lib.rs @@ -164,6 +164,29 @@ pub mod pallet { } Ok(().into()) } + + #[pallet::call_index(1)] + #[pallet::weight((10_000, DispatchClass::Normal, Pays::Yes))] + pub fn note_string( + origin: OriginFor, + // who is involved in this note (usually sender and recipient) + link_to: Vec, + payload: Vec, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + ensure!(link_to.len() < 3, Error::::TooManyLinkedAccounts); + let note = TimestampedTrustedNote:: { + timestamp: Timestamp::::get(), + version: NOTE_VERSION, + note: TrustedNote::String(payload), + }; + let (bucket_index, note_index) = Self::store_note(note)?; + + for account in link_to { + >::mutate(bucket_index, account, |v| v.push(note_index)); + } + Ok(().into()) + } } } @@ -196,6 +219,8 @@ impl Pallet { if let Some(bucket) = Self::buckets(bucket_index) { if bucket.bytes + free <= T::MaxBucketSize::get() { return Ok(bucket) + } else { + >::mutate(|s| *s = s.saturating_add(bucket.bytes)); } } bucket_index.saturating_add(1) diff --git a/app-libs/sgx-runtime/pallets/notes/src/tests.rs b/app-libs/sgx-runtime/pallets/notes/src/tests.rs index 79e911fd0..7b1da075c 100644 --- a/app-libs/sgx-runtime/pallets/notes/src/tests.rs +++ b/app-libs/sgx-runtime/pallets/notes/src/tests.rs @@ -131,18 +131,42 @@ fn enforce_retention_limits_works() { assert_eq!(Notes::last_bucket_index(), Some(0)); assert_eq!(Notes::first_bucket_index(), Some(0)); + let second_bucket_size = MaxBucketSize::get() - 100; + let bucket = BucketInfo { + index: 1, + bytes: second_bucket_size, + begins_at: Default::default(), + ends_at: Default::default(), + }; + >::insert(1, bucket); + >::put(1); + + let third_bucket_size = MaxBucketSize::get() - 100; + let bucket = BucketInfo { + index: 2, + bytes: third_bucket_size, + begins_at: Default::default(), + ends_at: Default::default(), + }; + >::insert(2, bucket); + >::put(2); + let closed_buckets_size = MaxTotalSize::get() - MaxBucketSize::get() + 1; >::put(closed_buckets_size); - assert_eq!(Notes::get_bucket_with_room_for(500).unwrap().index, 0); + assert_eq!(Notes::get_bucket_with_room_for(99).unwrap().index, 2); let new_bucket = Notes::get_bucket_with_room_for(512).unwrap(); - assert_eq!(new_bucket.index, 1); + assert_eq!(new_bucket.index, 3); assert_eq!(new_bucket.bytes, 0); - assert_eq!(Notes::last_bucket_index(), Some(1)); - assert_eq!(Notes::first_bucket_index(), Some(1)); + assert_eq!(Notes::last_bucket_index(), Some(3)); + assert_eq!(Notes::first_bucket_index(), Some(2)); assert!(Notes::buckets(0).is_none()); - assert_eq!(Notes::closed_buckets_size(), closed_buckets_size - first_bucket_size); + assert!(Notes::buckets(1).is_none()); + assert_eq!( + Notes::closed_buckets_size(), + closed_buckets_size - first_bucket_size - second_bucket_size + third_bucket_size + ); }); } @@ -209,3 +233,30 @@ fn note_trusted_call_works() { ); }) } + +#[test] +fn note_string_works() { + new_test_ext().execute_with(|| { + System::set_block_number(0); + let now: u64 = 234; + set_timestamp(now); + let alice = AccountKeyring::Alice.to_account_id(); + let bob = AccountKeyring::Bob.to_account_id(); + let msg = "helllllooo Aliiicceee".to_string(); + assert_ok!(Notes::note_string( + RuntimeOrigin::signed(bob.clone()), + [bob.clone(), alice.clone()].into(), + msg.encode() + )); + assert_eq!(Notes::notes_lookup(0, alice.clone()), vec![0]); + assert_eq!(Notes::notes_lookup(0, bob.clone()), vec![0]); + let expected_note = TimestampedTrustedNote:: { + timestamp: now, + version: 1, + note: TrustedNote::String(msg.encode()), + }; + assert_eq!(Notes::notes(0, 0), Some(expected_note.clone())); + let bucket = Notes::buckets(0).unwrap(); + assert_eq!(bucket.bytes, expected_note.encoded_size() as u32); + }) +} diff --git a/app-libs/sgx-runtime/src/lib.rs b/app-libs/sgx-runtime/src/lib.rs index 1180b5641..3b5dd92c4 100644 --- a/app-libs/sgx-runtime/src/lib.rs +++ b/app-libs/sgx-runtime/src/lib.rs @@ -312,13 +312,20 @@ impl pallet_guess_the_number::Config for Runtime { type MaxWinners = ConstU8<12>; } +parameter_types! { + pub const MaxNoteSize: u32 = 512; + pub const MaxBucketSize: u32 = 51_200; + pub const MaxTotalSize: u32 = 5_120_000; +} + impl pallet_notes::Config for Runtime { type MomentsPerDay = MomentsPerDay; type Currency = Balances; - type MaxNoteSize = ConstU32<512>; - type MaxBucketSize = ConstU32<51200>; - type MaxTotalSize = ConstU32<5_120_000>; + type MaxNoteSize = MaxNoteSize; + type MaxBucketSize = MaxBucketSize; + type MaxTotalSize = MaxTotalSize; } + // The plain sgx-runtime without the `evm-pallet` #[cfg(not(feature = "evm"))] construct_runtime!( diff --git a/app-libs/stf/src/helpers.rs b/app-libs/stf/src/helpers.rs index 33c48725c..9e51ff116 100644 --- a/app-libs/stf/src/helpers.rs +++ b/app-libs/stf/src/helpers.rs @@ -108,6 +108,25 @@ pub fn ensure_enclave_signer_account( } } +pub fn ensure_maintainer_account>( + account: &AccountId, +) -> StfResult<()> { + let expected_maintainer_account: AccountId = AccountId::from([ + 148, 117, 87, 242, 252, 96, 167, 29, 118, 69, 87, 119, 15, 57, 142, 82, 216, 8, 210, 102, + 12, 213, 46, 76, 214, 5, 144, 153, 148, 113, 89, 95, + ]); + if &expected_maintainer_account == account { + Ok(()) + } else { + error!( + "Expected maintainer account {}, but found {}", + account_id_to_string(&expected_maintainer_account), + account_id_to_string(account) + ); + Err(StfError::RequireMaintainerAccount) + } +} + pub fn set_block_number(block_number: u32) { sp_io::storage::set(&storage_value_key("System", "Number"), &block_number.encode()); } diff --git a/app-libs/stf/src/trusted_call.rs b/app-libs/stf/src/trusted_call.rs index b949a358e..c143b44a8 100644 --- a/app-libs/stf/src/trusted_call.rs +++ b/app-libs/stf/src/trusted_call.rs @@ -26,8 +26,8 @@ use crate::evm_helpers::{create_code_hash, evm_create2_address, evm_create_addre use crate::{ guess_the_number::GuessTheNumberTrustedCall, helpers::{ - enclave_signer_account, ensure_enclave_signer_account, get_mortality, shard_vault, - shielding_target_genesis_hash, store_note, wrap_bytes, + enclave_signer_account, ensure_enclave_signer_account, ensure_maintainer_account, + get_mortality, shard_vault, shielding_target_genesis_hash, store_note, wrap_bytes, }, Getter, STF_SHIELDING_FEE_AMOUNT_DIVIDER, }; @@ -58,6 +58,7 @@ use itp_types::{ }; use itp_utils::stringify::account_id_to_string; use log::*; +use pallet_notes::{TimestampedTrustedNote, TrustedNote}; use sp_core::{ crypto::{AccountId32, UncheckedFrom}, ed25519, @@ -77,6 +78,8 @@ pub enum TrustedCall { balance_unshield(AccountId, AccountId, Balance, ShardIdentifier) = 3, // (AccountIncognito, BeneficiaryPublicAccount, Amount, Shard) balance_shield(AccountId, AccountId, Balance, ParentchainId) = 4, // (Root, AccountIncognito, Amount, origin parentchain) balance_transfer_with_note(AccountId, AccountId, Balance, Vec) = 5, + note_bloat(AccountId, u32) = 10, + waste_time(AccountId, u32) = 11, guess_the_number(GuessTheNumberTrustedCall) = 50, #[cfg(feature = "evm")] evm_withdraw(AccountId, H160, Balance) = 90, // (Origin, Address EVM Account, Value) @@ -136,6 +139,8 @@ impl TrustedCall { Self::balance_shield(sender_account, ..) => sender_account, Self::balance_transfer_with_note(sender_account, ..) => sender_account, Self::timestamp_set(sender_account, ..) => sender_account, + Self::note_bloat(sender_account, ..) => sender_account, + Self::waste_time(sender_account, ..) => sender_account, #[cfg(feature = "evm")] Self::evm_withdraw(sender_account, ..) => sender_account, #[cfg(feature = "evm")] @@ -463,7 +468,39 @@ where }; Ok(()) }, - + TrustedCall::note_bloat(sender, kilobytes) => { + ensure_maintainer_account(&sender)?; + if kilobytes >= 1_100 { + return Err(StfError::Dispatch("bloat value must be below 1.1 MB".to_string())) + } + std::println!("⣿STF⣿ bloating notes by {}kB", kilobytes); + // make sure we use exactly 512 bytes per note + let dummy = TimestampedTrustedNote { + timestamp: 0u64, + version: 0u16, + note: TrustedNote::String(vec![0u8; 400]), + }; + let msg = vec![111u8; 512 - (dummy.encoded_size() - 400)]; + for _ in 0..kilobytes * 2 { + ita_sgx_runtime::NotesCall::::note_string { + link_to: vec![], + payload: msg.clone(), + } + .dispatch_bypass_filter(ita_sgx_runtime::RuntimeOrigin::signed(sender.clone())) + .map_err(|e| StfError::Dispatch(format!("Store note error: {:?}", e.error)))?; + } + Ok(()) + }, + TrustedCall::waste_time(sender, milliseconds) => { + ensure_maintainer_account(&sender)?; + if milliseconds > 10_000 { + return Err(StfError::Dispatch("waste time value must be below 10s".to_string())) + } + std::println!("⣿STF⣿ waste time: {}ms", milliseconds); + std::thread::sleep(std::time::Duration::from_millis(milliseconds as u64)); + std::println!("⣿STF⣿ finished wasting time"); + Ok(()) + }, #[cfg(feature = "evm")] TrustedCall::evm_withdraw(from, address, value) => { debug!("evm_withdraw({}, {}, {})", account_id_to_string(&from), address, value); diff --git a/cli/src/trusted_base_cli/commands/mod.rs b/cli/src/trusted_base_cli/commands/mod.rs index 90b8757b6..88c403d7e 100644 --- a/cli/src/trusted_base_cli/commands/mod.rs +++ b/cli/src/trusted_base_cli/commands/mod.rs @@ -9,9 +9,11 @@ pub mod get_shard_vault; pub mod get_total_issuance; pub mod nonce; +pub mod note_bloat; pub mod transfer; pub mod unshield_funds; pub mod version; +pub mod waste_time; #[cfg(feature = "test")] pub mod set_balance; diff --git a/cli/src/trusted_base_cli/commands/note_bloat.rs b/cli/src/trusted_base_cli/commands/note_bloat.rs new file mode 100644 index 000000000..6c880f6b9 --- /dev/null +++ b/cli/src/trusted_base_cli/commands/note_bloat.rs @@ -0,0 +1,64 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_nonce, + trusted_cli::TrustedCli, + trusted_command_utils::{get_identifiers, get_pair_from_str}, + trusted_operation::{perform_trusted_operation, send_direct_request}, + Cli, CliResult, CliResultOk, +}; +use ita_stf::{Getter, Index, TrustedCall, TrustedCallSigned}; +use itp_stf_primitives::{ + traits::TrustedCallSigning, + types::{KeyPair, TrustedOperation}, +}; +use log::*; +use sp_core::{crypto::Ss58Codec, Pair}; +use std::boxed::Box; + +#[derive(Parser)] +pub struct NoteBloatCommand { + /// subject's AccountId in ss58check format. must have maintainer privilege + account: String, + + /// kilobytes of notes to store + kilobytes: u32, +} + +impl NoteBloatCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedCli) -> CliResult { + let signer = get_pair_from_str(trusted_args, &self.account); + info!("account ss58 is {}", signer.public().to_ss58check()); + + println!("send trusted call note-bloat({}kB)", self.kilobytes); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(signer, cli, trusted_args); + let top: TrustedOperation = + TrustedCall::note_bloat(signer.public().into(), self.kilobytes) + .sign(&KeyPair::Sr25519(Box::new(signer)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + + if trusted_args.direct { + Ok(send_direct_request(cli, trusted_args, &top).map(|_| CliResultOk::None)?) + } else { + Ok(perform_trusted_operation::<()>(cli, trusted_args, &top) + .map(|_| CliResultOk::None)?) + } + } +} diff --git a/cli/src/trusted_base_cli/commands/waste_time.rs b/cli/src/trusted_base_cli/commands/waste_time.rs new file mode 100644 index 000000000..15bf8c393 --- /dev/null +++ b/cli/src/trusted_base_cli/commands/waste_time.rs @@ -0,0 +1,64 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_nonce, + trusted_cli::TrustedCli, + trusted_command_utils::{get_identifiers, get_pair_from_str}, + trusted_operation::{perform_trusted_operation, send_direct_request}, + Cli, CliResult, CliResultOk, +}; +use ita_stf::{Getter, Index, TrustedCall, TrustedCallSigned}; +use itp_stf_primitives::{ + traits::TrustedCallSigning, + types::{KeyPair, TrustedOperation}, +}; +use log::*; +use sp_core::{crypto::Ss58Codec, Pair}; +use std::boxed::Box; + +#[derive(Parser)] +pub struct WasteTimeCommand { + /// subject's AccountId in ss58check format. must have maintainer privilege + account: String, + + /// milliseconds to waste + millis: u32, +} + +impl WasteTimeCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedCli) -> CliResult { + let signer = get_pair_from_str(trusted_args, &self.account); + info!("account ss58 is {}", signer.public().to_ss58check()); + + println!("send trusted call waste-time({}ms)", self.millis); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(signer, cli, trusted_args); + let top: TrustedOperation = + TrustedCall::waste_time(signer.public().into(), self.millis) + .sign(&KeyPair::Sr25519(Box::new(signer)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + + if trusted_args.direct { + Ok(send_direct_request(cli, trusted_args, &top).map(|_| CliResultOk::None)?) + } else { + Ok(perform_trusted_operation::<()>(cli, trusted_args, &top) + .map(|_| CliResultOk::None)?) + } + } +} diff --git a/cli/src/trusted_base_cli/mod.rs b/cli/src/trusted_base_cli/mod.rs index aead03235..05b4b9bf0 100644 --- a/cli/src/trusted_base_cli/mod.rs +++ b/cli/src/trusted_base_cli/mod.rs @@ -24,7 +24,9 @@ use crate::{ get_notes::GetNotesCommand, get_parentchains_info::GetParentchainsInfoCommand, get_shard::GetShardCommand, get_shard_vault::GetShardVaultCommand, get_total_issuance::GetTotalIssuanceCommand, nonce::NonceCommand, - transfer::TransferCommand, unshield_funds::UnshieldFundsCommand, version::VersionCommand, + note_bloat::NoteBloatCommand, transfer::TransferCommand, + unshield_funds::UnshieldFundsCommand, version::VersionCommand, + waste_time::WasteTimeCommand, }, trusted_cli::TrustedCli, trusted_command_utils::get_keystore_path, @@ -86,6 +88,12 @@ pub enum TrustedBaseCommand { /// get notes for account GetNotes(GetNotesCommand), + /// waste time for benchmarking + WasteTime(WasteTimeCommand), + + /// bloat state with dummy notes for benchmarking + NoteBloat(NoteBloatCommand), + /// get a version string for the enclave Version(VersionCommand), } @@ -109,6 +117,8 @@ impl TrustedBaseCommand { TrustedBaseCommand::GetShardVault(cmd) => cmd.run(cli, trusted_cli), TrustedBaseCommand::GetSidechainHeader(cmd) => cmd.run(cli, trusted_cli), TrustedBaseCommand::GetTotalIssuance(cmd) => cmd.run(cli, trusted_cli), + TrustedBaseCommand::NoteBloat(cmd) => cmd.run(cli, trusted_cli), + TrustedBaseCommand::WasteTime(cmd) => cmd.run(cli, trusted_cli), TrustedBaseCommand::Version(cmd) => cmd.run(cli, trusted_cli), } } diff --git a/core-primitives/stf-executor/src/executor.rs b/core-primitives/stf-executor/src/executor.rs index dbe12973e..a15a238ea 100644 --- a/core-primitives/stf-executor/src/executor.rs +++ b/core-primitives/stf-executor/src/executor.rs @@ -335,7 +335,7 @@ where for trusted_call_signed in trusted_calls.into_iter() { // Break if allowed time window is over. if ends_at < duration_now() { - info!("Aborting execution of trusted calls because slot time is up"); + info!("stopping execution of further trusted calls because slot time is up"); break } diff --git a/core-primitives/stf-primitives/src/error.rs b/core-primitives/stf-primitives/src/error.rs index 01d87e856..cfafaac6f 100644 --- a/core-primitives/stf-primitives/src/error.rs +++ b/core-primitives/stf-primitives/src/error.rs @@ -27,6 +27,8 @@ pub enum StfError { MissingPrivileges(AccountId), #[display(fmt = "Valid enclave signer account is required")] RequireEnclaveSignerAccount, + #[display(fmt = "Valid maintainer account is required")] + RequireMaintainerAccount, #[display(fmt = "Error dispatching runtime call. {:?}", _0)] Dispatch(String), #[display(fmt = "Not enough funds to perform operation")] diff --git a/sidechain/consensus/slots/src/lib.rs b/sidechain/consensus/slots/src/lib.rs index 9ac42a412..af06fcfba 100644 --- a/sidechain/consensus/slots/src/lib.rs +++ b/sidechain/consensus/slots/src/lib.rs @@ -363,12 +363,13 @@ pub trait SimpleSlotWorker { if !timestamp_within_slot(&slot_info, &proposing.block) { warn!( - - "⌛️ Discarding proposal for slot {}, block number {}; block production took too long", - *slot, proposing.block.block().header().block_number(), + "⌛️ overdue proposal for slot {}, block number {}; block production took too long", + *slot, + proposing.block.block().header().block_number(), ); - - return None + // fixme: currently, we can't abort here because the TOP pool will keep the long-running + // TOPs and we'll never produce blocks again. just warn for now + //return None } if last_imported_integritee_header.is_some() {