From 2ec5aad984cbc0df0d4479e1f06d0912967dea17 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 3 Oct 2022 13:48:11 +0200 Subject: [PATCH 01/87] FEAT: moved contract billing to offchain worker #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 240 ++++++++++++++---- .../pallets/pallet-smart-contract/src/mock.rs | 1 + substrate-node/runtime/src/lib.rs | 2 + 3 files changed, 194 insertions(+), 49 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index fb620ae99..e5c26492c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -15,7 +15,9 @@ use frame_support::{ weights::Pays, BoundedVec, }; -use frame_system::{self as system, ensure_signed}; +use frame_system::{self as system, ensure_signed, + offchain::{AppCrypto, CreateSignedTransaction, SendSignedTransaction, Signer}, +}; use pallet_tfgrid; use pallet_tfgrid::pallet::{InterfaceOf, PubConfigOf}; use pallet_tfgrid::types as pallet_tfgrid_types; @@ -26,15 +28,49 @@ use sp_runtime::{ }; use substrate_fixed::types::U64F64; use tfchain_support::{traits::ChangeNode, types::Node}; +use sp_core::crypto::KeyTypeId; pub use pallet::*; +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"demo"); + #[cfg(test)] mod mock; #[cfg(test)] mod tests; +pub mod crypto { + use crate::KEY_TYPE; + use sp_core::sr25519::Signature as Sr25519Signature; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::Verify, + MultiSignature, MultiSigner, + }; + use sp_std::convert::TryFrom; + + app_crypto!(sr25519, KEY_TYPE); + + pub struct AuthId; + + // implemented for ocw-runtime + impl frame_system::offchain::AppCrypto for AuthId { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } + + // implemented for mock runtime in test + impl frame_system::offchain::AppCrypto<::Signer, Sr25519Signature> + for AuthId + { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } +} + pub mod weights; pub mod cost; @@ -48,9 +84,9 @@ pub mod pallet { use super::*; use codec::FullCodec; use frame_support::pallet_prelude::*; + use frame_support::traits::Hooks; use frame_support::{ dispatch::DispatchResultWithPostInfo, - log, traits::{Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced}, }; use frame_system::pallet_prelude::*; @@ -166,7 +202,7 @@ pub mod pallet { #[pallet::config] pub trait Config: - frame_system::Config + CreateSignedTransaction> + frame_system::Config + pallet_timestamp::Config + pallet_balances::Config + pallet_tfgrid::Config @@ -182,6 +218,8 @@ pub mod pallet { type GracePeriod: Get; type WeightInfo: WeightInfo; type NodeChanged: ChangeNode, InterfaceOf>; + type AuthorityId: AppCrypto; + type Call: From>; #[pallet::constant] type MaxNameContractNameLength: Get; @@ -297,6 +335,7 @@ pub mod pallet { NodeNotAvailableToDeploy, CannotUpdateContractInGraceState, NumOverflow, + OffchainSignedTxError, NameContractNameToShort, NameContractNameToLong, InvalidProviderConfiguration, @@ -435,30 +474,70 @@ pub mod pallet { ::RestrictedOrigin::ensure_origin(origin)?; Self::_approve_solution_provider(solution_provider_id, approve) } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn bill_contract_for_block( + origin: OriginFor, + contract_id: u64, + block_number: T::BlockNumber + ) -> DispatchResultWithPostInfo { + //TODO needed? + let _account_id = ensure_signed(origin)?; + Self::_bill_contract_for_block(contract_id, block_number) + } } #[pallet::hooks] impl Hooks> for Pallet { - fn on_finalize(block: T::BlockNumber) { - match Self::_bill_contracts_at_block(block) { - Ok(_) => { - log::info!( - "types::NodeContract billed successfully at block: {:?}", - block - ); - } - Err(err) => { - log::error!( - "types::NodeContract billed failed at block: {:?} with err {:?}", - block, - err - ); + fn offchain_worker(block_number: T::BlockNumber) { + //println!("Offchain Worker!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + let current_block_u64: u64 = block_number.saturated_into::(); + let contracts = ContractsToBillAt::::get(current_block_u64); + let mut failed_contract_ids: Vec = Vec::new(); + + for contract_id in contracts { + match Self::offchain_signed_tx(block_number, contract_id) { + Ok(_) => (), + Err(_) => failed_contract_ids.push(contract_id), } } - // clean storage map for billed contracts at block - let current_block_u64: u64 = block.saturated_into::(); - ContractsToBillAt::::remove(current_block_u64); + + if failed_contract_ids.is_empty() { + log::info!( + "types::NodeContract billed successfully at block: {:?}", + block_number + ); + } else { + log::info!( + "types::NodeContract billed failed for some of the contracts in block: {:?}", + block_number + ); + //TODO add for next run + } } + + // TODO remove + //fn on_finalize(block: T::BlockNumber) { + // match Self::_bill_contracts_at_block(block) { + // Ok(_) => { + // log::info!( + // "types::NodeContract billed successfully at block: {:?}", + // block + // ); + // } + // Err(err) => { + // log::info!( + // "types::NodeContract billed failed at block: {:?} with err {:?}", + // block, + // err + // ); + // } + // } + // clean storage map for billed contracts at block + // let current_block_u64: u64 = block.saturated_into::(); + // ContractsToBillAt::::remove(current_block_u64); + // } } } @@ -874,45 +953,42 @@ impl Pallet { ContractBillingInformationByID::::insert(report.contract_id, &contract_billing_info); } - pub fn _bill_contracts_at_block(block: T::BlockNumber) -> DispatchResultWithPostInfo { - let current_block_u64: u64 = block.saturated_into::(); - let contracts = ContractsToBillAt::::get(current_block_u64); - for contract_id in contracts { + pub fn _bill_contract_for_block(contract_id: u64, block: T::BlockNumber) -> DispatchResultWithPostInfo { if !Contracts::::contains_key(contract_id) { - continue; - } + return Ok(().into()); + } let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - // Try to bill contract - match Self::bill_contract(&mut contract) { - Ok(_) => { - log::info!( - "billed contract with id {:?} at block {:?}", - contract_id, - block - ); - } - Err(err) => { - log::error!( - "error while billing contract with id {:?}: {:?}", - contract_id, - err - ); - // If billing the contract failed, we should delete the contract and clean up storage - Self::remove_contract(contract.contract_id); - continue; - } + // Try to bill contract + match Self::bill_contract(&mut contract) { + Ok(_) => { + log::info!( + "billed contract with id {:?} at block {:?}", + contract_id, + block + ); } + Err(err) => { + log::error!( + "error while billing contract with id {:?}: {:?}", + contract_id, + err + ); + // If billing the contract failed, we should delete the contract and clean up storage + Self::remove_contract(contract.contract_id); + return Ok(().into()); + } + } - // https://github.com/threefoldtech/tfchain/issues/264 - // if a contract is still in storage and actively getting billed whilst it is in state delete - // remove all associated storage and continue + // https://github.com/threefoldtech/tfchain/issues/264 + // if a contract is still in storage and actively getting billed whilst it is in state delete + // remove all associated storage and continue let ctr = Contracts::::get(contract_id); if let Some(contract) = ctr { if contract.contract_id != 0 && contract.is_state_delete() { Self::remove_contract(contract.contract_id); - continue; + return Ok(().into()); } // Reinsert into the next billing frequency @@ -922,6 +998,72 @@ impl Pallet { Ok(().into()) } + pub fn offchain_signed_tx(block_number: T::BlockNumber, contract_id: u64) -> Result<(), Error> { + let signer = Signer::::any_account(); + let result = signer.send_signed_transaction(|_acct| Call::bill_contract_for_block { + contract_id, + block_number, + }); + + if let Some((acc, res)) = result { + if res.is_err() { + log::error!("failure: offchain_signed_tx: tx sent: {:?}", acc.id); + return Err(>::OffchainSignedTxError); + } + // Transaction is sent successfully + return Ok(()); + } + // The case of `None`: no account is available for sending + log::error!("No local account available"); + return Err(>::OffchainSignedTxError); + } + + // TO BE REMOVED: + // pub fn _bill_contracts_at_block(block: T::BlockNumber) -> DispatchResultWithPostInfo { + // let current_block_u64: u64 = block.saturated_into::(); + // let contracts = ContractsToBillAt::::get(current_block_u64); + // for contract_id in contracts { + // let mut contract = Contracts::::get(contract_id); + // if contract.contract_id == 0 { + // continue; + // } + + // // Try to bill contract + // match Self::bill_contract(&mut contract) { + // Ok(_) => { + // log::info!( + // "billed contract with id {:?} at block {:?}", + // contract_id, + // block + // ); + // } + // Err(err) => { + // log::info!( + // "error while billing contract with id {:?}: {:?}", + // contract_id, + // err + // ); + // // If billing the contract failed, we should delete the contract and clean up storage + // Self::remove_contract(contract.contract_id); + // continue; + // } + // } + + // // https://github.com/threefoldtech/tfchain/issues/264 + // // if a contract is still in storage and actively getting billed whilst it is in state delete + // // remove all associated storage and continue + // let contract = Contracts::::get(contract_id); + // if contract.contract_id != 0 && contract.is_state_delete() { + // Self::remove_contract(contract.contract_id); + // continue; + // } + + // // Reinsert into the next billing frequency + // Self::_reinsert_contract_to_bill(contract.contract_id); + // } + // Ok(().into()) + // } + // Bills a contract (NodeContract or NameContract) // Calculates how much TFT is due by the user and distributes the rewards #[transactional] diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 36ae9bdd2..5db957399 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -289,6 +289,7 @@ pub fn get_staking_pool_account() -> AccountId { } pub fn new_test_ext() -> sp_io::TestExternalities { + //TODO add offchain worker see pallet-tft-price impl ExternalityBuilder let mut t = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); diff --git a/substrate-node/runtime/src/lib.rs b/substrate-node/runtime/src/lib.rs index a331a338e..a0b44eb37 100644 --- a/substrate-node/runtime/src/lib.rs +++ b/substrate-node/runtime/src/lib.rs @@ -396,6 +396,8 @@ impl pallet_smart_contract::Config for Runtime { type GracePeriod = GracePeriod; type WeightInfo = pallet_smart_contract::weights::SubstrateWeight; type NodeChanged = NodeChanged; + type AuthorityId = pallet_smart_contract::crypto::AuthId; + type Call = Call; type MaxNameContractNameLength = MaxNameContractNameLength; type NameContractName = pallet_smart_contract::name_contract::NameContractName; type RestrictedOrigin = EnsureRootOrCouncilApproval; From fba68f7d9053932a746026aff6a29f63054bc5eb Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 3 Oct 2022 13:50:38 +0200 Subject: [PATCH 02/87] FEAT: fixing tests #269 --- .../pallets/pallet-smart-contract/Cargo.toml | 2 ++ .../pallets/pallet-smart-contract/src/lib.rs | 7 +++--- .../pallets/pallet-smart-contract/src/mock.rs | 24 +++++++++++++++---- .../pallet-smart-contract/src/tests.rs | 6 ++++- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/Cargo.toml b/substrate-node/pallets/pallet-smart-contract/Cargo.toml index a96d3fbed..0bc9b1995 100644 --- a/substrate-node/pallets/pallet-smart-contract/Cargo.toml +++ b/substrate-node/pallets/pallet-smart-contract/Cargo.toml @@ -45,6 +45,8 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot- # Benchmarking frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false, optional = true } +sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } +env_logger = "*" [features] default = ['std'] std = [ diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index e5c26492c..e5b5509eb 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -481,7 +481,6 @@ pub mod pallet { contract_id: u64, block_number: T::BlockNumber ) -> DispatchResultWithPostInfo { - //TODO needed? let _account_id = ensure_signed(origin)?; Self::_bill_contract_for_block(contract_id, block_number) } @@ -490,8 +489,6 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn offchain_worker(block_number: T::BlockNumber) { - //println!("Offchain Worker!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - let current_block_u64: u64 = block_number.saturated_into::(); let contracts = ContractsToBillAt::::get(current_block_u64); let mut failed_contract_ids: Vec = Vec::new(); @@ -959,7 +956,8 @@ impl Pallet { } let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - + + log::info!("billing contract with id {:?} at block {:?}", contract_id, block); // Try to bill contract match Self::bill_contract(&mut contract) { Ok(_) => { @@ -999,6 +997,7 @@ impl Pallet { } pub fn offchain_signed_tx(block_number: T::BlockNumber, contract_id: u64) -> Result<(), Error> { + log::info!("Billing contract {:?} from block {:?}", contract_id, block_number); let signer = Signer::::any_account(); let result = signer.send_signed_transaction(|_acct| Call::bill_contract_for_block { contract_id, diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 5db957399..d56205bbf 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -15,9 +15,9 @@ use pallet_tfgrid::{ pub_ip::{GatewayIP, PublicIP}, twin::TwinIp, }; -use sp_core::{crypto::Ss58Codec, sr25519, Pair, Public, H256}; use sp_runtime::traits::{IdentifyAccount, Verify}; use sp_runtime::MultiSignature; +use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, Extrinsic as ExtrinsicT, IdentityLookup}, @@ -290,7 +290,7 @@ pub fn get_staking_pool_account() -> AccountId { pub fn new_test_ext() -> sp_io::TestExternalities { //TODO add offchain worker see pallet-tft-price impl ExternalityBuilder - let mut t = frame_system::GenesisConfig::default() + let mut storage = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); let genesis = pallet_balances::GenesisConfig:: { @@ -300,14 +300,28 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (charlie(), 150000), ], }; - genesis.assimilate_storage(&mut t).unwrap(); + genesis.assimilate_storage(&mut storage).unwrap(); let genesis = pallet_tft_price::GenesisConfig:: { allowed_origin: Some(bob()), min_tft_price: 10, max_tft_price: 1000, }; - genesis.assimilate_storage(&mut t).unwrap(); + genesis.assimilate_storage(&mut storage).unwrap(); - t.into() + let (offchain, _) = testing::TestOffchainExt::new(); + let (pool, _) = testing::TestTransactionPoolExt::new(); + let keystore = KeyStore::new(); + keystore + .sr25519_generate_new(KEY_TYPE, Some(&format!("//Alice"))) + .unwrap(); + + let mut t = sp_io::TestExternalities::from(storage); + t.register_extension(OffchainWorkerExt::new(offchain)); + t.register_extension(TransactionPoolExt::new(pool)); + t.register_extension(KeystoreExt(Arc::new(keystore))); + + t.execute_with(|| System::set_block_number(1)); + + t } diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index d19471688..3a198ef3b 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -2,7 +2,7 @@ use super::Event as SmartContractEvent; use crate::{mock::Event as MockEvent, mock::*, Error}; use frame_support::{ assert_noop, assert_ok, bounded_vec, - traits::{LockableCurrency, OnFinalize, OnInitialize, WithdrawReasons}, + traits::{LockableCurrency, OnFinalize, OnInitialize, WithdrawReasons, Hooks}, BoundedVec, }; use frame_system::{EventRecord, Phase, RawOrigin}; @@ -18,6 +18,8 @@ use tfchain_support::types::{FarmCertification, Location, NodeCertification, Pub const GIGABYTE: u64 = 1024 * 1024 * 1024; +use env_logger; + // NODE CONTRACT TESTS // // -------------------- // @@ -1380,6 +1382,7 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works #[test] fn test_name_contract_billing() { new_test_ext().execute_with(|| { + env_logger::init(); prepare_farm_and_node(); run_to_block(1); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); @@ -2510,6 +2513,7 @@ fn run_to_block(n: u64) { System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); SmartContractModule::on_initialize(System::block_number()); + SmartContractModule::offchain_worker(System::block_number()); } } From 5ed07feffa7cb6787e12e60059876b34437b1df0 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Wed, 3 Aug 2022 14:33:17 +0200 Subject: [PATCH 03/87] FEAT: modified pallet-smart-contract tests #269 --- substrate-node/pallets/pallet-smart-contract/src/lib.rs | 9 ++++++++- .../pallets/pallet-smart-contract/src/tests.rs | 6 ++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index e5b5509eb..c04a026bf 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -481,6 +481,7 @@ pub mod pallet { contract_id: u64, block_number: T::BlockNumber ) -> DispatchResultWithPostInfo { + log::info!("do I get here?"); let _account_id = ensure_signed(origin)?; Self::_bill_contract_for_block(contract_id, block_number) } @@ -493,6 +494,10 @@ pub mod pallet { let contracts = ContractsToBillAt::::get(current_block_u64); let mut failed_contract_ids: Vec = Vec::new(); + log::info!( + "contracts to bill: {:?}", + contracts + ); for contract_id in contracts { match Self::offchain_signed_tx(block_number, contract_id) { Ok(_) => (), @@ -1003,12 +1008,14 @@ impl Pallet { contract_id, block_number, }); - + if let Some((acc, res)) = result { if res.is_err() { log::error!("failure: offchain_signed_tx: tx sent: {:?}", acc.id); return Err(>::OffchainSignedTxError); } + log::info!("result for tx {:?}", res); + // Transaction is sent successfully return Ok(()); } diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 3a198ef3b..927241802 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -2,7 +2,7 @@ use super::Event as SmartContractEvent; use crate::{mock::Event as MockEvent, mock::*, Error}; use frame_support::{ assert_noop, assert_ok, bounded_vec, - traits::{LockableCurrency, OnFinalize, OnInitialize, WithdrawReasons, Hooks}, + traits::{LockableCurrency, OnFinalize, OnInitialize, WithdrawReasons, OffchainWorker}, BoundedVec, }; use frame_system::{EventRecord, Phase, RawOrigin}; @@ -2508,12 +2508,10 @@ pub fn create_twin(origin: AccountId) { fn run_to_block(n: u64) { Timestamp::set_timestamp((1628082000 * 1000) + (6000 * n)); while System::block_number() < n { - SmartContractModule::on_finalize(System::block_number()); + SmartContractModule::offchain_worker(System::block_number()); System::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); - SmartContractModule::on_initialize(System::block_number()); - SmartContractModule::offchain_worker(System::block_number()); } } From 0d097f98e9746d7735de130bfede5e595411077e Mon Sep 17 00:00:00 2001 From: brandonpille Date: Thu, 18 Aug 2022 09:34:32 +0200 Subject: [PATCH 04/87] FEAT: Move registering extensions to offchainify function #269 --- .../pallets/pallet-smart-contract/Cargo.toml | 1 + .../pallets/pallet-smart-contract/src/lib.rs | 6 ++++ .../pallets/pallet-smart-contract/src/mock.rs | 33 ++++++++++++------- .../pallet-smart-contract/src/tests.rs | 3 ++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/Cargo.toml b/substrate-node/pallets/pallet-smart-contract/Cargo.toml index 0bc9b1995..3563404c2 100644 --- a/substrate-node/pallets/pallet-smart-contract/Cargo.toml +++ b/substrate-node/pallets/pallet-smart-contract/Cargo.toml @@ -45,6 +45,7 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot- # Benchmarking frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false, optional = true } +parking_lot = '0.12.1' sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } env_logger = "*" [features] diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index c04a026bf..5d795caf3 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -492,6 +492,11 @@ pub mod pallet { fn offchain_worker(block_number: T::BlockNumber) { let current_block_u64: u64 = block_number.saturated_into::(); let contracts = ContractsToBillAt::::get(current_block_u64); + + if contracts.is_empty() { + return; + } + let mut failed_contract_ids: Vec = Vec::new(); log::info!( @@ -1015,6 +1020,7 @@ impl Pallet { return Err(>::OffchainSignedTxError); } log::info!("result for tx {:?}", res); + log::info!("acc for tx {:?}", acc.id); // Transaction is sent successfully return Ok(()); diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index d56205bbf..f8cf026be 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -1,7 +1,8 @@ #![cfg(test)] -use super::*; +use super::*; use crate::name_contract::NameContractName; +use parking_lot::RawRwLock; use crate::{self as pallet_smart_contract}; use frame_support::{ construct_runtime, parameter_types, @@ -15,7 +16,7 @@ use pallet_tfgrid::{ pub_ip::{GatewayIP, PublicIP}, twin::TwinIp, }; -use sp_runtime::traits::{IdentifyAccount, Verify}; +use sp_runtime::{traits::{IdentifyAccount, Verify}, offchain::testing::PoolState}; use sp_runtime::MultiSignature; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; use sp_runtime::{ @@ -309,19 +310,29 @@ pub fn new_test_ext() -> sp_io::TestExternalities { }; genesis.assimilate_storage(&mut storage).unwrap(); - let (offchain, _) = testing::TestOffchainExt::new(); - let (pool, _) = testing::TestTransactionPoolExt::new(); + + let t = sp_io::TestExternalities::from(storage); + + t +} + +pub fn offchainify(ext: &mut sp_io::TestExternalities, iterations: u32) -> Arc> { + let (offchain, offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); let keystore = KeyStore::new(); keystore .sr25519_generate_new(KEY_TYPE, Some(&format!("//Alice"))) .unwrap(); - let mut t = sp_io::TestExternalities::from(storage); - t.register_extension(OffchainWorkerExt::new(offchain)); - t.register_extension(TransactionPoolExt::new(pool)); - t.register_extension(KeystoreExt(Arc::new(keystore))); + let mut seed = [0_u8; 32]; + seed[0..4].copy_from_slice(&iterations.to_le_bytes()); + offchain_state.write().seed = seed; - t.execute_with(|| System::set_block_number(1)); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(OffchainDbExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(KeystoreExt(Arc::new(keystore))); - t -} + + pool_state +} \ No newline at end of file diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 927241802..06c26280f 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -2508,10 +2508,13 @@ pub fn create_twin(origin: AccountId) { fn run_to_block(n: u64) { Timestamp::set_timestamp((1628082000 * 1000) + (6000 * n)); while System::block_number() < n { + System::offchain_worker(System::block_number()); SmartContractModule::offchain_worker(System::block_number()); + SmartContractModule::on_finalize(System::block_number()); System::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); + SmartContractModule::on_initialize(System::block_number()); } } From 30640b9eb0fd2d4c43bf6f79782364bc4a85daec Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Wed, 3 Aug 2022 16:06:03 +0200 Subject: [PATCH 05/87] wip --- substrate-node/Cargo.lock | 3 ++ .../pallets/pallet-smart-contract/src/mock.rs | 30 ++++++++++++------- .../pallet-smart-contract/src/tests.rs | 5 ++-- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/substrate-node/Cargo.lock b/substrate-node/Cargo.lock index 64fdb5d4b..9ccea9f83 100644 --- a/substrate-node/Cargo.lock +++ b/substrate-node/Cargo.lock @@ -3903,6 +3903,7 @@ dependencies = [ name = "pallet-smart-contract" version = "3.0.0" dependencies = [ + "env_logger", "frame-benchmarking", "frame-support", "frame-system", @@ -3913,9 +3914,11 @@ dependencies = [ "pallet-tft-price", "pallet-timestamp", "parity-scale-codec 3.1.5", + "parking_lot 0.12.1", "scale-info", "sp-core", "sp-io", + "sp-keystore", "sp-runtime", "sp-std", "substrate-fixed", diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index f8cf026be..9339215f9 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -1,8 +1,7 @@ #![cfg(test)] -use super::*; +use super::*; use crate::name_contract::NameContractName; -use parking_lot::RawRwLock; use crate::{self as pallet_smart_contract}; use frame_support::{ construct_runtime, parameter_types, @@ -16,9 +15,19 @@ use pallet_tfgrid::{ pub_ip::{GatewayIP, PublicIP}, twin::TwinIp, }; -use sp_runtime::{traits::{IdentifyAccount, Verify}, offchain::testing::PoolState}; -use sp_runtime::MultiSignature; +use sp_core::{ + offchain::{ + testing::{self}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + sr25519, Pair, Public, H256, +}; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; +use sp_runtime::MultiSignature; +use sp_runtime::{ + offchain::testing::PoolState, + traits::{IdentifyAccount, Verify}, +}; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, Extrinsic as ExtrinsicT, IdentityLookup}, @@ -310,19 +319,19 @@ pub fn new_test_ext() -> sp_io::TestExternalities { }; genesis.assimilate_storage(&mut storage).unwrap(); - let t = sp_io::TestExternalities::from(storage); t } -pub fn offchainify(ext: &mut sp_io::TestExternalities, iterations: u32) -> Arc> { +pub fn offchainify(iterations: u32) -> (sp_io::TestExternalities, Arc>) { + let mut ext = new_test_ext(); let (offchain, offchain_state) = testing::TestOffchainExt::new(); let (pool, pool_state) = testing::TestTransactionPoolExt::new(); let keystore = KeyStore::new(); keystore - .sr25519_generate_new(KEY_TYPE, Some(&format!("//Alice"))) - .unwrap(); + .sr25519_generate_new(KEY_TYPE, Some(&format!("//Alice"))) + .unwrap(); let mut seed = [0_u8; 32]; seed[0..4].copy_from_slice(&iterations.to_le_bytes()); @@ -333,6 +342,5 @@ pub fn offchainify(ext: &mut sp_io::TestExternalities, iterations: u32) -> Arc Date: Wed, 3 Aug 2022 17:42:34 +0200 Subject: [PATCH 06/87] FEAT: fixed failing test #269 --- .../pallets/pallet-smart-contract/src/mock.rs | 2 +- .../pallet-smart-contract/src/tests.rs | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 9339215f9..ca39ac09c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -41,7 +41,7 @@ pub type Signature = MultiSignature; pub type AccountId = <::Signer as IdentifyAccount>::AccountId; pub type Moment = u64; -type Extrinsic = TestXt; +pub type Extrinsic = TestXt; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index a77f4fe82..ffb5980bf 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1,13 +1,14 @@ use super::Event as SmartContractEvent; -use crate::{mock::Event as MockEvent, mock::*, Error}; +use crate::{mock::Extrinsic, mock::Event as MockEvent, mock::*, Error}; +use sp_core::Decode; use frame_support::{ assert_noop, assert_ok, bounded_vec, traits::{LockableCurrency, OffchainWorker, OnFinalize, OnInitialize, WithdrawReasons}, BoundedVec, }; -use frame_system::{EventRecord, Phase, RawOrigin}; +use frame_system::{EventRecord, Phase, RawOrigin, extrinsics_data_root}; use sp_core::H256; -use sp_runtime::{assert_eq_error_rate, traits::SaturatedConversion, Perbill, Percent}; +use sp_runtime::{traits::{SaturatedConversion, BlockNumberProvider}, Perbill, Percent}; use substrate_fixed::types::U64F64; use super::types; @@ -1381,7 +1382,7 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works #[test] fn test_name_contract_billing() { - let (mut ext, _) = offchainify(0); + let (mut ext, pool_state) = offchainify(0); ext.execute_with(|| { env_logger::init(); prepare_farm_and_node(); @@ -1400,13 +1401,28 @@ fn test_name_contract_billing() { // because we bill every 10 blocks run_to_block(12); + // TODO: move this out in a function so that we can reuse it + // the list of transactions should contain 1 transaction! + assert_eq!(pool_state.read().transactions.len(), 1); + let encoded = pool_state.read().transactions[0].clone(); + let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); + assert_eq!(extrinsic.signature.unwrap().0, 0); + // the extrinsic call should be bill_contract_for_block with the arguments contract_id = contract_to_bill[0] and block_number = 11 + assert_eq!(extrinsic.call, Call::SmartContractModule(crate::Call::bill_contract_for_block {contract_id : contract_to_bill[0], block_number: System::current_block_number()-1 })); + // now execute the call so that we can check the events + assert_ok!(SmartContractModule::bill_contract_for_block( + Origin::signed(bob()), + contract_to_bill[0], + System::current_block_number()-1 + )); + + // the contractbill event should look like: let contract_bill_event = types::ContractBill { contract_id: 1, timestamp: 1628082072, discount_level: types::DiscountLevel::Gold, amount_billed: 2032, }; - let our_events = System::events(); println!("events: {:?}", our_events.clone()); assert_eq!( From 452298cfa2cfd1787bd768f79d2f751284c61d75 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 5 Aug 2022 17:05:00 +0200 Subject: [PATCH 07/87] FEAT: fixed all tests #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 44 +-- .../pallets/pallet-smart-contract/src/mock.rs | 14 +- .../pallet-smart-contract/src/test_utils.rs | 64 ++++ .../pallet-smart-contract/src/tests.rs | 315 +++++++++--------- 4 files changed, 235 insertions(+), 202 deletions(-) create mode 100644 substrate-node/pallets/pallet-smart-contract/src/test_utils.rs diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 5d795caf3..fd475702c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -40,6 +40,9 @@ mod mock; #[cfg(test)] mod tests; +#[cfg(test)] +mod test_utils; + pub mod crypto { use crate::KEY_TYPE; use sp_core::sr25519::Signature as Sr25519Signature; @@ -481,7 +484,6 @@ pub mod pallet { contract_id: u64, block_number: T::BlockNumber ) -> DispatchResultWithPostInfo { - log::info!("do I get here?"); let _account_id = ensure_signed(origin)?; Self::_bill_contract_for_block(contract_id, block_number) } @@ -495,14 +497,10 @@ pub mod pallet { if contracts.is_empty() { return; - } + } + log::info!("{:?} contracts to bill at block {:?}", contracts, block_number); let mut failed_contract_ids: Vec = Vec::new(); - - log::info!( - "contracts to bill: {:?}", - contracts - ); for contract_id in contracts { match Self::offchain_signed_tx(block_number, contract_id) { Ok(_) => (), @@ -512,39 +510,18 @@ pub mod pallet { if failed_contract_ids.is_empty() { log::info!( - "types::NodeContract billed successfully at block: {:?}", + "all contracts billed successfully at block: {:?}", block_number ); } else { log::info!( - "types::NodeContract billed failed for some of the contracts in block: {:?}", + "billing failed for some of the contracts at block: {:?}", block_number ); //TODO add for next run } } - // TODO remove - //fn on_finalize(block: T::BlockNumber) { - // match Self::_bill_contracts_at_block(block) { - // Ok(_) => { - // log::info!( - // "types::NodeContract billed successfully at block: {:?}", - // block - // ); - // } - // Err(err) => { - // log::info!( - // "types::NodeContract billed failed at block: {:?} with err {:?}", - // block, - // err - // ); - // } - // } - // clean storage map for billed contracts at block - // let current_block_u64: u64 = block.saturated_into::(); - // ContractsToBillAt::::remove(current_block_u64); - // } } } @@ -1007,7 +984,6 @@ impl Pallet { } pub fn offchain_signed_tx(block_number: T::BlockNumber, contract_id: u64) -> Result<(), Error> { - log::info!("Billing contract {:?} from block {:?}", contract_id, block_number); let signer = Signer::::any_account(); let result = signer.send_signed_transaction(|_acct| Call::bill_contract_for_block { contract_id, @@ -1016,12 +992,10 @@ impl Pallet { if let Some((acc, res)) = result { if res.is_err() { - log::error!("failure: offchain_signed_tx: tx sent: {:?}", acc.id); + log::error!("failed billing contract {:?} at block {:?} using account {:?} with error {:?}", + contract_id, block_number, acc.id, res); return Err(>::OffchainSignedTxError); } - log::info!("result for tx {:?}", res); - log::info!("acc for tx {:?}", acc.id); - // Transaction is sent successfully return Ok(()); } diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index ca39ac09c..4aa10d9c8 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -20,7 +20,7 @@ use sp_core::{ testing::{self}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - sr25519, Pair, Public, H256, + sr25519, Pair, Public, H256, }; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; use sp_runtime::MultiSignature; @@ -36,6 +36,10 @@ use sp_runtime::{ use sp_std::convert::{TryFrom, TryInto}; use tfchain_support::{traits::ChangeNode, types::Node}; +// set environment variable RUST_LOG=debug to see all logs when running the tests and call +// env_logger::init() at the beginning of the test +use env_logger; + pub type Signature = MultiSignature; pub type AccountId = <::Signer as IdentifyAccount>::AccountId; @@ -299,7 +303,9 @@ pub fn get_staking_pool_account() -> AccountId { } pub fn new_test_ext() -> sp_io::TestExternalities { - //TODO add offchain worker see pallet-tft-price impl ExternalityBuilder + // for showing logs in tests + let _ = env_logger::try_init(); + let mut storage = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); @@ -324,7 +330,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t } -pub fn offchainify(iterations: u32) -> (sp_io::TestExternalities, Arc>) { +pub fn new_test_ext_with_pool_state(iterations: u32) -> (sp_io::TestExternalities, Arc>) { let mut ext = new_test_ext(); let (offchain, offchain_state) = testing::TestOffchainExt::new(); let (pool, pool_state) = testing::TestTransactionPoolExt::new(); @@ -343,4 +349,4 @@ pub fn offchainify(iterations: u32) -> (sp_io::TestExternalities, Arc>) { + Timestamp::set_timestamp((1628082000 * 1000) + (6000 * n)); + while System::block_number() < n { + System::offchain_worker(System::block_number()); + SmartContractModule::offchain_worker(System::block_number()); + SmartContractModule::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + contracts_should_be_billed(pool_state); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + SmartContractModule::on_initialize(System::block_number()); + } +} + +fn contracts_should_be_billed(pool_state: &mut Arc>) { + let contracts_to_bill: Vec = SmartContractModule::contract_to_bill_at_block(System::block_number()); + assert_eq!(pool_state.read().transactions.len(), contracts_to_bill.len()); + for i in 0..contracts_to_bill.len() { + let encoded = pool_state.read().transactions[i].clone(); + let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); + + // the extrinsic call should be bill_contract_for_block with the arguments contract_id and block_number + assert_eq!(extrinsic.call, + Call::SmartContractModule( + crate::Call::bill_contract_for_block { + contract_id : contracts_to_bill[i], + block_number: System::block_number() + })); + + // now execute the call so that we can check the events + assert_ok!(SmartContractModule::bill_contract_for_block( + Origin::signed(bob()), + contracts_to_bill[i], + System::block_number() + )); + } + pool_state.write().transactions.clear(); +} \ No newline at end of file diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index ffb5980bf..b0fcc1634 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1,14 +1,15 @@ use super::Event as SmartContractEvent; -use crate::{mock::Extrinsic, mock::Event as MockEvent, mock::*, Error}; -use sp_core::Decode; +use crate::{mock::Event as MockEvent, mock::*, Error, test_utils::*}; + use frame_support::{ assert_noop, assert_ok, bounded_vec, - traits::{LockableCurrency, OffchainWorker, OnFinalize, OnInitialize, WithdrawReasons}, + traits::{LockableCurrency, WithdrawReasons}, BoundedVec, }; -use frame_system::{EventRecord, Phase, RawOrigin, extrinsics_data_root}; +use frame_system::{EventRecord, Phase, RawOrigin}; use sp_core::H256; -use sp_runtime::{traits::{SaturatedConversion, BlockNumberProvider}, Perbill, Percent}; +use sp_io::TestExternalities; +use sp_runtime::{traits::{SaturatedConversion}, Perbill, Percent}; use substrate_fixed::types::U64F64; use super::types; @@ -19,8 +20,6 @@ use tfchain_support::types::{FarmCertification, Location, NodeCertification, Pub const GIGABYTE: u64 = 1024 * 1024 * 1024; -use env_logger; - // NODE CONTRACT TESTS // // -------------------- // @@ -702,9 +701,10 @@ fn test_cancel_rent_contract_with_active_node_contracts_fails() { #[test] fn test_node_contract_billing_details() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(0); + run_to_block_and_check_extrinsics(0, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let twin = TfgridModule::twins(2).unwrap(); @@ -731,7 +731,7 @@ fn test_node_contract_billing_details() { let mut i = 0; while i != 24 { i += 1; - run_to_block(i * 10 + 1); + run_to_block_and_check_extrinsics(i * 10 + 1, &mut pool_state); } let free_balance = Balances::free_balance(&twin.account_id); @@ -831,9 +831,10 @@ fn test_node_contract_billing_details_with_solution_provider() { #[test] fn test_multiple_contracts_billing_loop_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -849,10 +850,10 @@ fn test_multiple_contracts_billing_loop_works() { "some_name".as_bytes().to_vec(), )); - let contract_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); - assert_eq!(contract_to_bill_at_block.len(), 2); + let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); + assert_eq!(contracts_to_bill_at_block.len(), 2); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); // Test that the expected events were emitted let our_events = System::events(); @@ -867,9 +868,10 @@ fn test_multiple_contracts_billing_loop_works() { #[test] fn test_node_contract_billing_cycles() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -886,7 +888,7 @@ fn test_node_contract_billing_cycles() { push_contract_resources_used(1); let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); check_report_cost(1, amount_due_1, 12, discount_received); let twin = TfgridModule::twins(twin_id).unwrap(); @@ -900,11 +902,11 @@ fn test_node_contract_billing_cycles() { ); let (amount_due_2, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(22); + run_to_block_and_check_extrinsics(22, &mut pool_state); check_report_cost(1, amount_due_2, 22, discount_received); let (amount_due_3, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(32); + run_to_block_and_check_extrinsics(32, &mut pool_state); check_report_cost(1, amount_due_3, 32, discount_received); let twin = TfgridModule::twins(twin_id).unwrap(); @@ -921,9 +923,10 @@ fn test_node_contract_billing_cycles() { #[test] fn test_node_multiple_contract_billing_cycles() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -948,11 +951,11 @@ fn test_node_multiple_contract_billing_cycles() { push_contract_resources_used(2); let (amount_due_contract_1, discount_received) = calculate_tft_cost(1, twin_id, 11); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); check_report_cost(1, amount_due_contract_1, 12, discount_received); let (amount_due_contract_2, discount_received) = calculate_tft_cost(2, twin_id, 11); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); check_report_cost(2, amount_due_contract_2, 12, discount_received); let twin = TfgridModule::twins(twin_id).unwrap(); @@ -969,9 +972,10 @@ fn test_node_multiple_contract_billing_cycles() { #[test] fn test_node_contract_billing_cycles_delete_node_cancels_contract() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -988,27 +992,27 @@ fn test_node_contract_billing_cycles_delete_node_cancels_contract() { push_contract_resources_used(1); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); check_report_cost(1, amount_due_as_u128, 12, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(22); + run_to_block_and_check_extrinsics(22, &mut pool_state); check_report_cost(1, amount_due_as_u128, 22, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(32); + run_to_block_and_check_extrinsics(32, &mut pool_state); check_report_cost(1, amount_due_as_u128, 32, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(42); + run_to_block_and_check_extrinsics(42, &mut pool_state); check_report_cost(1, amount_due_as_u128, 42, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(52); + run_to_block_and_check_extrinsics(52, &mut pool_state); check_report_cost(1, amount_due_as_u128, 52, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 4); - run_to_block(56); + run_to_block_and_check_extrinsics(56, &mut pool_state); // Delete node TfgridModule::delete_node_farm(Origin::signed(alice()), 1).unwrap(); @@ -1058,9 +1062,10 @@ fn test_node_contract_billing_cycles_delete_node_cancels_contract() { #[test] fn test_node_contract_only_public_ip_billing_cycles() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1076,32 +1081,33 @@ fn test_node_contract_only_public_ip_billing_cycles() { let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); assert_ne!(amount_due_as_u128, 0); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); check_report_cost(1, amount_due_as_u128, 12, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(22); + run_to_block_and_check_extrinsics(22, &mut pool_state); check_report_cost(1, amount_due_as_u128, 22, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(32); + run_to_block_and_check_extrinsics(32, &mut pool_state); check_report_cost(1, amount_due_as_u128, 32, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(42); + run_to_block_and_check_extrinsics(42, &mut pool_state); check_report_cost(1, amount_due_as_u128, 42, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(52); + run_to_block_and_check_extrinsics(52, &mut pool_state); check_report_cost(1, amount_due_as_u128, 52, discount_received); }); } #[test] fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1119,21 +1125,21 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { push_contract_resources_used(1); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); check_report_cost(1, amount_due_as_u128, 12, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(22); + run_to_block_and_check_extrinsics(22, &mut pool_state); check_report_cost(1, amount_due_as_u128, 22, discount_received); - run_to_block(28); + run_to_block_and_check_extrinsics(28, &mut pool_state); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 6); assert_ok!(SmartContractModule::cancel_contract( Origin::signed(bob()), 1 )); - run_to_block(29); + run_to_block_and_check_extrinsics(29, &mut pool_state); check_report_cost(1, amount_due_as_u128, 28, discount_received); let contract = SmartContractModule::contracts(1); @@ -1216,9 +1222,10 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balanc #[test] fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1233,11 +1240,11 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { push_contract_resources_used(1); // cycle 1 - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); // cycle 2 // user does not have enough funds to pay for 2 cycles - run_to_block(22); + run_to_block_and_check_extrinsics(22, &mut pool_state); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); @@ -1259,9 +1266,10 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { #[test] fn test_restore_node_contract_in_grace_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1276,11 +1284,11 @@ fn test_restore_node_contract_in_grace_works() { push_contract_resources_used(1); // cycle 1 - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); // cycle 2 // user does not have enough funds to pay for 2 cycles - run_to_block(22); + run_to_block_and_check_extrinsics(22, &mut pool_state); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); @@ -1300,22 +1308,22 @@ fn test_restore_node_contract_in_grace_works() { let contract_to_bill = SmartContractModule::contract_to_bill_at_block(31); assert_eq!(contract_to_bill.len(), 1); - run_to_block(32); + run_to_block_and_check_extrinsics(32, &mut pool_state); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(41); assert_eq!(contract_to_bill.len(), 1); - run_to_block(42); + run_to_block_and_check_extrinsics(42, &mut pool_state); // Transfer some balance to the owner of the contract to trigger the grace period to stop Balances::transfer(Origin::signed(bob()), charlie(), 100000000).unwrap(); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(51); assert_eq!(contract_to_bill.len(), 1); - run_to_block(52); + run_to_block_and_check_extrinsics(52, &mut pool_state); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(61); assert_eq!(contract_to_bill.len(), 1); - run_to_block(62); + run_to_block_and_check_extrinsics(62, &mut pool_state); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::Created); @@ -1324,9 +1332,10 @@ fn test_restore_node_contract_in_grace_works() { #[test] fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1341,11 +1350,11 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works push_contract_resources_used(1); // cycle 1 - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); // cycle 2 // user does not have enough funds to pay for 2 cycles - run_to_block(22); + run_to_block_and_check_extrinsics(22, &mut pool_state); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); @@ -1363,17 +1372,17 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works true ); - run_to_block(32); - run_to_block(42); - run_to_block(52); - run_to_block(62); - run_to_block(72); - run_to_block(82); - run_to_block(92); - run_to_block(102); - run_to_block(112); - run_to_block(122); - run_to_block(132); + run_to_block_and_check_extrinsics(32, &mut pool_state); + run_to_block_and_check_extrinsics(42, &mut pool_state); + run_to_block_and_check_extrinsics(52, &mut pool_state); + run_to_block_and_check_extrinsics(62, &mut pool_state); + run_to_block_and_check_extrinsics(72, &mut pool_state); + run_to_block_and_check_extrinsics(82, &mut pool_state); + run_to_block_and_check_extrinsics(92, &mut pool_state); + run_to_block_and_check_extrinsics(102, &mut pool_state); + run_to_block_and_check_extrinsics(112, &mut pool_state); + run_to_block_and_check_extrinsics(122, &mut pool_state); + run_to_block_and_check_extrinsics(132, &mut pool_state); let c1 = SmartContractModule::contracts(1); assert_eq!(c1, None); @@ -1382,11 +1391,10 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works #[test] fn test_name_contract_billing() { - let (mut ext, pool_state) = offchainify(0); + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { - env_logger::init(); prepare_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_name_contract( @@ -1394,27 +1402,12 @@ fn test_name_contract_billing() { "foobar".as_bytes().to_vec() )); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(11); - assert_eq!(contract_to_bill, [1]); + let contracts_to_bill = SmartContractModule::contract_to_bill_at_block(11); + assert_eq!(contracts_to_bill, [1]); // let mature 11 blocks // because we bill every 10 blocks - run_to_block(12); - - // TODO: move this out in a function so that we can reuse it - // the list of transactions should contain 1 transaction! - assert_eq!(pool_state.read().transactions.len(), 1); - let encoded = pool_state.read().transactions[0].clone(); - let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); - assert_eq!(extrinsic.signature.unwrap().0, 0); - // the extrinsic call should be bill_contract_for_block with the arguments contract_id = contract_to_bill[0] and block_number = 11 - assert_eq!(extrinsic.call, Call::SmartContractModule(crate::Call::bill_contract_for_block {contract_id : contract_to_bill[0], block_number: System::current_block_number()-1 })); - // now execute the call so that we can check the events - assert_ok!(SmartContractModule::bill_contract_for_block( - Origin::signed(bob()), - contract_to_bill[0], - System::current_block_number()-1 - )); + run_to_block_and_check_extrinsics(12, &mut pool_state); // the contractbill event should look like: let contract_bill_event = types::ContractBill { @@ -1438,9 +1431,10 @@ fn test_name_contract_billing() { #[test] fn test_rent_contract_billing() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1457,7 +1451,7 @@ fn test_rent_contract_billing() { types::ContractData::RentContract(rent_contract) ); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); assert_ne!(amount_due_as_u128, 0); @@ -1467,9 +1461,10 @@ fn test_rent_contract_billing() { #[test] fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1486,7 +1481,7 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { types::ContractData::RentContract(rent_contract) ); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); assert_ne!(amount_due_as_u128, 0); @@ -1497,7 +1492,7 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { let free_balance = Balances::free_balance(&twin.account_id); assert_ne!(usable_balance, free_balance); - run_to_block(14); + run_to_block_and_check_extrinsics(14, &mut pool_state); // cancel contract let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 2); assert_ok!(SmartContractModule::cancel_contract( @@ -1510,7 +1505,7 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { assert_ne!(usable_balance, 0); Balances::transfer(Origin::signed(bob()), alice(), usable_balance).unwrap(); - run_to_block(22); + run_to_block_and_check_extrinsics(22, &mut pool_state); // Last amount due is the same as the first one assert_ne!(amount_due_as_u128, 0); @@ -1571,9 +1566,10 @@ fn test_rent_contract_canceled_mid_cycle_should_bill_for_remainder() { #[test] fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billing_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1593,7 +1589,7 @@ fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billi )); push_contract_resources_used(2); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); assert_ne!(amount_due_as_u128, 0); @@ -1610,9 +1606,10 @@ fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billi #[test] fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1632,18 +1629,18 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ )); push_contract_resources_used(2); - run_to_block(12); - run_to_block(22); - run_to_block(32); - run_to_block(42); - run_to_block(52); - run_to_block(62); - run_to_block(72); - run_to_block(82); - run_to_block(92); - run_to_block(102); - run_to_block(112); - run_to_block(122); + run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block_and_check_extrinsics(32, &mut pool_state); + run_to_block_and_check_extrinsics(42, &mut pool_state); + run_to_block_and_check_extrinsics(52, &mut pool_state); + run_to_block_and_check_extrinsics(62, &mut pool_state); + run_to_block_and_check_extrinsics(72, &mut pool_state); + run_to_block_and_check_extrinsics(82, &mut pool_state); + run_to_block_and_check_extrinsics(92, &mut pool_state); + run_to_block_and_check_extrinsics(102, &mut pool_state); + run_to_block_and_check_extrinsics(112, &mut pool_state); + run_to_block_and_check_extrinsics(122, &mut pool_state); // let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); // assert_ne!(amount_due_as_u128, 0); @@ -1707,9 +1704,10 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ #[test] fn test_create_rent_contract_and_node_contract_with_ip_billing_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1728,7 +1726,7 @@ fn test_create_rent_contract_and_node_contract_with_ip_billing_works() { None )); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); // check contract 1 costs (Rent Contract) let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); @@ -1752,9 +1750,10 @@ fn test_create_rent_contract_and_node_contract_with_ip_billing_works() { #[test] fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1766,7 +1765,7 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { // cycle 1 // user does not have enough funds to pay for 1 cycle - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1788,9 +1787,10 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { #[test] fn test_restore_rent_contract_in_grace_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1801,7 +1801,7 @@ fn test_restore_rent_contract_in_grace_works() { )); // cycle 1 - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1821,22 +1821,22 @@ fn test_restore_rent_contract_in_grace_works() { let contract_to_bill = SmartContractModule::contract_to_bill_at_block(21); assert_eq!(contract_to_bill.len(), 1); - run_to_block(22); + run_to_block_and_check_extrinsics(22, &mut pool_state); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(31); assert_eq!(contract_to_bill.len(), 1); - run_to_block(32); + run_to_block_and_check_extrinsics(32, &mut pool_state); // Transfer some balance to the owner of the contract to trigger the grace period to stop Balances::transfer(Origin::signed(bob()), charlie(), 100000000).unwrap(); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(41); assert_eq!(contract_to_bill.len(), 1); - run_to_block(42); + run_to_block_and_check_extrinsics(42, &mut pool_state); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(51); assert_eq!(contract_to_bill.len(), 1); - run_to_block(52); + run_to_block_and_check_extrinsics(52, &mut pool_state); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::Created); @@ -1944,9 +1944,10 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { #[test] fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1957,7 +1958,7 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works )); // cycle 1 - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1975,18 +1976,18 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works true ); - run_to_block(22); - run_to_block(32); - run_to_block(42); - run_to_block(52); - run_to_block(62); - run_to_block(72); - run_to_block(82); - run_to_block(92); - run_to_block(102); - run_to_block(112); - run_to_block(122); - run_to_block(132); + run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block_and_check_extrinsics(32, &mut pool_state); + run_to_block_and_check_extrinsics(42, &mut pool_state); + run_to_block_and_check_extrinsics(52, &mut pool_state); + run_to_block_and_check_extrinsics(62, &mut pool_state); + run_to_block_and_check_extrinsics(72, &mut pool_state); + run_to_block_and_check_extrinsics(82, &mut pool_state); + run_to_block_and_check_extrinsics(92, &mut pool_state); + run_to_block_and_check_extrinsics(102, &mut pool_state); + run_to_block_and_check_extrinsics(112, &mut pool_state); + run_to_block_and_check_extrinsics(122, &mut pool_state); + run_to_block_and_check_extrinsics(132, &mut pool_state); let c1 = SmartContractModule::contracts(1); assert_eq!(c1, None); @@ -1995,9 +1996,10 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works #[test] fn test_rent_contract_and_node_contract_canceled_when_node_is_deleted_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block_and_check_extrinsics(1, &mut pool_state); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -2017,9 +2019,9 @@ fn test_rent_contract_and_node_contract_canceled_when_node_is_deleted_works() { )); push_contract_resources_used(2); - run_to_block(12); + run_to_block_and_check_extrinsics(12, &mut pool_state); - run_to_block(16); + run_to_block_and_check_extrinsics(16, &mut pool_state); // Delete node TfgridModule::delete_node_farm(Origin::signed(alice()), 1).unwrap(); @@ -2522,19 +2524,6 @@ pub fn create_twin(origin: AccountId) { )); } -fn run_to_block(n: u64) { - Timestamp::set_timestamp((1628082000 * 1000) + (6000 * n)); - while System::block_number() < n { - System::offchain_worker(System::block_number()); - SmartContractModule::offchain_worker(System::block_number()); - SmartContractModule::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - SmartContractModule::on_initialize(System::block_number()); - } -} - fn create_farming_policies() { let name = "f1".as_bytes().to_vec(); assert_ok!(TfgridModule::create_farming_policy( From 113fff39a0a99f8edd2a975b40e485a2238dc4a7 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Thu, 11 Aug 2022 17:41:43 +0200 Subject: [PATCH 08/87] FEAT:wrote our own TransactionPool mock that allows you to tell what should be executed and what the result should be #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 159 +++--- .../pallets/pallet-smart-contract/src/mock.rs | 78 ++- .../pallet-smart-contract/src/test_utils.rs | 85 ++- .../pallet-smart-contract/src/tests.rs | 529 ++++++++++++++---- 4 files changed, 620 insertions(+), 231 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index fd475702c..53f1120ff 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +use core::hash::Hasher; + use sp_std::prelude::*; use frame_support::{ @@ -15,22 +17,31 @@ use frame_support::{ weights::Pays, BoundedVec, }; -use frame_system::{self as system, ensure_signed, +use frame_system::{ + self as system, ensure_signed, offchain::{AppCrypto, CreateSignedTransaction, SendSignedTransaction, Signer}, }; +pub use pallet::*; use pallet_tfgrid; use pallet_tfgrid::pallet::{InterfaceOf, PubConfigOf}; use pallet_tfgrid::types as pallet_tfgrid_types; use pallet_timestamp as timestamp; +use sp_core::crypto::KeyTypeId; +use sp_runtime::traits::BlockNumberProvider; use sp_runtime::{ + offchain::{ + storage::StorageValueRef, + storage_lock::{BlockAndTime, StorageLock, Time}, + Duration, + }, traits::{CheckedSub, SaturatedConversion}, Perbill, }; use substrate_fixed::types::U64F64; -use tfchain_support::{traits::ChangeNode, types::Node}; -use sp_core::crypto::KeyTypeId; - -pub use pallet::*; +use tfchain_support::{ + traits::ChangeNode, + types::{Node, NodeCertification, Resources}, +} pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"demo"); @@ -82,6 +93,7 @@ pub mod types; #[frame_support::pallet] pub mod pallet { + use super::types::*; use super::weights::WeightInfo; use super::*; @@ -108,6 +120,12 @@ pub mod pallet { pub const GRID_LOCK_ID: LockIdentifier = *b"gridlock"; + pub const FAILED_CONTRACTS_STORAGE_ID: [u8; 52] = + *b"pallet-smart-contract::failed-contracts-when-billing"; + pub const FAILED_CONTRACTS_STORAGE_LOCK: [u8; 44] = + *b"pallet-smart-contract::failed-contracts-lock"; + pub const FAILED_CONTRACTS_TIMEOUT: Duration = Duration::from_millis(60000); + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::without_storage_info] @@ -205,7 +223,8 @@ pub mod pallet { #[pallet::config] pub trait Config: - CreateSignedTransaction> + frame_system::Config + CreateSignedTransaction> + + frame_system::Config + pallet_timestamp::Config + pallet_balances::Config + pallet_tfgrid::Config @@ -482,7 +501,7 @@ pub mod pallet { pub fn bill_contract_for_block( origin: OriginFor, contract_id: u64, - block_number: T::BlockNumber + block_number: T::BlockNumber, ) -> DispatchResultWithPostInfo { let _account_id = ensure_signed(origin)?; Self::_bill_contract_for_block(contract_id, block_number) @@ -493,13 +512,24 @@ pub mod pallet { impl Hooks> for Pallet { fn offchain_worker(block_number: T::BlockNumber) { let current_block_u64: u64 = block_number.saturated_into::(); - let contracts = ContractsToBillAt::::get(current_block_u64); + let mut contracts = Self::get_failed_contract_ids_from_storage(); + if !contracts.is_empty() { + log::info!( + "trying to bill {:?} failed contracts from last block", + contracts + ); + } + contracts.extend(ContractsToBillAt::::get(current_block_u64)); if contracts.is_empty() { return; - } + } - log::info!("{:?} contracts to bill at block {:?}", contracts, block_number); + log::info!( + "{:?} contracts to bill at block {:?}", + contracts, + block_number + ); let mut failed_contract_ids: Vec = Vec::new(); for contract_id in contracts { match Self::offchain_signed_tx(block_number, contract_id) { @@ -518,8 +548,8 @@ pub mod pallet { "billing failed for some of the contracts at block: {:?}", block_number ); - //TODO add for next run } + Self::save_failed_contract_ids_in_storage(failed_contract_ids); } } @@ -937,8 +967,12 @@ impl Pallet { ContractBillingInformationByID::::insert(report.contract_id, &contract_billing_info); } - pub fn _bill_contract_for_block(contract_id: u64, block: T::BlockNumber) -> DispatchResultWithPostInfo { - if !Contracts::::contains_key(contract_id) { + pub fn _bill_contract_for_block( + contract_id: u64, + block: T::BlockNumber, + ) -> DispatchResultWithPostInfo { + if !Contracts::::contains_key(contract_id) { + return Ok(().into()); } let mut contract = @@ -960,9 +994,7 @@ impl Pallet { contract_id, err ); - // If billing the contract failed, we should delete the contract and clean up storage - Self::remove_contract(contract.contract_id); - return Ok(().into()); + return Err(err); } } @@ -983,17 +1015,23 @@ impl Pallet { Ok(().into()) } - pub fn offchain_signed_tx(block_number: T::BlockNumber, contract_id: u64) -> Result<(), Error> { - let signer = Signer::::any_account(); - let result = signer.send_signed_transaction(|_acct| Call::bill_contract_for_block { - contract_id, - block_number, - }); - + fn offchain_signed_tx(block_number: T::BlockNumber, contract_id: u64) -> Result<(), Error> { + let signer = Signer::::any_account(); + let result = + signer.send_signed_transaction(|_acct: &Account| Call::bill_contract_for_block { + contract_id, + block_number, + }); + if let Some((acc, res)) = result { if res.is_err() { - log::error!("failed billing contract {:?} at block {:?} using account {:?} with error {:?}", - contract_id, block_number, acc.id, res); + log::error!( + "failed billing contract {:?} at block {:?} using account {:?} with error {:?}", + contract_id, + block_number, + acc.id, + res + ); return Err(>::OffchainSignedTxError); } // Transaction is sent successfully @@ -1004,51 +1042,31 @@ impl Pallet { return Err(>::OffchainSignedTxError); } - // TO BE REMOVED: - // pub fn _bill_contracts_at_block(block: T::BlockNumber) -> DispatchResultWithPostInfo { - // let current_block_u64: u64 = block.saturated_into::(); - // let contracts = ContractsToBillAt::::get(current_block_u64); - // for contract_id in contracts { - // let mut contract = Contracts::::get(contract_id); - // if contract.contract_id == 0 { - // continue; - // } - - // // Try to bill contract - // match Self::bill_contract(&mut contract) { - // Ok(_) => { - // log::info!( - // "billed contract with id {:?} at block {:?}", - // contract_id, - // block - // ); - // } - // Err(err) => { - // log::info!( - // "error while billing contract with id {:?}: {:?}", - // contract_id, - // err - // ); - // // If billing the contract failed, we should delete the contract and clean up storage - // Self::remove_contract(contract.contract_id); - // continue; - // } - // } - - // // https://github.com/threefoldtech/tfchain/issues/264 - // // if a contract is still in storage and actively getting billed whilst it is in state delete - // // remove all associated storage and continue - // let contract = Contracts::::get(contract_id); - // if contract.contract_id != 0 && contract.is_state_delete() { - // Self::remove_contract(contract.contract_id); - // continue; - // } - - // // Reinsert into the next billing frequency - // Self::_reinsert_contract_to_bill(contract.contract_id); - // } - // Ok(().into()) - // } + fn save_failed_contract_ids_in_storage(failed_ids: Vec) { + log::info!("saving {:?} failed contracts", failed_ids); + let s_contracts = + StorageValueRef::persistent(b"pallet-smart-contract::failed-contracts-when-billing"); + + let mut lock = StorageLock::>>::with_block_deadline( + &FAILED_CONTRACTS_STORAGE_LOCK, + 5, + ); + { + let _guard = lock.lock(); + s_contracts.set(&failed_ids); + } + } + + fn get_failed_contract_ids_from_storage() -> Vec { + let s_contracts = + StorageValueRef::persistent(b"pallet-smart-contract::failed-contracts-when-billing"); + + if let Ok(Some(failed_contract_ids)) = s_contracts.get::>() { + return failed_contract_ids; + } + + return Vec::new(); + } // Bills a contract (NodeContract or NameContract) // Calculates how much TFT is due by the user and distributes the rewards @@ -1346,6 +1364,7 @@ impl Pallet { Contracts::::remove(contract_id); ContractLock::::remove(contract_id); } + log::info!("Average prise is {:?}", avg_tft_price); } // Following: https://library.threefold.me/info/threefold#/tfgrid/farming/threefold__proof_of_utilization diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 4aa10d9c8..1051ce4af 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -1,4 +1,5 @@ #![cfg(test)] +use std::panic; use super::*; use crate::name_contract::NameContractName; @@ -20,12 +21,11 @@ use sp_core::{ testing::{self}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - sr25519, Pair, Public, H256, + sr25519, Pair, Public, H256, }; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; -use sp_runtime::MultiSignature; +use sp_runtime::{MultiSignature, offchain::TransactionPool}; use sp_runtime::{ - offchain::testing::PoolState, traits::{IdentifyAccount, Verify}, }; use sp_runtime::{ @@ -36,7 +36,7 @@ use sp_runtime::{ use sp_std::convert::{TryFrom, TryInto}; use tfchain_support::{traits::ChangeNode, types::Node}; -// set environment variable RUST_LOG=debug to see all logs when running the tests and call +// set environment variable RUST_LOG=debug to see all logs when running the tests and call // env_logger::init() at the beginning of the test use env_logger; @@ -329,11 +329,78 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t } +pub type TransactionCall = pallet_smart_contract::Call; + +#[derive(Default)] +pub struct PoolState { + /// A vector of calls that we expect should be executed + pub expected_calls: Vec<(TransactionCall, Result<(), ()>)>, + pub calls_to_execute: Vec<(TransactionCall, Result<(), ()>)>, + pub i: usize, +} + +impl PoolState { + pub fn should_call(&mut self, expected_call: TransactionCall, expected_result: Result<(), ()>) { + self.expected_calls.push((expected_call, expected_result)); + } +} + +impl Drop for PoolState{ + fn drop(&mut self) { + if self.i < self.expected_calls.len() { + panic!("Not all expected calls have been executed! The following calls were still expected: {:?}", &self.expected_calls[self.i..]); + } + } +} + +#[derive(Default)] +pub struct MockedTransactionPoolExt(Arc>); + +impl MockedTransactionPoolExt { + /// Create new `TestTransactionPoolExt` and a reference to the internal state. + pub fn new() -> (Self, Arc>) { + let ext = Self::default(); + let state = ext.0.clone(); + (ext, state) + } +} + +impl TransactionPool for MockedTransactionPoolExt { + fn submit_transaction(&mut self, extrinsic: Vec) -> Result<(), ()> { + if self.0.read().expected_calls.is_empty() { + return Ok(()); + } + + let extrinsic_decoded: Extrinsic = Decode::decode(&mut &*extrinsic).unwrap(); + + if self.0.read().i < self.0.read().expected_calls.len() { + let i = self.0.read().i.clone(); + + log::info!("Call {:?}: {:?}", i, extrinsic_decoded.call); + + // the extrinsic should match the expected call at position i + assert_eq!(extrinsic_decoded.call, Call::SmartContractModule(self.0.read().expected_calls[i].0.clone())); + // increment i for the next iteration + let call_to_execute = self.0.read().expected_calls[i].clone(); + self.0.write().calls_to_execute.push(call_to_execute); + self.0.write().i = i+1; + + // return the expected return value + return self.0.read().expected_calls[i].1; + } + + // we should not end here as it would mean we did not expect any more calls + panic!("Did not expect any more calls! Still have the call {:?} left.", extrinsic_decoded.call); + } +} + + pub fn new_test_ext_with_pool_state(iterations: u32) -> (sp_io::TestExternalities, Arc>) { let mut ext = new_test_ext(); let (offchain, offchain_state) = testing::TestOffchainExt::new(); - let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let (pool, pool_state) = MockedTransactionPoolExt::new(); + testing::TestTransactionPoolExt::new(); let keystore = KeyStore::new(); keystore .sr25519_generate_new(KEY_TYPE, Some(&format!("//Alice"))) @@ -348,5 +415,6 @@ pub fn new_test_ext_with_pool_state(iterations: u32) -> (sp_io::TestExternalitie ext.register_extension(TransactionPoolExt::new(pool)); ext.register_extension(KeystoreExt(Arc::new(keystore))); + //(ext, pool_state) (ext, pool_state) } \ No newline at end of file diff --git a/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs b/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs index b9f15c764..879197b9e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs @@ -1,64 +1,63 @@ #![cfg(test)] -use crate::mock::{Call, Extrinsic, SmartContractModule, Origin, bob, Timestamp, System}; +use crate::mock::{ + bob, Origin, PoolState, SmartContractModule, System, Timestamp, +}; use codec::alloc::sync::Arc; -use frame_support::assert_ok; use frame_support::traits::Hooks; use parking_lot::RwLock; -use sp_core::Decode; -use sp_runtime::offchain::testing::PoolState; +//use sp_runtime::offchain::testing::PoolState; - -pub fn run_to_block(n: u64) { +pub fn run_to_block(n: u64, mut pool_state: Option<&mut Arc>>) { Timestamp::set_timestamp((1628082000 * 1000) + (6000 * n)); while System::block_number() < n { - System::offchain_worker(System::block_number()); + //System::offchain_worker(System::block_number()); SmartContractModule::offchain_worker(System::block_number()); SmartContractModule::on_finalize(System::block_number()); System::on_finalize(System::block_number()); + if pool_state.is_some() { + contracts_should_be_billed(*pool_state.as_mut().unwrap()); + } System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); SmartContractModule::on_initialize(System::block_number()); } } -// TODO check for option -pub fn run_to_block_and_check_extrinsics(n: u64, pool_state: &mut Arc>) { - Timestamp::set_timestamp((1628082000 * 1000) + (6000 * n)); - while System::block_number() < n { - System::offchain_worker(System::block_number()); - SmartContractModule::offchain_worker(System::block_number()); - SmartContractModule::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - contracts_should_be_billed(pool_state); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - SmartContractModule::on_initialize(System::block_number()); +fn contracts_should_be_billed(pool_state: &mut Arc>) { + //TODO inlinde doc + if pool_state.read().calls_to_execute.len() == 0 { + return; } -} -fn contracts_should_be_billed(pool_state: &mut Arc>) { - let contracts_to_bill: Vec = SmartContractModule::contract_to_bill_at_block(System::block_number()); - assert_eq!(pool_state.read().transactions.len(), contracts_to_bill.len()); - for i in 0..contracts_to_bill.len() { - let encoded = pool_state.read().transactions[i].clone(); - let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); - - // the extrinsic call should be bill_contract_for_block with the arguments contract_id and block_number - assert_eq!(extrinsic.call, - Call::SmartContractModule( - crate::Call::bill_contract_for_block { - contract_id : contracts_to_bill[i], - block_number: System::block_number() - })); - - // now execute the call so that we can check the events - assert_ok!(SmartContractModule::bill_contract_for_block( - Origin::signed(bob()), - contracts_to_bill[i], - System::block_number() - )); + for call_to_execute in pool_state.read().calls_to_execute.iter() { + let result = match call_to_execute.0 { + // matches bill_contract_for_block + crate::Call::bill_contract_for_block { + contract_id, + block_number, + } => SmartContractModule::bill_contract_for_block( + Origin::signed(bob()), + contract_id, + block_number, + ), + // did not match anything => unkown call => this means you should add a capture for that function here + _ => panic!("Unknown call!"), + }; + + let result = match result { + Ok(_) => Ok(()), + Err(_) => Err(()), + }; + + assert_eq!( + call_to_execute.1, + result, + "The result of call to {:?} was not as expected!", + call_to_execute.0 + ); } - pool_state.write().transactions.clear(); -} \ No newline at end of file + + pool_state.write().calls_to_execute.clear(); +} diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index b0fcc1634..1e5796457 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1,5 +1,5 @@ -use super::Event as SmartContractEvent; -use crate::{mock::Event as MockEvent, mock::*, Error, test_utils::*}; +use super::{types, Event as SmartContractEvent}; +use crate::{mock::Event as MockEvent, mock::*, test_utils::*, Error}; use frame_support::{ assert_noop, assert_ok, bounded_vec, @@ -8,11 +8,12 @@ use frame_support::{ }; use frame_system::{EventRecord, Phase, RawOrigin}; use sp_core::H256; -use sp_io::TestExternalities; -use sp_runtime::{traits::{SaturatedConversion}, Perbill, Percent}; +use sp_runtime::{ + traits::SaturatedConversion, Perbill, Percent, +}; +use sp_std::convert::TryInto; use substrate_fixed::types::U64F64; -use super::types; use crate::cost; use pallet_tfgrid::types as pallet_tfgrid_types; use sp_std::convert::{TryFrom, TryInto}; @@ -668,9 +669,10 @@ fn test_create_node_contract_when_someone_else_has_rent_contract_fails() { #[test] fn test_cancel_rent_contract_with_active_node_contracts_fails() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -704,7 +706,7 @@ fn test_node_contract_billing_details() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(0, &mut pool_state); + run_to_block(0, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let twin = TfgridModule::twins(2).unwrap(); @@ -730,8 +732,15 @@ fn test_node_contract_billing_details() { // advance 25 cycles let mut i = 0; while i != 24 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 10 + i * 10, + }, + Ok(()), + ); i += 1; - run_to_block_and_check_extrinsics(i * 10 + 1, &mut pool_state); + run_to_block(i * 10 + 1, Some(&mut pool_state)); } let free_balance = Balances::free_balance(&twin.account_id); @@ -834,7 +843,7 @@ fn test_multiple_contracts_billing_loop_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -853,7 +862,21 @@ fn test_multiple_contracts_billing_loop_works() { let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); assert_eq!(contracts_to_bill_at_block.len(), 2); - run_to_block_and_check_extrinsics(12, &mut pool_state); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11, + }, + Ok(()), + ); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 2, + block_number: 11, + }, + Ok(()), + ); + run_to_block(12, Some(&mut pool_state)); // Test that the expected events were emitted let our_events = System::events(); @@ -871,7 +894,7 @@ fn test_node_contract_billing_cycles() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -887,8 +910,18 @@ fn test_node_contract_billing_cycles() { push_contract_resources_used(1); + for i in 0..3 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11 + 10 * i, + }, + Ok(()), + ); + } + let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); check_report_cost(1, amount_due_1, 12, discount_received); let twin = TfgridModule::twins(twin_id).unwrap(); @@ -902,11 +935,11 @@ fn test_node_contract_billing_cycles() { ); let (amount_due_2, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); check_report_cost(1, amount_due_2, 22, discount_received); let (amount_due_3, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(32, &mut pool_state); + run_to_block(32, Some(&mut pool_state)); check_report_cost(1, amount_due_3, 32, discount_received); let twin = TfgridModule::twins(twin_id).unwrap(); @@ -926,7 +959,7 @@ fn test_node_multiple_contract_billing_cycles() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -947,15 +980,29 @@ fn test_node_multiple_contract_billing_cycles() { )); let twin_id = 2; + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11, + }, + Ok(()), + ); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 2, + block_number: 11, + }, + Ok(()), + ); push_contract_resources_used(1); push_contract_resources_used(2); let (amount_due_contract_1, discount_received) = calculate_tft_cost(1, twin_id, 11); - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); check_report_cost(1, amount_due_contract_1, 12, discount_received); let (amount_due_contract_2, discount_received) = calculate_tft_cost(2, twin_id, 11); - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); check_report_cost(2, amount_due_contract_2, 12, discount_received); let twin = TfgridModule::twins(twin_id).unwrap(); @@ -975,7 +1022,7 @@ fn test_node_contract_billing_cycles_delete_node_cancels_contract() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -989,30 +1036,39 @@ fn test_node_contract_billing_cycles_delete_node_cancels_contract() { let contract_id = 1; let twin_id = 2; + for i in 0..5 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11 + 10 * i, + }, + Ok(()), + ); + } push_contract_resources_used(1); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 12, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 22, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(32, &mut pool_state); + run_to_block(32, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 32, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(42, &mut pool_state); + run_to_block(42, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 42, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(52, &mut pool_state); + run_to_block(52, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 52, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 4); - run_to_block_and_check_extrinsics(56, &mut pool_state); + run_to_block(56, Some(&mut pool_state)); // Delete node TfgridModule::delete_node_farm(Origin::signed(alice()), 1).unwrap(); @@ -1065,7 +1121,7 @@ fn test_node_contract_only_public_ip_billing_cycles() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1079,25 +1135,35 @@ fn test_node_contract_only_public_ip_billing_cycles() { let contract_id = 1; let twin_id = 2; + for i in 0..5 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: contract_id, + block_number: 11 + i * 10, + }, + Ok(()), + ); + } + let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); assert_ne!(amount_due_as_u128, 0); - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 12, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 22, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(32, &mut pool_state); + run_to_block(32, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 32, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(42, &mut pool_state); + run_to_block(42, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 42, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(52, &mut pool_state); + run_to_block(52, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 52, discount_received); }); } @@ -1107,7 +1173,7 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1122,24 +1188,33 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { let contract_id = 1; let twin_id = 2; + for i in 0..2 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: contract_id, + block_number: 11 + 10 * i, + }, + Ok(()), + ); + } push_contract_resources_used(1); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 12, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 22, discount_received); - run_to_block_and_check_extrinsics(28, &mut pool_state); + run_to_block(28, Some(&mut pool_state)); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 6); assert_ok!(SmartContractModule::cancel_contract( Origin::signed(bob()), 1 )); - run_to_block_and_check_extrinsics(29, &mut pool_state); + run_to_block(29, Some(&mut pool_state)); check_report_cost(1, amount_due_as_u128, 28, discount_received); let contract = SmartContractModule::contracts(1); @@ -1150,6 +1225,55 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { }); } +#[test] +fn test_node_contract_billing_fails() { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { + // Creates a farm and node and sets the price of tft to 0 which raises an error later + prepare_farm_and_node_for_failing(); + run_to_block(1, Some(&mut pool_state)); + + assert_ok!(SmartContractModule::create_node_contract( + Origin::signed(bob()), + 1, + "some_data".as_bytes().to_vec(), + "hash".as_bytes().to_vec(), + 1 + )); + + let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); + assert_eq!(contracts_to_bill_at_block.len(), 1); + + let mut block_number = 11; + // at block 11 billing should fail because of the value of tft being 0 + // the offchain worker should save the failed ids in local storage and try again + // in subsequent blocks (which will also fail) + for _ in 0..3 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: contracts_to_bill_at_block[0], + block_number: block_number, + }, + Err(()), + ); + block_number += 1; + run_to_block(block_number, Some(&mut pool_state)); + } + + // after setting the price back to normal the operation should succeed + TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: contracts_to_bill_at_block[0], + block_number: block_number, + }, + Ok(()), + ); + block_number += 1; + run_to_block(block_number, Some(&mut pool_state)); + }); +} + #[test] fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balance_works() { new_test_ext().execute_with(|| { @@ -1225,7 +1349,7 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1237,14 +1361,23 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { None )); + for i in 0..2 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11 + i*10, + }, + Ok(()), + ); + } push_contract_resources_used(1); // cycle 1 - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); // cycle 2 // user does not have enough funds to pay for 2 cycles - run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); @@ -1269,7 +1402,7 @@ fn test_restore_node_contract_in_grace_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1281,14 +1414,23 @@ fn test_restore_node_contract_in_grace_works() { None )); + for i in 0..6 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11 + i*10, + }, + Ok(()), + ); + } push_contract_resources_used(1); // cycle 1 - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); // cycle 2 // user does not have enough funds to pay for 2 cycles - run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); @@ -1308,22 +1450,22 @@ fn test_restore_node_contract_in_grace_works() { let contract_to_bill = SmartContractModule::contract_to_bill_at_block(31); assert_eq!(contract_to_bill.len(), 1); - run_to_block_and_check_extrinsics(32, &mut pool_state); + run_to_block(32, Some(&mut pool_state)); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(41); assert_eq!(contract_to_bill.len(), 1); - run_to_block_and_check_extrinsics(42, &mut pool_state); + run_to_block(42, Some(&mut pool_state)); // Transfer some balance to the owner of the contract to trigger the grace period to stop Balances::transfer(Origin::signed(bob()), charlie(), 100000000).unwrap(); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(51); assert_eq!(contract_to_bill.len(), 1); - run_to_block_and_check_extrinsics(52, &mut pool_state); + run_to_block(52, Some(&mut pool_state)); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(61); assert_eq!(contract_to_bill.len(), 1); - run_to_block_and_check_extrinsics(62, &mut pool_state); + run_to_block(62, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::Created); @@ -1335,7 +1477,7 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_node_contract( @@ -1347,14 +1489,23 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works None )); + for i in 0..12 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11 + i*10, + }, + Ok(()), + ); + } push_contract_resources_used(1); // cycle 1 - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); // cycle 2 // user does not have enough funds to pay for 2 cycles - run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); @@ -1372,17 +1523,18 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works true ); - run_to_block_and_check_extrinsics(32, &mut pool_state); - run_to_block_and_check_extrinsics(42, &mut pool_state); - run_to_block_and_check_extrinsics(52, &mut pool_state); - run_to_block_and_check_extrinsics(62, &mut pool_state); - run_to_block_and_check_extrinsics(72, &mut pool_state); - run_to_block_and_check_extrinsics(82, &mut pool_state); - run_to_block_and_check_extrinsics(92, &mut pool_state); - run_to_block_and_check_extrinsics(102, &mut pool_state); - run_to_block_and_check_extrinsics(112, &mut pool_state); - run_to_block_and_check_extrinsics(122, &mut pool_state); - run_to_block_and_check_extrinsics(132, &mut pool_state); + run_to_block(32, Some(&mut pool_state)); + run_to_block(42, Some(&mut pool_state)); + run_to_block(52, Some(&mut pool_state)); + run_to_block(62, Some(&mut pool_state)); + run_to_block(72, Some(&mut pool_state)); + run_to_block(82, Some(&mut pool_state)); + run_to_block(92, Some(&mut pool_state)); + run_to_block(102, Some(&mut pool_state)); + run_to_block(112, Some(&mut pool_state)); + // grace period stops after 100 blocknumbers, so after 121 + run_to_block(122, Some(&mut pool_state)); + run_to_block(132, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1); assert_eq!(c1, None); @@ -1394,7 +1546,7 @@ fn test_name_contract_billing() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); assert_ok!(SmartContractModule::create_name_contract( @@ -1405,9 +1557,16 @@ fn test_name_contract_billing() { let contracts_to_bill = SmartContractModule::contract_to_bill_at_block(11); assert_eq!(contracts_to_bill, [1]); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11, + }, + Ok(()), + ); // let mature 11 blocks // because we bill every 10 blocks - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); // the contractbill event should look like: let contract_bill_event = types::ContractBill { @@ -1434,7 +1593,7 @@ fn test_rent_contract_billing() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1451,7 +1610,14 @@ fn test_rent_contract_billing() { types::ContractData::RentContract(rent_contract) ); - run_to_block_and_check_extrinsics(12, &mut pool_state); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11, + }, + Ok(()), + ); + run_to_block(12, Some(&mut pool_state)); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); assert_ne!(amount_due_as_u128, 0); @@ -1464,7 +1630,7 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1481,7 +1647,16 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { types::ContractData::RentContract(rent_contract) ); - run_to_block_and_check_extrinsics(12, &mut pool_state); + for i in 0..2 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11 + i*10, + }, + Ok(()), + ); + } + run_to_block(12, Some(&mut pool_state)); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); assert_ne!(amount_due_as_u128, 0); @@ -1492,7 +1667,7 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { let free_balance = Balances::free_balance(&twin.account_id); assert_ne!(usable_balance, free_balance); - run_to_block_and_check_extrinsics(14, &mut pool_state); + run_to_block(14, Some(&mut pool_state)); // cancel contract let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 2); assert_ok!(SmartContractModule::cancel_contract( @@ -1505,7 +1680,7 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { assert_ne!(usable_balance, 0); Balances::transfer(Origin::signed(bob()), alice(), usable_balance).unwrap(); - run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); // Last amount due is the same as the first one assert_ne!(amount_due_as_u128, 0); @@ -1519,9 +1694,10 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { #[test] fn test_rent_contract_canceled_mid_cycle_should_bill_for_remainder() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1545,7 +1721,7 @@ fn test_rent_contract_canceled_mid_cycle_should_bill_for_remainder() { let locked_balance = free_balance - usable_balance; println!("locked balance: {:?}", locked_balance); - run_to_block(8); + run_to_block(8, Some(&mut pool_state)); // Calculate the cost for 7 blocks of runtime (created a block 1, canceled at block 8) let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 7); // cancel rent contract at block 8 @@ -1569,7 +1745,7 @@ fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billi let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1588,8 +1764,22 @@ fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billi None )); push_contract_resources_used(2); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11, + }, + Ok(()), + ); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 2, + block_number: 11, + }, + Ok(()), + ); - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); assert_ne!(amount_due_as_u128, 0); @@ -1609,7 +1799,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1629,18 +1819,35 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ )); push_contract_resources_used(2); - run_to_block_and_check_extrinsics(12, &mut pool_state); - run_to_block_and_check_extrinsics(22, &mut pool_state); - run_to_block_and_check_extrinsics(32, &mut pool_state); - run_to_block_and_check_extrinsics(42, &mut pool_state); - run_to_block_and_check_extrinsics(52, &mut pool_state); - run_to_block_and_check_extrinsics(62, &mut pool_state); - run_to_block_and_check_extrinsics(72, &mut pool_state); - run_to_block_and_check_extrinsics(82, &mut pool_state); - run_to_block_and_check_extrinsics(92, &mut pool_state); - run_to_block_and_check_extrinsics(102, &mut pool_state); - run_to_block_and_check_extrinsics(112, &mut pool_state); - run_to_block_and_check_extrinsics(122, &mut pool_state); + for i in 0..11 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11 + i*10, + }, + Ok(()), + ); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 2, + block_number: 11 + i*10, + }, + Ok(()), + ); + } + + run_to_block(12, Some(&mut pool_state)); + run_to_block(22, Some(&mut pool_state)); + run_to_block(32, Some(&mut pool_state)); + run_to_block(42, Some(&mut pool_state)); + run_to_block(52, Some(&mut pool_state)); + run_to_block(62, Some(&mut pool_state)); + run_to_block(72, Some(&mut pool_state)); + run_to_block(82, Some(&mut pool_state)); + run_to_block(92, Some(&mut pool_state)); + run_to_block(102, Some(&mut pool_state)); + run_to_block(112, Some(&mut pool_state)); + run_to_block(122, Some(&mut pool_state)); // let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); // assert_ne!(amount_due_as_u128, 0); @@ -1707,7 +1914,7 @@ fn test_create_rent_contract_and_node_contract_with_ip_billing_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1725,8 +1932,22 @@ fn test_create_rent_contract_and_node_contract_with_ip_billing_works() { 1, None )); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11, + }, + Ok(()), + ); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 2, + block_number: 11, + }, + Ok(()), + ); - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); // check contract 1 costs (Rent Contract) let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); @@ -1753,7 +1974,7 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1763,9 +1984,17 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { None )); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11, + }, + Ok(()), + ); + // cycle 1 // user does not have enough funds to pay for 1 cycle - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1790,7 +2019,7 @@ fn test_restore_rent_contract_in_grace_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1800,8 +2029,18 @@ fn test_restore_rent_contract_in_grace_works() { None )); + for i in 0..5 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11 + i*10, + }, + Ok(()), + ); + } + // cycle 1 - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1821,22 +2060,22 @@ fn test_restore_rent_contract_in_grace_works() { let contract_to_bill = SmartContractModule::contract_to_bill_at_block(21); assert_eq!(contract_to_bill.len(), 1); - run_to_block_and_check_extrinsics(22, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(31); assert_eq!(contract_to_bill.len(), 1); - run_to_block_and_check_extrinsics(32, &mut pool_state); + run_to_block(32, Some(&mut pool_state)); // Transfer some balance to the owner of the contract to trigger the grace period to stop Balances::transfer(Origin::signed(bob()), charlie(), 100000000).unwrap(); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(41); assert_eq!(contract_to_bill.len(), 1); - run_to_block_and_check_extrinsics(42, &mut pool_state); + run_to_block(42, Some(&mut pool_state)); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(51); assert_eq!(contract_to_bill.len(), 1); - run_to_block_and_check_extrinsics(52, &mut pool_state); + run_to_block(52, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::Created); @@ -1947,7 +2186,7 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1957,8 +2196,18 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works None )); + for i in 0..11 { + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11 + i*10, + }, + Ok(()), + ); + } + // cycle 1 - run_to_block_and_check_extrinsics(12, &mut pool_state); + run_to_block(12, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1976,18 +2225,18 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works true ); - run_to_block_and_check_extrinsics(22, &mut pool_state); - run_to_block_and_check_extrinsics(32, &mut pool_state); - run_to_block_and_check_extrinsics(42, &mut pool_state); - run_to_block_and_check_extrinsics(52, &mut pool_state); - run_to_block_and_check_extrinsics(62, &mut pool_state); - run_to_block_and_check_extrinsics(72, &mut pool_state); - run_to_block_and_check_extrinsics(82, &mut pool_state); - run_to_block_and_check_extrinsics(92, &mut pool_state); - run_to_block_and_check_extrinsics(102, &mut pool_state); - run_to_block_and_check_extrinsics(112, &mut pool_state); - run_to_block_and_check_extrinsics(122, &mut pool_state); - run_to_block_and_check_extrinsics(132, &mut pool_state); + run_to_block(22, Some(&mut pool_state)); + run_to_block(32, Some(&mut pool_state)); + run_to_block(42, Some(&mut pool_state)); + run_to_block(52, Some(&mut pool_state)); + run_to_block(62, Some(&mut pool_state)); + run_to_block(72, Some(&mut pool_state)); + run_to_block(82, Some(&mut pool_state)); + run_to_block(92, Some(&mut pool_state)); + run_to_block(102, Some(&mut pool_state)); + run_to_block(112, Some(&mut pool_state)); + run_to_block(122, Some(&mut pool_state)); + run_to_block(132, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1); assert_eq!(c1, None); @@ -1999,7 +2248,7 @@ fn test_rent_contract_and_node_contract_canceled_when_node_is_deleted_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block_and_check_extrinsics(1, &mut pool_state); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -2019,9 +2268,24 @@ fn test_rent_contract_and_node_contract_canceled_when_node_is_deleted_works() { )); push_contract_resources_used(2); - run_to_block_and_check_extrinsics(12, &mut pool_state); - run_to_block_and_check_extrinsics(16, &mut pool_state); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 1, + block_number: 11, + }, + Ok(()), + ); + pool_state.write().should_call( + crate::Call::bill_contract_for_block { + contract_id: 2, + block_number: 11, + }, + Ok(()), + ); + run_to_block(12, Some(&mut pool_state)); + + run_to_block(16, Some(&mut pool_state)); // Delete node TfgridModule::delete_node_farm(Origin::signed(alice()), 1).unwrap(); @@ -2431,6 +2695,45 @@ pub fn prepare_farm(source: AccountId, dedicated: bool) { TfgridModule::set_farm_dedicated(RawOrigin::Root.into(), 1, true).unwrap(); } +pub fn prepare_farm_and_node_for_failing() { + TFTPriceModule::set_prices(Origin::signed(bob()), 0, 101).unwrap(); + + create_farming_policies(); + + prepare_twins(); + + prepare_farm(alice(), false); + + // random location + let location = Location { + longitude: "12.233213231".as_bytes().to_vec(), + latitude: "32.323112123".as_bytes().to_vec(), + }; + + let resources = Resources { + hru: 1024 * GIGABYTE, + sru: 512 * GIGABYTE, + cru: 8, + mru: 16 * GIGABYTE, + }; + + let country = "Belgium".as_bytes().to_vec(); + let city = "Ghent".as_bytes().to_vec(); + TfgridModule::create_node( + Origin::signed(alice()), + 1, + resources, + location, + country, + city, + Vec::new(), + false, + false, + "some_serial".as_bytes().to_vec(), + ) + .unwrap(); +} + pub fn prepare_farm_and_node() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); From 50b98b693044e6bf751eec7624485caa2ffeba23 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 12 Aug 2022 11:58:48 +0200 Subject: [PATCH 09/87] FEAT:rewrote some tests + inline documentation + better mocking #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 6 +- .../pallets/pallet-smart-contract/src/mock.rs | 127 +++++-- .../pallet-smart-contract/src/test_utils.rs | 48 +-- .../pallet-smart-contract/src/tests.rs | 349 +++++------------- 4 files changed, 192 insertions(+), 338 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 53f1120ff..5a4a20ae6 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1043,13 +1043,15 @@ impl Pallet { } fn save_failed_contract_ids_in_storage(failed_ids: Vec) { - log::info!("saving {:?} failed contracts", failed_ids); + if !failed_ids.is_empty() { + log::info!("saving {:?} failed contracts", failed_ids); + } let s_contracts = StorageValueRef::persistent(b"pallet-smart-contract::failed-contracts-when-billing"); let mut lock = StorageLock::>>::with_block_deadline( &FAILED_CONTRACTS_STORAGE_LOCK, - 5, + 1, ); { let _guard = lock.lock(); diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 1051ce4af..1c743cca7 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -1,9 +1,9 @@ #![cfg(test)] -use std::panic; +use std::{panic, thread}; use super::*; use crate::name_contract::NameContractName; -use crate::{self as pallet_smart_contract}; +use crate::{self as pallet_smart_contract, types::BlockNumber}; use frame_support::{ construct_runtime, parameter_types, traits::{ConstU32, GenesisBuild}, @@ -24,10 +24,8 @@ use sp_core::{ sr25519, Pair, Public, H256, }; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; -use sp_runtime::{MultiSignature, offchain::TransactionPool}; -use sp_runtime::{ - traits::{IdentifyAccount, Verify}, -}; +use sp_runtime::traits::{IdentifyAccount, Verify}; +use sp_runtime::{offchain::TransactionPool, MultiSignature}; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, Extrinsic as ExtrinsicT, IdentityLookup}, @@ -333,74 +331,143 @@ pub type TransactionCall = pallet_smart_contract::Call; #[derive(Default)] pub struct PoolState { - /// A vector of calls that we expect should be executed - pub expected_calls: Vec<(TransactionCall, Result<(), ()>)>, + /// A vector of calls that we expect should be executed + pub expected_calls: Vec<(TransactionCall, Result<(), ()>)>, pub calls_to_execute: Vec<(TransactionCall, Result<(), ()>)>, pub i: usize, } impl PoolState { + pub fn should_call_bill_contract( + &mut self, + contract_id: u64, + block_number: BlockNumber, + expected_result: Result<(), ()>, + ) { + self.expected_calls.push(( + crate::Call::bill_contract_for_block { + contract_id, + block_number, + }, + expected_result, + )); + } + pub fn should_call(&mut self, expected_call: TransactionCall, expected_result: Result<(), ()>) { self.expected_calls.push((expected_call, expected_result)); } + + pub fn execute_calls_and_check_results(&mut self) { + if self.calls_to_execute.len() == 0 { + return; + } + + // execute the calls that were submitted to the pool and compare the result + for call_to_execute in self.calls_to_execute.iter() { + let result = match call_to_execute.0 { + // matches bill_contract_for_block + crate::Call::bill_contract_for_block { + contract_id, + block_number, + } => SmartContractModule::bill_contract_for_block( + Origin::signed(bob()), + contract_id, + block_number, + ), + // did not match anything => unkown call => this means you should add + // a capture for that function here + _ => panic!("Unknown call!"), + }; + + let result = match result { + Ok(_) => Ok(()), + Err(_) => Err(()), + }; + + // the call should return what we expect it to return + assert_eq!( + call_to_execute.1, result, + "The result of call to {:?} was not as expected!", + call_to_execute.0 + ); + } + + self.calls_to_execute.clear(); + } } -impl Drop for PoolState{ +impl Drop for PoolState { fn drop(&mut self) { - if self.i < self.expected_calls.len() { - panic!("Not all expected calls have been executed! The following calls were still expected: {:?}", &self.expected_calls[self.i..]); + // do not panic if the thread is already panicking! + if !thread::panicking() && self.i < self.expected_calls.len() { + panic!("\nNot all expected calls have been executed! The following calls were still expected: {:?}\n", &self.expected_calls[self.i..]); } } } +/// Implementation of mocked transaction pool used for testing +/// +/// This transaction pool mocks submitting the transactions to the pool. It does +/// not execute the transactions. Instead it keeps them in list. It does compare +/// the submitted call to the expected call. #[derive(Default)] pub struct MockedTransactionPoolExt(Arc>); impl MockedTransactionPoolExt { - /// Create new `TestTransactionPoolExt` and a reference to the internal state. - pub fn new() -> (Self, Arc>) { - let ext = Self::default(); - let state = ext.0.clone(); - (ext, state) - } + /// Create new `TestTransactionPoolExt` and a reference to the internal state. + pub fn new() -> (Self, Arc>) { + let ext = Self::default(); + let state = ext.0.clone(); + (ext, state) + } } impl TransactionPool for MockedTransactionPoolExt { - fn submit_transaction(&mut self, extrinsic: Vec) -> Result<(), ()> { + fn submit_transaction(&mut self, extrinsic: Vec) -> Result<(), ()> { if self.0.read().expected_calls.is_empty() { return Ok(()); } let extrinsic_decoded: Extrinsic = Decode::decode(&mut &*extrinsic).unwrap(); - if self.0.read().i < self.0.read().expected_calls.len() { let i = self.0.read().i.clone(); - log::info!("Call {:?}: {:?}", i, extrinsic_decoded.call); - + log::debug!("Call {:?}: {:?}", i, extrinsic_decoded.call); // the extrinsic should match the expected call at position i - assert_eq!(extrinsic_decoded.call, Call::SmartContractModule(self.0.read().expected_calls[i].0.clone())); + if extrinsic_decoded.call + != Call::SmartContractModule(self.0.read().expected_calls[i].0.clone()) + { + panic!( + "\nEXPECTED call: {:?}\nACTUAL call: {:?}\n", + self.0.read().expected_calls[i].0, + extrinsic_decoded.call + ); + } + // increment i for the next iteration let call_to_execute = self.0.read().expected_calls[i].clone(); + // we push the call to be executed later in the "test thread" self.0.write().calls_to_execute.push(call_to_execute); - self.0.write().i = i+1; + self.0.write().i = i + 1; // return the expected return value return self.0.read().expected_calls[i].1; } // we should not end here as it would mean we did not expect any more calls - panic!("Did not expect any more calls! Still have the call {:?} left.", extrinsic_decoded.call); - } + panic!( + "\nDid not expect any more calls! Still have the call {:?} left.\n", + extrinsic_decoded.call + ); + } } - - -pub fn new_test_ext_with_pool_state(iterations: u32) -> (sp_io::TestExternalities, Arc>) { +pub fn new_test_ext_with_pool_state( + iterations: u32, +) -> (sp_io::TestExternalities, Arc>) { let mut ext = new_test_ext(); let (offchain, offchain_state) = testing::TestOffchainExt::new(); let (pool, pool_state) = MockedTransactionPoolExt::new(); - testing::TestTransactionPoolExt::new(); let keystore = KeyStore::new(); keystore .sr25519_generate_new(KEY_TYPE, Some(&format!("//Alice"))) @@ -417,4 +484,4 @@ pub fn new_test_ext_with_pool_state(iterations: u32) -> (sp_io::TestExternalitie //(ext, pool_state) (ext, pool_state) -} \ No newline at end of file +} diff --git a/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs b/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs index 879197b9e..add898f47 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs @@ -1,63 +1,23 @@ #![cfg(test)] -use crate::mock::{ - bob, Origin, PoolState, SmartContractModule, System, Timestamp, -}; + +use crate::mock::{PoolState, SmartContractModule, System, Timestamp}; use codec::alloc::sync::Arc; use frame_support::traits::Hooks; use parking_lot::RwLock; -//use sp_runtime::offchain::testing::PoolState; -pub fn run_to_block(n: u64, mut pool_state: Option<&mut Arc>>) { +pub fn run_to_block(n: u64, pool_state: Option<&mut Arc>>) { Timestamp::set_timestamp((1628082000 * 1000) + (6000 * n)); while System::block_number() < n { - //System::offchain_worker(System::block_number()); SmartContractModule::offchain_worker(System::block_number()); SmartContractModule::on_finalize(System::block_number()); System::on_finalize(System::block_number()); if pool_state.is_some() { - contracts_should_be_billed(*pool_state.as_mut().unwrap()); + pool_state.as_ref().unwrap().write().execute_calls_and_check_results(); } System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); SmartContractModule::on_initialize(System::block_number()); } } - -fn contracts_should_be_billed(pool_state: &mut Arc>) { - //TODO inlinde doc - if pool_state.read().calls_to_execute.len() == 0 { - return; - } - - for call_to_execute in pool_state.read().calls_to_execute.iter() { - let result = match call_to_execute.0 { - // matches bill_contract_for_block - crate::Call::bill_contract_for_block { - contract_id, - block_number, - } => SmartContractModule::bill_contract_for_block( - Origin::signed(bob()), - contract_id, - block_number, - ), - // did not match anything => unkown call => this means you should add a capture for that function here - _ => panic!("Unknown call!"), - }; - - let result = match result { - Ok(_) => Ok(()), - Err(_) => Err(()), - }; - - assert_eq!( - call_to_execute.1, - result, - "The result of call to {:?} was not as expected!", - call_to_execute.0 - ); - } - - pool_state.write().calls_to_execute.clear(); -} diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 1e5796457..0f36d28df 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -8,9 +8,7 @@ use frame_support::{ }; use frame_system::{EventRecord, Phase, RawOrigin}; use sp_core::H256; -use sp_runtime::{ - traits::SaturatedConversion, Perbill, Percent, -}; +use sp_runtime::{traits::SaturatedConversion, Perbill, Percent}; use sp_std::convert::TryInto; use substrate_fixed::types::U64F64; @@ -730,17 +728,11 @@ fn test_node_contract_billing_details() { let initial_total_issuance = Balances::total_issuance(); // advance 25 cycles - let mut i = 0; - while i != 24 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 10 + i * 10, - }, - Ok(()), - ); - i += 1; - run_to_block(i * 10 + 1, Some(&mut pool_state)); + for i in 0..25 { + pool_state + .write() + .should_call_bill_contract(1, 10 + i * 10, Ok(())); + run_to_block(11 + i * 10, Some(&mut pool_state)); } let free_balance = Balances::free_balance(&twin.account_id); @@ -862,20 +854,9 @@ fn test_multiple_contracts_billing_loop_works() { let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); assert_eq!(contracts_to_bill_at_block.len(), 2); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11, - }, - Ok(()), - ); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 2, - block_number: 11, - }, - Ok(()), - ); + // 2 contracts => 2 billings + pool_state.write().should_call_bill_contract(1, 11, Ok(())); + pool_state.write().should_call_bill_contract(2, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); // Test that the expected events were emitted @@ -910,17 +891,8 @@ fn test_node_contract_billing_cycles() { push_contract_resources_used(1); - for i in 0..3 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11 + 10 * i, - }, - Ok(()), - ); - } - let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); + pool_state.write().should_call_bill_contract(1, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); check_report_cost(1, amount_due_1, 12, discount_received); @@ -935,10 +907,12 @@ fn test_node_contract_billing_cycles() { ); let (amount_due_2, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); + pool_state.write().should_call_bill_contract(1, 21, Ok(())); run_to_block(22, Some(&mut pool_state)); check_report_cost(1, amount_due_2, 22, discount_received); let (amount_due_3, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); + pool_state.write().should_call_bill_contract(1, 31, Ok(())); run_to_block(32, Some(&mut pool_state)); check_report_cost(1, amount_due_3, 32, discount_received); @@ -980,20 +954,8 @@ fn test_node_multiple_contract_billing_cycles() { )); let twin_id = 2; - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11, - }, - Ok(()), - ); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 2, - block_number: 11, - }, - Ok(()), - ); + pool_state.write().should_call_bill_contract(1, 11, Ok(())); + pool_state.write().should_call_bill_contract(2, 11, Ok(())); push_contract_resources_used(1); push_contract_resources_used(2); @@ -1037,13 +999,9 @@ fn test_node_contract_billing_cycles_delete_node_cancels_contract() { let twin_id = 2; for i in 0..5 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11 + 10 * i, - }, - Ok(()), - ); + pool_state + .write() + .should_call_bill_contract(1, 11 + 10 * i, Ok(())); } push_contract_resources_used(1); @@ -1135,14 +1093,10 @@ fn test_node_contract_only_public_ip_billing_cycles() { let contract_id = 1; let twin_id = 2; - for i in 0..5 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: contract_id, - block_number: 11 + i * 10, - }, - Ok(()), - ); + for i in 0..5 { + pool_state + .write() + .should_call_bill_contract(contract_id, 11 + i * 10, Ok(())); } let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); @@ -1188,14 +1142,11 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { let contract_id = 1; let twin_id = 2; + // 2 cycles for billing for i in 0..2 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: contract_id, - block_number: 11 + 10 * i, - }, - Ok(()), - ); + pool_state + .write() + .should_call_bill_contract(contract_id, 11 + 10 * i, Ok(())); } push_contract_resources_used(1); @@ -1244,33 +1195,20 @@ fn test_node_contract_billing_fails() { let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); assert_eq!(contracts_to_bill_at_block.len(), 1); - let mut block_number = 11; // at block 11 billing should fail because of the value of tft being 0 // the offchain worker should save the failed ids in local storage and try again // in subsequent blocks (which will also fail) - for _ in 0..3 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: contracts_to_bill_at_block[0], - block_number: block_number, - }, - Err(()), - ); - block_number += 1; - run_to_block(block_number, Some(&mut pool_state)); + for i in 0..3 { + pool_state + .write() + .should_call_bill_contract(1, 11 + i, Err(())); + run_to_block(12 + i, Some(&mut pool_state)); } // after setting the price back to normal the operation should succeed TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: contracts_to_bill_at_block[0], - block_number: block_number, - }, - Ok(()), - ); - block_number += 1; - run_to_block(block_number, Some(&mut pool_state)); + pool_state.write().should_call_bill_contract(1, 14, Ok(())); + run_to_block(15, Some(&mut pool_state)); }); } @@ -1361,22 +1299,15 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { None )); - for i in 0..2 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11 + i*10, - }, - Ok(()), - ); - } push_contract_resources_used(1); // cycle 1 + pool_state.write().should_call_bill_contract(1, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); // cycle 2 // user does not have enough funds to pay for 2 cycles + pool_state.write().should_call_bill_contract(1, 21, Ok(())); run_to_block(22, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); @@ -1415,13 +1346,9 @@ fn test_restore_node_contract_in_grace_works() { )); for i in 0..6 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11 + i*10, - }, - Ok(()), - ); + pool_state + .write() + .should_call_bill_contract(1, 11 + i * 10, Ok(())); } push_contract_resources_used(1); @@ -1489,22 +1416,15 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works None )); - for i in 0..12 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11 + i*10, - }, - Ok(()), - ); - } push_contract_resources_used(1); // cycle 1 + pool_state.write().should_call_bill_contract(1, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); // cycle 2 // user does not have enough funds to pay for 2 cycles + pool_state.write().should_call_bill_contract(1, 21, Ok(())); run_to_block(22, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); @@ -1523,18 +1443,15 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works true ); - run_to_block(32, Some(&mut pool_state)); - run_to_block(42, Some(&mut pool_state)); - run_to_block(52, Some(&mut pool_state)); - run_to_block(62, Some(&mut pool_state)); - run_to_block(72, Some(&mut pool_state)); - run_to_block(82, Some(&mut pool_state)); - run_to_block(92, Some(&mut pool_state)); - run_to_block(102, Some(&mut pool_state)); - run_to_block(112, Some(&mut pool_state)); // grace period stops after 100 blocknumbers, so after 121 - run_to_block(122, Some(&mut pool_state)); - run_to_block(132, Some(&mut pool_state)); + for i in 0..10 { + pool_state + .write() + .should_call_bill_contract(1, 31 + i * 10, Ok(())); + } + for i in 0..11 { + run_to_block(32 + i * 10, Some(&mut pool_state)); + } let c1 = SmartContractModule::contracts(1); assert_eq!(c1, None); @@ -1557,15 +1474,9 @@ fn test_name_contract_billing() { let contracts_to_bill = SmartContractModule::contract_to_bill_at_block(11); assert_eq!(contracts_to_bill, [1]); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11, - }, - Ok(()), - ); // let mature 11 blocks // because we bill every 10 blocks + pool_state.write().should_call_bill_contract(1, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); // the contractbill event should look like: @@ -1610,13 +1521,7 @@ fn test_rent_contract_billing() { types::ContractData::RentContract(rent_contract) ); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11, - }, - Ok(()), - ); + pool_state.write().should_call_bill_contract(1, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); @@ -1648,13 +1553,9 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { ); for i in 0..2 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11 + i*10, - }, - Ok(()), - ); + pool_state + .write() + .should_call_bill_contract(1, 11 + i * 10, Ok(())); } run_to_block(12, Some(&mut pool_state)); @@ -1764,21 +1665,8 @@ fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billi None )); push_contract_resources_used(2); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11, - }, - Ok(()), - ); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 2, - block_number: 11, - }, - Ok(()), - ); - + pool_state.write().should_call_bill_contract(1, 11, Ok(())); + pool_state.write().should_call_bill_contract(2, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); @@ -1819,35 +1707,18 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ )); push_contract_resources_used(2); + // run 12 cycles, contracts should cancel after 11 due to lack of funds for i in 0..11 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11 + i*10, - }, - Ok(()), - ); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 2, - block_number: 11 + i*10, - }, - Ok(()), - ); + pool_state + .write() + .should_call_bill_contract(1, 11 + i * 10, Ok(())); + pool_state + .write() + .should_call_bill_contract(2, 11 + i * 10, Ok(())); + } + for i in 0..12 { + run_to_block(12 + 10 * i, Some(&mut pool_state)); } - - run_to_block(12, Some(&mut pool_state)); - run_to_block(22, Some(&mut pool_state)); - run_to_block(32, Some(&mut pool_state)); - run_to_block(42, Some(&mut pool_state)); - run_to_block(52, Some(&mut pool_state)); - run_to_block(62, Some(&mut pool_state)); - run_to_block(72, Some(&mut pool_state)); - run_to_block(82, Some(&mut pool_state)); - run_to_block(92, Some(&mut pool_state)); - run_to_block(102, Some(&mut pool_state)); - run_to_block(112, Some(&mut pool_state)); - run_to_block(122, Some(&mut pool_state)); // let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); // assert_ne!(amount_due_as_u128, 0); @@ -1932,21 +1803,10 @@ fn test_create_rent_contract_and_node_contract_with_ip_billing_works() { 1, None )); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11, - }, - Ok(()), - ); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 2, - block_number: 11, - }, - Ok(()), - ); + // 2 contracts => we expect 2 calls to bill_contract + pool_state.write().should_call_bill_contract(1, 11, Ok(())); + pool_state.write().should_call_bill_contract(2, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); // check contract 1 costs (Rent Contract) @@ -1984,16 +1844,9 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { None )); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11, - }, - Ok(()), - ); - // cycle 1 // user does not have enough funds to pay for 1 cycle + pool_state.write().should_call_bill_contract(1, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); @@ -2029,17 +1882,8 @@ fn test_restore_rent_contract_in_grace_works() { None )); - for i in 0..5 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11 + i*10, - }, - Ok(()), - ); - } - // cycle 1 + pool_state.write().should_call_bill_contract(1, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); @@ -2060,10 +1904,12 @@ fn test_restore_rent_contract_in_grace_works() { let contract_to_bill = SmartContractModule::contract_to_bill_at_block(21); assert_eq!(contract_to_bill.len(), 1); + pool_state.write().should_call_bill_contract(1, 21, Ok(())); run_to_block(22, Some(&mut pool_state)); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(31); assert_eq!(contract_to_bill.len(), 1); + pool_state.write().should_call_bill_contract(1, 31, Ok(())); run_to_block(32, Some(&mut pool_state)); // Transfer some balance to the owner of the contract to trigger the grace period to stop @@ -2071,10 +1917,12 @@ fn test_restore_rent_contract_in_grace_works() { let contract_to_bill = SmartContractModule::contract_to_bill_at_block(41); assert_eq!(contract_to_bill.len(), 1); + pool_state.write().should_call_bill_contract(1, 41, Ok(())); run_to_block(42, Some(&mut pool_state)); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(51); assert_eq!(contract_to_bill.len(), 1); + pool_state.write().should_call_bill_contract(1, 51, Ok(())); run_to_block(52, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); @@ -2196,17 +2044,8 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works None )); - for i in 0..11 { - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11 + i*10, - }, - Ok(()), - ); - } - // cycle 1 + pool_state.write().should_call_bill_contract(1, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); @@ -2225,18 +2064,16 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works true ); - run_to_block(22, Some(&mut pool_state)); - run_to_block(32, Some(&mut pool_state)); - run_to_block(42, Some(&mut pool_state)); - run_to_block(52, Some(&mut pool_state)); - run_to_block(62, Some(&mut pool_state)); - run_to_block(72, Some(&mut pool_state)); - run_to_block(82, Some(&mut pool_state)); - run_to_block(92, Some(&mut pool_state)); - run_to_block(102, Some(&mut pool_state)); - run_to_block(112, Some(&mut pool_state)); - run_to_block(122, Some(&mut pool_state)); - run_to_block(132, Some(&mut pool_state)); + // run 12 cycles, after 10 cycles grace period has finished so no more + // billing! + for i in 0..10 { + pool_state + .write() + .should_call_bill_contract(1, 21 + i * 10, Ok(())); + } + for i in 0..12 { + run_to_block(22 + i * 10, Some(&mut pool_state)); + } let c1 = SmartContractModule::contracts(1); assert_eq!(c1, None); @@ -2268,21 +2105,9 @@ fn test_rent_contract_and_node_contract_canceled_when_node_is_deleted_works() { )); push_contract_resources_used(2); - - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 1, - block_number: 11, - }, - Ok(()), - ); - pool_state.write().should_call( - crate::Call::bill_contract_for_block { - contract_id: 2, - block_number: 11, - }, - Ok(()), - ); + // 2 contracts => 2 calls to bill_contract + pool_state.write().should_call_bill_contract(1, 11, Ok(())); + pool_state.write().should_call_bill_contract(2, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); run_to_block(16, Some(&mut pool_state)); From 25a44dccf169ba4b0e528400ee17dfc09f068896 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 12 Aug 2022 16:42:28 +0200 Subject: [PATCH 10/87] FEAT: removed unused imports #269 --- substrate-node/pallets/pallet-smart-contract/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 5a4a20ae6..cfbc36871 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -27,11 +27,10 @@ use pallet_tfgrid::pallet::{InterfaceOf, PubConfigOf}; use pallet_tfgrid::types as pallet_tfgrid_types; use pallet_timestamp as timestamp; use sp_core::crypto::KeyTypeId; -use sp_runtime::traits::BlockNumberProvider; use sp_runtime::{ offchain::{ storage::StorageValueRef, - storage_lock::{BlockAndTime, StorageLock, Time}, + storage_lock::{BlockAndTime, StorageLock}, Duration, }, traits::{CheckedSub, SaturatedConversion}, From 8146d202f43ccd9b735a2dc7711345c5c2f0ed9f Mon Sep 17 00:00:00 2001 From: brandonpille Date: Wed, 17 Aug 2022 19:05:42 +0200 Subject: [PATCH 11/87] FEAT: fixed a small mistake + commented the tests #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 38 ++++--- .../pallet-smart-contract/src/tests.rs | 11 +- .../pallets/test_offchain_worker.py | 105 ++++++++++++++++++ .../pallets/test_offchain_worker_setup.py | 19 ++++ 4 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 substrate-node/pallets/test_offchain_worker.py create mode 100644 substrate-node/pallets/test_offchain_worker_setup.py diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index cfbc36871..5e30a7107 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1,7 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] -use core::hash::Hasher; - use sp_std::prelude::*; use frame_support::{ @@ -42,7 +40,7 @@ use tfchain_support::{ types::{Node, NodeCertification, Resources}, } -pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"demo"); +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"smct"); #[cfg(test)] mod mock; @@ -519,8 +517,13 @@ pub mod pallet { ); } contracts.extend(ContractsToBillAt::::get(current_block_u64)); + // filter out the contracts that have been deleted in the meantime + contracts = contracts + .into_iter() + .filter(|contract_id| Contracts::::get(contract_id).contract_id != 0).collect::>(); if contracts.is_empty() { + log::debug!("No contracts to bill at block {:?}", block_number); return; } @@ -841,10 +844,7 @@ impl Pallet { Error::::NodeHasActiveContracts ); } - - // Update state Self::_update_contract_state(&mut contract, &types::ContractState::Deleted(cause))?; - // Bill contract Self::bill_contract(&mut contract)?; // Remove all associated storage Self::remove_contract(contract.contract_id); @@ -1016,11 +1016,10 @@ impl Pallet { fn offchain_signed_tx(block_number: T::BlockNumber, contract_id: u64) -> Result<(), Error> { let signer = Signer::::any_account(); - let result = - signer.send_signed_transaction(|_acct: &Account| Call::bill_contract_for_block { - contract_id, - block_number, - }); + let result = signer.send_signed_transaction(|_acct| Call::bill_contract_for_block { + contract_id, + block_number, + }); if let Some((acc, res)) = result { if res.is_err() { @@ -1029,7 +1028,7 @@ impl Pallet { contract_id, block_number, acc.id, - res + res.err() ); return Err(>::OffchainSignedTxError); } @@ -1098,10 +1097,15 @@ impl Pallet { }; // Handle grace + let was_grace = matches!(contract.state, types::ContractState::GracePeriod(_)); let contract = Self::handle_grace(contract, usable_balance, amount_due)?; // Handle contract lock operations - Self::handle_lock(contract, amount_due)?; + // not if the contract status was grace and changed to deleted because it means + // the grace period ended and there were still no funds + if !(was_grace && matches!(contract.state, types::ContractState::Deleted(_))) { + Self::handle_lock(contract, amount_due)?; + } // Always emit a contract billed event let contract_bill = types::ContractBill { @@ -1163,6 +1167,7 @@ impl Pallet { // we can still update the internal contract lock object to figure out later how much was due // whilst in grace period if amount_due >= usable_balance { + log::info!("Grace period started at block {:?} due to lack of funds", current_block); Self::_update_contract_state( contract, &types::ContractState::GracePeriod(current_block), @@ -1300,8 +1305,11 @@ impl Pallet { &pricing_policy, contract_lock.amount_locked, ) { - Ok(_) => (), - Err(err) => log::error!("error while distributing cultivation rewards {:?}", err), + Ok(_) => {} + Err(err) => { + log::error!("error while distributing cultivation rewards {:?}", err); + return Err(err); + } }; // Reset values contract_lock.lock_updated = now; diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 0f36d28df..36ae0aece 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1552,11 +1552,7 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { types::ContractData::RentContract(rent_contract) ); - for i in 0..2 { - pool_state - .write() - .should_call_bill_contract(1, 11 + i * 10, Ok(())); - } + pool_state.write().should_call_bill_contract(1, 11, Ok(())); run_to_block(12, Some(&mut pool_state)); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); @@ -1570,6 +1566,8 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { run_to_block(14, Some(&mut pool_state)); // cancel contract + // it will bill before removing the contract and it should bill all + // reserverd balance let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 2); assert_ok!(SmartContractModule::cancel_contract( Origin::signed(bob()), @@ -1581,6 +1579,9 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { assert_ne!(usable_balance, 0); Balances::transfer(Origin::signed(bob()), alice(), usable_balance).unwrap(); + // we do not call bill contract here as the contract is removed during + // cancel_contract. The contract id will still be in ContractsToBillAt + // but the contract itself will no longer exist run_to_block(22, Some(&mut pool_state)); // Last amount due is the same as the first one diff --git a/substrate-node/pallets/test_offchain_worker.py b/substrate-node/pallets/test_offchain_worker.py new file mode 100644 index 000000000..b397d9af4 --- /dev/null +++ b/substrate-node/pallets/test_offchain_worker.py @@ -0,0 +1,105 @@ +from re import sub +from substrateinterface import SubstrateInterface, Keypair, KeypairType + +GIGABYTE = 1024*1024*1024 + +substrate = SubstrateInterface( + url="ws://127.0.0.1:9945", + ss58_format=42, + type_registry_preset='polkadot' +) + +key_alice = Keypair.create_from_uri("//Alice") + +alice_insert_key_params = ["tft!", "//Alice", key_alice.public_key.hex()] +substrate.rpc_request("author_insertKey", alice_insert_key_params) + +alice_insert_key_params = ["smct", "//Alice", key_alice.public_key.hex()] +substrate.rpc_request("author_insertKey", alice_insert_key_params) + + +call_user_accept_tc = substrate.compose_call("TfgridModule", + "user_accept_tc", + { + "document_link": "garbage", + "document_hash": "garbage" + } + ) +call_user_accept_tc_signed = substrate.create_signed_extrinsic(call_user_accept_tc, key_alice) + +response = substrate.submit_extrinsic(call_user_accept_tc_signed, wait_for_finalization=True) +if not response.is_success: + print(response.error_message) + exit(-1) + + + + +call_create_twin = substrate.compose_call("TfgridModule", + "create_twin", + { + "ip": "::1" + }) +call_create_twin_signed = substrate.create_signed_extrinsic(call_create_twin, key_alice) +response = substrate.submit_extrinsic(call_create_twin_signed, wait_for_finalization=True) +if not response.is_success: + print(response.error_message) + exit(-1) + + + +call_create_farm = substrate.compose_call("TfgridModule", + "create_farm", + { + "name": "myfarm", + "public_ips": [] + }) +call_create_farm_signed = substrate.create_signed_extrinsic(call_create_farm, key_alice) +response = substrate.submit_extrinsic(call_create_farm_signed, wait_for_finalization=True) +if not response.is_success: + print(response.error_message) + exit(-1) + + + +call_create_node = substrate.compose_call("TfgridModule", + "create_node", + { + "farm_id": "1", + "resources": { + "hru": 1024 * GIGABYTE, + "sru": 512 * GIGABYTE, + "cru": 8, + "mru": 16 * GIGABYTE}, + "location": { + "longitude": "garbage", + "latitude": "garbage" + }, + "country": "Belgium", + "city": "Ghent", + "interfaces": [], + "secure_boot": False, + "virtualized": False, + "serial_number": "garbage" + }) +call_create_node_signed = substrate.create_signed_extrinsic(call_create_node, key_alice) +response = substrate.submit_extrinsic(call_create_node_signed, wait_for_finalization=True) +if not response.is_success: + print(response.error_message) + exit(-1) + + +call_create_contract = substrate.compose_call("SmartContractModule", + "create_node_contract", + { + "node_id": 1, + "data": "garbage", + "deployment_hash": "garbage", + "public_ips": 0 + }) +call_create_contract_signed = substrate.create_signed_extrinsic(call_create_contract, key_alice) +response = substrate.submit_extrinsic(call_create_contract_signed, wait_for_finalization=True) +if not response.is_success: + print(response.error_message) + exit(-1) + \ No newline at end of file diff --git a/substrate-node/pallets/test_offchain_worker_setup.py b/substrate-node/pallets/test_offchain_worker_setup.py new file mode 100644 index 000000000..64c28d996 --- /dev/null +++ b/substrate-node/pallets/test_offchain_worker_setup.py @@ -0,0 +1,19 @@ +from re import sub +from substrateinterface import SubstrateInterface, Keypair, KeypairType + +GIGABYTE = 1024*1024*1024 + +substrate = SubstrateInterface( + url="ws://127.0.0.1:9946", + ss58_format=42, + type_registry_preset='polkadot' +) + +key_alice = Keypair.create_from_uri("//Alice") + +alice_insert_key_params = ["tft!", "//Alice", key_alice.public_key.hex()] +substrate.rpc_request("author_insertKey", alice_insert_key_params) + +alice_insert_key_params = ["smct", "//Alice", key_alice.public_key.hex()] +substrate.rpc_request("author_insertKey", alice_insert_key_params) + From b29d5c2fe35772b45304d0bfc0437ed9dbc35033 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Thu, 18 Aug 2022 10:56:03 +0200 Subject: [PATCH 12/87] FEAT: fixed merge conflicts #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 31 +++++------ .../pallets/pallet-smart-contract/src/mock.rs | 4 ++ .../pallet-smart-contract/src/tests.rs | 51 ++++++++++++------- 3 files changed, 50 insertions(+), 36 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 5e30a7107..dd159335c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -37,8 +37,8 @@ use sp_runtime::{ use substrate_fixed::types::U64F64; use tfchain_support::{ traits::ChangeNode, - types::{Node, NodeCertification, Resources}, -} + types::Node, +}; pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"smct"); @@ -520,7 +520,7 @@ pub mod pallet { // filter out the contracts that have been deleted in the meantime contracts = contracts .into_iter() - .filter(|contract_id| Contracts::::get(contract_id).contract_id != 0).collect::>(); + .filter(|contract_id| Contracts::::contains_key(contract_id)).collect::>(); if contracts.is_empty() { log::debug!("No contracts to bill at block {:?}", block_number); @@ -971,12 +971,10 @@ impl Pallet { block: T::BlockNumber, ) -> DispatchResultWithPostInfo { if !Contracts::::contains_key(contract_id) { - return Ok(().into()); } - let mut contract = - Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - + let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + log::info!("billing contract with id {:?} at block {:?}", contract_id, block); // Try to bill contract match Self::bill_contract(&mut contract) { @@ -1000,17 +998,17 @@ impl Pallet { // https://github.com/threefoldtech/tfchain/issues/264 // if a contract is still in storage and actively getting billed whilst it is in state delete // remove all associated storage and continue - let ctr = Contracts::::get(contract_id); - if let Some(contract) = ctr { - if contract.contract_id != 0 && contract.is_state_delete() { - Self::remove_contract(contract.contract_id); - return Ok(().into()); - } - - // Reinsert into the next billing frequency - Self::_reinsert_contract_to_bill(contract.contract_id); + let ctr = Contracts::::get(contract_id); + if let Some(contract) = ctr { + if contract.contract_id != 0 && contract.is_state_delete() { + Self::remove_contract(contract.contract_id); + return Ok(().into()); } + + // Reinsert into the next billing frequency + Self::_reinsert_contract_to_bill(contract.contract_id); } + Ok(().into()) } @@ -1373,7 +1371,6 @@ impl Pallet { Contracts::::remove(contract_id); ContractLock::::remove(contract_id); } - log::info!("Average prise is {:?}", avg_tft_price); } // Following: https://library.threefold.me/info/threefold#/tfgrid/farming/threefold__proof_of_utilization diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 1c743cca7..ae690db0d 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -16,7 +16,9 @@ use pallet_tfgrid::{ pub_ip::{GatewayIP, PublicIP}, twin::TwinIp, }; +use parking_lot::RwLock; use sp_core::{ + crypto::Ss58Codec, offchain::{ testing::{self}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, @@ -218,6 +220,8 @@ impl pallet_smart_contract::Config for TestRuntime { type RestrictedOrigin = EnsureRoot; type MaxDeploymentDataLength = MaxDeploymentDataLength; type MaxNodeContractPublicIps = MaxNodeContractPublicIPs; + type AuthorityId = pallet_smart_contract::crypto::AuthId; + type Call = Call; } type AccountPublic = ::Signer; diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 36ae0aece..c067cffc4 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -9,12 +9,11 @@ use frame_support::{ use frame_system::{EventRecord, Phase, RawOrigin}; use sp_core::H256; use sp_runtime::{traits::SaturatedConversion, Perbill, Percent}; -use sp_std::convert::TryInto; +use sp_std::convert::{TryFrom, TryInto}; use substrate_fixed::types::U64F64; use crate::cost; use pallet_tfgrid::types as pallet_tfgrid_types; -use sp_std::convert::{TryFrom, TryInto}; use tfchain_support::types::{FarmCertification, Location, NodeCertification, PublicIP, Resources}; const GIGABYTE: u64 = 1024 * 1024 * 1024; @@ -784,12 +783,13 @@ fn test_node_contract_billing_details() { #[test] fn test_node_contract_billing_details_with_solution_provider() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); prepare_solution_provider(); - run_to_block(0); + run_to_block(0, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let twin = TfgridModule::twins(2).unwrap(); @@ -813,10 +813,11 @@ fn test_node_contract_billing_details_with_solution_provider() { assert_eq!(contract_to_bill, [1]); // advance 25 cycles - let mut i = 0; - while i != 24 { - i += 1; - run_to_block(i * 10 + 1); + for i in 0..25 { + pool_state + .write() + .should_call_bill_contract(1, 10 + i * 10, Ok(())); + run_to_block(11 + i * 10, Some(&mut pool_state)); } let usable_balance = Balances::usable_balance(&twin.account_id); @@ -1187,9 +1188,10 @@ fn test_node_contract_billing_fails() { assert_ok!(SmartContractModule::create_node_contract( Origin::signed(bob()), 1, - "some_data".as_bytes().to_vec(), - "hash".as_bytes().to_vec(), - 1 + generate_deployment_hash(), + get_deployment_data(), + 1, + None )); let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); @@ -1579,7 +1581,7 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { assert_ne!(usable_balance, 0); Balances::transfer(Origin::signed(bob()), alice(), usable_balance).unwrap(); - // we do not call bill contract here as the contract is removed during + // we do not call bill contract here as the contract is removed during // cancel_contract. The contract id will still be in ContractsToBillAt // but the contract itself will no longer exist run_to_block(22, Some(&mut pool_state)); @@ -1933,9 +1935,10 @@ fn test_restore_rent_contract_in_grace_works() { #[test] fn test_restore_rent_contract_and_node_contracts_in_grace_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_dedicated_farm_and_node(); - run_to_block(1); + run_to_block(1, None); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; @@ -1955,7 +1958,9 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { push_contract_resources_used(2); // cycle 1 - run_to_block(12); + pool_state.write().should_call_bill_contract(1, 11, Ok(())); + pool_state.write().should_call_bill_contract(2, 11, Ok(())); + run_to_block(12, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1986,22 +1991,30 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { let contract_to_bill = SmartContractModule::contract_to_bill_at_block(21); assert_eq!(contract_to_bill.len(), 2); - run_to_block(22); + pool_state.write().should_call_bill_contract(1, 21, Ok(())); + pool_state.write().should_call_bill_contract(2, 21, Ok(())); + run_to_block(22, Some(&mut pool_state)); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(31); assert_eq!(contract_to_bill.len(), 2); - run_to_block(32); + pool_state.write().should_call_bill_contract(1, 31, Ok(())); + pool_state.write().should_call_bill_contract(2, 31, Ok(())); + run_to_block(32, Some(&mut pool_state)); // Transfer some balance to the owner of the contract to trigger the grace period to stop Balances::transfer(Origin::signed(bob()), charlie(), 100000000).unwrap(); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(41); assert_eq!(contract_to_bill.len(), 2); - run_to_block(42); + pool_state.write().should_call_bill_contract(1, 41, Ok(())); + pool_state.write().should_call_bill_contract(2, 41, Ok(())); + run_to_block(42, Some(&mut pool_state)); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(51); assert_eq!(contract_to_bill.len(), 2); - run_to_block(52); + pool_state.write().should_call_bill_contract(1, 51, Ok(())); + pool_state.write().should_call_bill_contract(2, 51, Ok(())); + run_to_block(52, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::Created); From b751c1ef71f0a45cb5c4df84c1c4b3ffc26d9ace Mon Sep 17 00:00:00 2001 From: brandonpille Date: Thu, 18 Aug 2022 17:10:40 +0200 Subject: [PATCH 13/87] FEAT: fixed transaction pool error handeling behavior #269 --- .../pallets/pallet-smart-contract/Cargo.toml | 2 + .../pallets/pallet-smart-contract/src/lib.rs | 50 +++++++++++-------- .../pallet-smart-contract/src/tests.rs | 3 +- .../pallets/test_offchain_worker.py | 32 +++++------- 4 files changed, 47 insertions(+), 40 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/Cargo.toml b/substrate-node/pallets/pallet-smart-contract/Cargo.toml index 3563404c2..ff77e55c8 100644 --- a/substrate-node/pallets/pallet-smart-contract/Cargo.toml +++ b/substrate-node/pallets/pallet-smart-contract/Cargo.toml @@ -45,9 +45,11 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot- # Benchmarking frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false, optional = true } +[dev-dependencies] parking_lot = '0.12.1' sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } env_logger = "*" + [features] default = ['std'] std = [ diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index dd159335c..6155978d3 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -35,10 +35,7 @@ use sp_runtime::{ Perbill, }; use substrate_fixed::types::U64F64; -use tfchain_support::{ - traits::ChangeNode, - types::Node, -}; +use tfchain_support::{traits::ChangeNode, types::Node}; pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"smct"); @@ -516,11 +513,14 @@ pub mod pallet { contracts ); } + Self::save_failed_contract_ids_in_storage(Vec::new()); contracts.extend(ContractsToBillAt::::get(current_block_u64)); // filter out the contracts that have been deleted in the meantime contracts = contracts .into_iter() - .filter(|contract_id| Contracts::::contains_key(contract_id)).collect::>(); + .filter(|contract_id| Contracts::::contains_key(contract_id)) + .collect::>(); + contracts.dedup(); if contracts.is_empty() { log::debug!("No contracts to bill at block {:?}", block_number); @@ -545,13 +545,7 @@ pub mod pallet { "all contracts billed successfully at block: {:?}", block_number ); - } else { - log::info!( - "billing failed for some of the contracts at block: {:?}", - block_number - ); } - Self::save_failed_contract_ids_in_storage(failed_contract_ids); } } @@ -975,7 +969,11 @@ impl Pallet { } let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - log::info!("billing contract with id {:?} at block {:?}", contract_id, block); + log::info!( + "billing contract with id {:?} at block {:?}", + contract_id, + block + ); // Try to bill contract match Self::bill_contract(&mut contract) { Ok(_) => { @@ -986,12 +984,15 @@ impl Pallet { ); } Err(err) => { + // if billing failed append it to the failed ids in local storage + // billing will be retried at the next block log::error!( "error while billing contract with id {:?}: {:?}", contract_id, - err + err.error ); - return Err(err); + Self::append_failed_contract_ids_to_storage(contract_id); + return Ok(().into()); } } @@ -1008,7 +1009,7 @@ impl Pallet { // Reinsert into the next billing frequency Self::_reinsert_contract_to_bill(contract.contract_id); } - + Ok(().into()) } @@ -1022,11 +1023,10 @@ impl Pallet { if let Some((acc, res)) = result { if res.is_err() { log::error!( - "failed billing contract {:?} at block {:?} using account {:?} with error {:?}", + "signed transaction failed for billing contract {:?} at block {:?} using account {:?}", contract_id, block_number, - acc.id, - res.err() + acc.id ); return Err(>::OffchainSignedTxError); } @@ -1038,6 +1038,13 @@ impl Pallet { return Err(>::OffchainSignedTxError); } + fn append_failed_contract_ids_to_storage(failed_id: u64) { + let mut failed_ids = Self::get_failed_contract_ids_from_storage(); + failed_ids.push(failed_id); + + Self::save_failed_contract_ids_in_storage(failed_ids); + } + fn save_failed_contract_ids_in_storage(failed_ids: Vec) { if !failed_ids.is_empty() { log::info!("saving {:?} failed contracts", failed_ids); @@ -1099,7 +1106,7 @@ impl Pallet { let contract = Self::handle_grace(contract, usable_balance, amount_due)?; // Handle contract lock operations - // not if the contract status was grace and changed to deleted because it means + // skip when the contract status was grace and changed to deleted because that means // the grace period ended and there were still no funds if !(was_grace && matches!(contract.state, types::ContractState::Deleted(_))) { Self::handle_lock(contract, amount_due)?; @@ -1165,7 +1172,10 @@ impl Pallet { // we can still update the internal contract lock object to figure out later how much was due // whilst in grace period if amount_due >= usable_balance { - log::info!("Grace period started at block {:?} due to lack of funds", current_block); + log::info!( + "Grace period started at block {:?} due to lack of funds", + current_block + ); Self::_update_contract_state( contract, &types::ContractState::GracePeriod(current_block), diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index c067cffc4..a9621681f 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1200,10 +1200,11 @@ fn test_node_contract_billing_fails() { // at block 11 billing should fail because of the value of tft being 0 // the offchain worker should save the failed ids in local storage and try again // in subsequent blocks (which will also fail) + // TODO change description for i in 0..3 { pool_state .write() - .should_call_bill_contract(1, 11 + i, Err(())); + .should_call_bill_contract(1, 11 + i, Ok(())); run_to_block(12 + i, Some(&mut pool_state)); } diff --git a/substrate-node/pallets/test_offchain_worker.py b/substrate-node/pallets/test_offchain_worker.py index b397d9af4..83432da96 100644 --- a/substrate-node/pallets/test_offchain_worker.py +++ b/substrate-node/pallets/test_offchain_worker.py @@ -1,4 +1,4 @@ -from re import sub +from random import randbytes from substrateinterface import SubstrateInterface, Keypair, KeypairType GIGABYTE = 1024*1024*1024 @@ -28,10 +28,8 @@ call_user_accept_tc_signed = substrate.create_signed_extrinsic(call_user_accept_tc, key_alice) response = substrate.submit_extrinsic(call_user_accept_tc_signed, wait_for_finalization=True) -if not response.is_success: - print(response.error_message) - exit(-1) - +if response.error_message: + print(response.error_message) @@ -42,9 +40,8 @@ }) call_create_twin_signed = substrate.create_signed_extrinsic(call_create_twin, key_alice) response = substrate.submit_extrinsic(call_create_twin_signed, wait_for_finalization=True) -if not response.is_success: +if response.error_message: print(response.error_message) - exit(-1) @@ -56,9 +53,8 @@ }) call_create_farm_signed = substrate.create_signed_extrinsic(call_create_farm, key_alice) response = substrate.submit_extrinsic(call_create_farm_signed, wait_for_finalization=True) -if not response.is_success: +if response.error_message: print(response.error_message) - exit(-1) @@ -84,22 +80,20 @@ }) call_create_node_signed = substrate.create_signed_extrinsic(call_create_node, key_alice) response = substrate.submit_extrinsic(call_create_node_signed, wait_for_finalization=True) -if not response.is_success: +if response.error_message: print(response.error_message) - exit(-1) - + call_create_contract = substrate.compose_call("SmartContractModule", "create_node_contract", { "node_id": 1, - "data": "garbage", - "deployment_hash": "garbage", - "public_ips": 0 + "deployment_data": randbytes(32), + "deployment_hash": randbytes(32), + "public_ips": 0, + "solution_provider_id": None }) call_create_contract_signed = substrate.create_signed_extrinsic(call_create_contract, key_alice) response = substrate.submit_extrinsic(call_create_contract_signed, wait_for_finalization=True) -if not response.is_success: - print(response.error_message) - exit(-1) - \ No newline at end of file +if response.error_message: + print(response.error_message) \ No newline at end of file From ed4de4a9b1ddbeb4c8ff10b00f59f72ccd239782 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 19 Aug 2022 16:45:47 +0200 Subject: [PATCH 14/87] FEAT: cleaning up code #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 57 +++++++------------ .../pallet-smart-contract/src/tests.rs | 1 - 2 files changed, 19 insertions(+), 39 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 6155978d3..a7bba4289 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -26,11 +26,7 @@ use pallet_tfgrid::types as pallet_tfgrid_types; use pallet_timestamp as timestamp; use sp_core::crypto::KeyTypeId; use sp_runtime::{ - offchain::{ - storage::StorageValueRef, - storage_lock::{BlockAndTime, StorageLock}, - Duration, - }, + offchain::storage::StorageValueRef, traits::{CheckedSub, SaturatedConversion}, Perbill, }; @@ -114,12 +110,6 @@ pub mod pallet { pub const GRID_LOCK_ID: LockIdentifier = *b"gridlock"; - pub const FAILED_CONTRACTS_STORAGE_ID: [u8; 52] = - *b"pallet-smart-contract::failed-contracts-when-billing"; - pub const FAILED_CONTRACTS_STORAGE_LOCK: [u8; 44] = - *b"pallet-smart-contract::failed-contracts-lock"; - pub const FAILED_CONTRACTS_TIMEOUT: Duration = Duration::from_millis(60000); - #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::without_storage_info] @@ -513,6 +503,7 @@ pub mod pallet { contracts ); } + //reset the failed contract ids for next run Self::save_failed_contract_ids_in_storage(Vec::new()); contracts.extend(ContractsToBillAt::::get(current_block_u64)); // filter out the contracts that have been deleted in the meantime @@ -532,19 +523,9 @@ pub mod pallet { contracts, block_number ); - let mut failed_contract_ids: Vec = Vec::new(); - for contract_id in contracts { - match Self::offchain_signed_tx(block_number, contract_id) { - Ok(_) => (), - Err(_) => failed_contract_ids.push(contract_id), - } - } - if failed_contract_ids.is_empty() { - log::info!( - "all contracts billed successfully at block: {:?}", - block_number - ); + for contract_id in contracts { + let _res = Self::bill_contract_using_signed_transaction(block_number, contract_id); } } @@ -987,7 +968,7 @@ impl Pallet { // if billing failed append it to the failed ids in local storage // billing will be retried at the next block log::error!( - "error while billing contract with id {:?}: {:?}", + "error while billing contract with id {:?}: <{:?}>", contract_id, err.error ); @@ -1013,7 +994,10 @@ impl Pallet { Ok(().into()) } - fn offchain_signed_tx(block_number: T::BlockNumber, contract_id: u64) -> Result<(), Error> { + fn bill_contract_using_signed_transaction( + block_number: T::BlockNumber, + contract_id: u64, + ) -> Result<(), Error> { let signer = Signer::::any_account(); let result = signer.send_signed_transaction(|_acct| Call::bill_contract_for_block { contract_id, @@ -1021,6 +1005,10 @@ impl Pallet { }); if let Some((acc, res)) = result { + // if res is an error this means sending the transaction failed + // this means the transaction was already send before (probably by another node) + // unfortunately the error is always empty (substrate just logs the error and + // returns Err()) if res.is_err() { log::error!( "signed transaction failed for billing contract {:?} at block {:?} using account {:?}", @@ -1030,15 +1018,18 @@ impl Pallet { ); return Err(>::OffchainSignedTxError); } - // Transaction is sent successfully return Ok(()); } - // The case of `None`: no account is available for sending log::error!("No local account available"); return Err(>::OffchainSignedTxError); } fn append_failed_contract_ids_to_storage(failed_id: u64) { + log::info!( + "billing contract {:?} will be retried in the next block", + failed_id + ); + let mut failed_ids = Self::get_failed_contract_ids_from_storage(); failed_ids.push(failed_id); @@ -1046,20 +1037,10 @@ impl Pallet { } fn save_failed_contract_ids_in_storage(failed_ids: Vec) { - if !failed_ids.is_empty() { - log::info!("saving {:?} failed contracts", failed_ids); - } let s_contracts = StorageValueRef::persistent(b"pallet-smart-contract::failed-contracts-when-billing"); - let mut lock = StorageLock::>>::with_block_deadline( - &FAILED_CONTRACTS_STORAGE_LOCK, - 1, - ); - { - let _guard = lock.lock(); - s_contracts.set(&failed_ids); - } + s_contracts.set(&failed_ids); } fn get_failed_contract_ids_from_storage() -> Vec { diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index a9621681f..7af5deaa1 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1200,7 +1200,6 @@ fn test_node_contract_billing_fails() { // at block 11 billing should fail because of the value of tft being 0 // the offchain worker should save the failed ids in local storage and try again // in subsequent blocks (which will also fail) - // TODO change description for i in 0..3 { pool_state .write() From c64203186b15d37c5b4dc92f92bd7b9a1c15fec8 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 19 Aug 2022 17:00:37 +0200 Subject: [PATCH 15/87] FEAT: last log improvement #269 --- substrate-node/pallets/pallet-smart-contract/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index a7bba4289..240f315be 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -959,7 +959,7 @@ impl Pallet { match Self::bill_contract(&mut contract) { Ok(_) => { log::info!( - "billed contract with id {:?} at block {:?}", + "successfully billed contract with id {:?} at block {:?}", contract_id, block ); From 99c59e6ada61cbca8b24928990bc6ab264bfbd86 Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Thu, 25 Aug 2022 14:15:37 +0200 Subject: [PATCH 16/87] chore: extend scripts --- .../pallets/test_offchain_worker.py | 24 +++++++++++++++---- .../pallets/test_offchain_worker_setup.py | 10 ++++---- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/substrate-node/pallets/test_offchain_worker.py b/substrate-node/pallets/test_offchain_worker.py index 83432da96..2056308ff 100644 --- a/substrate-node/pallets/test_offchain_worker.py +++ b/substrate-node/pallets/test_offchain_worker.py @@ -11,11 +11,11 @@ key_alice = Keypair.create_from_uri("//Alice") -alice_insert_key_params = ["tft!", "//Alice", key_alice.public_key.hex()] -substrate.rpc_request("author_insertKey", alice_insert_key_params) +# alice_insert_key_params = ["tft!", "//Alice", key_alice.public_key.hex()] +# substrate.rpc_request("author_insertKey", alice_insert_key_params) -alice_insert_key_params = ["smct", "//Alice", key_alice.public_key.hex()] -substrate.rpc_request("author_insertKey", alice_insert_key_params) +# alice_insert_key_params = ["smct", "//Alice", key_alice.public_key.hex()] +# substrate.rpc_request("author_insertKey", alice_insert_key_params) call_user_accept_tc = substrate.compose_call("TfgridModule", @@ -95,5 +95,21 @@ }) call_create_contract_signed = substrate.create_signed_extrinsic(call_create_contract, key_alice) response = substrate.submit_extrinsic(call_create_contract_signed, wait_for_finalization=True) +if response.error_message: + print(response.error_message) + +call_create_contract = substrate.compose_call("SmartContractModule", + "report_contract_resources", + { + "contract_id": 1, + "used": { + "hru": 0, + "sru": 20 * GIGABYTE, + "cru": 2, + "mru": 4 * GIGABYTE + } + }) +call_create_contract_signed = substrate.create_signed_extrinsic(call_create_contract, key_alice) +response = substrate.submit_extrinsic(call_create_contract_signed, wait_for_finalization=True) if response.error_message: print(response.error_message) \ No newline at end of file diff --git a/substrate-node/pallets/test_offchain_worker_setup.py b/substrate-node/pallets/test_offchain_worker_setup.py index 64c28d996..f23311675 100644 --- a/substrate-node/pallets/test_offchain_worker_setup.py +++ b/substrate-node/pallets/test_offchain_worker_setup.py @@ -9,11 +9,11 @@ type_registry_preset='polkadot' ) -key_alice = Keypair.create_from_uri("//Alice") +key_bob = Keypair.create_from_uri("//Bob") -alice_insert_key_params = ["tft!", "//Alice", key_alice.public_key.hex()] -substrate.rpc_request("author_insertKey", alice_insert_key_params) +bob_insert_key_params = ["tft!", "//Bob", key_bob.public_key.hex()] +substrate.rpc_request("author_insertKey", bob_insert_key_params) -alice_insert_key_params = ["smct", "//Alice", key_alice.public_key.hex()] -substrate.rpc_request("author_insertKey", alice_insert_key_params) +bob_insert_key_params = ["smct", "//Bob", key_bob.public_key.hex()] +substrate.rpc_request("author_insertKey", bob_insert_key_params) From f0f7583b8e7d98e144ae55245a8d1cbd89748be7 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 5 Sep 2022 10:17:51 +0200 Subject: [PATCH 17/87] FEAT: remove duplicates before reinserting for next block #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 1 + .../pallets/test_offchain_worker.py | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 240f315be..d8259baa4 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1500,6 +1500,7 @@ impl Pallet { let future_block = now + BillingFrequency::::get(); let mut contracts = ContractsToBillAt::::get(future_block); contracts.push(contract_id); + contracts.dedup(); ContractsToBillAt::::insert(future_block, &contracts); log::info!( "Insert contracts: {:?}, to be billed at block {:?}", diff --git a/substrate-node/pallets/test_offchain_worker.py b/substrate-node/pallets/test_offchain_worker.py index 2056308ff..f4b69ae26 100644 --- a/substrate-node/pallets/test_offchain_worker.py +++ b/substrate-node/pallets/test_offchain_worker.py @@ -11,11 +11,11 @@ key_alice = Keypair.create_from_uri("//Alice") -# alice_insert_key_params = ["tft!", "//Alice", key_alice.public_key.hex()] -# substrate.rpc_request("author_insertKey", alice_insert_key_params) +alice_insert_key_params = ["tft!", "//Alice", key_alice.public_key.hex()] +substrate.rpc_request("author_insertKey", alice_insert_key_params) -# alice_insert_key_params = ["smct", "//Alice", key_alice.public_key.hex()] -# substrate.rpc_request("author_insertKey", alice_insert_key_params) +alice_insert_key_params = ["smct", "//Alice", key_alice.public_key.hex()] +substrate.rpc_request("author_insertKey", alice_insert_key_params) call_user_accept_tc = substrate.compose_call("TfgridModule", @@ -101,13 +101,17 @@ call_create_contract = substrate.compose_call("SmartContractModule", "report_contract_resources", { - "contract_id": 1, - "used": { - "hru": 0, - "sru": 20 * GIGABYTE, - "cru": 2, - "mru": 4 * GIGABYTE - } + "contract_resources": [ + { + "contract_id": 1, + "used": { + "hru": 0, + "sru": 20 * GIGABYTE, + "cru": 2, + "mru": 4 * GIGABYTE + } + } + ] }) call_create_contract_signed = substrate.create_signed_extrinsic(call_create_contract, key_alice) response = substrate.submit_extrinsic(call_create_contract_signed, wait_for_finalization=True) From 25a18047a96b997427428995e8514ec4a2feb7c6 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 5 Sep 2022 10:23:37 +0200 Subject: [PATCH 18/87] FEAT: more proper way to avoid dupplicates #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index d8259baa4..2c9676c9e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1499,14 +1499,15 @@ impl Pallet { // Save the contract to be billed in now + BILLING_FREQUENCY_IN_BLOCKS let future_block = now + BillingFrequency::::get(); let mut contracts = ContractsToBillAt::::get(future_block); - contracts.push(contract_id); - contracts.dedup(); - ContractsToBillAt::::insert(future_block, &contracts); - log::info!( - "Insert contracts: {:?}, to be billed at block {:?}", - contracts, - future_block - ); + if !contracts.contains(&contract_id) { + contracts.push(contract_id); + ContractsToBillAt::::insert(future_block, &contracts); + log::info!( + "Insert contracts: {:?}, to be billed at block {:?}", + contracts, + future_block + ); + } } // Helper function that updates the contract state and manages storage accordingly From 461bceafdace9c50483d3d8ef2aad5fdff2f84fe Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 5 Sep 2022 14:58:30 +0200 Subject: [PATCH 19/87] FEAT: fixed tests #269 --- .../pallet-smart-contract/src/tests.rs | 69 +++++-------------- 1 file changed, 18 insertions(+), 51 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 7af5deaa1..13ef4c05a 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -14,7 +14,7 @@ use substrate_fixed::types::U64F64; use crate::cost; use pallet_tfgrid::types as pallet_tfgrid_types; -use tfchain_support::types::{FarmCertification, Location, NodeCertification, PublicIP, Resources}; +use tfchain_support::{types::{FarmCertification, Location, NodeCertification, PublicIP, Resources}}; const GIGABYTE: u64 = 1024 * 1024 * 1024; @@ -820,8 +820,8 @@ fn test_node_contract_billing_details_with_solution_provider() { run_to_block(11 + i * 10, Some(&mut pool_state)); } - let usable_balance = Balances::usable_balance(&twin.account_id); - let total_amount_billed = initial_twin_balance - usable_balance; + let free_balance = Balances::free_balance(&twin.account_id); + let total_amount_billed = initial_twin_balance - free_balance; validate_distribution_rewards(initial_total_issuance, total_amount_billed, true); @@ -1182,7 +1182,7 @@ fn test_node_contract_billing_fails() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { // Creates a farm and node and sets the price of tft to 0 which raises an error later - prepare_farm_and_node_for_failing(); + prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); assert_ok!(SmartContractModule::create_node_contract( @@ -1197,7 +1197,14 @@ fn test_node_contract_billing_fails() { let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); assert_eq!(contracts_to_bill_at_block.len(), 1); - // at block 11 billing should fail because of the value of tft being 0 + let contract_id = contracts_to_bill_at_block[0]; + + // delete twin to make the billing fail + assert_ok!(TfgridModule::delete_twin( + Origin::signed(bob()), + SmartContractModule::contracts(contract_id).unwrap().twin_id, + )); + // the offchain worker should save the failed ids in local storage and try again // in subsequent blocks (which will also fail) for i in 0..3 { @@ -1206,11 +1213,6 @@ fn test_node_contract_billing_fails() { .should_call_bill_contract(1, 11 + i, Ok(())); run_to_block(12 + i, Some(&mut pool_state)); } - - // after setting the price back to normal the operation should succeed - TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - pool_state.write().should_call_bill_contract(1, 14, Ok(())); - run_to_block(15, Some(&mut pool_state)); }); } @@ -1241,11 +1243,15 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balanc push_contract_resources_used(1); let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - run_to_block(12); + pool_state + .write() + .should_call_bill_contract(contract_id, 11, Ok(())); check_report_cost(1, amount_due_1, 12, discount_received); let (amount_due_2, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(22); + pool_state + .write() + .should_call_bill_contract(contract_id, 21, Ok(())); check_report_cost(1, amount_due_2, 22, discount_received); // Run halfway ish next cycle and cancel @@ -2534,45 +2540,6 @@ pub fn prepare_farm(source: AccountId, dedicated: bool) { TfgridModule::set_farm_dedicated(RawOrigin::Root.into(), 1, true).unwrap(); } -pub fn prepare_farm_and_node_for_failing() { - TFTPriceModule::set_prices(Origin::signed(bob()), 0, 101).unwrap(); - - create_farming_policies(); - - prepare_twins(); - - prepare_farm(alice(), false); - - // random location - let location = Location { - longitude: "12.233213231".as_bytes().to_vec(), - latitude: "32.323112123".as_bytes().to_vec(), - }; - - let resources = Resources { - hru: 1024 * GIGABYTE, - sru: 512 * GIGABYTE, - cru: 8, - mru: 16 * GIGABYTE, - }; - - let country = "Belgium".as_bytes().to_vec(); - let city = "Ghent".as_bytes().to_vec(); - TfgridModule::create_node( - Origin::signed(alice()), - 1, - resources, - location, - country, - city, - Vec::new(), - false, - false, - "some_serial".as_bytes().to_vec(), - ) - .unwrap(); -} - pub fn prepare_farm_and_node() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); From 15c09074bdb12ae21e359c1ca89dec5ef9516733 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 5 Sep 2022 15:35:46 +0200 Subject: [PATCH 20/87] FEAT: debug logging when amount is 0 #269 --- substrate-node/pallets/pallet-smart-contract/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 2c9676c9e..0fd8360dd 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1079,6 +1079,7 @@ impl Pallet { // If there is nothing to be paid, return if amount_due == BalanceOf::::saturated_from(0 as u128) { + log::debug!("amount to be billed is 0, nothing to do"); return Ok(().into()); }; From 970d10f8e9c1ad5a04f9d0f03d516aba068d9c78 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 6 Sep 2022 16:26:51 +0200 Subject: [PATCH 21/87] FEAT: implementation unauthorized billing #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 70 +++++++++++++++++-- .../pallets/pallet-smart-contract/src/mock.rs | 1 - .../pallet-smart-contract/src/tests.rs | 60 +++++++++++++++- 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 0fd8360dd..29c261d7b 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -185,6 +185,10 @@ pub mod pallet { #[pallet::getter(fn contract_id)] pub type ContractID = StorageValue<_, u64, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn allowed_origin)] + pub type AllowedOrigin = StorageValue<_, Vec, OptionQuery>; + #[pallet::storage] #[pallet::getter(fn solution_providers)] pub type SolutionProviders = @@ -347,6 +351,8 @@ pub mod pallet { InvalidProviderConfiguration, NoSuchSolutionProvider, SolutionProviderNotApproved, + NotAuthorizedToBill, + AllowedOriginIsEmpty, } #[pallet::genesis_config] @@ -373,6 +379,36 @@ pub mod pallet { #[pallet::call] impl Pallet { + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn add_allowed_origin( + origin: OriginFor, + account_id: T::AccountId, + ) -> DispatchResultWithPostInfo { + ::RestrictedOrigin::ensure_origin(origin)?; + let mut allowed_origin = AllowedOrigin::::get().unwrap_or(Vec::new()); + + if !allowed_origin.contains(&account_id) { + allowed_origin.push(account_id); + } + + AllowedOrigin::::set(Some(allowed_origin)); + Ok(().into()) + } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn remove_allowed_origin( + origin: OriginFor, + account_id: T::AccountId, + ) -> DispatchResultWithPostInfo { + ::RestrictedOrigin::ensure_origin(origin)?; + let mut allowed_origin = AllowedOrigin::::get().unwrap_or(Vec::new()); + + allowed_origin.retain(|x| *x != account_id); + + AllowedOrigin::::set(Some(allowed_origin)); + Ok(().into()) + } + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn create_node_contract( origin: OriginFor, @@ -487,8 +523,16 @@ pub mod pallet { contract_id: u64, block_number: T::BlockNumber, ) -> DispatchResultWithPostInfo { - let _account_id = ensure_signed(origin)?; - Self::_bill_contract_for_block(contract_id, block_number) + let account_id = ensure_signed(origin)?; + + let res = Self::is_allowed_to_bill(account_id); + if !res.is_ok() { + Self::append_failed_contract_ids_to_storage(contract_id); + return res; + } + let _res = Self::_bill_contract_for_block(contract_id, block_number)?; + + Ok(Pays::No.into()) } } @@ -945,11 +989,11 @@ impl Pallet { contract_id: u64, block: T::BlockNumber, ) -> DispatchResultWithPostInfo { - if !Contracts::::contains_key(contract_id) { + let contract = Contracts::::get(contract_id); + if contract.is_none() { return Ok(().into()); } - let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - + let mut contract = contract.unwrap(); log::info!( "billing contract with id {:?} at block {:?}", contract_id, @@ -982,7 +1026,7 @@ impl Pallet { // remove all associated storage and continue let ctr = Contracts::::get(contract_id); if let Some(contract) = ctr { - if contract.contract_id != 0 && contract.is_state_delete() { + if contract.is_state_delete() { Self::remove_contract(contract.contract_id); return Ok(().into()); } @@ -1054,6 +1098,20 @@ impl Pallet { return Vec::new(); } + fn is_allowed_to_bill(who: T::AccountId) -> DispatchResultWithPostInfo { + if let Some(allowed) = AllowedOrigin::::get() { + if !allowed.contains(&who) { + log::error!("{:?} not authorized to bill contracts", who); + return Err(Error::::NotAuthorizedToBill.into()); + } + } else { + log::error!("AllowedOrigin is empty!"); + return Err(Error::::AllowedOriginIsEmpty.into()); + } + + Ok(().into()) + } + // Bills a contract (NodeContract or NameContract) // Calculates how much TFT is due by the user and distributes the rewards #[transactional] diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index ae690db0d..891f9fcb7 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -486,6 +486,5 @@ pub fn new_test_ext_with_pool_state( ext.register_extension(TransactionPoolExt::new(pool)); ext.register_extension(KeystoreExt(Arc::new(keystore))); - //(ext, pool_state) (ext, pool_state) } diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 13ef4c05a..47756b8a2 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1181,7 +1181,6 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { fn test_node_contract_billing_fails() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { - // Creates a farm and node and sets the price of tft to 0 which raises an error later prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); @@ -1212,6 +1211,10 @@ fn test_node_contract_billing_fails() { .write() .should_call_bill_contract(1, 11 + i, Ok(())); run_to_block(12 + i, Some(&mut pool_state)); + + let failed_contract_ids = SmartContractModule::get_failed_contract_ids_from_storage(); + assert_eq!(failed_contract_ids.len(), 1); + assert_eq!(failed_contract_ids[0], contract_id); } }); } @@ -1466,6 +1469,49 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works }); } +#[test] +fn test_unauthorized_billing() { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { + prepare_farm_and_node(); + + // remove Bob from the allowed origin as we are using bob to send bill transactions in our mock + assert_ok!(SmartContractModule::remove_allowed_origin(RawOrigin::Root.into(), bob())); + + run_to_block(1, Some(&mut pool_state)); + TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + + assert_ok!(SmartContractModule::create_node_contract( + Origin::signed(bob()), + 1, + generate_deployment_hash(), + get_deployment_data(), + 0, + None + )); + + push_contract_resources_used(1); + + // billing should fail as bob is not allowed to send the transaction + pool_state.write().should_call_bill_contract(1, 11, Err(())); + run_to_block(12, Some(&mut pool_state)); + + // failed billings should be retried on next block + let failed_contract_ids = SmartContractModule::get_failed_contract_ids_from_storage(); + assert_eq!(failed_contract_ids.len(), 1); + assert_eq!(failed_contract_ids[0], 1); + + // add bob back to the allowed origin + assert_ok!(SmartContractModule::add_allowed_origin(RawOrigin::Root.into(), bob())); + pool_state.write().should_call_bill_contract(1, 12, Ok(())); + run_to_block(13, Some(&mut pool_state)); + + // failed contracts should be empty now + let failed_contract_ids = SmartContractModule::get_failed_contract_ids_from_storage(); + assert_eq!(failed_contract_ids.len(), 0); + }); +} + #[test] fn test_name_contract_billing() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); @@ -2472,6 +2518,12 @@ fn calculate_tft_cost(contract_id: u64, twin_id: u32, blocks: u64) -> (u64, type (amount_due, discount_received) } +pub fn add_allowed_origin() { + SmartContractModule::add_allowed_origin(RawOrigin::Root.into(), alice()).unwrap(); + SmartContractModule::add_allowed_origin(RawOrigin::Root.into(), bob()).unwrap(); + SmartContractModule::add_allowed_origin(RawOrigin::Root.into(), charlie()).unwrap(); +} + pub fn prepare_twins() { create_twin(alice()); create_twin(bob()); @@ -2577,8 +2629,12 @@ pub fn prepare_farm_and_node() { "some_serial".as_bytes().to_vec(), ) .unwrap(); + + add_allowed_origin(); } + + pub fn prepare_dedicated_farm_and_node() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); create_farming_policies(); @@ -2615,6 +2671,8 @@ pub fn prepare_dedicated_farm_and_node() { "some_serial".as_bytes().to_vec(), ) .unwrap(); + + add_allowed_origin(); } pub fn create_twin(origin: AccountId) { From e7815f0cef06fbe260ec2c37ac51948129eebd95 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Wed, 7 Sep 2022 09:47:20 +0200 Subject: [PATCH 22/87] FEAT: check if signer can sign before sending transaction #269 --- substrate-node/pallets/pallet-smart-contract/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 29c261d7b..ded7b82f1 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1043,6 +1043,14 @@ impl Pallet { contract_id: u64, ) -> Result<(), Error> { let signer = Signer::::any_account(); + if !signer.can_sign() { + log::error!( + "failed billing contract {:?}: at block {:?} account cannot be used to sign transaction", + contract_id, + block_number + ); + return Err(>::OffchainSignedTxError); + } let result = signer.send_signed_transaction(|_acct| Call::bill_contract_for_block { contract_id, block_number, From 41bd1a16877ad76f7a3981631620ca1881211520 Mon Sep 17 00:00:00 2001 From: dylanVerstraete Date: Tue, 20 Sep 2022 16:03:48 +0200 Subject: [PATCH 23/87] Revert "FEAT: implementation unauthorized billing #269" This reverts commit 5dacc1f34903dd7a77c38147a5ef4ba58f2e492a. --- .../pallets/pallet-smart-contract/src/lib.rs | 70 ++----------------- .../pallets/pallet-smart-contract/src/mock.rs | 1 + .../pallet-smart-contract/src/tests.rs | 60 +--------------- 3 files changed, 8 insertions(+), 123 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index ded7b82f1..81727a2f9 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -185,10 +185,6 @@ pub mod pallet { #[pallet::getter(fn contract_id)] pub type ContractID = StorageValue<_, u64, ValueQuery>; - #[pallet::storage] - #[pallet::getter(fn allowed_origin)] - pub type AllowedOrigin = StorageValue<_, Vec, OptionQuery>; - #[pallet::storage] #[pallet::getter(fn solution_providers)] pub type SolutionProviders = @@ -351,8 +347,6 @@ pub mod pallet { InvalidProviderConfiguration, NoSuchSolutionProvider, SolutionProviderNotApproved, - NotAuthorizedToBill, - AllowedOriginIsEmpty, } #[pallet::genesis_config] @@ -379,36 +373,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn add_allowed_origin( - origin: OriginFor, - account_id: T::AccountId, - ) -> DispatchResultWithPostInfo { - ::RestrictedOrigin::ensure_origin(origin)?; - let mut allowed_origin = AllowedOrigin::::get().unwrap_or(Vec::new()); - - if !allowed_origin.contains(&account_id) { - allowed_origin.push(account_id); - } - - AllowedOrigin::::set(Some(allowed_origin)); - Ok(().into()) - } - - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn remove_allowed_origin( - origin: OriginFor, - account_id: T::AccountId, - ) -> DispatchResultWithPostInfo { - ::RestrictedOrigin::ensure_origin(origin)?; - let mut allowed_origin = AllowedOrigin::::get().unwrap_or(Vec::new()); - - allowed_origin.retain(|x| *x != account_id); - - AllowedOrigin::::set(Some(allowed_origin)); - Ok(().into()) - } - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn create_node_contract( origin: OriginFor, @@ -523,16 +487,8 @@ pub mod pallet { contract_id: u64, block_number: T::BlockNumber, ) -> DispatchResultWithPostInfo { - let account_id = ensure_signed(origin)?; - - let res = Self::is_allowed_to_bill(account_id); - if !res.is_ok() { - Self::append_failed_contract_ids_to_storage(contract_id); - return res; - } - let _res = Self::_bill_contract_for_block(contract_id, block_number)?; - - Ok(Pays::No.into()) + let _account_id = ensure_signed(origin)?; + Self::_bill_contract_for_block(contract_id, block_number) } } @@ -989,11 +945,11 @@ impl Pallet { contract_id: u64, block: T::BlockNumber, ) -> DispatchResultWithPostInfo { - let contract = Contracts::::get(contract_id); - if contract.is_none() { + if !Contracts::::contains_key(contract_id) { return Ok(().into()); } - let mut contract = contract.unwrap(); + let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + log::info!( "billing contract with id {:?} at block {:?}", contract_id, @@ -1026,7 +982,7 @@ impl Pallet { // remove all associated storage and continue let ctr = Contracts::::get(contract_id); if let Some(contract) = ctr { - if contract.is_state_delete() { + if contract.contract_id != 0 && contract.is_state_delete() { Self::remove_contract(contract.contract_id); return Ok(().into()); } @@ -1106,20 +1062,6 @@ impl Pallet { return Vec::new(); } - fn is_allowed_to_bill(who: T::AccountId) -> DispatchResultWithPostInfo { - if let Some(allowed) = AllowedOrigin::::get() { - if !allowed.contains(&who) { - log::error!("{:?} not authorized to bill contracts", who); - return Err(Error::::NotAuthorizedToBill.into()); - } - } else { - log::error!("AllowedOrigin is empty!"); - return Err(Error::::AllowedOriginIsEmpty.into()); - } - - Ok(().into()) - } - // Bills a contract (NodeContract or NameContract) // Calculates how much TFT is due by the user and distributes the rewards #[transactional] diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 891f9fcb7..ae690db0d 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -486,5 +486,6 @@ pub fn new_test_ext_with_pool_state( ext.register_extension(TransactionPoolExt::new(pool)); ext.register_extension(KeystoreExt(Arc::new(keystore))); + //(ext, pool_state) (ext, pool_state) } diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 47756b8a2..13ef4c05a 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1181,6 +1181,7 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { fn test_node_contract_billing_fails() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { + // Creates a farm and node and sets the price of tft to 0 which raises an error later prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); @@ -1211,10 +1212,6 @@ fn test_node_contract_billing_fails() { .write() .should_call_bill_contract(1, 11 + i, Ok(())); run_to_block(12 + i, Some(&mut pool_state)); - - let failed_contract_ids = SmartContractModule::get_failed_contract_ids_from_storage(); - assert_eq!(failed_contract_ids.len(), 1); - assert_eq!(failed_contract_ids[0], contract_id); } }); } @@ -1469,49 +1466,6 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works }); } -#[test] -fn test_unauthorized_billing() { - let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); - ext.execute_with(|| { - prepare_farm_and_node(); - - // remove Bob from the allowed origin as we are using bob to send bill transactions in our mock - assert_ok!(SmartContractModule::remove_allowed_origin(RawOrigin::Root.into(), bob())); - - run_to_block(1, Some(&mut pool_state)); - TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - - assert_ok!(SmartContractModule::create_node_contract( - Origin::signed(bob()), - 1, - generate_deployment_hash(), - get_deployment_data(), - 0, - None - )); - - push_contract_resources_used(1); - - // billing should fail as bob is not allowed to send the transaction - pool_state.write().should_call_bill_contract(1, 11, Err(())); - run_to_block(12, Some(&mut pool_state)); - - // failed billings should be retried on next block - let failed_contract_ids = SmartContractModule::get_failed_contract_ids_from_storage(); - assert_eq!(failed_contract_ids.len(), 1); - assert_eq!(failed_contract_ids[0], 1); - - // add bob back to the allowed origin - assert_ok!(SmartContractModule::add_allowed_origin(RawOrigin::Root.into(), bob())); - pool_state.write().should_call_bill_contract(1, 12, Ok(())); - run_to_block(13, Some(&mut pool_state)); - - // failed contracts should be empty now - let failed_contract_ids = SmartContractModule::get_failed_contract_ids_from_storage(); - assert_eq!(failed_contract_ids.len(), 0); - }); -} - #[test] fn test_name_contract_billing() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); @@ -2518,12 +2472,6 @@ fn calculate_tft_cost(contract_id: u64, twin_id: u32, blocks: u64) -> (u64, type (amount_due, discount_received) } -pub fn add_allowed_origin() { - SmartContractModule::add_allowed_origin(RawOrigin::Root.into(), alice()).unwrap(); - SmartContractModule::add_allowed_origin(RawOrigin::Root.into(), bob()).unwrap(); - SmartContractModule::add_allowed_origin(RawOrigin::Root.into(), charlie()).unwrap(); -} - pub fn prepare_twins() { create_twin(alice()); create_twin(bob()); @@ -2629,12 +2577,8 @@ pub fn prepare_farm_and_node() { "some_serial".as_bytes().to_vec(), ) .unwrap(); - - add_allowed_origin(); } - - pub fn prepare_dedicated_farm_and_node() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); create_farming_policies(); @@ -2671,8 +2615,6 @@ pub fn prepare_dedicated_farm_and_node() { "some_serial".as_bytes().to_vec(), ) .unwrap(); - - add_allowed_origin(); } pub fn create_twin(origin: AccountId) { From 35b15246468190c6183ca916d6ad1b99d205f3b8 Mon Sep 17 00:00:00 2001 From: dylanVerstraete Date: Tue, 20 Sep 2022 16:08:54 +0200 Subject: [PATCH 24/87] chore: reinstate one line of code --- substrate-node/pallets/pallet-smart-contract/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 81727a2f9..18d56d844 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -982,7 +982,7 @@ impl Pallet { // remove all associated storage and continue let ctr = Contracts::::get(contract_id); if let Some(contract) = ctr { - if contract.contract_id != 0 && contract.is_state_delete() { + if contract.is_state_delete() { Self::remove_contract(contract.contract_id); return Ok(().into()); } @@ -1508,7 +1508,7 @@ impl Pallet { // Save the contract to be billed in now + BILLING_FREQUENCY_IN_BLOCKS let future_block = now + BillingFrequency::::get(); let mut contracts = ContractsToBillAt::::get(future_block); - if !contracts.contains(&contract_id) { + if !contracts.contains(&contract_id) { contracts.push(contract_id); ContractsToBillAt::::insert(future_block, &contracts); log::info!( From 2ce047a0de29389fdb29997ab7f12502a1b6f0e0 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 3 Oct 2022 12:02:39 +0200 Subject: [PATCH 25/87] feat: review comments #269 --- substrate-node/pallets/pallet-smart-contract/src/mock.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index ae690db0d..891f9fcb7 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -486,6 +486,5 @@ pub fn new_test_ext_with_pool_state( ext.register_extension(TransactionPoolExt::new(pool)); ext.register_extension(KeystoreExt(Arc::new(keystore))); - //(ext, pool_state) (ext, pool_state) } From b63dfac8af439dbcbf9809a95a5656fb5a1dd0eb Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 3 Oct 2022 14:17:35 +0200 Subject: [PATCH 26/87] feat: fixed issue due to rebase #269 --- .../pallets/pallet-smart-contract/src/lib.rs | 2 +- .../pallets/pallet-smart-contract/src/mock.rs | 1 + .../pallets/pallet-smart-contract/src/tests.rs | 13 ++++++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 18d56d844..0d3178fc9 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -998,7 +998,7 @@ impl Pallet { block_number: T::BlockNumber, contract_id: u64, ) -> Result<(), Error> { - let signer = Signer::::any_account(); + let signer = Signer::::AuthorityId>::any_account(); if !signer.can_sign() { log::error!( "failed billing contract {:?}: at block {:?} account cannot be used to sign transaction", diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 891f9fcb7..5e8b54817 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -4,6 +4,7 @@ use std::{panic, thread}; use super::*; use crate::name_contract::NameContractName; use crate::{self as pallet_smart_contract, types::BlockNumber}; +use codec::{alloc::sync::Arc, Decode}; use frame_support::{ construct_runtime, parameter_types, traits::{ConstU32, GenesisBuild}, diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 13ef4c05a..491b1031b 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -8,7 +8,7 @@ use frame_support::{ }; use frame_system::{EventRecord, Phase, RawOrigin}; use sp_core::H256; -use sp_runtime::{traits::SaturatedConversion, Perbill, Percent}; +use sp_runtime::{traits::SaturatedConversion, Perbill, Percent, assert_eq_error_rate}; use sp_std::convert::{TryFrom, TryInto}; use substrate_fixed::types::U64F64; @@ -1218,9 +1218,10 @@ fn test_node_contract_billing_fails() { #[test] fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balance_works() { - new_test_ext().execute_with(|| { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(1); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let twin = TfgridModule::twins(2).unwrap(); @@ -1246,16 +1247,18 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balanc pool_state .write() .should_call_bill_contract(contract_id, 11, Ok(())); + run_to_block(12, Some(&mut pool_state)); check_report_cost(1, amount_due_1, 12, discount_received); let (amount_due_2, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); pool_state .write() .should_call_bill_contract(contract_id, 21, Ok(())); + run_to_block(22, Some(&mut pool_state)); check_report_cost(1, amount_due_2, 22, discount_received); // Run halfway ish next cycle and cancel - run_to_block(25); + run_to_block(25, Some(&mut pool_state)); let usable_balance = Balances::usable_balance(&twin.account_id); let total_amount_billed = initial_twin_balance - usable_balance; @@ -1276,7 +1279,7 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balanc 1 )); - run_to_block(29); + run_to_block(29, Some(&mut pool_state)); // After canceling contract, and not being able to pay for the remainder of the cycle // where the cancel was excecuted, the remaining balance should still be the same From 668ef5fad1545bd1953840d623fc46c09846441d Mon Sep 17 00:00:00 2001 From: Dylan Verstraete Date: Wed, 12 Oct 2022 09:20:57 +0200 Subject: [PATCH 27/87] feat: rework contract billing storage (#477) --- .github/workflows/build_test.yaml | 6 + .../pallets/pallet-smart-contract/src/lib.rs | 226 ++++---- .../pallet-smart-contract/src/migration.rs | 91 ++++ .../pallets/pallet-smart-contract/src/mock.rs | 59 +- .../pallet-smart-contract/src/test_utils.rs | 9 +- .../pallet-smart-contract/src/tests.rs | 504 +++++++++++------- .../pallet-smart-contract/src/types.rs | 3 +- substrate-node/runtime/src/lib.rs | 2 +- substrate-node/tests/SubstrateNetwork.py | 4 +- 9 files changed, 524 insertions(+), 380 deletions(-) create mode 100644 substrate-node/pallets/pallet-smart-contract/src/migration.rs diff --git a/.github/workflows/build_test.yaml b/.github/workflows/build_test.yaml index 57a3deca1..7d73d336f 100644 --- a/.github/workflows/build_test.yaml +++ b/.github/workflows/build_test.yaml @@ -47,3 +47,9 @@ jobs: cd substrate-node/tests robot -d _output_tests/ . + - uses: actions/upload-artifact@v3 + if: always() + with: + name: integration test output + path: substrate-node/tests/_output_tests/ + diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 0d3178fc9..437bf7333 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -11,7 +11,6 @@ use frame_support::{ Currency, EnsureOrigin, ExistenceRequirement, ExistenceRequirement::KeepAlive, Get, LockableCurrency, OnUnbalanced, WithdrawReasons, }, - transactional, weights::Pays, BoundedVec, }; @@ -26,7 +25,6 @@ use pallet_tfgrid::types as pallet_tfgrid_types; use pallet_timestamp as timestamp; use sp_core::crypto::KeyTypeId; use sp_runtime::{ - offchain::storage::StorageValueRef, traits::{CheckedSub, SaturatedConversion}, Perbill, }; @@ -78,6 +76,7 @@ pub mod crypto { pub mod weights; pub mod cost; +pub mod migration; pub mod name_contract; pub mod types; @@ -199,7 +198,9 @@ pub mod pallet { pub type PalletVersion = StorageValue<_, types::StorageVersion, ValueQuery>; #[pallet::type_value] - pub fn DefaultBillingFrequency() -> u64 { T::BillingFrequency::get() } + pub fn DefaultBillingFrequency() -> u64 { + T::BillingFrequency::get() + } #[pallet::storage] #[pallet::getter(fn billing_frequency)] @@ -350,26 +351,26 @@ pub mod pallet { } #[pallet::genesis_config] - pub struct GenesisConfig { + pub struct GenesisConfig { pub billing_frequency: u64, - } + } // The default value for the genesis config type. - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { billing_frequency: 600, } - } - } + } + } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { BillingFrequency::::put(self.billing_frequency); - } - } + } + } #[pallet::call] impl Pallet { @@ -485,36 +486,27 @@ pub mod pallet { pub fn bill_contract_for_block( origin: OriginFor, contract_id: u64, - block_number: T::BlockNumber, ) -> DispatchResultWithPostInfo { let _account_id = ensure_signed(origin)?; - Self::_bill_contract_for_block(contract_id, block_number) + Self::_bill_contract(contract_id) } } #[pallet::hooks] impl Hooks> for Pallet { fn offchain_worker(block_number: T::BlockNumber) { - let current_block_u64: u64 = block_number.saturated_into::(); - let mut contracts = Self::get_failed_contract_ids_from_storage(); - if !contracts.is_empty() { - log::info!( - "trying to bill {:?} failed contracts from last block", - contracts - ); - } - //reset the failed contract ids for next run - Self::save_failed_contract_ids_in_storage(Vec::new()); - contracts.extend(ContractsToBillAt::::get(current_block_u64)); - // filter out the contracts that have been deleted in the meantime - contracts = contracts - .into_iter() - .filter(|contract_id| Contracts::::contains_key(contract_id)) - .collect::>(); - contracts.dedup(); + // Let offchain worker check if there are contracts on the map at current index + // Index being current block number % (mod) Billing Frequency + let current_index: u64 = + block_number.saturated_into::() % BillingFrequency::::get(); + let contracts = ContractsToBillAt::::get(current_index); if contracts.is_empty() { - log::debug!("No contracts to bill at block {:?}", block_number); + log::info!( + "No contracts to bill at block {:?}, index: {:?}", + block_number, + current_index + ); return; } @@ -525,10 +517,9 @@ pub mod pallet { ); for contract_id in contracts { - let _res = Self::bill_contract_using_signed_transaction(block_number, contract_id); + let _res = Self::bill_contract_using_signed_transaction(contract_id); } } - } } @@ -730,7 +721,7 @@ impl Pallet { // Start billing frequency loop // Will always be block now + frequency - Self::_reinsert_contract_to_bill(id); + Self::insert_contract_to_bill(id); // insert into contracts map Contracts::::insert(id, &contract); @@ -819,7 +810,8 @@ impl Pallet { Error::::NodeHasActiveContracts ); } - Self::_update_contract_state(&mut contract, &types::ContractState::Deleted(cause))?; + + contract.state = types::ContractState::Deleted(cause); Self::bill_contract(&mut contract)?; // Remove all associated storage Self::remove_contract(contract.contract_id); @@ -941,39 +933,35 @@ impl Pallet { ContractBillingInformationByID::::insert(report.contract_id, &contract_billing_info); } - pub fn _bill_contract_for_block( - contract_id: u64, - block: T::BlockNumber, - ) -> DispatchResultWithPostInfo { + pub fn _bill_contract(contract_id: u64) -> DispatchResultWithPostInfo { if !Contracts::::contains_key(contract_id) { + log::debug!("cleaning up deleted contract from storage"); + + let index = Self::get_contract_index(); + + // Remove contract from billing list + let mut contracts = ContractsToBillAt::::get(index); + contracts.retain(|&c| c != contract_id); + ContractsToBillAt::::insert(index, contracts); + return Ok(().into()); } let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - log::info!( - "billing contract with id {:?} at block {:?}", - contract_id, - block - ); + log::info!("billing contract with id {:?}", contract_id); + // Try to bill contract match Self::bill_contract(&mut contract) { Ok(_) => { - log::info!( - "successfully billed contract with id {:?} at block {:?}", - contract_id, - block - ); + log::info!("successfully billed contract with id {:?}", contract_id,); } Err(err) => { - // if billing failed append it to the failed ids in local storage - // billing will be retried at the next block log::error!( "error while billing contract with id {:?}: <{:?}>", contract_id, err.error ); - Self::append_failed_contract_ids_to_storage(contract_id); - return Ok(().into()); + return Err(err); } } @@ -986,31 +974,22 @@ impl Pallet { Self::remove_contract(contract.contract_id); return Ok(().into()); } - - // Reinsert into the next billing frequency - Self::_reinsert_contract_to_bill(contract.contract_id); } Ok(().into()) } - fn bill_contract_using_signed_transaction( - block_number: T::BlockNumber, - contract_id: u64, - ) -> Result<(), Error> { + fn bill_contract_using_signed_transaction(contract_id: u64) -> Result<(), Error> { let signer = Signer::::AuthorityId>::any_account(); if !signer.can_sign() { log::error!( - "failed billing contract {:?}: at block {:?} account cannot be used to sign transaction", + "failed billing contract {:?} account cannot be used to sign transaction", contract_id, - block_number ); return Err(>::OffchainSignedTxError); } - let result = signer.send_signed_transaction(|_acct| Call::bill_contract_for_block { - contract_id, - block_number, - }); + let result = + signer.send_signed_transaction(|_acct| Call::bill_contract_for_block { contract_id }); if let Some((acc, res)) = result { // if res is an error this means sending the transaction failed @@ -1019,9 +998,8 @@ impl Pallet { // returns Err()) if res.is_err() { log::error!( - "signed transaction failed for billing contract {:?} at block {:?} using account {:?}", + "signed transaction failed for billing contract {:?} using account {:?}", contract_id, - block_number, acc.id ); return Err(>::OffchainSignedTxError); @@ -1032,39 +1010,8 @@ impl Pallet { return Err(>::OffchainSignedTxError); } - fn append_failed_contract_ids_to_storage(failed_id: u64) { - log::info!( - "billing contract {:?} will be retried in the next block", - failed_id - ); - - let mut failed_ids = Self::get_failed_contract_ids_from_storage(); - failed_ids.push(failed_id); - - Self::save_failed_contract_ids_in_storage(failed_ids); - } - - fn save_failed_contract_ids_in_storage(failed_ids: Vec) { - let s_contracts = - StorageValueRef::persistent(b"pallet-smart-contract::failed-contracts-when-billing"); - - s_contracts.set(&failed_ids); - } - - fn get_failed_contract_ids_from_storage() -> Vec { - let s_contracts = - StorageValueRef::persistent(b"pallet-smart-contract::failed-contracts-when-billing"); - - if let Ok(Some(failed_contract_ids)) = s_contracts.get::>() { - return failed_contract_ids; - } - - return Vec::new(); - } - // Bills a contract (NodeContract or NameContract) // Calculates how much TFT is due by the user and distributes the rewards - #[transactional] fn bill_contract(contract: &mut types::Contract) -> DispatchResultWithPostInfo { let twin = pallet_tfgrid::Twins::::get(contract.twin_id).ok_or(Error::::TwinNotExists)?; @@ -1085,23 +1032,26 @@ impl Pallet { let (amount_due, discount_received) = contract.calculate_contract_cost_tft(usable_balance, seconds_elapsed)?; - // If there is nothing to be paid, return - if amount_due == BalanceOf::::saturated_from(0 as u128) { + // If there is nothing to be paid and the contract is not in state delete, return + // Can be that the users cancels the contract in the same block that it's getting billed + // where elapsed seconds would be 0, but we still have to distribute rewards + if amount_due == BalanceOf::::saturated_from(0 as u128) && !contract.is_state_delete() { log::debug!("amount to be billed is 0, nothing to do"); return Ok(().into()); }; // Handle grace - let was_grace = matches!(contract.state, types::ContractState::GracePeriod(_)); let contract = Self::handle_grace(contract, usable_balance, amount_due)?; - // Handle contract lock operations - // skip when the contract status was grace and changed to deleted because that means - // the grace period ended and there were still no funds - if !(was_grace && matches!(contract.state, types::ContractState::Deleted(_))) { - Self::handle_lock(contract, amount_due)?; + // If still in grace period, no need to continue doing locking and other stuff + if matches!(contract.state, types::ContractState::GracePeriod(_)) { + log::debug!("contract {} is still in grace", contract.contract_id); + return Ok(().into()); } + // Handle contract lock operations + Self::handle_lock(contract, amount_due)?; + // Always emit a contract billed event let contract_bill = types::ContractBill { contract_id: contract.contract_id, @@ -1244,11 +1194,6 @@ impl Pallet { ContractLock::::insert(contract.contract_id, &contract_lock); } - // Contract is in grace state, don't actually lock tokens or distribute rewards - if matches!(contract.state, types::ContractState::GracePeriod(_)) { - return Ok(().into()); - } - // Only lock an amount from the user's balance if the contract is in create state // The lock is specified on the user's account, since a user can have multiple contracts // Just extend the lock with the amount due for this contract billing period (lock will be created if not exists) @@ -1265,9 +1210,8 @@ impl Pallet { ); } - let is_canceled = matches!(contract.state, types::ContractState::Deleted(_)); let canceled_and_not_zero = - is_canceled && contract_lock.amount_locked.saturated_into::() > 0; + contract.is_state_delete() && contract_lock.amount_locked.saturated_into::() > 0; // When the cultivation rewards are ready to be distributed or it's in delete state // Unlock all reserved balance and distribute if contract_lock.cycles >= T::DistributionFrequency::get() || canceled_and_not_zero { @@ -1294,6 +1238,11 @@ impl Pallet { WithdrawReasons::all(), ); + // Fetch twin balance, if the amount locked in the contract lock exceeds the current unlocked + // balance we can only transfer out the remaining balance + // https://github.com/threefoldtech/tfchain/issues/479 + let twin_balance = Self::get_usable_balance(&twin.account_id); + // Fetch the default pricing policy let pricing_policy = pallet_tfgrid::PricingPolicies::::get(1) .ok_or(Error::::PricingPolicyNotExists)?; @@ -1301,7 +1250,7 @@ impl Pallet { match Self::_distribute_cultivation_rewards( &contract, &pricing_policy, - contract_lock.amount_locked, + twin_balance.min(contract_lock.amount_locked), ) { Ok(_) => {} Err(err) => { @@ -1430,7 +1379,7 @@ impl Pallet { .iter() .map(|provider| { let share = Perbill::from_percent(provider.take as u32) * amount; - frame_support::log::info!( + log::info!( "Transfering: {:?} from contract twin {:?} to provider account {:?}", &share, &twin.account_id, @@ -1458,9 +1407,9 @@ impl Pallet { let share = Perbill::from_percent(sales_share.into()) * amount; // Transfer the remaining share to the sales account // By default it is 50%, if a contract has solution providers it can be less - frame_support::log::info!( + log::info!( "Transfering: {:?} from contract twin {:?} to sales account {:?}", - &sales_share, + &share, &twin.account_id, &pricing_policy.certified_sales_account ); @@ -1489,7 +1438,14 @@ impl Pallet { WithdrawReasons::FEE, ExistenceRequirement::KeepAlive, )?; + + log::info!( + "Burning: {:?} from contract twin {:?}", + amount_to_burn, + &twin.account_id + ); T::Burn::on_unbalanced(to_burn); + Self::deposit_event(Event::TokensBurned { contract_id: contract.contract_id, amount: amount_to_burn, @@ -1498,23 +1454,24 @@ impl Pallet { Ok(().into()) } - // Reinserts a contract by id at the next interval we need to bill the contract - pub fn _reinsert_contract_to_bill(contract_id: u64) { + // Inserts a contract in a list where the index is the current block % billing frequency + // This way, we don't need to reinsert the contract everytime it gets billed + pub fn insert_contract_to_bill(contract_id: u64) { if contract_id == 0 { return; } - let now = >::block_number().saturated_into::(); - // Save the contract to be billed in now + BILLING_FREQUENCY_IN_BLOCKS - let future_block = now + BillingFrequency::::get(); - let mut contracts = ContractsToBillAt::::get(future_block); + // Save the contract to be billed in (now -1 %(mod) BILLING_FREQUENCY_IN_BLOCKS) + let index = Self::get_contract_index().checked_sub(1).unwrap_or(0); + let mut contracts = ContractsToBillAt::::get(index); + if !contracts.contains(&contract_id) { contracts.push(contract_id); - ContractsToBillAt::::insert(future_block, &contracts); + ContractsToBillAt::::insert(index, &contracts); log::info!( - "Insert contracts: {:?}, to be billed at block {:?}", + "Insert contracts: {:?}, to be billed at index {:?}", contracts, - future_block + index ); } } @@ -1793,6 +1750,11 @@ impl Pallet { } Ok(().into()) } + + pub fn get_contract_index() -> u64 { + let now = >::block_number().saturated_into::(); + now % BillingFrequency::::get() + } } impl ChangeNode, InterfaceOf> for Pallet { diff --git a/substrate-node/pallets/pallet-smart-contract/src/migration.rs b/substrate-node/pallets/pallet-smart-contract/src/migration.rs new file mode 100644 index 000000000..76250c0e0 --- /dev/null +++ b/substrate-node/pallets/pallet-smart-contract/src/migration.rs @@ -0,0 +1,91 @@ +use super::*; +use frame_support::weights::Weight; + +pub mod v5 { + use super::*; + use crate::Config; + + use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade}; + use sp_std::marker::PhantomData; + pub struct ContractMigrationV5(PhantomData); + + impl OnRuntimeUpgrade for ContractMigrationV5 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + info!("current pallet version: {:?}", PalletVersion::::get()); + assert!(PalletVersion::::get() == types::StorageVersion::V5); + + info!("👥 Smart Contract pallet to v6 passes PRE migrate checks ✅",); + Ok(()) + } + + fn on_runtime_upgrade() -> Weight { + migrate_to_version_6::() + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + info!("current pallet version: {:?}", PalletVersion::::get()); + assert!(PalletVersion::::get() == types::StorageVersion::V6); + + info!( + "👥 Smart Contract pallet to {:?} passes POST migrate checks ✅", + PalletVersion::::get() + ); + + Ok(()) + } + } +} + +pub fn migrate_to_version_6() -> frame_support::weights::Weight { + if PalletVersion::::get() == types::StorageVersion::V5 { + info!( + " >>> Starting contract pallet migration, pallet version: {:?}", + PalletVersion::::get() + ); + + let mut migrated_count = 0; + + // Collect ContractsToBillAt storage in memory + let contracts_to_bill_at = ContractsToBillAt::::iter().collect::>(); + + // Remove all items under ContractsToBillAt + frame_support::migration::remove_storage_prefix( + b"SmartContractModule", + b"ContractsToBillAt", + b"", + ); + + let billing_freq = 600; + BillingFrequency::::put(billing_freq); + + for (block_number, contract_ids) in contracts_to_bill_at { + migrated_count += 1; + // Construct new index + let index = (block_number - 1) % billing_freq; + // Reinsert items under the new key + info!( + "inserted contracts:{:?} at index: {:?}", + contract_ids.clone(), + index + ); + ContractsToBillAt::::insert(index, contract_ids); + } + + info!( + " <<< Contracts storage updated! Migrated {} Contracts ✅", + migrated_count + ); + + // Update pallet storage version + PalletVersion::::set(types::StorageVersion::V6); + info!(" <<< Storage version upgraded"); + + // Return the weight consumed by the migration. + T::DbWeight::get().reads_writes(migrated_count as Weight + 1, migrated_count as Weight + 1) + } else { + info!(" >>> Unused migration"); + return 0; + } +} diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 5e8b54817..d767097ae 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -3,11 +3,12 @@ use std::{panic, thread}; use super::*; use crate::name_contract::NameContractName; -use crate::{self as pallet_smart_contract, types::BlockNumber}; +use crate::{self as pallet_smart_contract}; use codec::{alloc::sync::Arc, Decode}; use frame_support::{ construct_runtime, parameter_types, traits::{ConstU32, GenesisBuild}, + weights::PostDispatchInfo, }; use frame_system::EnsureRoot; use pallet_tfgrid::{ @@ -333,12 +334,13 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t } pub type TransactionCall = pallet_smart_contract::Call; +pub type ExtrinsicResult = Result; #[derive(Default)] pub struct PoolState { /// A vector of calls that we expect should be executed - pub expected_calls: Vec<(TransactionCall, Result<(), ()>)>, - pub calls_to_execute: Vec<(TransactionCall, Result<(), ()>)>, + pub expected_calls: Vec<(TransactionCall, ExtrinsicResult, u64)>, + pub calls_to_execute: Vec<(TransactionCall, ExtrinsicResult, u64)>, pub i: usize, } @@ -346,57 +348,43 @@ impl PoolState { pub fn should_call_bill_contract( &mut self, contract_id: u64, - block_number: BlockNumber, - expected_result: Result<(), ()>, + expected_result: ExtrinsicResult, + block_number: u64, ) { self.expected_calls.push(( - crate::Call::bill_contract_for_block { - contract_id, - block_number, - }, + crate::Call::bill_contract_for_block { contract_id }, expected_result, + block_number, )); } - pub fn should_call(&mut self, expected_call: TransactionCall, expected_result: Result<(), ()>) { - self.expected_calls.push((expected_call, expected_result)); - } - - pub fn execute_calls_and_check_results(&mut self) { + pub fn execute_calls_and_check_results(&mut self, block_number: u64) { if self.calls_to_execute.len() == 0 { return; } - + // execute the calls that were submitted to the pool and compare the result for call_to_execute in self.calls_to_execute.iter() { let result = match call_to_execute.0 { // matches bill_contract_for_block - crate::Call::bill_contract_for_block { - contract_id, - block_number, - } => SmartContractModule::bill_contract_for_block( - Origin::signed(bob()), - contract_id, - block_number, - ), + crate::Call::bill_contract_for_block { contract_id } => { + SmartContractModule::bill_contract_for_block(Origin::signed(bob()), contract_id) + } // did not match anything => unkown call => this means you should add // a capture for that function here _ => panic!("Unknown call!"), }; - - let result = match result { - Ok(_) => Ok(()), - Err(_) => Err(()), - }; - + // the call should return what we expect it to return assert_eq!( call_to_execute.1, result, "The result of call to {:?} was not as expected!", call_to_execute.0 ); + + assert_eq!(block_number, call_to_execute.2); } - + self.calls_to_execute.clear(); } } @@ -411,10 +399,10 @@ impl Drop for PoolState { } /// Implementation of mocked transaction pool used for testing -/// -/// This transaction pool mocks submitting the transactions to the pool. It does +/// +/// This transaction pool mocks submitting the transactions to the pool. It does /// not execute the transactions. Instead it keeps them in list. It does compare -/// the submitted call to the expected call. +/// the submitted call to the expected call. #[derive(Default)] pub struct MockedTransactionPoolExt(Arc>); @@ -456,7 +444,10 @@ impl TransactionPool for MockedTransactionPoolExt { self.0.write().i = i + 1; // return the expected return value - return self.0.read().expected_calls[i].1; + return self.0.read().expected_calls[i] + .1 + .map_err(|_| ()) + .map(|_| ()); } // we should not end here as it would mean we did not expect any more calls diff --git a/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs b/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs index add898f47..55e4bcf7e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/test_utils.rs @@ -1,6 +1,5 @@ #![cfg(test)] - use crate::mock::{PoolState, SmartContractModule, System, Timestamp}; use codec::alloc::sync::Arc; @@ -9,12 +8,16 @@ use parking_lot::RwLock; pub fn run_to_block(n: u64, pool_state: Option<&mut Arc>>) { Timestamp::set_timestamp((1628082000 * 1000) + (6000 * n)); - while System::block_number() < n { + while System::block_number() <= n { SmartContractModule::offchain_worker(System::block_number()); SmartContractModule::on_finalize(System::block_number()); System::on_finalize(System::block_number()); if pool_state.is_some() { - pool_state.as_ref().unwrap().write().execute_calls_and_check_results(); + pool_state + .as_ref() + .unwrap() + .write() + .execute_calls_and_check_results(System::block_number() as u64); } System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 491b1031b..60971d367 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -4,17 +4,19 @@ use crate::{mock::Event as MockEvent, mock::*, test_utils::*, Error}; use frame_support::{ assert_noop, assert_ok, bounded_vec, traits::{LockableCurrency, WithdrawReasons}, + weights::Pays, BoundedVec, }; use frame_system::{EventRecord, Phase, RawOrigin}; use sp_core::H256; -use sp_runtime::{traits::SaturatedConversion, Perbill, Percent, assert_eq_error_rate}; +use sp_runtime::{assert_eq_error_rate, traits::SaturatedConversion, Perbill, Percent}; use sp_std::convert::{TryFrom, TryInto}; use substrate_fixed::types::U64F64; use crate::cost; +use log::info; use pallet_tfgrid::types as pallet_tfgrid_types; -use tfchain_support::{types::{FarmCertification, Location, NodeCertification, PublicIP, Resources}}; +use tfchain_support::types::{FarmCertification, Location, NodeCertification, PublicIP, Resources}; const GIGABYTE: u64 = 1024 * 1024 * 1024; @@ -24,6 +26,7 @@ const GIGABYTE: u64 = 1024 * 1024 * 1024; #[test] fn test_create_node_contract_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_node_contract( @@ -40,6 +43,7 @@ fn test_create_node_contract_works() { #[test] fn test_create_node_contract_with_public_ips_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_node_contract( @@ -76,6 +80,7 @@ fn test_create_node_contract_with_public_ips_works() { #[test] fn test_create_node_contract_with_undefined_node_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_noop!( @@ -95,6 +100,7 @@ fn test_create_node_contract_with_undefined_node_fails() { #[test] fn test_create_node_contract_with_same_hash_and_node_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); let h = generate_deployment_hash(); @@ -124,6 +130,7 @@ fn test_create_node_contract_with_same_hash_and_node_fails() { #[test] fn test_create_node_contract_which_was_canceled_before_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); let h = generate_deployment_hash(); @@ -160,6 +167,7 @@ fn test_create_node_contract_which_was_canceled_before_works() { #[test] fn test_update_node_contract_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_node_contract( @@ -214,6 +222,7 @@ fn test_update_node_contract_works() { #[test] fn test_update_node_contract_not_exists_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_noop!( @@ -231,6 +240,7 @@ fn test_update_node_contract_not_exists_fails() { #[test] fn test_update_node_contract_wrong_twins_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_node_contract( @@ -257,6 +267,7 @@ fn test_update_node_contract_wrong_twins_fails() { #[test] fn test_cancel_node_contract_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_node_contract( @@ -284,6 +295,7 @@ fn test_cancel_node_contract_works() { #[test] fn test_create_multiple_node_contracts_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_node_contract( @@ -330,6 +342,7 @@ fn test_create_multiple_node_contracts_works() { #[test] fn test_cancel_node_contract_frees_public_ips_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_node_contract( @@ -369,6 +382,7 @@ fn test_cancel_node_contract_not_exists_fails() { #[test] fn test_cancel_node_contract_wrong_twins_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_node_contract( @@ -393,6 +407,7 @@ fn test_cancel_node_contract_wrong_twins_fails() { #[test] fn test_create_name_contract_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_name_contract( @@ -405,6 +420,7 @@ fn test_create_name_contract_works() { #[test] fn test_cancel_name_contract_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_name_contract( @@ -430,6 +446,7 @@ fn test_cancel_name_contract_works() { #[test] fn test_create_name_contract_double_with_same_name_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_name_contract( @@ -449,6 +466,7 @@ fn test_create_name_contract_double_with_same_name_fails() { #[test] fn test_recreate_name_contract_after_cancel_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_name_contract( @@ -471,6 +489,7 @@ fn test_recreate_name_contract_after_cancel_works() { #[test] fn test_create_name_contract_with_invalid_dns_name_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_noop!( @@ -513,6 +532,7 @@ fn test_create_name_contract_with_invalid_dns_name_fails() { #[test] fn test_create_rent_contract_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_dedicated_farm_and_node(); let node_id = 1; @@ -534,6 +554,7 @@ fn test_create_rent_contract_works() { #[test] fn test_cancel_rent_contract_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_dedicated_farm_and_node(); let node_id = 1; @@ -563,6 +584,7 @@ fn test_cancel_rent_contract_works() { #[test] fn test_create_rent_contract_on_node_in_use_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); assert_ok!(SmartContractModule::create_node_contract( @@ -584,6 +606,7 @@ fn test_create_rent_contract_on_node_in_use_fails() { #[test] fn test_create_rent_contract_non_dedicated_empty_node_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); let node_id = 1; @@ -598,6 +621,7 @@ fn test_create_rent_contract_non_dedicated_empty_node_works() { #[test] fn test_create_node_contract_on_dedicated_node_without_rent_contract_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_dedicated_farm_and_node(); assert_noop!( @@ -617,6 +641,7 @@ fn test_create_node_contract_on_dedicated_node_without_rent_contract_fails() { #[test] fn test_create_node_contract_when_having_a_rentcontract_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_dedicated_farm_and_node(); assert_ok!(SmartContractModule::create_rent_contract( @@ -639,6 +664,7 @@ fn test_create_node_contract_when_having_a_rentcontract_works() { #[test] fn test_create_node_contract_when_someone_else_has_rent_contract_fails() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_dedicated_farm_and_node(); // create rent contract with bob @@ -703,7 +729,7 @@ fn test_node_contract_billing_details() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); - run_to_block(0, Some(&mut pool_state)); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let twin = TfgridModule::twins(2).unwrap(); @@ -722,7 +748,7 @@ fn test_node_contract_billing_details() { push_nru_report_for_contract(1, 10); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(10); + let contract_to_bill = SmartContractModule::contract_to_bill_at_block(1); assert_eq!(contract_to_bill, [1]); let initial_total_issuance = Balances::total_issuance(); @@ -730,18 +756,18 @@ fn test_node_contract_billing_details() { for i in 0..25 { pool_state .write() - .should_call_bill_contract(1, 10 + i * 10, Ok(())); + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); run_to_block(11 + i * 10, Some(&mut pool_state)); } let free_balance = Balances::free_balance(&twin.account_id); let total_amount_billed = initial_twin_balance - free_balance; - println!("locked balance {:?}", total_amount_billed); + info!("locked balance {:?}", total_amount_billed); - println!("total locked balance {:?}", total_amount_billed); + info!("total locked balance {:?}", total_amount_billed); let staking_pool_account_balance = Balances::free_balance(&get_staking_pool_account()); - println!( + info!( "staking pool account balance, {:?}", staking_pool_account_balance ); @@ -770,9 +796,10 @@ fn test_node_contract_billing_details() { let total_issuance = Balances::total_issuance(); // total issueance is now previous total - amount burned from contract billed (35%) let burned_amount = Perbill::from_percent(35) * total_amount_billed; - assert_eq!( + assert_eq_error_rate!( total_issuance, - initial_total_issuance - burned_amount as u64 + initial_total_issuance - burned_amount as u64, + 1 ); // amount unbilled should have been reset after a transfer between contract owner and farmer @@ -789,7 +816,7 @@ fn test_node_contract_billing_details_with_solution_provider() { prepare_solution_provider(); - run_to_block(0, Some(&mut pool_state)); + run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let twin = TfgridModule::twins(2).unwrap(); @@ -809,14 +836,14 @@ fn test_node_contract_billing_details_with_solution_provider() { push_nru_report_for_contract(1, 10); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(10); + let contract_to_bill = SmartContractModule::contract_to_bill_at_block(1); assert_eq!(contract_to_bill, [1]); // advance 25 cycles for i in 0..25 { pool_state .write() - .should_call_bill_contract(1, 10 + i * 10, Ok(())); + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); run_to_block(11 + i * 10, Some(&mut pool_state)); } @@ -852,13 +879,17 @@ fn test_multiple_contracts_billing_loop_works() { "some_name".as_bytes().to_vec(), )); - let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); + let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(1); assert_eq!(contracts_to_bill_at_block.len(), 2); // 2 contracts => 2 billings - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - pool_state.write().should_call_bill_contract(2, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); // Test that the expected events were emitted let our_events = System::events(); @@ -892,10 +923,12 @@ fn test_node_contract_billing_cycles() { push_contract_resources_used(1); - let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); - check_report_cost(1, amount_due_1, 12, discount_received); + let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); + check_report_cost(1, amount_due_1, 11, discount_received); let twin = TfgridModule::twins(twin_id).unwrap(); let usable_balance = Balances::usable_balance(&twin.account_id); @@ -908,14 +941,18 @@ fn test_node_contract_billing_cycles() { ); let (amount_due_2, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - pool_state.write().should_call_bill_contract(1, 21, Ok(())); - run_to_block(22, Some(&mut pool_state)); - check_report_cost(1, amount_due_2, 22, discount_received); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21); + run_to_block(21, Some(&mut pool_state)); + check_report_cost(1, amount_due_2, 21, discount_received); let (amount_due_3, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - pool_state.write().should_call_bill_contract(1, 31, Ok(())); - run_to_block(32, Some(&mut pool_state)); - check_report_cost(1, amount_due_3, 32, discount_received); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 31); + run_to_block(31, Some(&mut pool_state)); + check_report_cost(1, amount_due_3, 31, discount_received); let twin = TfgridModule::twins(twin_id).unwrap(); let usable_balance = Balances::usable_balance(&twin.account_id); @@ -955,8 +992,12 @@ fn test_node_multiple_contract_billing_cycles() { )); let twin_id = 2; - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - pool_state.write().should_call_bill_contract(2, 11, Ok(())); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); push_contract_resources_used(1); push_contract_resources_used(2); @@ -1002,43 +1043,43 @@ fn test_node_contract_billing_cycles_delete_node_cancels_contract() { for i in 0..5 { pool_state .write() - .should_call_bill_contract(1, 11 + 10 * i, Ok(())); + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); } push_contract_resources_used(1); - let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - run_to_block(12, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 12, discount_received); + let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); + run_to_block(11, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 11, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(22, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 22, discount_received); + run_to_block(21, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 21, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(32, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 32, discount_received); + run_to_block(31, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 31, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(42, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 42, discount_received); + run_to_block(41, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 41, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(52, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 52, discount_received); + run_to_block(51, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 51, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 4); - run_to_block(56, Some(&mut pool_state)); + run_to_block(55, None); // Delete node TfgridModule::delete_node_farm(Origin::signed(alice()), 1).unwrap(); // After deleting a node, the contract gets billed before it's canceled - check_report_cost(1, amount_due_as_u128, 56, discount_received); + check_report_cost(1, amount_due_as_u128, 55, discount_received); let our_events = System::events(); for e in our_events.clone().iter() { - println!("{:?}", e); + info!("{:?}", e); } let public_ip = PublicIP { @@ -1095,31 +1136,33 @@ fn test_node_contract_only_public_ip_billing_cycles() { let twin_id = 2; for i in 0..5 { - pool_state - .write() - .should_call_bill_contract(contract_id, 11 + i * 10, Ok(())); + pool_state.write().should_call_bill_contract( + contract_id, + Ok(Pays::Yes.into()), + 11 + i * 10, + ); } - let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); + let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); assert_ne!(amount_due_as_u128, 0); - run_to_block(12, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 12, discount_received); + run_to_block(11, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 11, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(22, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 22, discount_received); + run_to_block(21, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 21, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(32, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 32, discount_received); + run_to_block(31, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 31, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(42, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 42, discount_received); + run_to_block(41, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 41, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(52, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 52, discount_received); + run_to_block(51, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 51, discount_received); }); } @@ -1145,22 +1188,24 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { // 2 cycles for billing for i in 0..2 { - pool_state - .write() - .should_call_bill_contract(contract_id, 11 + 10 * i, Ok(())); + pool_state.write().should_call_bill_contract( + contract_id, + Ok(Pays::Yes.into()), + 11 + i * 10, + ); } push_contract_resources_used(1); - let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); - run_to_block(12, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 12, discount_received); + let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); + run_to_block(11, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 11, discount_received); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); - run_to_block(22, Some(&mut pool_state)); - check_report_cost(1, amount_due_as_u128, 22, discount_received); + run_to_block(21, Some(&mut pool_state)); + check_report_cost(1, amount_due_as_u128, 21, discount_received); run_to_block(28, Some(&mut pool_state)); - let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 6); + let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 7); assert_ok!(SmartContractModule::cancel_contract( Origin::signed(bob()), 1 @@ -1181,9 +1226,9 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { fn test_node_contract_billing_fails() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { + run_to_block(1, Some(&mut pool_state)); // Creates a farm and node and sets the price of tft to 0 which raises an error later prepare_farm_and_node(); - run_to_block(1, Some(&mut pool_state)); assert_ok!(SmartContractModule::create_node_contract( Origin::signed(bob()), @@ -1194,7 +1239,7 @@ fn test_node_contract_billing_fails() { None )); - let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(11); + let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(1); assert_eq!(contracts_to_bill_at_block.len(), 1); let contract_id = contracts_to_bill_at_block[0]; @@ -1207,11 +1252,13 @@ fn test_node_contract_billing_fails() { // the offchain worker should save the failed ids in local storage and try again // in subsequent blocks (which will also fail) - for i in 0..3 { - pool_state - .write() - .should_call_bill_contract(1, 11 + i, Ok(())); - run_to_block(12 + i, Some(&mut pool_state)); + for i in 1..3 { + pool_state.write().should_call_bill_contract( + 1, + Err(Error::::TwinNotExists.into()), + 1 + i * 10, + ); + run_to_block(11 * i, Some(&mut pool_state)); } }); } @@ -1226,7 +1273,7 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balanc let twin = TfgridModule::twins(2).unwrap(); let initial_twin_balance = Balances::free_balance(&twin.account_id); - println!("initial twin balance: {:?}", initial_twin_balance); + info!("initial twin balance: {:?}", initial_twin_balance); let initial_total_issuance = Balances::total_issuance(); assert_ok!(SmartContractModule::create_node_contract( @@ -1243,19 +1290,19 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balanc push_contract_resources_used(1); - let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 11); + let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); pool_state .write() - .should_call_bill_contract(contract_id, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); - check_report_cost(1, amount_due_1, 12, discount_received); + .should_call_bill_contract(contract_id, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); + check_report_cost(1, amount_due_1, 11, discount_received); let (amount_due_2, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); pool_state .write() - .should_call_bill_contract(contract_id, 21, Ok(())); - run_to_block(22, Some(&mut pool_state)); - check_report_cost(1, amount_due_2, 22, discount_received); + .should_call_bill_contract(contract_id, Ok(Pays::Yes.into()), 21); + run_to_block(21, Some(&mut pool_state)); + check_report_cost(1, amount_due_2, 21, discount_received); // Run halfway ish next cycle and cancel run_to_block(25, Some(&mut pool_state)); @@ -1279,7 +1326,10 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balanc 1 )); - run_to_block(29, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(contract_id, Ok(Pays::Yes.into()), 31); + run_to_block(31, Some(&mut pool_state)); // After canceling contract, and not being able to pay for the remainder of the cycle // where the cancel was excecuted, the remaining balance should still be the same @@ -1313,13 +1363,17 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { push_contract_resources_used(1); // cycle 1 - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); // cycle 2 // user does not have enough funds to pay for 2 cycles - pool_state.write().should_call_bill_contract(1, 21, Ok(())); - run_to_block(22, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21); + run_to_block(21, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); @@ -1359,16 +1413,16 @@ fn test_restore_node_contract_in_grace_works() { for i in 0..6 { pool_state .write() - .should_call_bill_contract(1, 11 + i * 10, Ok(())); + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); } push_contract_resources_used(1); // cycle 1 - run_to_block(12, Some(&mut pool_state)); + run_to_block(11, Some(&mut pool_state)); // cycle 2 // user does not have enough funds to pay for 2 cycles - run_to_block(22, Some(&mut pool_state)); + run_to_block(21, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); @@ -1386,23 +1440,11 @@ fn test_restore_node_contract_in_grace_works() { true ); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(31); - assert_eq!(contract_to_bill.len(), 1); - run_to_block(32, Some(&mut pool_state)); - - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(41); - assert_eq!(contract_to_bill.len(), 1); - run_to_block(42, Some(&mut pool_state)); - + run_to_block(31, Some(&mut pool_state)); + run_to_block(41, Some(&mut pool_state)); // Transfer some balance to the owner of the contract to trigger the grace period to stop Balances::transfer(Origin::signed(bob()), charlie(), 100000000).unwrap(); - - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(51); - assert_eq!(contract_to_bill.len(), 1); run_to_block(52, Some(&mut pool_state)); - - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(61); - assert_eq!(contract_to_bill.len(), 1); run_to_block(62, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); @@ -1430,13 +1472,17 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works push_contract_resources_used(1); // cycle 1 - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); // cycle 2 // user does not have enough funds to pay for 2 cycles - pool_state.write().should_call_bill_contract(1, 21, Ok(())); - run_to_block(22, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21); + run_to_block(21, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); @@ -1455,15 +1501,21 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works ); // grace period stops after 100 blocknumbers, so after 121 - for i in 0..10 { + for i in 1..10 { pool_state .write() - .should_call_bill_contract(1, 31 + i * 10, Ok(())); + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21 + i * 10); } - for i in 0..11 { - run_to_block(32 + i * 10, Some(&mut pool_state)); + + for i in 1..10 { + run_to_block(21 + i * 10, Some(&mut pool_state)); } + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 121); + run_to_block(121, Some(&mut pool_state)); + let c1 = SmartContractModule::contracts(1); assert_eq!(c1, None); }); @@ -1482,23 +1534,25 @@ fn test_name_contract_billing() { "foobar".as_bytes().to_vec() )); - let contracts_to_bill = SmartContractModule::contract_to_bill_at_block(11); + let contracts_to_bill = SmartContractModule::contract_to_bill_at_block(1); assert_eq!(contracts_to_bill, [1]); // let mature 11 blocks // because we bill every 10 blocks - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); // the contractbill event should look like: let contract_bill_event = types::ContractBill { contract_id: 1, - timestamp: 1628082072, + timestamp: 1628082066, discount_level: types::DiscountLevel::Gold, - amount_billed: 2032, + amount_billed: 1848, }; let our_events = System::events(); - println!("events: {:?}", our_events.clone()); + info!("events: {:?}", our_events.clone()); assert_eq!( our_events[3], record(MockEvent::SmartContractModule(SmartContractEvent::< @@ -1532,12 +1586,14 @@ fn test_rent_contract_billing() { types::ContractData::RentContract(rent_contract) ); - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); - let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); + let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 10); assert_ne!(amount_due_as_u128, 0); - check_report_cost(1, amount_due_as_u128, 12, discount_received); + check_report_cost(1, amount_due_as_u128, 11, discount_received); }); } @@ -1563,19 +1619,21 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { types::ContractData::RentContract(rent_contract) ); - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); - let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); + let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 10); assert_ne!(amount_due_as_u128, 0); - check_report_cost(1, amount_due_as_u128, 12, discount_received.clone()); + check_report_cost(1, amount_due_as_u128, 11, discount_received.clone()); let twin = TfgridModule::twins(2).unwrap(); let usable_balance = Balances::usable_balance(&twin.account_id); let free_balance = Balances::free_balance(&twin.account_id); assert_ne!(usable_balance, free_balance); - run_to_block(14, Some(&mut pool_state)); + run_to_block(13, Some(&mut pool_state)); // cancel contract // it will bill before removing the contract and it should bill all // reserverd balance @@ -1593,11 +1651,15 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { // we do not call bill contract here as the contract is removed during // cancel_contract. The contract id will still be in ContractsToBillAt // but the contract itself will no longer exist + // But the + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21); run_to_block(22, Some(&mut pool_state)); // Last amount due is the same as the first one assert_ne!(amount_due_as_u128, 0); - check_report_cost(1, amount_due_as_u128, 14, discount_received); + check_report_cost(1, amount_due_as_u128, 13, discount_received); let usable_balance = Balances::usable_balance(&twin.account_id); let free_balance = Balances::free_balance(&twin.account_id); @@ -1632,7 +1694,7 @@ fn test_rent_contract_canceled_mid_cycle_should_bill_for_remainder() { let free_balance = Balances::free_balance(&twin.account_id); let locked_balance = free_balance - usable_balance; - println!("locked balance: {:?}", locked_balance); + info!("locked balance: {:?}", locked_balance); run_to_block(8, Some(&mut pool_state)); // Calculate the cost for 7 blocks of runtime (created a block 1, canceled at block 8) @@ -1677,13 +1739,17 @@ fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billi None )); push_contract_resources_used(2); - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - pool_state.write().should_call_bill_contract(2, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); - let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); + let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 10); assert_ne!(amount_due_as_u128, 0); - check_report_cost(1, amount_due_as_u128, 12, discount_received); + check_report_cost(1, amount_due_as_u128, 11, discount_received); let our_events = System::events(); // Event 1: Rent contract created @@ -1723,12 +1789,12 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ for i in 0..11 { pool_state .write() - .should_call_bill_contract(1, 11 + i * 10, Ok(())); + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); pool_state .write() - .should_call_bill_contract(2, 11 + i * 10, Ok(())); + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11 + i * 10); } - for i in 0..12 { + for i in 0..11 { run_to_block(12 + 10 * i, Some(&mut pool_state)); } @@ -1746,7 +1812,11 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ // Event 18: Node contract canceled // Event 19: Rent contract Canceled // => no Node Contract billed event - assert_eq!(our_events.len(), 20); + assert_eq!(our_events.len(), 10); + + for e in our_events.clone().iter() { + info!("event: {:?}", e); + } assert_eq!( our_events[5], @@ -1772,7 +1842,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ ); assert_eq!( - our_events[18], + our_events[8], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::NodeContractCanceled { @@ -1782,7 +1852,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ })) ); assert_eq!( - our_events[19], + our_events[9], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::RentContractCanceled { @@ -1817,18 +1887,22 @@ fn test_create_rent_contract_and_node_contract_with_ip_billing_works() { )); // 2 contracts => we expect 2 calls to bill_contract - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - pool_state.write().should_call_bill_contract(2, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); // check contract 1 costs (Rent Contract) - let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 11); + let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 10); assert_ne!(amount_due_as_u128, 0); - check_report_cost(1, amount_due_as_u128, 12, discount_received); + check_report_cost(1, amount_due_as_u128, 11, discount_received); // check contract 2 costs (Node Contract) - let (amount_due_as_u128, discount_received) = calculate_tft_cost(2, 2, 11); + let (amount_due_as_u128, discount_received) = calculate_tft_cost(2, 2, 10); assert_ne!(amount_due_as_u128, 0); - check_report_cost(2, amount_due_as_u128, 12, discount_received); + check_report_cost(2, amount_due_as_u128, 11, discount_received); let our_events = System::events(); // Event 1: Price Stored @@ -1858,8 +1932,10 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { // cycle 1 // user does not have enough funds to pay for 1 cycle - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1895,8 +1971,10 @@ fn test_restore_rent_contract_in_grace_works() { )); // cycle 1 - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1914,28 +1992,28 @@ fn test_restore_rent_contract_in_grace_works() { })) ); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(21); - assert_eq!(contract_to_bill.len(), 1); - pool_state.write().should_call_bill_contract(1, 21, Ok(())); - run_to_block(22, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21); + run_to_block(21, Some(&mut pool_state)); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(31); - assert_eq!(contract_to_bill.len(), 1); - pool_state.write().should_call_bill_contract(1, 31, Ok(())); - run_to_block(32, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 31); + run_to_block(31, Some(&mut pool_state)); // Transfer some balance to the owner of the contract to trigger the grace period to stop Balances::transfer(Origin::signed(bob()), charlie(), 100000000).unwrap(); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(41); - assert_eq!(contract_to_bill.len(), 1); - pool_state.write().should_call_bill_contract(1, 41, Ok(())); - run_to_block(42, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 41); + run_to_block(41, Some(&mut pool_state)); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(51); - assert_eq!(contract_to_bill.len(), 1); - pool_state.write().should_call_bill_contract(1, 51, Ok(())); - run_to_block(52, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 51); + run_to_block(51, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::Created); @@ -1967,9 +2045,13 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { push_contract_resources_used(2); // cycle 1 - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - pool_state.write().should_call_bill_contract(2, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -1998,39 +2080,48 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { })) ); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(21); - assert_eq!(contract_to_bill.len(), 2); - pool_state.write().should_call_bill_contract(1, 21, Ok(())); - pool_state.write().should_call_bill_contract(2, 21, Ok(())); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 21); run_to_block(22, Some(&mut pool_state)); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(31); - assert_eq!(contract_to_bill.len(), 2); - pool_state.write().should_call_bill_contract(1, 31, Ok(())); - pool_state.write().should_call_bill_contract(2, 31, Ok(())); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 31); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 31); run_to_block(32, Some(&mut pool_state)); // Transfer some balance to the owner of the contract to trigger the grace period to stop Balances::transfer(Origin::signed(bob()), charlie(), 100000000).unwrap(); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(41); - assert_eq!(contract_to_bill.len(), 2); - pool_state.write().should_call_bill_contract(1, 41, Ok(())); - pool_state.write().should_call_bill_contract(2, 41, Ok(())); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 41); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 41); run_to_block(42, Some(&mut pool_state)); - let contract_to_bill = SmartContractModule::contract_to_bill_at_block(51); - assert_eq!(contract_to_bill.len(), 2); - pool_state.write().should_call_bill_contract(1, 51, Ok(())); - pool_state.write().should_call_bill_contract(2, 51, Ok(())); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 51); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 51); run_to_block(52, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::Created); let our_events = System::events(); + assert_eq!( - our_events[11], + our_events[8], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodEnded { @@ -2040,7 +2131,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { })) ); assert_eq!( - our_events[12], + our_events[9], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodEnded { @@ -2068,8 +2159,10 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works )); // cycle 1 - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(11)); @@ -2089,13 +2182,13 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works // run 12 cycles, after 10 cycles grace period has finished so no more // billing! - for i in 0..10 { + for i in 0..11 { pool_state .write() - .should_call_bill_contract(1, 21 + i * 10, Ok(())); + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21 + i * 10); } for i in 0..12 { - run_to_block(22 + i * 10, Some(&mut pool_state)); + run_to_block(21 + i * 10, Some(&mut pool_state)); } let c1 = SmartContractModule::contracts(1); @@ -2129,9 +2222,13 @@ fn test_rent_contract_and_node_contract_canceled_when_node_is_deleted_works() { push_contract_resources_used(2); // 2 contracts => 2 calls to bill_contract - pool_state.write().should_call_bill_contract(1, 11, Ok(())); - pool_state.write().should_call_bill_contract(2, 11, Ok(())); - run_to_block(12, Some(&mut pool_state)); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + pool_state + .write() + .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); run_to_block(16, Some(&mut pool_state)); @@ -2217,6 +2314,7 @@ fn test_create_solution_provider_fails_if_take_to_high() { #[test] fn test_create_node_contract_with_solution_provider_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_farm_and_node(); prepare_solution_provider(); @@ -2342,10 +2440,10 @@ fn validate_distribution_rewards( total_amount_billed: u64, had_solution_provider: bool, ) { - println!("total locked balance {:?}", total_amount_billed); + info!("total locked balance {:?}", total_amount_billed); let staking_pool_account_balance = Balances::free_balance(&get_staking_pool_account()); - println!( + info!( "staking pool account balance, {:?}", staking_pool_account_balance ); @@ -2376,7 +2474,7 @@ fn validate_distribution_rewards( let solution_provider = SmartContractModule::solution_providers(1).unwrap(); let solution_provider_1_balance = Balances::free_balance(solution_provider.providers[0].who.clone()); - println!("solution provider b: {:?}", solution_provider_1_balance); + info!("solution provider b: {:?}", solution_provider_1_balance); assert_eq!( solution_provider_1_balance, Perbill::from_percent(10) * total_amount_billed @@ -2455,14 +2553,6 @@ fn check_report_cost( ))), true ); - // assert_eq!( - // our_events[index], - // record(MockEvent::SmartContractModule(SmartContractEvent::< - // TestRuntime, - // >::ContractBilled( - // contract_bill_event - // ))) - // ); } fn calculate_tft_cost(contract_id: u64, twin_id: u32, blocks: u64) -> (u64, types::DiscountLevel) { diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index 59281177b..d53a03838 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -19,11 +19,12 @@ pub enum StorageVersion { V3, V4, V5, + V6, } impl Default for StorageVersion { fn default() -> StorageVersion { - StorageVersion::V3 + StorageVersion::V5 } } diff --git a/substrate-node/runtime/src/lib.rs b/substrate-node/runtime/src/lib.rs index a0b44eb37..095dd5965 100644 --- a/substrate-node/runtime/src/lib.rs +++ b/substrate-node/runtime/src/lib.rs @@ -757,7 +757,7 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPalletsWithSystem, - (), + pallet_smart_contract::migration::v5::ContractMigrationV5, >; impl_runtime_apis! { diff --git a/substrate-node/tests/SubstrateNetwork.py b/substrate-node/tests/SubstrateNetwork.py index a5f64465c..dfd2b8424 100644 --- a/substrate-node/tests/SubstrateNetwork.py +++ b/substrate-node/tests/SubstrateNetwork.py @@ -128,7 +128,7 @@ def setup_multi_node_network(self, log_name: str = "", amt: int = 2): self._nodes["alice"] = run_node(log_file_alice, "/tmp/alice", "alice", port, ws_port, rpc_port, node_key="0000000000000000000000000000000000000000000000000000000000000001") wait_till_node_ready(log_file_alice) - setup_offchain_workers(ws_port) + setup_offchain_workers(ws_port, "Alice", "Bob") log_file = "" for x in range(1, amt): @@ -140,7 +140,7 @@ def setup_multi_node_network(self, log_name: str = "", amt: int = 2): self._nodes[name] = run_node(log_file, f"/tmp/{name}", name, port, ws_port, rpc_port, node_key=None, bootnodes="/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp") wait_till_node_ready(log_file) - setup_offchain_workers(ws_port) + setup_offchain_workers(ws_port, "Bob", "Alice") logging.info("Network is up and running.") From e990ad9b37414e9c0029ee168b898e1372366a03 Mon Sep 17 00:00:00 2001 From: dylanVerstraete Date: Wed, 12 Oct 2022 16:27:55 +0200 Subject: [PATCH 28/87] chore: cleanup --- .../pallets/pallet-smart-contract/src/lib.rs | 78 ++++++------------- 1 file changed, 25 insertions(+), 53 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 437bf7333..f92e4398d 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -488,7 +488,7 @@ pub mod pallet { contract_id: u64, ) -> DispatchResultWithPostInfo { let _account_id = ensure_signed(origin)?; - Self::_bill_contract(contract_id) + Self::bill_contract(contract_id) } } @@ -811,8 +811,8 @@ impl Pallet { ); } - contract.state = types::ContractState::Deleted(cause); - Self::bill_contract(&mut contract)?; + Self::_update_contract_state(&mut contract, &types::ContractState::Deleted(cause))?; + Self::bill_contract(contract.contract_id)?; // Remove all associated storage Self::remove_contract(contract.contract_id); @@ -933,52 +933,6 @@ impl Pallet { ContractBillingInformationByID::::insert(report.contract_id, &contract_billing_info); } - pub fn _bill_contract(contract_id: u64) -> DispatchResultWithPostInfo { - if !Contracts::::contains_key(contract_id) { - log::debug!("cleaning up deleted contract from storage"); - - let index = Self::get_contract_index(); - - // Remove contract from billing list - let mut contracts = ContractsToBillAt::::get(index); - contracts.retain(|&c| c != contract_id); - ContractsToBillAt::::insert(index, contracts); - - return Ok(().into()); - } - let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - - log::info!("billing contract with id {:?}", contract_id); - - // Try to bill contract - match Self::bill_contract(&mut contract) { - Ok(_) => { - log::info!("successfully billed contract with id {:?}", contract_id,); - } - Err(err) => { - log::error!( - "error while billing contract with id {:?}: <{:?}>", - contract_id, - err.error - ); - return Err(err); - } - } - - // https://github.com/threefoldtech/tfchain/issues/264 - // if a contract is still in storage and actively getting billed whilst it is in state delete - // remove all associated storage and continue - let ctr = Contracts::::get(contract_id); - if let Some(contract) = ctr { - if contract.is_state_delete() { - Self::remove_contract(contract.contract_id); - return Ok(().into()); - } - } - - Ok(().into()) - } - fn bill_contract_using_signed_transaction(contract_id: u64) -> Result<(), Error> { let signer = Signer::::AuthorityId>::any_account(); if !signer.can_sign() { @@ -1012,7 +966,23 @@ impl Pallet { // Bills a contract (NodeContract or NameContract) // Calculates how much TFT is due by the user and distributes the rewards - fn bill_contract(contract: &mut types::Contract) -> DispatchResultWithPostInfo { + fn bill_contract(contract_id: u64) -> DispatchResultWithPostInfo { + // Clean up contract from blling loop if it not exists anymore + if !Contracts::::contains_key(contract_id) { + log::debug!("cleaning up deleted contract from storage"); + + let index = Self::get_contract_index(); + + // Remove contract from billing list + let mut contracts = ContractsToBillAt::::get(index); + contracts.retain(|&c| c != contract_id); + ContractsToBillAt::::insert(index, contracts); + + return Ok(().into()); + } + + let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + let twin = pallet_tfgrid::Twins::::get(contract.twin_id).ok_or(Error::::TwinNotExists)?; let usable_balance = Self::get_usable_balance(&twin.account_id); @@ -1041,7 +1011,7 @@ impl Pallet { }; // Handle grace - let contract = Self::handle_grace(contract, usable_balance, amount_due)?; + let contract = Self::handle_grace(&mut contract, usable_balance, amount_due)?; // If still in grace period, no need to continue doing locking and other stuff if matches!(contract.state, types::ContractState::GracePeriod(_)) { @@ -1072,6 +1042,8 @@ impl Pallet { Self::remove_contract(contract.contract_id); } + log::info!("successfully billed contract with id {:?}", contract_id,); + Ok(().into()) } @@ -1774,7 +1746,7 @@ impl ChangeNode, InterfaceOf> for Pallet { &mut contract, &types::ContractState::Deleted(types::Cause::CanceledByUser), ); - let _ = Self::bill_contract(&mut contract); + let _ = Self::bill_contract(node_contract_id); Self::remove_contract(node_contract_id); } } @@ -1787,7 +1759,7 @@ impl ChangeNode, InterfaceOf> for Pallet { &mut contract, &types::ContractState::Deleted(types::Cause::CanceledByUser), ); - let _ = Self::bill_contract(&mut contract); + let _ = Self::bill_contract(contract.contract_id); Self::remove_contract(contract.contract_id); } } From 052d7a15a30aea4a129f697de3f6a155e4c522f2 Mon Sep 17 00:00:00 2001 From: dylanVerstraete Date: Wed, 12 Oct 2022 17:02:08 +0200 Subject: [PATCH 29/87] chore: update chart --- .../charts/substrate-node/Chart.yaml | 2 +- .../substrate-node/templates/deployment.yaml | 19 +++++++++++++++++++ .../charts/substrate-node/values.yaml | 3 +++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/substrate-node/charts/substrate-node/Chart.yaml b/substrate-node/charts/substrate-node/Chart.yaml index a46a04302..c68187134 100644 --- a/substrate-node/charts/substrate-node/Chart.yaml +++ b/substrate-node/charts/substrate-node/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: substrate-node description: Tfchain node type: application -version: 0.2.4 +version: 0.2.5 appVersion: "2.1.1" diff --git a/substrate-node/charts/substrate-node/templates/deployment.yaml b/substrate-node/charts/substrate-node/templates/deployment.yaml index 5334987b3..57f3874d9 100644 --- a/substrate-node/charts/substrate-node/templates/deployment.yaml +++ b/substrate-node/charts/substrate-node/templates/deployment.yaml @@ -155,6 +155,25 @@ spec: - name: keys mountPath: /keys readOnly: true + - name: insert-smct-key + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: [ + "key", + "insert", + "--keystore-path=/keystore", + "--key-type", "smct", + "--suri","/keys/scmt", + "--scheme=sr25519" + ] + volumeMounts: + - name: keystore + mountPath: /keystore + - name: keys + mountPath: /keys + readOnly: true {{ end }} volumes: - name: keystore diff --git a/substrate-node/charts/substrate-node/values.yaml b/substrate-node/charts/substrate-node/values.yaml index 091627f25..b6b97c88b 100644 --- a/substrate-node/charts/substrate-node/values.yaml +++ b/substrate-node/charts/substrate-node/values.yaml @@ -60,6 +60,9 @@ keys: [] # secret: 1a... # - name: tft # secret: "kkjghjfkkj kjhgkkhhgg" +## Smart contract billing offchain worker key +# - name: smct +# secret: "fsfdsfsdf" # boot_node: "/ip4/10.42.1.134/tcp/30333/p2p/12D3KooWGX8JFxZu2dDmGVpa9t9enZnFCLhH4NUBA7PDuhEVQTMg" From 880185ca9dd64175ae8cd5b9cd8f8db76cb496a2 Mon Sep 17 00:00:00 2001 From: dylanVerstraete Date: Wed, 12 Oct 2022 17:38:08 +0200 Subject: [PATCH 30/87] chore: add adr for changes --- .../0002-smart-contract-billing-changed.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/architecture/0002-smart-contract-billing-changed.md diff --git a/docs/architecture/0002-smart-contract-billing-changed.md b/docs/architecture/0002-smart-contract-billing-changed.md new file mode 100644 index 000000000..2f5c1c41d --- /dev/null +++ b/docs/architecture/0002-smart-contract-billing-changed.md @@ -0,0 +1,28 @@ +# 2. Changed smart contract billing workflow + +Date: 2022-10-12 + +## Status + +Accepted + +## Context + +TFchain bills contract using an internal feature of susbtrate called `on_finalize`. This function / hook is executed after every block, in this function the time to compute is limited since this runs before the block production is finished. We are worried that if the usage of the grid grows, this on finalize method would need to do too much computations and that the block time would be impacted. + +## Decision + +We searched for an alternative to this `on_finalize` method within the substrate framework. We found a hook that fires after a block is produced, but in that hook the result of some computation must be submitted onchain with an extrinsic. This offchain worker cannot modify chain storage directly, rather it can only do that through an extrinsic. + +## Consequences + +### the good + +- The billing for a contract is now executed after a block is produced and not within that same block. +- Atleast one offchain worker must run with the `smct` keytype in order to bill contracts. +- Contracts billing cycles do not rely anymore on a previously successful bill. +- External users can call `bill_contract` but don't need to. + +### the worrying + +- If no validators run an offchain worker with the `smct` keytype, no contracts will be billed. From 5714a03b694b6af714200b442eb963b7c5a5725f Mon Sep 17 00:00:00 2001 From: dylanVerstraete Date: Wed, 5 Oct 2022 18:24:36 +0200 Subject: [PATCH 31/87] wip --- substrate-node/pallets/pallet-smart-contract/src/lib.rs | 2 ++ substrate-node/pallets/pallet-smart-contract/src/mock.rs | 5 +---- substrate-node/pallets/pallet-smart-contract/src/tests.rs | 4 ++++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index f92e4398d..430fb43d3 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1437,6 +1437,8 @@ impl Pallet { let index = Self::get_contract_index().checked_sub(1).unwrap_or(0); let mut contracts = ContractsToBillAt::::get(index); + + println!("now: {:?}, index: {:?}", now, index); if !contracts.contains(&contract_id) { contracts.push(contract_id); ContractsToBillAt::::insert(index, &contracts); diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index d767097ae..598e4bfac 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -345,12 +345,9 @@ pub struct PoolState { } impl PoolState { - pub fn should_call_bill_contract( - &mut self, - contract_id: u64, + pub fn should_call_bill_contract(&mut self, contract_id: u64, expected_result: Result<(), ()>) { expected_result: ExtrinsicResult, block_number: u64, - ) { self.expected_calls.push(( crate::Call::bill_contract_for_block { contract_id }, expected_result, diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 60971d367..0a4023dd8 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -2540,6 +2540,10 @@ fn check_report_cost( ) { let our_events = System::events(); + for e in our_events.clone().iter() { + println!("event: {:?}", e); + } + let contract_bill_event = types::ContractBill { contract_id, timestamp: 1628082000 + (6 * block_number), From ee927331faf858f84a2cbd7e72f16864cfc40f2b Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Thu, 6 Oct 2022 15:20:37 +0200 Subject: [PATCH 32/87] more wip --- .../pallets/pallet-smart-contract/src/lib.rs | 7 ++- .../pallets/pallet-smart-contract/src/mock.rs | 4 +- .../pallet-smart-contract/src/tests.rs | 46 +++++++++---------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 430fb43d3..5ccad7311 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -933,6 +933,7 @@ impl Pallet { ContractBillingInformationByID::::insert(report.contract_id, &contract_billing_info); } + fn bill_contract_using_signed_transaction(contract_id: u64) -> Result<(), Error> { let signer = Signer::::AuthorityId>::any_account(); if !signer.can_sign() { @@ -991,12 +992,17 @@ impl Pallet { // Calculate amount of seconds elapsed based on the contract lock struct let now = >::get().saturated_into::() / 1000; + log::debug!("now timestamp: {:?}", now); // this will set the seconds elapsed to the default billing cycle duration in seconds // if there is no contract lock object yet. A contract lock object will be created later in this function // https://github.com/threefoldtech/tfchain/issues/261 let contract_lock = ContractLock::::get(contract.contract_id); + log::debug!("contract lock: {:?}", contract_lock); + let now_block = >::block_number().saturated_into::(); + log::debug!("current block: {:?}", now_block); if contract_lock.lock_updated != 0 { seconds_elapsed = now.checked_sub(contract_lock.lock_updated).unwrap_or(0); + log::debug!("seconds elapsed: {:?}", seconds_elapsed); } let (amount_due, discount_received) = @@ -1438,7 +1444,6 @@ impl Pallet { let mut contracts = ContractsToBillAt::::get(index); - println!("now: {:?}, index: {:?}", now, index); if !contracts.contains(&contract_id) { contracts.push(contract_id); ContractsToBillAt::::insert(index, &contracts); diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index 598e4bfac..daac582ff 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -345,9 +345,11 @@ pub struct PoolState { } impl PoolState { - pub fn should_call_bill_contract(&mut self, contract_id: u64, expected_result: Result<(), ()>) { + pub fn should_call_bill_contract( expected_result: ExtrinsicResult, block_number: u64, + expected_result: ExtrinsicResult, + ) { self.expected_calls.push(( crate::Call::bill_contract_for_block { contract_id }, expected_result, diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 0a4023dd8..fee231602 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1040,7 +1040,7 @@ fn test_node_contract_billing_cycles_delete_node_cancels_contract() { let contract_id = 1; let twin_id = 2; - for i in 0..5 { + for _ in 0..5 { pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); @@ -1135,7 +1135,7 @@ fn test_node_contract_only_public_ip_billing_cycles() { let contract_id = 1; let twin_id = 2; - for i in 0..5 { + for _ in 0..5 { pool_state.write().should_call_bill_contract( contract_id, Ok(Pays::Yes.into()), @@ -1187,7 +1187,7 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { let twin_id = 2; // 2 cycles for billing - for i in 0..2 { + for _ in 0..2 { pool_state.write().should_call_bill_contract( contract_id, Ok(Pays::Yes.into()), @@ -1376,7 +1376,7 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { run_to_block(21, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(21)); + assert_eq!(c1.state, types::ContractState::GracePeriod(20)); let our_events = System::events(); assert_eq!( @@ -1385,7 +1385,7 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 21 + block_number: 20 } ))), true @@ -1410,7 +1410,7 @@ fn test_restore_node_contract_in_grace_works() { None )); - for i in 0..6 { + for _ in 0..6 { pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); @@ -1425,7 +1425,7 @@ fn test_restore_node_contract_in_grace_works() { run_to_block(21, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(21)); + assert_eq!(c1.state, types::ContractState::GracePeriod(20)); let our_events = System::events(); assert_eq!( @@ -1434,7 +1434,7 @@ fn test_restore_node_contract_in_grace_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 21 + block_number: 20 } ))), true @@ -1485,7 +1485,7 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works run_to_block(21, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(21)); + assert_eq!(c1.state, types::ContractState::GracePeriod(20)); let our_events = System::events(); assert_eq!( @@ -1494,7 +1494,7 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works contract_id: 1, node_id: 1, twin_id: 3, - block_number: 21 + block_number: 20 } ))), true @@ -1534,7 +1534,7 @@ fn test_name_contract_billing() { "foobar".as_bytes().to_vec() )); - let contracts_to_bill = SmartContractModule::contract_to_bill_at_block(1); + let contracts_to_bill = SmartContractModule::contract_to_bill_at_block(0); assert_eq!(contracts_to_bill, [1]); // let mature 11 blocks @@ -1786,7 +1786,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ push_contract_resources_used(2); // run 12 cycles, contracts should cancel after 11 due to lack of funds - for i in 0..11 { + for _ in 0..11 { pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); @@ -1826,7 +1826,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ contract_id: 1, node_id: 1, twin_id: 3, - block_number: 11 + block_number: 10 })) ); assert_eq!( @@ -1837,7 +1837,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ contract_id: 2, node_id: 1, twin_id: 3, - block_number: 11 + block_number: 10 })) ); @@ -1938,7 +1938,7 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(11)); + assert_eq!(c1.state, types::ContractState::GracePeriod(10)); let our_events = System::events(); assert_eq!( @@ -1947,7 +1947,7 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 11 + block_number: 10 } ))), true @@ -1977,7 +1977,7 @@ fn test_restore_rent_contract_in_grace_works() { run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(11)); + assert_eq!(c1.state, types::ContractState::GracePeriod(10)); let our_events = System::events(); assert_eq!( @@ -1988,7 +1988,7 @@ fn test_restore_rent_contract_in_grace_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 11 + block_number: 10 })) ); @@ -2054,7 +2054,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(11)); + assert_eq!(c1.state, types::ContractState::GracePeriod(10)); let our_events = System::events(); assert_eq!( @@ -2065,7 +2065,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 11 + block_number: 10 })) ); assert_eq!( @@ -2076,7 +2076,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { contract_id: 2, node_id: 1, twin_id: 3, - block_number: 11 + block_number: 10 })) ); @@ -2165,7 +2165,7 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(11)); + assert_eq!(c1.state, types::ContractState::GracePeriod(10)); let our_events = System::events(); assert_eq!( @@ -2174,7 +2174,7 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works contract_id: 1, node_id: 1, twin_id: 3, - block_number: 11 + block_number: 10 } ))), true From d5160a573a29634b5b457c060bffda485b552d5a Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Fri, 7 Oct 2022 11:12:31 +0200 Subject: [PATCH 33/87] chore: some cleaning --- substrate-node/pallets/pallet-smart-contract/src/lib.rs | 5 ----- substrate-node/pallets/pallet-smart-contract/src/tests.rs | 4 ---- 2 files changed, 9 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 5ccad7311..35af14e21 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -992,17 +992,12 @@ impl Pallet { // Calculate amount of seconds elapsed based on the contract lock struct let now = >::get().saturated_into::() / 1000; - log::debug!("now timestamp: {:?}", now); // this will set the seconds elapsed to the default billing cycle duration in seconds // if there is no contract lock object yet. A contract lock object will be created later in this function // https://github.com/threefoldtech/tfchain/issues/261 let contract_lock = ContractLock::::get(contract.contract_id); - log::debug!("contract lock: {:?}", contract_lock); - let now_block = >::block_number().saturated_into::(); - log::debug!("current block: {:?}", now_block); if contract_lock.lock_updated != 0 { seconds_elapsed = now.checked_sub(contract_lock.lock_updated).unwrap_or(0); - log::debug!("seconds elapsed: {:?}", seconds_elapsed); } let (amount_due, discount_received) = diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index fee231602..9699284f1 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -2540,10 +2540,6 @@ fn check_report_cost( ) { let our_events = System::events(); - for e in our_events.clone().iter() { - println!("event: {:?}", e); - } - let contract_bill_event = types::ContractBill { contract_id, timestamp: 1628082000 + (6 * block_number), From fc5b015ca05f36c6ac9270e07e02907f60fa701e Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Fri, 7 Oct 2022 16:12:11 +0200 Subject: [PATCH 34/87] chore: test artifact upload --- .github/workflows/build_test.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build_test.yaml b/.github/workflows/build_test.yaml index 7d73d336f..fcf4a9ef3 100644 --- a/.github/workflows/build_test.yaml +++ b/.github/workflows/build_test.yaml @@ -46,6 +46,10 @@ jobs: python3.10 -m pip install robotframework cryptography substrate-interface cd substrate-node/tests robot -d _output_tests/ . + - uses: actions/upload-artifact@v3 + with: + name: integration test output + path: substrate-node/tests/_output_tests/ - uses: actions/upload-artifact@v3 if: always() From 53836f79d699986db27828b862747604ecd15548 Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Fri, 7 Oct 2022 16:15:27 +0200 Subject: [PATCH 35/87] chore: fix yaml --- .github/workflows/build_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_test.yaml b/.github/workflows/build_test.yaml index fcf4a9ef3..6c3d3b7d4 100644 --- a/.github/workflows/build_test.yaml +++ b/.github/workflows/build_test.yaml @@ -42,7 +42,7 @@ jobs: $HOME/.cargo/bin/cargo +nightly-2022-05-11 test --no-fail-fast - name: Integration tests - run: | + - run: | python3.10 -m pip install robotframework cryptography substrate-interface cd substrate-node/tests robot -d _output_tests/ . From 3be2d5f659f84c21b19dddc286647fb948c21227 Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Fri, 7 Oct 2022 16:16:18 +0200 Subject: [PATCH 36/87] chore: fix yaml again --- .github/workflows/build_test.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build_test.yaml b/.github/workflows/build_test.yaml index 6c3d3b7d4..daffe9d6b 100644 --- a/.github/workflows/build_test.yaml +++ b/.github/workflows/build_test.yaml @@ -42,14 +42,15 @@ jobs: $HOME/.cargo/bin/cargo +nightly-2022-05-11 test --no-fail-fast - name: Integration tests - - run: | + run: | python3.10 -m pip install robotframework cryptography substrate-interface cd substrate-node/tests robot -d _output_tests/ . - - uses: actions/upload-artifact@v3 - with: - name: integration test output - path: substrate-node/tests/_output_tests/ + + - uses: actions/upload-artifact@v3 + with: + name: integration test output + path: substrate-node/tests/_output_tests/ - uses: actions/upload-artifact@v3 if: always() From 7b13f78da6c384a346c65a968db33a011918305d Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Fri, 7 Oct 2022 16:47:23 +0200 Subject: [PATCH 37/87] chore: upload artifact if job failed --- .github/workflows/build_test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_test.yaml b/.github/workflows/build_test.yaml index daffe9d6b..90b9ae08e 100644 --- a/.github/workflows/build_test.yaml +++ b/.github/workflows/build_test.yaml @@ -48,6 +48,7 @@ jobs: robot -d _output_tests/ . - uses: actions/upload-artifact@v3 + if: failure() with: name: integration test output path: substrate-node/tests/_output_tests/ From 4caf435c8774587b5050f719750e69bd469c100a Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Mon, 10 Oct 2022 09:30:29 +0200 Subject: [PATCH 38/87] chore: wait x blocks after cancel --- substrate-node/tests/integration_tests.robot | 1 + 1 file changed, 1 insertion(+) diff --git a/substrate-node/tests/integration_tests.robot b/substrate-node/tests/integration_tests.robot index fe571a4a9..7002c13f0 100644 --- a/substrate-node/tests/integration_tests.robot +++ b/substrate-node/tests/integration_tests.robot @@ -478,6 +478,7 @@ Test Solution Provider Wait X Blocks ${6} # Cancel the contract so that the bill is distributed and so that the providers get their part Cancel Node Contract contract_id=${1} who=Bob + Wait X Blocks ${1} # Verification: both providers should have received their part ${balance_charlie_after} = Balance Data who=Charlie From 4320ee01c01462ab23e4af7f62d55148cfd57da4 Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Mon, 10 Oct 2022 10:24:10 +0200 Subject: [PATCH 39/87] chore: move get index to function --- substrate-node/tests/integration_tests.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate-node/tests/integration_tests.robot b/substrate-node/tests/integration_tests.robot index 7002c13f0..dadccecdd 100644 --- a/substrate-node/tests/integration_tests.robot +++ b/substrate-node/tests/integration_tests.robot @@ -478,7 +478,7 @@ Test Solution Provider Wait X Blocks ${6} # Cancel the contract so that the bill is distributed and so that the providers get their part Cancel Node Contract contract_id=${1} who=Bob - Wait X Blocks ${1} + Wait X Blocks ${2} # Verification: both providers should have received their part ${balance_charlie_after} = Balance Data who=Charlie From a5cce78fdff9e785f3053aa2fcce7a7eb0789d84 Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Mon, 10 Oct 2022 13:52:24 +0200 Subject: [PATCH 40/87] chore: refactor --- .../pallet-smart-contract/src/tests.rs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 9699284f1..c086e3014 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1376,7 +1376,7 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { run_to_block(21, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(20)); + assert_eq!(c1.state, types::ContractState::GracePeriod(21)); let our_events = System::events(); assert_eq!( @@ -1385,7 +1385,7 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 20 + block_number: 21 } ))), true @@ -1425,7 +1425,7 @@ fn test_restore_node_contract_in_grace_works() { run_to_block(21, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(20)); + assert_eq!(c1.state, types::ContractState::GracePeriod(21)); let our_events = System::events(); assert_eq!( @@ -1434,7 +1434,7 @@ fn test_restore_node_contract_in_grace_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 20 + block_number: 21 } ))), true @@ -1485,7 +1485,7 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works run_to_block(21, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(20)); + assert_eq!(c1.state, types::ContractState::GracePeriod(21)); let our_events = System::events(); assert_eq!( @@ -1494,7 +1494,7 @@ fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works contract_id: 1, node_id: 1, twin_id: 3, - block_number: 20 + block_number: 21 } ))), true @@ -1534,7 +1534,7 @@ fn test_name_contract_billing() { "foobar".as_bytes().to_vec() )); - let contracts_to_bill = SmartContractModule::contract_to_bill_at_block(0); + let contracts_to_bill = SmartContractModule::contract_to_bill_at_block(1); assert_eq!(contracts_to_bill, [1]); // let mature 11 blocks @@ -1826,7 +1826,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ contract_id: 1, node_id: 1, twin_id: 3, - block_number: 10 + block_number: 11 })) ); assert_eq!( @@ -1837,7 +1837,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ contract_id: 2, node_id: 1, twin_id: 3, - block_number: 10 + block_number: 11 })) ); @@ -1938,7 +1938,7 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(10)); + assert_eq!(c1.state, types::ContractState::GracePeriod(11)); let our_events = System::events(); assert_eq!( @@ -1947,7 +1947,7 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 10 + block_number: 11 } ))), true @@ -1977,7 +1977,7 @@ fn test_restore_rent_contract_in_grace_works() { run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(10)); + assert_eq!(c1.state, types::ContractState::GracePeriod(11)); let our_events = System::events(); assert_eq!( @@ -1988,7 +1988,7 @@ fn test_restore_rent_contract_in_grace_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 10 + block_number: 11 })) ); @@ -2054,7 +2054,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(10)); + assert_eq!(c1.state, types::ContractState::GracePeriod(11)); let our_events = System::events(); assert_eq!( @@ -2065,7 +2065,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { contract_id: 1, node_id: 1, twin_id: 3, - block_number: 10 + block_number: 11 })) ); assert_eq!( @@ -2076,7 +2076,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { contract_id: 2, node_id: 1, twin_id: 3, - block_number: 10 + block_number: 11 })) ); @@ -2165,7 +2165,7 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); - assert_eq!(c1.state, types::ContractState::GracePeriod(10)); + assert_eq!(c1.state, types::ContractState::GracePeriod(11)); let our_events = System::events(); assert_eq!( @@ -2174,7 +2174,7 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works contract_id: 1, node_id: 1, twin_id: 3, - block_number: 10 + block_number: 11 } ))), true From 0321751ff83427a6e610ccf51dfd363786db13c2 Mon Sep 17 00:00:00 2001 From: dylanverstraete Date: Mon, 10 Oct 2022 15:55:23 +0200 Subject: [PATCH 41/87] fix: billing --- substrate-node/tests/integration_tests.robot | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate-node/tests/integration_tests.robot b/substrate-node/tests/integration_tests.robot index dadccecdd..fe571a4a9 100644 --- a/substrate-node/tests/integration_tests.robot +++ b/substrate-node/tests/integration_tests.robot @@ -478,7 +478,6 @@ Test Solution Provider Wait X Blocks ${6} # Cancel the contract so that the bill is distributed and so that the providers get their part Cancel Node Contract contract_id=${1} who=Bob - Wait X Blocks ${2} # Verification: both providers should have received their part ${balance_charlie_after} = Balance Data who=Charlie From 74bfc0d4172b36280ae18e60caffc3e4ca0277ad Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 4 Oct 2022 09:19:17 +0200 Subject: [PATCH 42/87] feat: initial commit #473 --- substrate-node/pallets/pallet-tfgrid/src/lib.rs | 2 ++ substrate-node/support/src/types.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/substrate-node/pallets/pallet-tfgrid/src/lib.rs b/substrate-node/pallets/pallet-tfgrid/src/lib.rs index dffd243c3..5348341a3 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/lib.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/lib.rs @@ -969,6 +969,7 @@ pub mod pallet { id = id + 1; let created = >::get().saturated_into::() / 1000; + let power_target = true; let mut new_node = Node { version: TFGRID_NODE_VERSION, @@ -979,6 +980,7 @@ pub mod pallet { location, country, city, + power_target, public_config: None, created, farming_policy_id: 0, diff --git a/substrate-node/support/src/types.rs b/substrate-node/support/src/types.rs index a7fb09309..78d2f2084 100644 --- a/substrate-node/support/src/types.rs +++ b/substrate-node/support/src/types.rs @@ -78,6 +78,7 @@ pub struct Node { pub location: Location, pub country: Vec, pub city: Vec, + pub power_target: bool, // optional public config pub public_config: Option, pub created: u64, From b621682c3922f1c2e5dfaa0a85ed5beb190ed358 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Wed, 5 Oct 2022 17:50:17 +0200 Subject: [PATCH 43/87] feat: added events and extrinsic for creating deployment contract #473 #469 --- .../pallets/pallet-smart-contract/src/cost.rs | 3 + .../pallets/pallet-smart-contract/src/lib.rs | 183 ++++++++++++++++-- .../pallet-smart-contract/src/types.rs | 19 ++ .../pallets/pallet-tfgrid/src/lib.rs | 7 +- substrate-node/support/src/types.rs | 15 +- 5 files changed, 213 insertions(+), 14 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index fbae0c3c7..ebf2fd1c3 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -53,6 +53,9 @@ impl Contract { seconds_elapsed: u64, ) -> Result { let total_cost = match &self.contract_type { + types::ContractData::DeploymentContract(_) => { + 0 + }, // Calculate total cost for a node contract types::ContractData::NodeContract(node_contract) => { // Get the contract billing info to view the amount unbilled for NRU (network resource units) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 35af14e21..c81a2b374 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -29,7 +29,10 @@ use sp_runtime::{ Perbill, }; use substrate_fixed::types::U64F64; -use tfchain_support::{traits::ChangeNode, types::Node}; +use tfchain_support::{ + traits::ChangeNode, + types::{Node, PowerTarget, Resources}, +}; pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"smct"); @@ -311,6 +314,12 @@ pub mod pallet { }, SolutionProviderCreated(types::SolutionProvider), SolutionProviderApproved(u64, bool), + /// Send an event to zero os to change its state + ChangePowerTarget { + farm_id: u32, + node_id: u32, + power_target: PowerTarget, + }, } #[pallet::error] @@ -433,6 +442,17 @@ pub mod pallet { Self::_create_name_contract(account_id, name) } + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn create_deployment_contract( + origin: OriginFor, + farm_id: u32, + resources: Resources, + rent_contract_id: Option, + ) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_create_deployment_contract(account_id, farm_id, resources, rent_contract_id) + } + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn add_nru_reports( origin: OriginFor, @@ -616,6 +636,34 @@ impl Pallet { Ok(().into()) } + pub fn _create_deployment_contract( + account_id: T::AccountId, + farm_id: u32, + resources: Resources, + rent_contract_id: Option, + ) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + + let node_id = Self::_find_suitable_node_in_farm(farm_id, resources)?; + + // Prepare NodeContract struct + let deployment_contract = types::DeploymentContract { node_id: node_id }; + + // Create contract + let contract = Self::_create_contract( + twin_id, + types::ContractData::DeploymentContract(deployment_contract.clone()), + None, + )?; + + // TODO: insert contract in pending_node_farm_contracts + + Self::deposit_event(Event::ContractCreated(contract)); + + Ok(().into()) + } + pub fn _create_rent_contract( account_id: T::AccountId, node_id: u32, @@ -642,6 +690,8 @@ impl Pallet { // Create contract let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; + // claim all the resources + Self::_claim_resources_on_node(node_id, node.resources)?; let contract = Self::_create_contract( twin_id, types::ContractData::RentContract(types::RentContract { node_id }), @@ -695,6 +745,103 @@ impl Pallet { Ok(().into()) } + fn _claim_resources_on_node(node_id: u32, resources: Resources) -> DispatchResultWithPostInfo { + let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + + ensure!( + node.available_resources.hru >= resources.hru + && node.available_resources.sru >= resources.sru + && node.available_resources.cru >= resources.cru + && node.available_resources.mru >= resources.mru, + Error::::NotEnoughResourcesOnNode + ); + + // if the node is down => wake it up + if matches!(node.power_target, PowerTarget::Down) { + Self::deposit_event(Event::ChangePowerTarget { + farm_id: node.farm_id, + node_id: node_id, + power_target: PowerTarget::Up, + }); + node.power_target = PowerTarget::Up; + } + + //update the available resources + node.available_resources.hru -= resources.hru; + node.available_resources.sru -= resources.sru; + node.available_resources.cru -= resources.cru; + node.available_resources.mru -= resources.mru; + pallet_tfgrid::Nodes::::insert(node.id, &node); + + Ok(().into()) + } + + fn _un_claim_resources_on_node( + node_id: u32, + resources: Resources, + ) -> DispatchResultWithPostInfo { + let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + + //update the available resources + node.available_resources.hru += resources.hru; + node.available_resources.sru += resources.sru; + node.available_resources.cru += resources.cru; + node.available_resources.mru += resources.mru; + + // if all resources are free shutdown the node + if node.available_resources.hru == node.resources.hru + && node.available_resources.sru == node.resources.sru + && node.available_resources.cru == node.resources.cru + && node.available_resources.mru == node.resources.mru + { + Self::deposit_event(Event::ChangePowerTarget { + farm_id: node.farm_id, + node_id: node_id, + power_target: PowerTarget::Down, + }); + node.power_target = PowerTarget::Down; + } + + pallet_tfgrid::Nodes::::insert(node.id, &node); + + Ok(().into()) + } + + fn _find_suitable_node_in_farm( + farm_id: u32, + resources: Resources, + ) -> Result { + ensure!( + pallet_tfgrid::Farms::::contains_key(farm_id), + Error::::FarmNotExists + ); + + let nodes_in_farm = pallet_tfgrid::NodesByFarmID::::get(farm_id); + let mut suitable_nodes = Vec::new(); + for node_id in nodes_in_farm { + let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + if node.available_resources.hru >= resources.hru + && node.available_resources.sru >= resources.sru + && node.available_resources.cru >= resources.cru + && node.available_resources.mru >= resources.mru + { + suitable_nodes.push(node); + } + } + + ensure!( + !suitable_nodes.is_empty(), + Error::::NotEnoughResourcesOnNode + ); + + // sort the suitable nodes on power_target: the nodes that are Up will be first in the list + suitable_nodes.sort_by(|a, b| a.power_target.cmp(&b.power_target)); + + Self::_claim_resources_on_node(suitable_nodes[0].id, resources)?; + + Ok(suitable_nodes[0].id) + } + fn _create_contract( twin_id: u32, mut contract_type: types::ContractData, @@ -799,16 +946,27 @@ impl Pallet { ); // If it's a rent contract and it still has active workloads, don't allow cancellation. - if matches!( - &contract.contract_type, - types::ContractData::RentContract(_) - ) { - let rent_contract = Self::get_rent_contract(&contract)?; - let active_node_contracts = ActiveNodeContracts::::get(rent_contract.node_id); - ensure!( - active_node_contracts.len() == 0, - Error::::NodeHasActiveContracts - ); + match contract.contract_type { + types::ContractData::RentContract(ref c) => { + let rent_contract = Self::get_rent_contract(&contract)?; + let active_node_contracts = ActiveNodeContracts::::get(rent_contract.node_id); + ensure!( + active_node_contracts.len() == 0, + Error::::NodeHasActiveContracts + ); + let node = + pallet_tfgrid::Nodes::::get(c.node_id).ok_or(Error::::NodeNotExists)?; + let used_resources = node.resources; + Self::_un_claim_resources_on_node(c.node_id, used_resources)?; + }, + types::ContractData::DeploymentContract(ref c) => { + let used_resources = NodeContractResources::::get(contract_id).used; + Self::_un_claim_resources_on_node(c.node_id, used_resources)?; + }, + _ => { + //TODO + } + } Self::_update_contract_state(&mut contract, &types::ContractState::Deleted(cause))?; @@ -1249,6 +1407,9 @@ impl Pallet { if let Some(contract) = contract { match contract.contract_type.clone() { + types::ContractData::DeploymentContract(_) => { + //TODO + } types::ContractData::NodeContract(mut node_contract) => { Self::remove_active_node_contract(node_contract.node_id, contract_id); if node_contract.public_ips > 0 { diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index d53a03838..d68bd8dff 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -50,6 +50,7 @@ impl Contract { ContractData::RentContract(c) => c.node_id, ContractData::NodeContract(c) => c.node_id, ContractData::NameContract(_) => 0, + ContractData::DeploymentContract(c) => c.node_id, } } } @@ -91,6 +92,23 @@ pub struct RentContract { pub node_id: u32, } +#[derive( + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Encode, + Decode, + Default, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, +)] +pub struct DeploymentContract { + pub node_id: u32, +} + #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] @@ -98,6 +116,7 @@ pub enum ContractData { NodeContract(NodeContract), NameContract(NameContract), RentContract(RentContract), + DeploymentContract(DeploymentContract), } impl Default for ContractData { diff --git a/substrate-node/pallets/pallet-tfgrid/src/lib.rs b/substrate-node/pallets/pallet-tfgrid/src/lib.rs index 5348341a3..c6d94c1dd 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/lib.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/lib.rs @@ -15,7 +15,7 @@ use sp_runtime::SaturatedConversion; use tfchain_support::types::PublicIP; use tfchain_support::{ resources, - types::{Interface, Node, PublicConfig, IP}, + types::{Interface, Node, PowerTarget, PublicConfig, IP}, }; // Re-export pallet items so that they can be accessed from the crate namespace. @@ -969,7 +969,9 @@ pub mod pallet { id = id + 1; let created = >::get().saturated_into::() / 1000; - let power_target = true; + let power_target = PowerTarget::Up; + + let available_resources = resources.clone(); let mut new_node = Node { version: TFGRID_NODE_VERSION, @@ -977,6 +979,7 @@ pub mod pallet { farm_id, twin_id, resources, + available_resources, location, country, city, diff --git a/substrate-node/support/src/types.rs b/substrate-node/support/src/types.rs index 78d2f2084..12fda42eb 100644 --- a/substrate-node/support/src/types.rs +++ b/substrate-node/support/src/types.rs @@ -68,6 +68,18 @@ pub struct FarmingPolicyLimit { pub node_certification: bool, } +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo)] +pub enum PowerTarget { + Up, + Down, +} + +impl Default for PowerTarget { + fn default() -> PowerTarget { + PowerTarget::Down + } +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo)] pub struct Node { pub version: u32, @@ -75,10 +87,11 @@ pub struct Node { pub farm_id: u32, pub twin_id: u32, pub resources: Resources, + pub available_resources: Resources, pub location: Location, pub country: Vec, pub city: Vec, - pub power_target: bool, + pub power_target: PowerTarget, // optional public config pub public_config: Option, pub created: u64, From 1d916a3753361264fb0626f3b518aa36a9258bf7 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 7 Oct 2022 11:15:11 +0200 Subject: [PATCH 44/87] feat: major implementation for feature #473 --- .../pallets/pallet-smart-contract/src/cost.rs | 11 +- .../pallets/pallet-smart-contract/src/lib.rs | 362 +++++++++--------- .../pallet-smart-contract/src/types.rs | 26 +- .../pallets/pallet-tfgrid/src/lib.rs | 4 +- substrate-node/support/src/types.rs | 18 +- 5 files changed, 207 insertions(+), 214 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index ebf2fd1c3..cdcf4219b 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -53,11 +53,8 @@ impl Contract { seconds_elapsed: u64, ) -> Result { let total_cost = match &self.contract_type { - types::ContractData::DeploymentContract(_) => { - 0 - }, // Calculate total cost for a node contract - types::ContractData::NodeContract(node_contract) => { + types::ContractData::DeploymentContract(node_contract) => { // Get the contract billing info to view the amount unbilled for NRU (network resource units) let contract_billing_info = self.get_billing_info(); // Get the node @@ -67,8 +64,8 @@ impl Contract { // We know the contract is using resources, now calculate the cost for each used resource - let node_contract_resources = - pallet::Pallet::::node_contract_resources(self.contract_id); + //let node_contract_resources = + // pallet::Pallet::::node_contract_resources(self.contract_id); let mut bill_resources = true; // If this node contract is deployed on a node which has a rent contract @@ -78,7 +75,7 @@ impl Contract { } let contract_cost = calculate_resources_cost::( - node_contract_resources.used, + node_contract.resources, node_contract.public_ips, seconds_elapsed, &pricing_policy, diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index c81a2b374..b50dd0e76 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -137,10 +137,10 @@ pub mod pallet { pub type ContractBillingInformationByID = StorageMap<_, Blake2_128Concat, u64, ContractBillingInformation, ValueQuery>; - #[pallet::storage] - #[pallet::getter(fn node_contract_resources)] - pub type NodeContractResources = - StorageMap<_, Blake2_128Concat, u64, ContractResources, ValueQuery>; + //#[pallet::storage] + //#[pallet::getter(fn node_contract_resources)] + //pub type NodeContractResources = + // StorageMap<_, Blake2_128Concat, u64, ContractResources, ValueQuery>; #[pallet::storage] #[pallet::getter(fn node_contract_by_hash)] @@ -291,7 +291,7 @@ pub mod pallet { contract_id: u64, amount: BalanceOf, }, - /// Contract resources got updated + /// Deprecated event UpdatedUsedResources(types::ContractResources), /// Network resources report received for contract NruConsumptionReportReceived(types::NruConsumption), @@ -357,6 +357,8 @@ pub mod pallet { InvalidProviderConfiguration, NoSuchSolutionProvider, SolutionProviderNotApproved, + NoSuitableNodeInFarm, + NotAuthorizedToCreateDeploymentContract, } #[pallet::genesis_config] @@ -385,22 +387,14 @@ pub mod pallet { impl Pallet { #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn create_node_contract( - origin: OriginFor, - node_id: u32, - deployment_hash: DeploymentHash, - deployment_data: DeploymentDataInput, - public_ips: u32, - solution_provider_id: Option, + _origin: OriginFor, + _node_id: u32, + _deployment_hash: DeploymentHash, + _deployment_data: DeploymentDataInput, + _public_ips: u32, + _solution_provider_id: Option, ) -> DispatchResultWithPostInfo { - let account_id = ensure_signed(origin)?; - Self::_create_node_contract( - account_id, - node_id, - deployment_hash, - deployment_data, - public_ips, - solution_provider_id, - ) + Err(DispatchErrorWithPostInfo::from(Error::::MethodIsDeprecated).into()) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] @@ -409,9 +403,16 @@ pub mod pallet { contract_id: u64, deployment_hash: DeploymentHash, deployment_data: DeploymentDataInput, + extra_resources: Option, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_update_node_contract(account_id, contract_id, deployment_hash, deployment_data) + Self::_update_node_contract( + account_id, + contract_id, + deployment_hash, + deployment_data, + extra_resources, + ) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] @@ -446,11 +447,24 @@ pub mod pallet { pub fn create_deployment_contract( origin: OriginFor, farm_id: u32, + deployment_hash: DeploymentHash, + deployment_data: DeploymentDataInput, resources: Resources, + public_ips: u32, + solution_provider_id: Option, rent_contract_id: Option, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_create_deployment_contract(account_id, farm_id, resources, rent_contract_id) + Self::_create_deployment_contract( + account_id, + farm_id, + deployment_hash, + deployment_data, + resources, + public_ips, + solution_provider_id, + rent_contract_id, + ) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] @@ -464,11 +478,10 @@ pub mod pallet { #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn report_contract_resources( - origin: OriginFor, - contract_resources: Vec, + _origin: OriginFor, + _contract_resources: Vec, ) -> DispatchResultWithPostInfo { - let account_id = ensure_signed(origin)?; - Self::_report_contract_resources(account_id, contract_resources) + Err(DispatchErrorWithPostInfo::from(Error::::MethodIsDeprecated).into()) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] @@ -550,38 +563,39 @@ use sp_std::convert::{TryFrom, TryInto}; use tfchain_support::types::PublicIP; // Internal functions of the pallet impl Pallet { - pub fn _create_node_contract( + #[transactional] + pub fn _create_deployment_contract( account_id: T::AccountId, - node_id: u32, + farm_id: u32, deployment_hash: DeploymentHash, deployment_data: DeploymentDataInput, + resources: Resources, public_ips: u32, solution_provider_id: Option, + rent_contract_id: Option, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - let farm = pallet_tfgrid::Farms::::get(node.farm_id).ok_or(Error::::FarmNotExists)?; - - if farm.dedicated_farm && !ActiveRentContractForNode::::contains_key(node_id) { - return Err(Error::::NodeNotAvailableToDeploy.into()); - } - - // If the user is trying to deploy on a node that has an active rent contract - // only allow the user who created the rent contract to actually deploy a node contract on it - if let Some(contract_id) = ActiveRentContractForNode::::get(node_id) { - let rent_contract = - Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - if rent_contract.twin_id != twin_id { - return Err(Error::::NodeHasRentContract.into()); - } - } + // if rent_contract_id is an actual rent contract let's use that node to create deployment contract and + // only allow the user who created the rent contract to actually deploy a deployment contract on it + // else find a suitable node in the provided farm + let node_id = if let Some(contract_id) = rent_contract_id { + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + ensure!( + contract.twin_id == twin_id, + Error::::NotAuthorizedToCreateDeploymentContract + ); + Self::get_rent_contract(&contract)?.node_id + } else { + Self::_find_suitable_node_in_farm(farm_id, resources)? + }; // If the contract with hash and node id exists and it's in any other state then // contractState::Deleted then we don't allow the creation of it. // If it exists we allow the user to "restore" this contract if ContractIDByNodeIDAndHash::::contains_key(node_id, &deployment_hash) { + //TODO decide what to do with this let contract_id = ContractIDByNodeIDAndHash::::get(node_id, &deployment_hash); let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; if !contract.is_state_delete() { @@ -596,19 +610,20 @@ impl Pallet { >, MaxNodeContractPublicIPs, > = vec![].try_into().unwrap(); - // Prepare NodeContract struct - let node_contract = types::NodeContract { - node_id, + // Prepare DeploymentContract struct + let node_contract = types::DeploymentContract { + node_id: node_id, deployment_hash: deployment_hash.clone(), - deployment_data, - public_ips, - public_ips_list, + deployment_data: deployment_data, + public_ips: public_ips, + public_ips_list: public_ips_list, + resources: resources, }; // Create contract let contract = Self::_create_contract( twin_id, - types::ContractData::NodeContract(node_contract.clone()), + types::ContractData::DeploymentContract(node_contract.clone()), solution_provider_id, )?; @@ -636,34 +651,6 @@ impl Pallet { Ok(().into()) } - pub fn _create_deployment_contract( - account_id: T::AccountId, - farm_id: u32, - resources: Resources, - rent_contract_id: Option, - ) -> DispatchResultWithPostInfo { - let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) - .ok_or(Error::::TwinNotExists)?; - - let node_id = Self::_find_suitable_node_in_farm(farm_id, resources)?; - - // Prepare NodeContract struct - let deployment_contract = types::DeploymentContract { node_id: node_id }; - - // Create contract - let contract = Self::_create_contract( - twin_id, - types::ContractData::DeploymentContract(deployment_contract.clone()), - None, - )?; - - // TODO: insert contract in pending_node_farm_contracts - - Self::deposit_event(Event::ContractCreated(contract)); - - Ok(().into()) - } - pub fn _create_rent_contract( account_id: T::AccountId, node_id: u32, @@ -690,8 +677,6 @@ impl Pallet { // Create contract let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - // claim all the resources - Self::_claim_resources_on_node(node_id, node.resources)?; let contract = Self::_create_contract( twin_id, types::ContractData::RentContract(types::RentContract { node_id }), @@ -745,32 +730,51 @@ impl Pallet { Ok(().into()) } - fn _claim_resources_on_node(node_id: u32, resources: Resources) -> DispatchResultWithPostInfo { + fn _change_power_target_node( + node_id: u32, + power_target: PowerTarget, + ) -> DispatchResultWithPostInfo { let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - ensure!( - node.available_resources.hru >= resources.hru - && node.available_resources.sru >= resources.sru - && node.available_resources.cru >= resources.cru - && node.available_resources.mru >= resources.mru, - Error::::NotEnoughResourcesOnNode + pallet_tfgrid::Farms::::contains_key(node.farm_id), + Error::::FarmNotExists ); - // if the node is down => wake it up - if matches!(node.power_target, PowerTarget::Down) { + // we never shut down the first node in the of a farm, there should always be one node up per farm + if matches!(power_target, PowerTarget::Down) + && pallet_tfgrid::NodesByFarmID::::get(node.farm_id)[0] == node_id + { + return Ok(().into()); + } + + // if the power_target is not correct => change it and emit event + if node.power_target != power_target { Self::deposit_event(Event::ChangePowerTarget { farm_id: node.farm_id, node_id: node_id, - power_target: PowerTarget::Up, + power_target: power_target.clone(), }); - node.power_target = PowerTarget::Up; + node.power_target = power_target; + pallet_tfgrid::Nodes::::insert(node.id, &node); } + Ok(().into()) + } + + fn _claim_resources_on_node(node_id: u32, resources: Resources) -> DispatchResultWithPostInfo { + let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + + ensure!( + node.can_claim_resources(resources), + Error::::NotEnoughResourcesOnNode + ); + //update the available resources - node.available_resources.hru -= resources.hru; - node.available_resources.sru -= resources.sru; - node.available_resources.cru -= resources.cru; - node.available_resources.mru -= resources.mru; + node.used_resources.hru += resources.hru; + node.used_resources.sru += resources.sru; + node.used_resources.cru += resources.cru; + node.used_resources.mru += resources.mru; + pallet_tfgrid::Nodes::::insert(node.id, &node); Ok(().into()) @@ -783,17 +787,13 @@ impl Pallet { let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; //update the available resources - node.available_resources.hru += resources.hru; - node.available_resources.sru += resources.sru; - node.available_resources.cru += resources.cru; - node.available_resources.mru += resources.mru; + node.used_resources.hru -= resources.hru; + node.used_resources.sru -= resources.sru; + node.used_resources.cru -= resources.cru; + node.used_resources.mru -= resources.mru; // if all resources are free shutdown the node - if node.available_resources.hru == node.resources.hru - && node.available_resources.sru == node.resources.sru - && node.available_resources.cru == node.resources.cru - && node.available_resources.mru == node.resources.mru - { + if node.can_be_shutdown() { Self::deposit_event(Event::ChangePowerTarget { farm_id: node.farm_id, node_id: node_id, @@ -820,25 +820,18 @@ impl Pallet { let mut suitable_nodes = Vec::new(); for node_id in nodes_in_farm { let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - if node.available_resources.hru >= resources.hru - && node.available_resources.sru >= resources.sru - && node.available_resources.cru >= resources.cru - && node.available_resources.mru >= resources.mru + if node.can_claim_resources(resources) + && !ActiveRentContractForNode::::contains_key(node_id) { suitable_nodes.push(node); } } - ensure!( - !suitable_nodes.is_empty(), - Error::::NotEnoughResourcesOnNode - ); + ensure!(!suitable_nodes.is_empty(), Error::::NoSuitableNodeInFarm); // sort the suitable nodes on power_target: the nodes that are Up will be first in the list suitable_nodes.sort_by(|a, b| a.power_target.cmp(&b.power_target)); - Self::_claim_resources_on_node(suitable_nodes[0].id, resources)?; - Ok(suitable_nodes[0].id) } @@ -851,9 +844,17 @@ impl Pallet { let mut id = ContractID::::get(); id = id + 1; - if let types::ContractData::NodeContract(ref mut nc) = contract_type { - Self::_reserve_ip(id, nc)?; - }; + match contract_type { + types::ContractData::DeploymentContract(ref mut nc) => { + Self::_reserve_ip(id, nc)?; + Self::_claim_resources_on_node(nc.node_id, nc.resources)?; + Self::_change_power_target_node(nc.node_id, PowerTarget::Up)?; + }, + types::ContractData::RentContract(ref mut nc) => { + Self::_change_power_target_node(nc.node_id, PowerTarget::Up)?; + }, + _ => {} + } Self::validate_solution_provider(solution_provider_id)?; @@ -884,11 +885,13 @@ impl Pallet { Ok(contract) } + #[transactional] pub fn _update_node_contract( account_id: T::AccountId, contract_id: u64, deployment_hash: DeploymentHash, deployment_data: DeploymentDataInput, + extra_resources: Option, ) -> DispatchResultWithPostInfo { let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; let twin = @@ -920,9 +923,16 @@ impl Pallet { node_contract.deployment_hash = deployment_hash; node_contract.deployment_data = deployment_data; + // update the resources with the extra resources + if let Some(extra_resources) = extra_resources { + Self::_claim_resources_on_node(node_contract.node_id, extra_resources)?; + let resources = node_contract.resources; + resources.add(&extra_resources); + node_contract.resources = resources; + } // override values - contract.contract_type = types::ContractData::NodeContract(node_contract); + contract.contract_type = types::ContractData::DeploymentContract(node_contract); let state = contract.state.clone(); Self::_update_contract_state(&mut contract, &state)?; @@ -932,6 +942,7 @@ impl Pallet { Ok(().into()) } + #[transactional] pub fn _cancel_contract( account_id: T::AccountId, contract_id: u64, @@ -947,26 +958,18 @@ impl Pallet { // If it's a rent contract and it still has active workloads, don't allow cancellation. match contract.contract_type { - types::ContractData::RentContract(ref c) => { - let rent_contract = Self::get_rent_contract(&contract)?; + types::ContractData::RentContract(ref rent_contract) => { let active_node_contracts = ActiveNodeContracts::::get(rent_contract.node_id); ensure!( active_node_contracts.len() == 0, Error::::NodeHasActiveContracts ); - let node = - pallet_tfgrid::Nodes::::get(c.node_id).ok_or(Error::::NodeNotExists)?; - let used_resources = node.resources; - Self::_un_claim_resources_on_node(c.node_id, used_resources)?; - }, - types::ContractData::DeploymentContract(ref c) => { - let used_resources = NodeContractResources::::get(contract_id).used; - Self::_un_claim_resources_on_node(c.node_id, used_resources)?; - }, - _ => { - //TODO + Self::_change_power_target_node(rent_contract.node_id, PowerTarget::Down)?; } - + types::ContractData::DeploymentContract(ref deployment_contract) => { + Self::_un_claim_resources_on_node(deployment_contract.node_id, deployment_contract.resources)?; + } + _ => {} } Self::_update_contract_state(&mut contract, &types::ContractState::Deleted(cause))?; @@ -977,44 +980,44 @@ impl Pallet { Ok(().into()) } - pub fn _report_contract_resources( - source: T::AccountId, - contract_resources: Vec, - ) -> DispatchResultWithPostInfo { - ensure!( - pallet_tfgrid::TwinIdByAccountID::::contains_key(&source), - Error::::TwinNotExists - ); - let twin_id = - pallet_tfgrid::TwinIdByAccountID::::get(&source).ok_or(Error::::TwinNotExists)?; - ensure!( - pallet_tfgrid::NodeIdByTwinID::::contains_key(twin_id), - Error::::NodeNotExists - ); - let node_id = pallet_tfgrid::NodeIdByTwinID::::get(twin_id); - - for contract_resource in contract_resources { - // we know contract exists, fetch it - // if the node is trying to send garbage data we can throw an error here - if let Some(contract) = Contracts::::get(contract_resource.contract_id) { - let node_contract = Self::get_node_contract(&contract)?; - ensure!( - node_contract.node_id == node_id, - Error::::NodeNotAuthorizedToComputeReport - ); - - // Do insert - NodeContractResources::::insert( - contract_resource.contract_id, - &contract_resource, - ); - // deposit event - Self::deposit_event(Event::UpdatedUsedResources(contract_resource)); - } - } - - Ok(Pays::No.into()) - } + // pub fn _report_contract_resources( + // source: T::AccountId, + // contract_resources: Vec, + // ) -> DispatchResultWithPostInfo { + // ensure!( + // pallet_tfgrid::TwinIdByAccountID::::contains_key(&source), + // Error::::TwinNotExists + // ); + // let twin_id = + // pallet_tfgrid::TwinIdByAccountID::::get(&source).ok_or(Error::::TwinNotExists)?; + // ensure!( + // pallet_tfgrid::NodeIdByTwinID::::contains_key(twin_id), + // Error::::NodeNotExists + // ); + // let node_id = pallet_tfgrid::NodeIdByTwinID::::get(twin_id); + + // for contract_resource in contract_resources { + // // we know contract exists, fetch it + // // if the node is trying to send garbage data we can throw an error here + // if let Some(contract) = Contracts::::get(contract_resource.contract_id) { + // let node_contract = Self::get_node_contract(&contract)?; + // ensure!( + // node_contract.node_id == node_id, + // Error::::NodeNotAuthorizedToComputeReport + // ); + + // // Do insert + // NodeContractResources::::insert( + // contract_resource.contract_id, + // &contract_resource, + // ); + // // deposit event + // Self::deposit_event(Event::UpdatedUsedResources(contract_resource)); + // } + // } + + // Ok(Pays::No.into()) + // } pub fn _compute_reports( source: T::AccountId, @@ -1123,7 +1126,7 @@ impl Pallet { return Err(>::OffchainSignedTxError); } - // Bills a contract (NodeContract or NameContract) + // Bills a contract (DeploymentContract or NameContract) // Calculates how much TFT is due by the user and distributes the rewards fn bill_contract(contract_id: u64) -> DispatchResultWithPostInfo { // Clean up contract from blling loop if it not exists anymore @@ -1407,10 +1410,7 @@ impl Pallet { if let Some(contract) = contract { match contract.contract_type.clone() { - types::ContractData::DeploymentContract(_) => { - //TODO - } - types::ContractData::NodeContract(mut node_contract) => { + types::ContractData::DeploymentContract(mut node_contract) => { Self::remove_active_node_contract(node_contract.node_id, contract_id); if node_contract.public_ips > 0 { match Self::_free_ip(contract_id, &mut node_contract) { @@ -1426,7 +1426,7 @@ impl Pallet { node_contract.node_id, &node_contract.deployment_hash, ); - NodeContractResources::::remove(contract_id); + //NodeContractResources::::remove(contract_id); ContractBillingInformationByID::::remove(contract_id); Self::deposit_event(Event::NodeContractCanceled { @@ -1668,7 +1668,7 @@ impl Pallet { pub fn _reserve_ip( contract_id: u64, - node_contract: &mut types::NodeContract, + node_contract: &mut types::DeploymentContract, ) -> DispatchResultWithPostInfo { if node_contract.public_ips == 0 { return Ok(().into()); @@ -1736,7 +1736,7 @@ impl Pallet { pub fn _free_ip( contract_id: u64, - node_contract: &mut types::NodeContract, + node_contract: &mut types::DeploymentContract, ) -> DispatchResultWithPostInfo { let node = pallet_tfgrid::Nodes::::get(node_contract.node_id) .ok_or(Error::::NodeNotExists)?; @@ -1777,9 +1777,9 @@ impl Pallet { pub fn get_node_contract( contract: &types::Contract, - ) -> Result, DispatchErrorWithPostInfo> { + ) -> Result, DispatchErrorWithPostInfo> { match contract.contract_type.clone() { - types::ContractData::NodeContract(c) => Ok(c), + types::ContractData::DeploymentContract(c) => Ok(c), _ => { return Err(DispatchErrorWithPostInfo::from( Error::::InvalidContractType, diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index d68bd8dff..42e1585e8 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -48,9 +48,8 @@ impl Contract { pub fn get_node_id(&self) -> u32 { match self.contract_type.clone() { ContractData::RentContract(c) => c.node_id, - ContractData::NodeContract(c) => c.node_id, - ContractData::NameContract(_) => 0, ContractData::DeploymentContract(c) => c.node_id, + ContractData::NameContract(_) => 0, } } } @@ -58,7 +57,7 @@ impl Contract { #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] -pub struct NodeContract { +pub struct DeploymentContract { pub node_id: u32, // Hash of the deployment, set by the user // Max 32 bytes @@ -66,6 +65,7 @@ pub struct NodeContract { pub deployment_data: BoundedVec>, pub public_ips: u32, pub public_ips_list: BoundedVec, MaxNodeContractPublicIPs>, + pub resources: Resources, } #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] @@ -92,31 +92,13 @@ pub struct RentContract { pub node_id: u32, } -#[derive( - PartialEq, - Eq, - PartialOrd, - Ord, - Clone, - Encode, - Decode, - Default, - RuntimeDebugNoBound, - TypeInfo, - MaxEncodedLen, -)] -pub struct DeploymentContract { - pub node_id: u32, -} - #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub enum ContractData { - NodeContract(NodeContract), + DeploymentContract(DeploymentContract), NameContract(NameContract), RentContract(RentContract), - DeploymentContract(DeploymentContract), } impl Default for ContractData { diff --git a/substrate-node/pallets/pallet-tfgrid/src/lib.rs b/substrate-node/pallets/pallet-tfgrid/src/lib.rs index c6d94c1dd..1f2ffccb7 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/lib.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/lib.rs @@ -971,15 +971,13 @@ pub mod pallet { let created = >::get().saturated_into::() / 1000; let power_target = PowerTarget::Up; - let available_resources = resources.clone(); - let mut new_node = Node { version: TFGRID_NODE_VERSION, id, farm_id, twin_id, resources, - available_resources, + used_resources: Resources {hru:0, sru:0, cru:0, mru:0 }, location, country, city, diff --git a/substrate-node/support/src/types.rs b/substrate-node/support/src/types.rs index 12fda42eb..c5049daf3 100644 --- a/substrate-node/support/src/types.rs +++ b/substrate-node/support/src/types.rs @@ -87,7 +87,7 @@ pub struct Node { pub farm_id: u32, pub twin_id: u32, pub resources: Resources, - pub available_resources: Resources, + pub used_resources: Resources, pub location: Location, pub country: Vec, pub city: Vec, @@ -104,6 +104,22 @@ pub struct Node { pub connection_price: u32, } +impl Node { + pub fn can_claim_resources(&self, resources: Resources) -> bool { + self.resources.hru - self.used_resources.hru >= resources.hru + && self.resources.hru - self.used_resources.sru >= resources.sru + && self.resources.hru - self.used_resources.cru >= resources.cru + && self.resources.hru - self.used_resources.mru >= resources.mru + } + + pub fn can_be_shutdown(&self) -> bool { + self.used_resources.hru == 0 + && self.used_resources.sru == 0 + && self.used_resources.cru == 0 + && self.used_resources.mru == 0 + } +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo)] pub struct Interface { pub name: Name, From 62c20277f5e44e11d09d42fa05900444cc78a2b7 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 7 Oct 2022 11:35:10 +0200 Subject: [PATCH 45/87] feat: made update_node_contract deprecated #473 --- .../pallets/pallet-smart-contract/src/lib.rs | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index b50dd0e76..0fc44f2fa 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -385,6 +385,7 @@ pub mod pallet { #[pallet::call] impl Pallet { + // DEPRECATED #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn create_node_contract( _origin: OriginFor, @@ -396,23 +397,16 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { Err(DispatchErrorWithPostInfo::from(Error::::MethodIsDeprecated).into()) } - + + // DEPRECATED #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn update_node_contract( - origin: OriginFor, - contract_id: u64, - deployment_hash: DeploymentHash, - deployment_data: DeploymentDataInput, - extra_resources: Option, + _origin: OriginFor, + _contract_id: u64, + _deployment_hash: DeploymentHash, + _deployment_data: DeploymentDataInput, ) -> DispatchResultWithPostInfo { - let account_id = ensure_signed(origin)?; - Self::_update_node_contract( - account_id, - contract_id, - deployment_hash, - deployment_data, - extra_resources, - ) + Err(DispatchErrorWithPostInfo::from(Error::::MethodIsDeprecated).into()) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] @@ -467,6 +461,24 @@ pub mod pallet { ) } + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn update_deployment_contract( + origin: OriginFor, + contract_id: u64, + deployment_hash: DeploymentHash, + deployment_data: DeploymentDataInput, + extra_resources: Option, + ) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_update_deployment_contract( + account_id, + contract_id, + deployment_hash, + deployment_data, + extra_resources, + ) + } + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn add_nru_reports( origin: OriginFor, @@ -849,10 +861,10 @@ impl Pallet { Self::_reserve_ip(id, nc)?; Self::_claim_resources_on_node(nc.node_id, nc.resources)?; Self::_change_power_target_node(nc.node_id, PowerTarget::Up)?; - }, + } types::ContractData::RentContract(ref mut nc) => { Self::_change_power_target_node(nc.node_id, PowerTarget::Up)?; - }, + } _ => {} } @@ -886,7 +898,7 @@ impl Pallet { } #[transactional] - pub fn _update_node_contract( + pub fn _update_deployment_contract( account_id: T::AccountId, contract_id: u64, deployment_hash: DeploymentHash, @@ -967,7 +979,10 @@ impl Pallet { Self::_change_power_target_node(rent_contract.node_id, PowerTarget::Down)?; } types::ContractData::DeploymentContract(ref deployment_contract) => { - Self::_un_claim_resources_on_node(deployment_contract.node_id, deployment_contract.resources)?; + Self::_un_claim_resources_on_node( + deployment_contract.node_id, + deployment_contract.resources, + )?; } _ => {} } From 8a44388d2a45a6918d58391ea6e99478b8068cda Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 7 Oct 2022 12:44:57 +0200 Subject: [PATCH 46/87] feat: fixed tests #473 --- .../pallets/pallet-smart-contract/src/lib.rs | 5 +- .../pallet-smart-contract/src/tests.rs | 424 ++++++++++-------- 2 files changed, 248 insertions(+), 181 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 0fc44f2fa..6691f646f 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -397,7 +397,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { Err(DispatchErrorWithPostInfo::from(Error::::MethodIsDeprecated).into()) } - + // DEPRECATED #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn update_node_contract( @@ -600,6 +600,9 @@ impl Pallet { ); Self::get_rent_contract(&contract)?.node_id } else { + // the farm cannot be a dedicated farm unless you provide a rent contract id + let farm = pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; + ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); Self::_find_suitable_node_in_farm(farm_id, resources)? }; diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index c086e3014..6ebcfbd76 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -24,41 +24,45 @@ const GIGABYTE: u64 = 1024 * 1024 * 1024; // -------------------- // #[test] -fn test_create_node_contract_works() { +fn test_create_deployment_contract_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - None + None, + None, )); }); } #[test] -fn test_create_node_contract_with_public_ips_works() { +fn test_create_deployment_contract_with_public_ips_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, - None + None, + None, )); - let node_contract = SmartContractModule::contracts(1).unwrap(); + let deployment_contract = SmartContractModule::contracts(1).unwrap(); - match node_contract.contract_type.clone() { - types::ContractData::NodeContract(c) => { + match deployment_contract.contract_type.clone() { + types::ContractData::DeploymentContract(c) => { let farm = TfgridModule::farms(1).unwrap(); assert_eq!(farm.public_ips[0].contract_id, 1); @@ -78,48 +82,54 @@ fn test_create_node_contract_with_public_ips_works() { } #[test] -fn test_create_node_contract_with_undefined_node_fails() { +fn test_create_deployment_contract_with_undefined_node_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); assert_noop!( - SmartContractModule::create_node_contract( + SmartContractModule::create_deployment_contract( Origin::signed(alice()), 2, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - None + None, + None, ), - Error::::NodeNotExists + Error::::FarmNotExists ); }); } #[test] -fn test_create_node_contract_with_same_hash_and_node_fails() { +fn test_create_deployment_contract_with_same_hash_and_node_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); let h = generate_deployment_hash(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, h, get_deployment_data(), + get_resources(), 0, - None + None, + None, )); assert_noop!( - SmartContractModule::create_node_contract( + SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, h, get_deployment_data(), + get_resources(), 0, + None, None ), Error::::ContractIsNotUnique @@ -128,19 +138,21 @@ fn test_create_node_contract_with_same_hash_and_node_fails() { } #[test] -fn test_create_node_contract_which_was_canceled_before_works() { +fn test_create_deployment_contract_which_was_canceled_before_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); let h = generate_deployment_hash(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, h, get_deployment_data(), + get_resources(), 0, - None + None, + None, )); let contract_id = SmartContractModule::node_contract_by_hash(1, h); assert_eq!(contract_id, 1); @@ -151,12 +163,14 @@ fn test_create_node_contract_which_was_canceled_before_works() { )); let h = generate_deployment_hash(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, h, get_deployment_data(), + get_resources(), 0, + None, None )); let contract_id = SmartContractModule::node_contract_by_hash(1, h); @@ -164,38 +178,52 @@ fn test_create_node_contract_which_was_canceled_before_works() { }); } +// todo test resources on a contract +// todo test updating resources (increase) +// todo test choosing the best node +// todo test multiple nodes same farm so that there are no more nodes left +// todo test waking up a node +// todo test shutting down nodes (and not shutting down the lest node in the farm) +// todo test creating deployment contract using rent contract id (+ all possible failures) +// todo test billing +// todo test grouping contracts + #[test] -fn test_update_node_contract_works() { +fn test_update_deployment_contract_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - - assert_ok!(SmartContractModule::create_node_contract( + let resources = get_resources(); + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + resources.clone(), 0, - None + None, + None, )); let new_hash = generate_deployment_hash(); let deployment_data = get_deployment_data(); - assert_ok!(SmartContractModule::update_node_contract( + assert_ok!(SmartContractModule::update_deployment_contract( Origin::signed(alice()), 1, new_hash, - get_deployment_data() + get_deployment_data(), + None, )); - let node_contract = types::NodeContract { + let deployment_contract = types::DeploymentContract { node_id: 1, deployment_hash: new_hash, deployment_data, public_ips: 0, public_ips_list: Vec::new().try_into().unwrap(), + resources: resources, }; - let contract_type = types::ContractData::NodeContract(node_contract); + let contract_type = types::ContractData::DeploymentContract(deployment_contract); let expected_contract_value = types::Contract { contract_id: 1, @@ -206,31 +234,32 @@ fn test_update_node_contract_works() { solution_provider_id: None, }; - let node_contract = SmartContractModule::contracts(1).unwrap(); - assert_eq!(node_contract, expected_contract_value); + let deployment_contract = SmartContractModule::contracts(1).unwrap(); + assert_eq!(deployment_contract, expected_contract_value); let contracts = SmartContractModule::active_node_contracts(1); assert_eq!(contracts.len(), 1); assert_eq!(contracts[0], 1); - let node_contract_id_by_hash = SmartContractModule::node_contract_by_hash(1, new_hash); - assert_eq!(node_contract_id_by_hash, 1); + let deployment_contract_id_by_hash = SmartContractModule::node_contract_by_hash(1, new_hash); + assert_eq!(deployment_contract_id_by_hash, 1); }); } #[test] -fn test_update_node_contract_not_exists_fails() { +fn test_update_deployment_contract_not_exists_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); assert_noop!( - SmartContractModule::update_node_contract( + SmartContractModule::update_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), - get_deployment_data() + get_deployment_data(), + None, ), Error::::ContractNotExists ); @@ -238,26 +267,29 @@ fn test_update_node_contract_not_exists_fails() { } #[test] -fn test_update_node_contract_wrong_twins_fails() { +fn test_update_deployment_contract_wrong_twins_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - None + None, + None, )); assert_noop!( - SmartContractModule::update_node_contract( + SmartContractModule::update_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), - get_deployment_data() + get_deployment_data(), + Some(Resources { cru: 1, hru: 0, mru: 1 * GIGABYTE, sru: 10 * GIGABYTE }), ), Error::::TwinNotAuthorizedToUpdateContract ); @@ -265,18 +297,20 @@ fn test_update_node_contract_wrong_twins_fails() { } #[test] -fn test_cancel_node_contract_works() { +fn test_cancel_deployment_contract_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - None + None, + None, )); assert_ok!(SmartContractModule::cancel_contract( @@ -284,8 +318,8 @@ fn test_cancel_node_contract_works() { 1 )); - let node_contract = SmartContractModule::contracts(1); - assert_eq!(node_contract, None); + let deployment_contract = SmartContractModule::contracts(1); + assert_eq!(deployment_contract, None); let contracts = SmartContractModule::active_node_contracts(1); assert_eq!(contracts.len(), 0); @@ -293,40 +327,46 @@ fn test_cancel_node_contract_works() { } #[test] -fn test_create_multiple_node_contracts_works() { +fn test_create_multiple_deployment_contracts_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); - let node_contracts = SmartContractModule::active_node_contracts(1); - assert_eq!(node_contracts.len(), 3); + let deployment_contracts = SmartContractModule::active_node_contracts(1); + assert_eq!(deployment_contracts.len(), 3); // now cancel 1 and check if the storage maps are updated correctly assert_ok!(SmartContractModule::cancel_contract( @@ -334,23 +374,25 @@ fn test_create_multiple_node_contracts_works() { 1 )); - let node_contracts = SmartContractModule::active_node_contracts(1); - assert_eq!(node_contracts.len(), 2); + let deployment_contracts = SmartContractModule::active_node_contracts(1); + assert_eq!(deployment_contracts.len(), 2); }); } #[test] -fn test_cancel_node_contract_frees_public_ips_works() { +fn test_cancel_deployment_contract_frees_public_ips_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, + None, None )); @@ -368,7 +410,7 @@ fn test_cancel_node_contract_frees_public_ips_works() { } #[test] -fn test_cancel_node_contract_not_exists_fails() { +fn test_cancel_deployment_contract_not_exists_fails() { new_test_ext().execute_with(|| { prepare_farm_and_node(); @@ -380,17 +422,19 @@ fn test_cancel_node_contract_not_exists_fails() { } #[test] -fn test_cancel_node_contract_wrong_twins_fails() { +fn test_cancel_deployment_contract_wrong_twins_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); @@ -587,13 +631,15 @@ fn test_create_rent_contract_on_node_in_use_fails() { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, - None + None, + None, )); assert_noop!( @@ -619,18 +665,20 @@ fn test_create_rent_contract_non_dedicated_empty_node_works() { } #[test] -fn test_create_node_contract_on_dedicated_node_without_rent_contract_fails() { +fn test_create_deployment_contract_on_dedicated_node_without_rent_contract_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); assert_noop!( - SmartContractModule::create_node_contract( + SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, + None, None ), Error::::NodeNotAvailableToDeploy @@ -639,7 +687,7 @@ fn test_create_node_contract_on_dedicated_node_without_rent_contract_fails() { } #[test] -fn test_create_node_contract_when_having_a_rentcontract_works() { +fn test_create_deployment_contract_when_having_a_rentcontract_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); @@ -650,19 +698,21 @@ fn test_create_node_contract_when_having_a_rentcontract_works() { None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, - None + None, + Some(1) )); }) } #[test] -fn test_create_node_contract_when_someone_else_has_rent_contract_fails() { +fn test_create_deployment_contract_when_someone_else_has_rent_contract_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); @@ -675,23 +725,25 @@ fn test_create_node_contract_when_someone_else_has_rent_contract_fails() { )); // try to create node contract with Alice - // Alice not the owner of the rent contract so she is unauthorized to deploy a node contract + // Alice not the owner of the rent contract so she is unauthorized to deploy a deployment contract assert_noop!( - SmartContractModule::create_node_contract( + SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, - None + None, + Some(1), ), - Error::::NodeHasRentContract + Error::::NotAuthorizedToCreateDeploymentContract ); }) } #[test] -fn test_cancel_rent_contract_with_active_node_contracts_fails() { +fn test_cancel_rent_contract_with_active_deployment_contracts_fails() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -705,13 +757,15 @@ fn test_cancel_rent_contract_with_active_node_contracts_fails() { None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, - None + None, + Some(1) )); assert_noop!( @@ -725,7 +779,7 @@ fn test_cancel_rent_contract_with_active_node_contracts_fails() { // ----------------------- // #[test] -fn test_node_contract_billing_details() { +fn test_deployment_contract_billing_details() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); @@ -735,17 +789,17 @@ fn test_node_contract_billing_details() { let twin = TfgridModule::twins(2).unwrap(); let initial_twin_balance = Balances::free_balance(&twin.account_id); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, + None, None )); - push_contract_resources_used(1); - push_nru_report_for_contract(1, 10); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(1); @@ -809,7 +863,7 @@ fn test_node_contract_billing_details() { } #[test] -fn test_node_contract_billing_details_with_solution_provider() { +fn test_deployment_contract_billing_details_with_solution_provider() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); @@ -823,17 +877,17 @@ fn test_node_contract_billing_details_with_solution_provider() { let initial_twin_balance = Balances::free_balance(&twin.account_id); let initial_total_issuance = Balances::total_issuance(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, - Some(1) + Some(1), + None, )); - push_contract_resources_used(1); - push_nru_report_for_contract(1, 10); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(1); @@ -866,12 +920,14 @@ fn test_multiple_contracts_billing_loop_works() { run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, + None, None )); assert_ok!(SmartContractModule::create_name_contract( @@ -903,26 +959,26 @@ fn test_multiple_contracts_billing_loop_works() { } #[test] -fn test_node_contract_billing_cycles() { +fn test_deployment_contract_billing_cycles() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); let contract_id = 1; let twin_id = 2; - push_contract_resources_used(1); - let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); pool_state .write() @@ -974,20 +1030,24 @@ fn test_node_multiple_contract_billing_cycles() { run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); let twin_id = 2; @@ -998,8 +1058,6 @@ fn test_node_multiple_contract_billing_cycles() { pool_state .write() .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); - push_contract_resources_used(1); - push_contract_resources_used(2); let (amount_due_contract_1, discount_received) = calculate_tft_cost(1, twin_id, 11); run_to_block(12, Some(&mut pool_state)); @@ -1022,19 +1080,21 @@ fn test_node_multiple_contract_billing_cycles() { } #[test] -fn test_node_contract_billing_cycles_delete_node_cancels_contract() { +fn test_deployment_contract_billing_cycles_delete_node_cancels_contract() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, + None, None )); let contract_id = 1; @@ -1045,7 +1105,6 @@ fn test_node_contract_billing_cycles_delete_node_cancels_contract() { .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); } - push_contract_resources_used(1); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); run_to_block(11, Some(&mut pool_state)); @@ -1117,19 +1176,21 @@ fn test_node_contract_billing_cycles_delete_node_cancels_contract() { } #[test] -fn test_node_contract_only_public_ip_billing_cycles() { +fn test_deployment_contract_only_public_ip_billing_cycles() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, + None, None )); let contract_id = 1; @@ -1167,19 +1228,21 @@ fn test_node_contract_only_public_ip_billing_cycles() { } #[test] -fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { +fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); @@ -1194,7 +1257,6 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { 11 + i * 10, ); } - push_contract_resources_used(1); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); run_to_block(11, Some(&mut pool_state)); @@ -1223,19 +1285,21 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_works() { } #[test] -fn test_node_contract_billing_fails() { +fn test_deployment_contract_billing_fails() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { run_to_block(1, Some(&mut pool_state)); // Creates a farm and node and sets the price of tft to 0 which raises an error later prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, + None, None )); @@ -1264,7 +1328,7 @@ fn test_node_contract_billing_fails() { } #[test] -fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balance_works() { +fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_without_balance_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); @@ -1276,20 +1340,20 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balanc info!("initial twin balance: {:?}", initial_twin_balance); let initial_total_issuance = Balances::total_issuance(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - None + None, + None, )); let contract_id = 1; let twin_id = 2; - push_contract_resources_used(1); - let (amount_due_1, discount_received) = calculate_tft_cost(contract_id, twin_id, 10); pool_state .write() @@ -1344,24 +1408,24 @@ fn test_node_contract_billing_cycles_cancel_contract_during_cycle_without_balanc } #[test] -fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { +fn test_deployment_contract_out_of_funds_should_move_state_to_graceperiod_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); - push_contract_resources_used(1); - // cycle 1 pool_state .write() @@ -1394,19 +1458,21 @@ fn test_node_contract_out_of_funds_should_move_state_to_graceperiod_works() { } #[test] -fn test_restore_node_contract_in_grace_works() { +fn test_restore_deployment_contract_in_grace_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); @@ -1415,7 +1481,6 @@ fn test_restore_node_contract_in_grace_works() { .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); } - push_contract_resources_used(1); // cycle 1 run_to_block(11, Some(&mut pool_state)); @@ -1453,24 +1518,24 @@ fn test_restore_node_contract_in_grace_works() { } #[test] -fn test_node_contract_grace_period_cancels_contract_when_grace_period_ends_works() { +fn test_deployment_contract_grace_period_cancels_contract_when_grace_period_ends_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, + None, None )); - push_contract_resources_used(1); - // cycle 1 pool_state .write() @@ -1716,7 +1781,7 @@ fn test_rent_contract_canceled_mid_cycle_should_bill_for_remainder() { } #[test] -fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billing_works() { +fn test_create_rent_contract_and_deployment_contract_excludes_deployment_contract_from_billing_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -1730,15 +1795,16 @@ fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billi None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - None + None, + Some(1) )); - push_contract_resources_used(2); pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); @@ -1752,16 +1818,20 @@ fn test_create_rent_contract_and_node_contract_excludes_node_contract_from_billi check_report_cost(1, amount_due_as_u128, 11, discount_received); let our_events = System::events(); - // Event 1: Rent contract created - // Event 2: Node Contract created - // Event 4: Rent contract billed - // => no Node Contract billed event - assert_eq!(our_events.len(), 6); + // Event 1: PriceStored + // Event 2: AveragePriceStored + // Event 3: Rent contract created + // Event 4: Node Contract created + // Event 5: Contract billed + for e in our_events.clone().iter() { + log::info!("{:?}", e); + } + assert_eq!(our_events.len(), 5); }); } #[test] -fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_works() { +fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_contracts_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -1775,15 +1845,16 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - None + None, + Some(1) )); - push_contract_resources_used(2); // run 12 cycles, contracts should cancel after 11 due to lack of funds for _ in 0..11 { @@ -1805,12 +1876,11 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ let our_events = System::events(); // Event 1: Rent contract created // Event 2: Node Contract created - // Event 3: Updated used resources - // Event 4: Grace period started rent contract - // Event 5: Grace period started node contract - // Event 6-17: Rent contract billed - // Event 18: Node contract canceled - // Event 19: Rent contract Canceled + // Event 3: Grace period started rent contract + // Event 4: Grace period started node contract + // Event 5-16: Rent contract billed + // Event 17: Node contract canceled + // Event 18: Rent contract Canceled // => no Node Contract billed event assert_eq!(our_events.len(), 10); @@ -1819,7 +1889,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ } assert_eq!( - our_events[5], + our_events[4], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodStarted { @@ -1830,7 +1900,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ })) ); assert_eq!( - our_events[6], + our_events[5], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodStarted { @@ -1842,7 +1912,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ ); assert_eq!( - our_events[8], + our_events[17], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::NodeContractCanceled { @@ -1852,7 +1922,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ })) ); assert_eq!( - our_events[9], + our_events[18], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::RentContractCanceled { @@ -1863,7 +1933,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_node_contracts_ } #[test] -fn test_create_rent_contract_and_node_contract_with_ip_billing_works() { +fn test_create_rent_contract_and_deployment_contract_with_ip_billing_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -1877,13 +1947,15 @@ fn test_create_rent_contract_and_node_contract_with_ip_billing_works() { None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 1, - None + None, + Some(1) )); // 2 contracts => we expect 2 calls to bill_contract @@ -2021,7 +2093,7 @@ fn test_restore_rent_contract_in_grace_works() { } #[test] -fn test_restore_rent_contract_and_node_contracts_in_grace_works() { +fn test_restore_rent_contract_and_deployment_contracts_in_grace_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2034,15 +2106,16 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { node_id, None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - None + None, + Some(1) )); - push_contract_resources_used(2); // cycle 1 pool_state @@ -2058,7 +2131,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { let our_events = System::events(); assert_eq!( - our_events[5], + our_events[4], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodStarted { @@ -2069,7 +2142,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { })) ); assert_eq!( - our_events[6], + our_events[5], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodStarted { @@ -2121,7 +2194,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { let our_events = System::events(); assert_eq!( - our_events[8], + our_events[10], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodEnded { @@ -2131,7 +2204,7 @@ fn test_restore_rent_contract_and_node_contracts_in_grace_works() { })) ); assert_eq!( - our_events[9], + our_events[11], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodEnded { @@ -2197,7 +2270,7 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works } #[test] -fn test_rent_contract_and_node_contract_canceled_when_node_is_deleted_works() { +fn test_rent_contract_and_deployment_contract_canceled_when_node_is_deleted_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2211,15 +2284,16 @@ fn test_rent_contract_and_node_contract_canceled_when_node_is_deleted_works() { None )); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - None + None, + Some(1) )); - push_contract_resources_used(2); // 2 contracts => 2 calls to bill_contract pool_state @@ -2312,26 +2386,28 @@ fn test_create_solution_provider_fails_if_take_to_high() { } #[test] -fn test_create_node_contract_with_solution_provider_works() { +fn test_create_deployment_contract_with_solution_provider_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); prepare_solution_provider(); - assert_ok!(SmartContractModule::create_node_contract( + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - Some(1) + Some(1), + None )); }); } #[test] -fn test_create_node_contract_with_solution_provider_fails_if_not_approved() { +fn test_create_deployment_contract_with_solution_provider_fails_if_not_approved() { new_test_ext().execute_with(|| { prepare_farm_and_node(); @@ -2349,13 +2425,15 @@ fn test_create_node_contract_with_solution_provider_fails_if_not_approved() { )); assert_noop!( - SmartContractModule::create_node_contract( + SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), + get_resources(), 0, - Some(1) + Some(1), + None ), Error::::SolutionProviderNotApproved ); @@ -2514,24 +2592,6 @@ fn push_nru_report_for_contract(contract_id: u64, block_number: u64) { )); } -fn push_contract_resources_used(contract_id: u64) { - let mut resources = Vec::new(); - resources.push(types::ContractResources { - contract_id, - used: Resources { - cru: 2, - hru: 0, - mru: 2 * GIGABYTE, - sru: 60 * GIGABYTE, - }, - }); - - assert_ok!(SmartContractModule::report_contract_resources( - Origin::signed(alice()), - resources - )); -} - fn check_report_cost( contract_id: u64, amount_billed: u64, @@ -2831,3 +2891,7 @@ fn get_deployment_data() -> crate::DeploymentDataInput { ) .unwrap() } + +fn get_resources() -> Resources { + Resources { cru: 2, hru: 0, mru: 2 * GIGABYTE, sru: 60 * GIGABYTE } +} \ No newline at end of file From 78731ca9cb0cd90443926bff06e3c1a4621e06cc Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 7 Oct 2022 15:57:40 +0200 Subject: [PATCH 47/87] feat: fixed integration tests #473 --- .../pallets/pallet-smart-contract/src/lib.rs | 8 +-- substrate-node/tests/TfChainClient.py | 59 ++++++++----------- substrate-node/tests/integration_tests.robot | 12 ++-- 3 files changed, 34 insertions(+), 45 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 6691f646f..73c91d0ba 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -137,10 +137,10 @@ pub mod pallet { pub type ContractBillingInformationByID = StorageMap<_, Blake2_128Concat, u64, ContractBillingInformation, ValueQuery>; - //#[pallet::storage] - //#[pallet::getter(fn node_contract_resources)] - //pub type NodeContractResources = - // StorageMap<_, Blake2_128Concat, u64, ContractResources, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn node_contract_resources)] + pub type NodeContractResources = + StorageMap<_, Blake2_128Concat, u64, ContractResources, ValueQuery>; #[pallet::storage] #[pallet::getter(fn node_contract_by_hash)] diff --git a/substrate-node/tests/TfChainClient.py b/substrate-node/tests/TfChainClient.py index 3ff42fa5e..dce5afc7c 100644 --- a/substrate-node/tests/TfChainClient.py +++ b/substrate-node/tests/TfChainClient.py @@ -367,20 +367,28 @@ def get_node(self, id: int = 1, port: int = DEFAULT_PORT): q = substrate.query("TfgridModule", "Nodes", [id]) return q.value - def create_node_contract(self, node_id: int = 1, deployment_data: bytes = randbytes(32), - deployment_hash: bytes = randbytes(32), public_ips: int = 0, - solution_provider_id: int | None = None, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + def create_deployment_contract(self, farm_id: int = 1, deployment_data: bytes = randbytes(32), + deployment_hash: bytes = randbytes(32), public_ips: int = 0, hru: int = 0, sru: int = 0, + cru: int = 0, mru: int = 0, solution_provider_id: int | None = None, + rent_contract_id: int | None = None, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") params = { - "node_id": node_id, + "farm_id": farm_id, "deployment_data": deployment_data, "deployment_hash": deployment_hash, "public_ips": public_ips, - "solution_provider_id": solution_provider_id + "resources": { + "hru": hru * GIGABYTE, + "sru": sru * GIGABYTE, + "cru": cru, + "mru": mru * GIGABYTE + }, + "solution_provider_id": solution_provider_id, + "rent_contract_id": rent_contract_id } call = substrate.compose_call( - "SmartContractModule", "create_node_contract", params) + "SmartContractModule", "create_deployment_contract", params) expected_events = [{ "module_id": "SmartContractModule", "event_id": "ContractCreated" @@ -388,14 +396,21 @@ def create_node_contract(self, node_id: int = 1, deployment_data: bytes = randby self._sign_extrinsic_submit_check_response( substrate, call, who, expected_events=expected_events) - def update_node_contract(self, contract_id: int = 1, deployment_data: bytes = randbytes(32), - deployment_hash: bytes = randbytes(32), port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + def update_deployment_contract(self, contract_id: int = 1, deployment_data: bytes = randbytes(32), + deployment_hash: bytes = randbytes(32), hru: int = 0, sru: int = 0, cru: int = 0, + mru: int = 0, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") - call = substrate.compose_call("SmartContractModule", "update_node_contract", { + call = substrate.compose_call("SmartContractModule", "update_deployment_contract", { "contract_id": contract_id, "deployment_data": deployment_data, - "deployment_hash": deployment_hash + "deployment_hash": deployment_hash, + "extra_resources": None if hru == 0 and sru == 0 and cru == 0 and mru == 0 else { + "hru": hru * GIGABYTE, + "sru": sru * GIGABYTE, + "cru": cru, + "mru": mru * GIGABYTE + } }) expected_events = [{ "module_id": "SmartContractModule", @@ -460,30 +475,6 @@ def cancel_node_contract(self, contract_id: int = 1, port: int = DEFAULT_PORT, w self._cancel_contract(contract_id=contract_id, type="Node", port=port, who=who) - def report_contract_resources(self, contract_id: int = 1, hru: int = 0, sru: int = 0, cru: int = 0, mru: int = 0, - port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): - substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") - - params = { - "contract_resources": [{ - "contract_id": contract_id, - "used": { - "hru": hru * GIGABYTE, - "sru": sru * GIGABYTE, - "cru": cru, - "mru": mru * GIGABYTE - } - }] - } - call = substrate.compose_call( - "SmartContractModule", "report_contract_resources", params) - expected_events = [{ - "module_id": "SmartContractModule", - "event_id": "UpdatedUsedResources" - }] - self._sign_extrinsic_submit_check_response( - substrate, call, who, expected_events=expected_events) - def add_nru_reports(self, contract_id: int = 1, nru: int = 1, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): block_number = self.get_block_number(port=port) substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") diff --git a/substrate-node/tests/integration_tests.robot b/substrate-node/tests/integration_tests.robot index fe571a4a9..50b6e707c 100644 --- a/substrate-node/tests/integration_tests.robot +++ b/substrate-node/tests/integration_tests.robot @@ -292,7 +292,7 @@ Test Create Update Cancel Node Contract: Success Create Node farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent interfaces=${interfaces} # Bob is the one creating the contract and thus being billed - Create Node Contract node_id=${1} public_ips=${1} who=Bob port=9946 + Create Deployment Contract farm_id=${1} public_ips=${1} who=Bob port=9946 ${farm} = Get Farm ${1} Should Not Be Equal ${farm} ${None} msg=Farm with id 1 doesn't exist @@ -302,7 +302,7 @@ Test Create Update Cancel Node Contract: Success Should Be Equal ${farm}[public_ips][0][gateway] 185.206.122.1 msg=The gateway should be 185.206.122.1 Should Be Equal ${farm}[public_ips][0][contract_id] ${1} msg=The public ip was claimed in contract with id 1 while the farm contains a different contract id for it - Update Node Contract contract_id=${1} who=Bob port=9946 + Update Deployment Contract contract_id=${1} who=Bob port=9946 Cancel Node Contract contract_id=${1} who=Bob port=9946 @@ -316,7 +316,7 @@ Test Create Node Contract: Failure Not Enough Public Ips Setup Network And Create Node # let's request 2 public ips which should result in an error Run Keyword And Expect Error *'FarmHasNotEnoughPublicIPs'* - ... Create Node Contract node_id=${1} public_ips=${2} + ... Create Deployment Contract farm_id=${1} public_ips=${2} Tear Down Multi Node Network @@ -428,8 +428,7 @@ Test Billing ${balance_alice} = Balance Data who=Alice ${balance_bob} = Balance Data who=Bob port=${9946} # Bob will be using the node: let's create a node contract in his name - Create Node Contract node_id=${1} port=${9946} who=Bob - Report Contract Resources contract_id=${1} hru=${20} sru=${20} cru=${2} mru=${4} + Create Deployment Contract farm_id=${1} hru=${20} sru=${20} cru=${2} mru=${4} port=${9946} who=Bob Add Nru Reports contract_id=${1} nru=${3} # Let it run 6 blocks so that the user will be billed 1 time @@ -471,8 +470,7 @@ Test Solution Provider ${balance_charlie_before} = Balance Data who=Charlie ${balance_dave_before} = Balance Data who=Dave # Bob will be using the node: let's create a node contract in his name - Create Node Contract node_id=${1} port=9946 who=Bob solution_provider_id=${1} - Report Contract Resources contract_id=${1} hru=${20} sru=${20} cru=${2} mru=${4} + Create Deployment Contract farm_id=${1} hru=${20} sru=${20} cru=${2} mru=${4} port=9946 who=Bob solution_provider_id=${1} Add Nru Reports contract_id=${1} nru=${3} # Wait 6 blocks: after 5 blocks Bob should be billed Wait X Blocks ${6} From f3b627740c467377df7cbb62708f2304175d7fa7 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 7 Oct 2022 17:51:33 +0200 Subject: [PATCH 48/87] feat: lets try fixing int tests #473 --- substrate-node/tests/integration_tests.robot | 2 ++ 1 file changed, 2 insertions(+) diff --git a/substrate-node/tests/integration_tests.robot b/substrate-node/tests/integration_tests.robot index 50b6e707c..c3dce1152 100644 --- a/substrate-node/tests/integration_tests.robot +++ b/substrate-node/tests/integration_tests.robot @@ -477,6 +477,8 @@ Test Solution Provider # Cancel the contract so that the bill is distributed and so that the providers get their part Cancel Node Contract contract_id=${1} who=Bob + Wait X Blocks ${2} + # Verification: both providers should have received their part ${balance_charlie_after} = Balance Data who=Charlie ${balance_dave_after} = Balance Data who=Dave From a38b72d4bac800b5a0f4fb4a887a7cb2e43e9a1e Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 10 Oct 2022 09:27:37 +0200 Subject: [PATCH 49/87] feat: modified event name #473 --- .../pallets/pallet-smart-contract/src/lib.rs | 19 ++++++++------- substrate-node/support/src/types.rs | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 73c91d0ba..952c75c73 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -315,7 +315,7 @@ pub mod pallet { SolutionProviderCreated(types::SolutionProvider), SolutionProviderApproved(u64, bool), /// Send an event to zero os to change its state - ChangePowerTarget { + PowerTargetChanged { farm_id: u32, node_id: u32, power_target: PowerTarget, @@ -359,6 +359,7 @@ pub mod pallet { SolutionProviderNotApproved, NoSuitableNodeInFarm, NotAuthorizedToCreateDeploymentContract, + InvalidResources, } #[pallet::genesis_config] @@ -467,7 +468,7 @@ pub mod pallet { contract_id: u64, deployment_hash: DeploymentHash, deployment_data: DeploymentDataInput, - extra_resources: Option, + resources: Option, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; Self::_update_deployment_contract( @@ -475,7 +476,7 @@ pub mod pallet { contract_id, deployment_hash, deployment_data, - extra_resources, + resources, ) } @@ -764,7 +765,7 @@ impl Pallet { // if the power_target is not correct => change it and emit event if node.power_target != power_target { - Self::deposit_event(Event::ChangePowerTarget { + Self::deposit_event(Event::PowerTargetChanged { farm_id: node.farm_id, node_id: node_id, power_target: power_target.clone(), @@ -809,7 +810,7 @@ impl Pallet { // if all resources are free shutdown the node if node.can_be_shutdown() { - Self::deposit_event(Event::ChangePowerTarget { + Self::deposit_event(Event::PowerTargetChanged { farm_id: node.farm_id, node_id: node_id, power_target: PowerTarget::Down, @@ -906,7 +907,7 @@ impl Pallet { contract_id: u64, deployment_hash: DeploymentHash, deployment_data: DeploymentDataInput, - extra_resources: Option, + resources: Option, ) -> DispatchResultWithPostInfo { let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; let twin = @@ -939,10 +940,10 @@ impl Pallet { node_contract.deployment_hash = deployment_hash; node_contract.deployment_data = deployment_data; // update the resources with the extra resources - if let Some(extra_resources) = extra_resources { + if let Some(resources) = resources { + ensure!(resources > node_contract.resources, Error::::InvalidResources); + let extra_resources = resources.substract(&node_contract.resources); Self::_claim_resources_on_node(node_contract.node_id, extra_resources)?; - let resources = node_contract.resources; - resources.add(&extra_resources); node_contract.resources = resources; } diff --git a/substrate-node/support/src/types.rs b/substrate-node/support/src/types.rs index c5049daf3..b31b64897 100644 --- a/substrate-node/support/src/types.rs +++ b/substrate-node/support/src/types.rs @@ -169,6 +169,30 @@ impl Resources { self.mru += other.mru; self } + pub fn substract(mut self, other: &Resources) -> Resources { + self.cru = if self.cru < other.cru { + 0 + } else { + self.cru - other.cru + }; + self.sru = if self.sru < other.sru { + 0 + } else { + self.sru - other.sru + }; + self.hru = if self.hru < other.hru { + 0 + } else { + self.hru - other.hru + }; + self.mru = if self.mru < other.mru { + 0 + } else { + self.mru - other.mru + }; + + self + } } // Store Location long and lat as string From 690775f57cabaf0554b94d6738a3baf03164cdd6 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 10 Oct 2022 17:40:57 +0200 Subject: [PATCH 50/87] feat: added extra tests #473 --- .../pallets/pallet-smart-contract/src/lib.rs | 56 +- .../pallet-smart-contract/src/tests.rs | 534 +++++++++++++++++- .../pallets/pallet-tfgrid/src/lib.rs | 8 +- substrate-node/support/src/types.rs | 15 +- substrate-node/tests/TfChainClient.py | 2 +- substrate-node/tests/integration_tests.robot | 2 - 6 files changed, 560 insertions(+), 57 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 952c75c73..e2629cb6b 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -140,7 +140,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn node_contract_resources)] pub type NodeContractResources = - StorageMap<_, Blake2_128Concat, u64, ContractResources, ValueQuery>; + StorageMap<_, Blake2_128Concat, u64, ContractResources, ValueQuery>; #[pallet::storage] #[pallet::getter(fn node_contract_by_hash)] @@ -751,20 +751,13 @@ impl Pallet { power_target: PowerTarget, ) -> DispatchResultWithPostInfo { let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - ensure!( - pallet_tfgrid::Farms::::contains_key(node.farm_id), - Error::::FarmNotExists - ); - - // we never shut down the first node in the of a farm, there should always be one node up per farm - if matches!(power_target, PowerTarget::Down) - && pallet_tfgrid::NodesByFarmID::::get(node.farm_id)[0] == node_id - { - return Ok(().into()); - } // if the power_target is not correct => change it and emit event if node.power_target != power_target { + ensure!( + pallet_tfgrid::Farms::::contains_key(node.farm_id), + Error::::FarmNotExists + ); Self::deposit_event(Event::PowerTargetChanged { farm_id: node.farm_id, node_id: node_id, @@ -786,13 +779,12 @@ impl Pallet { ); //update the available resources - node.used_resources.hru += resources.hru; - node.used_resources.sru += resources.sru; - node.used_resources.cru += resources.cru; - node.used_resources.mru += resources.mru; + node.used_resources = node.used_resources.add(&resources); pallet_tfgrid::Nodes::::insert(node.id, &node); + Self::_change_power_target_node(node_id, PowerTarget::Up)?; + Ok(().into()) } @@ -803,23 +795,21 @@ impl Pallet { let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; //update the available resources - node.used_resources.hru -= resources.hru; - node.used_resources.sru -= resources.sru; - node.used_resources.cru -= resources.cru; - node.used_resources.mru -= resources.mru; - - // if all resources are free shutdown the node - if node.can_be_shutdown() { - Self::deposit_event(Event::PowerTargetChanged { - farm_id: node.farm_id, - node_id: node_id, - power_target: PowerTarget::Down, - }); - node.power_target = PowerTarget::Down; - } + node.used_resources = node.used_resources.substract(&resources); pallet_tfgrid::Nodes::::insert(node.id, &node); + // we don't shutdown the node if: + // some of the resources are still being used + // it is the first node in the farm (one must stay up) + // there is a rent contract (canceling that rent contract will trigger the shutdown) + if node.can_be_shutdown() + && !ActiveRentContractForNode::::contains_key(node_id) + && pallet_tfgrid::NodesByFarmID::::get(node.farm_id)[0] != node_id + { + Self::_change_power_target_node(node_id, PowerTarget::Down)?; + } + Ok(().into()) } @@ -864,7 +854,6 @@ impl Pallet { types::ContractData::DeploymentContract(ref mut nc) => { Self::_reserve_ip(id, nc)?; Self::_claim_resources_on_node(nc.node_id, nc.resources)?; - Self::_change_power_target_node(nc.node_id, PowerTarget::Up)?; } types::ContractData::RentContract(ref mut nc) => { Self::_change_power_target_node(nc.node_id, PowerTarget::Up)?; @@ -941,7 +930,10 @@ impl Pallet { node_contract.deployment_data = deployment_data; // update the resources with the extra resources if let Some(resources) = resources { - ensure!(resources > node_contract.resources, Error::::InvalidResources); + ensure!( + resources > node_contract.resources, + Error::::InvalidResources + ); let extra_resources = resources.substract(&node_contract.resources); Self::_claim_resources_on_node(node_contract.node_id, extra_resources)?; node_contract.resources = resources; diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 6ebcfbd76..c64009ff5 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -16,7 +16,9 @@ use substrate_fixed::types::U64F64; use crate::cost; use log::info; use pallet_tfgrid::types as pallet_tfgrid_types; -use tfchain_support::types::{FarmCertification, Location, NodeCertification, PublicIP, Resources}; +use tfchain_support::types::{ + FarmCertification, Location, NodeCertification, PowerTarget, PublicIP, Resources, +}; const GIGABYTE: u64 = 1024 * 1024 * 1024; @@ -178,16 +180,245 @@ fn test_create_deployment_contract_which_was_canceled_before_works() { }); } -// todo test resources on a contract -// todo test updating resources (increase) -// todo test choosing the best node -// todo test multiple nodes same farm so that there are no more nodes left -// todo test waking up a node -// todo test shutting down nodes (and not shutting down the lest node in the farm) +#[test] +fn test_create_deployment_contract_no_node_in_farm_with_enough_resources() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_noop!( + SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + Resources { + cru: 10, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE + }, + 0, + None, + None, + ), + Error::::NoSuitableNodeInFarm + ); + }); +} + // todo test creating deployment contract using rent contract id (+ all possible failures) // todo test billing // todo test grouping contracts +#[test] +fn test_create_deployment_contract_finding_a_node() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + + prepare_farm_three_nodes_three_deployment_contracts(); + + // first contract should go to node 1 + match SmartContractModule::contracts(1).unwrap().contract_type { + types::ContractData::DeploymentContract(c) => { + assert_eq!(c.node_id, 1); + assert_eq!( + c.resources, + Resources { + cru: 4, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE, + } + ); + } + _ => { + panic!("Expecting a deployment contract!"); + } + } + + // second contract will take most resources but can still go to node 1 + match SmartContractModule::contracts(2).unwrap().contract_type { + types::ContractData::DeploymentContract(c) => { + assert_eq!(c.node_id, 1); + assert_eq!( + c.resources, + Resources { + cru: 4, + hru: 1000 * GIGABYTE, + mru: 10 * GIGABYTE, + sru: 100 * GIGABYTE, + } + ); + } + _ => { + panic!("Expecting a deployment contract!"); + } + } + + // third contract can no longer go to node 1 => node 2 should be started + match SmartContractModule::contracts(3).unwrap().contract_type { + types::ContractData::DeploymentContract(c) => { + assert_eq!(c.node_id, 2); + assert_eq!( + c.resources, + Resources { + cru: 2, + hru: 1024 * GIGABYTE, + mru: 4 * GIGABYTE, + sru: 50 * GIGABYTE, + } + ); + } + _ => { + panic!("Expecting a deployment contract!"); + } + } + + let our_events = System::events(); + for event in our_events.clone().iter() { + log::info!("Event: {:?}", event); + } + // node 2 should be started and event should be emitted + assert_eq!( + our_events.contains(&record(MockEvent::SmartContractModule( + SmartContractEvent::::PowerTargetChanged { + farm_id: 1, + node_id: 2, + power_target: PowerTarget::Up, + } + ))), + true + ); + }); +} + +#[test] +fn test_create_deployment_contract_finding_a_node_failure() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_three_nodes_three_deployment_contracts(); + + assert_noop!( + SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + Resources { + hru: 4096 * GIGABYTE, + sru: 2048 * GIGABYTE, + cru: 32, + mru: 48 * GIGABYTE, + }, + 0, + None, + None, + ), + Error::::NoSuitableNodeInFarm + ); + }); +} + +#[test] +fn test_cancel_deployment_contract_shutdown_node() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_three_nodes_three_deployment_contracts(); + + // cancel contract 2 = nothing should change + assert_ok!(SmartContractModule::cancel_contract( + Origin::signed(alice()), + 2 + )); + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(3).unwrap().power_target, + PowerTarget::Down + ); + // on node 1 there is only one contract left => used resources of node 1 should equal resources of contract 1 + assert_eq!( + TfgridModule::nodes(1).unwrap().used_resources, + Resources { + cru: 4, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE, + } + ); + + // cancel contract 3 = node 2 should shutdown + assert_ok!(SmartContractModule::cancel_contract( + Origin::signed(alice()), + 3 + )); + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Down + ); + assert_eq!( + TfgridModule::nodes(3).unwrap().power_target, + PowerTarget::Down + ); + // nothing else running on node 2 => used resources should be 0 + assert_eq!( + TfgridModule::nodes(2).unwrap().used_resources, + Resources::empty() + ); + + // cancel contract 1 (last contract running on node 1) => node may not be shutdown as it is the only + // one left running in the farm + assert_ok!(SmartContractModule::cancel_contract( + Origin::signed(alice()), + 1 + )); + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Down + ); + assert_eq!( + TfgridModule::nodes(3).unwrap().power_target, + PowerTarget::Down + ); + // nothing else running on node 1 => used resources should be 0 + assert_eq!( + TfgridModule::nodes(1).unwrap().used_resources, + Resources::empty() + ); + + let our_events = System::events(); + for event in our_events.clone().iter() { + log::info!("Event: {:?}", event); + } + + assert_eq!( + our_events.contains(&record(MockEvent::SmartContractModule( + SmartContractEvent::::PowerTargetChanged { + farm_id: 1, + node_id: 2, + power_target: PowerTarget::Down, + } + ))), + true + ); + }); +} + #[test] fn test_update_deployment_contract_works() { new_test_ext().execute_with(|| { @@ -201,18 +432,24 @@ fn test_update_deployment_contract_works() { get_deployment_data(), resources.clone(), 0, - None, + None, None, )); let new_hash = generate_deployment_hash(); let deployment_data = get_deployment_data(); + let updated_resources = resources.clone().add(&Resources { + cru: 1, + hru: 1 * GIGABYTE, + mru: 2 * GIGABYTE, + sru: 30 * GIGABYTE, + }); assert_ok!(SmartContractModule::update_deployment_contract( Origin::signed(alice()), 1, new_hash, get_deployment_data(), - None, + Some(updated_resources), )); let deployment_contract = types::DeploymentContract { @@ -221,7 +458,7 @@ fn test_update_deployment_contract_works() { deployment_data, public_ips: 0, public_ips_list: Vec::new().try_into().unwrap(), - resources: resources, + resources: updated_resources, }; let contract_type = types::ContractData::DeploymentContract(deployment_contract); @@ -242,10 +479,90 @@ fn test_update_deployment_contract_works() { assert_eq!(contracts[0], 1); - let deployment_contract_id_by_hash = SmartContractModule::node_contract_by_hash(1, new_hash); + let deployment_contract_id_by_hash = + SmartContractModule::node_contract_by_hash(1, new_hash); assert_eq!(deployment_contract_id_by_hash, 1); }); } +#[test] +fn test_update_deployment_contract_too_much_resources() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + Resources { + cru: 2, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE + }, + 0, + None, + None, + )); + // asking for too much resources + assert_noop!( + SmartContractModule::update_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + Some(Resources { + hru: 1024 * GIGABYTE, + sru: 512 * GIGABYTE, + cru: 10, + mru: 16 * GIGABYTE + }), + ), + Error::::NotEnoughResourcesOnNode + ); + }); +} + +#[test] +fn test_update_deployment_contract_invalid_resources() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + Resources { + cru: 2, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE + }, + 0, + None, + None, + )); + // resources should increase + assert_noop!( + SmartContractModule::update_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + Some(Resources { + cru: 1, + hru: 0, + mru: 1 * GIGABYTE, + sru: 30 * GIGABYTE + }), + ), + Error::::InvalidResources + ); + }); +} #[test] fn test_update_deployment_contract_not_exists_fails() { @@ -289,7 +606,12 @@ fn test_update_deployment_contract_wrong_twins_fails() { 1, generate_deployment_hash(), get_deployment_data(), - Some(Resources { cru: 1, hru: 0, mru: 1 * GIGABYTE, sru: 10 * GIGABYTE }), + Some(Resources { + cru: 1, + hru: 0, + mru: 1 * GIGABYTE, + sru: 10 * GIGABYTE + }), ), Error::::TwinNotAuthorizedToUpdateContract ); @@ -1781,7 +2103,8 @@ fn test_rent_contract_canceled_mid_cycle_should_bill_for_remainder() { } #[test] -fn test_create_rent_contract_and_deployment_contract_excludes_deployment_contract_from_billing_works() { +fn test_create_rent_contract_and_deployment_contract_excludes_deployment_contract_from_billing_works( +) { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2732,6 +3055,85 @@ pub fn prepare_farm_and_node() { .unwrap(); } +pub fn prepare_farm_with_three_nodes() { + prepare_farm_and_node(); + + // SECOND NODE + // random location + let location = Location { + longitude: "45.233213231".as_bytes().to_vec(), + latitude: "241.323112123".as_bytes().to_vec(), + }; + + let resources = Resources { + hru: 2048 * GIGABYTE, + sru: 1024 * GIGABYTE, + cru: 16, + mru: 32 * GIGABYTE, + }; + + let country = "Belgium".as_bytes().to_vec(); + let city = "Ghent".as_bytes().to_vec(); + TfgridModule::create_node( + Origin::signed(bob()), + 1, + resources, + location, + country, + city, + bounded_vec![], + false, + false, + "some_serial".as_bytes().to_vec(), + ) + .unwrap(); + + // SECOND NODE + // random location + let location = Location { + longitude: "6514.233213231".as_bytes().to_vec(), + latitude: "324.323112123".as_bytes().to_vec(), + }; + + let resources = Resources { + hru: 512 * GIGABYTE, + sru: 256 * GIGABYTE, + cru: 4, + mru: 8 * GIGABYTE, + }; + + let country = "Belgium".as_bytes().to_vec(); + let city = "Ghent".as_bytes().to_vec(); + TfgridModule::create_node( + Origin::signed(charlie()), + 1, + resources, + location, + country, + city, + bounded_vec![], + false, + false, + "some_serial".as_bytes().to_vec(), + ) + .unwrap(); + + let nodes_from_farm = TfgridModule::nodes_by_farm_id(1); + assert_eq!(nodes_from_farm.len(), 3); + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Down + ); + assert_eq!( + TfgridModule::nodes(3).unwrap().power_target, + PowerTarget::Down + ); +} + pub fn prepare_dedicated_farm_and_node() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); create_farming_policies(); @@ -2893,5 +3295,107 @@ fn get_deployment_data() -> crate::DeploymentDataInput { } fn get_resources() -> Resources { - Resources { cru: 2, hru: 0, mru: 2 * GIGABYTE, sru: 60 * GIGABYTE } -} \ No newline at end of file + Resources { + cru: 2, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE, + } +} + +fn prepare_farm_three_nodes_three_deployment_contracts() { + prepare_farm_with_three_nodes(); + + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Down + ); + assert_eq!( + TfgridModule::nodes(3).unwrap().power_target, + PowerTarget::Down + ); + + // first contract should go to node 1 + let resources_c1 = Resources { + cru: 4, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE, + }; + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + resources_c1, + 0, + None, + None, + )); + + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Down + ); + assert_eq!( + TfgridModule::nodes(3).unwrap().power_target, + PowerTarget::Down + ); + + // second contract will take most resources but can still go to node 1 + let resources_c2 = Resources { + cru: 4, + hru: 1000 * GIGABYTE, + mru: 10 * GIGABYTE, + sru: 100 * GIGABYTE, + }; + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + resources_c2, + 0, + None, + None, + ),); + + // third contract will take most resources but can still go to node 1 + let resources_c3 = Resources { + cru: 2, + hru: 1024 * GIGABYTE, + mru: 4 * GIGABYTE, + sru: 50 * GIGABYTE, + }; + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + resources_c3, + 0, + None, + None, + ),); + + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(3).unwrap().power_target, + PowerTarget::Down + ); +} diff --git a/substrate-node/pallets/pallet-tfgrid/src/lib.rs b/substrate-node/pallets/pallet-tfgrid/src/lib.rs index 1f2ffccb7..1f854ad63 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/lib.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/lib.rs @@ -969,7 +969,11 @@ pub mod pallet { id = id + 1; let created = >::get().saturated_into::() / 1000; - let power_target = PowerTarget::Up; + let power_target = if NodesByFarmID::::get(farm_id).is_empty() { + PowerTarget::Up + } else { + PowerTarget::Down + }; let mut new_node = Node { version: TFGRID_NODE_VERSION, @@ -977,7 +981,7 @@ pub mod pallet { farm_id, twin_id, resources, - used_resources: Resources {hru:0, sru:0, cru:0, mru:0 }, + used_resources: Resources::empty(), location, country, city, diff --git a/substrate-node/support/src/types.rs b/substrate-node/support/src/types.rs index b31b64897..9c2bad611 100644 --- a/substrate-node/support/src/types.rs +++ b/substrate-node/support/src/types.rs @@ -106,10 +106,11 @@ pub struct Node { impl Node { pub fn can_claim_resources(&self, resources: Resources) -> bool { - self.resources.hru - self.used_resources.hru >= resources.hru - && self.resources.hru - self.used_resources.sru >= resources.sru - && self.resources.hru - self.used_resources.cru >= resources.cru - && self.resources.hru - self.used_resources.mru >= resources.mru + + (self.resources.hru - self.used_resources.hru) >= resources.hru + && (self.resources.sru - self.used_resources.sru) >= resources.sru + && (self.resources.cru - self.used_resources.cru) >= resources.cru + && (self.resources.mru - self.used_resources.mru) >= resources.mru } pub fn can_be_shutdown(&self) -> bool { @@ -162,6 +163,10 @@ pub struct Resources { } impl Resources { + pub fn empty() -> Resources { + Resources { hru: 0, sru: 0, cru: 0, mru: 0 } + } + pub fn add(mut self, other: &Resources) -> Resources { self.cru += other.cru; self.sru += other.sru; @@ -190,7 +195,7 @@ impl Resources { } else { self.mru - other.mru }; - + self } } diff --git a/substrate-node/tests/TfChainClient.py b/substrate-node/tests/TfChainClient.py index dce5afc7c..e0c262e84 100644 --- a/substrate-node/tests/TfChainClient.py +++ b/substrate-node/tests/TfChainClient.py @@ -405,7 +405,7 @@ def update_deployment_contract(self, contract_id: int = 1, deployment_data: byte "contract_id": contract_id, "deployment_data": deployment_data, "deployment_hash": deployment_hash, - "extra_resources": None if hru == 0 and sru == 0 and cru == 0 and mru == 0 else { + "resources": None if hru == 0 and sru == 0 and cru == 0 and mru == 0 else { "hru": hru * GIGABYTE, "sru": sru * GIGABYTE, "cru": cru, diff --git a/substrate-node/tests/integration_tests.robot b/substrate-node/tests/integration_tests.robot index c3dce1152..50b6e707c 100644 --- a/substrate-node/tests/integration_tests.robot +++ b/substrate-node/tests/integration_tests.robot @@ -477,8 +477,6 @@ Test Solution Provider # Cancel the contract so that the bill is distributed and so that the providers get their part Cancel Node Contract contract_id=${1} who=Bob - Wait X Blocks ${2} - # Verification: both providers should have received their part ${balance_charlie_after} = Balance Data who=Charlie ${balance_dave_after} = Balance Data who=Dave From 77f9cb8378ff74ca191aa6cc8da085dbfd2eff47 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 11 Oct 2022 10:40:22 +0200 Subject: [PATCH 51/87] feat: extra tests and cleanup code #473 --- .../pallets/pallet-smart-contract/src/cost.rs | 16 +-- .../pallets/pallet-smart-contract/src/lib.rs | 51 ++----- .../pallet-smart-contract/src/tests.rs | 126 +++++++++++++++++- 3 files changed, 139 insertions(+), 54 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index cdcf4219b..c95af3118 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -62,17 +62,15 @@ impl Contract { return Err(DispatchErrorWithPostInfo::from(Error::::NodeNotExists)); } - // We know the contract is using resources, now calculate the cost for each used resource - - //let node_contract_resources = - // pallet::Pallet::::node_contract_resources(self.contract_id); - - let mut bill_resources = true; // If this node contract is deployed on a node which has a rent contract // We can ignore billing for the resources used by this node contract - if pallet::ActiveRentContractForNode::::contains_key(node_contract.node_id) { - bill_resources = false - } + let bill_resources = if pallet::ActiveRentContractForNode::::contains_key( + node_contract.node_id, + ) { + false + } else { + true + }; let contract_cost = calculate_resources_cost::( node_contract.resources, diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index e2629cb6b..5bde6433d 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -752,6 +752,13 @@ impl Pallet { ) -> DispatchResultWithPostInfo { let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + // do not power down if it is the first node in the list of nodes from that farm + if matches!(power_target, PowerTarget::Down) + && pallet_tfgrid::NodesByFarmID::::get(node.farm_id)[0] == node_id + { + return Ok(().into()); + } + // if the power_target is not correct => change it and emit event if node.power_target != power_target { ensure!( @@ -803,10 +810,7 @@ impl Pallet { // some of the resources are still being used // it is the first node in the farm (one must stay up) // there is a rent contract (canceling that rent contract will trigger the shutdown) - if node.can_be_shutdown() - && !ActiveRentContractForNode::::contains_key(node_id) - && pallet_tfgrid::NodesByFarmID::::get(node.farm_id)[0] != node_id - { + if node.can_be_shutdown() && !ActiveRentContractForNode::::contains_key(node_id) { Self::_change_power_target_node(node_id, PowerTarget::Down)?; } @@ -991,45 +995,6 @@ impl Pallet { Ok(().into()) } - // pub fn _report_contract_resources( - // source: T::AccountId, - // contract_resources: Vec, - // ) -> DispatchResultWithPostInfo { - // ensure!( - // pallet_tfgrid::TwinIdByAccountID::::contains_key(&source), - // Error::::TwinNotExists - // ); - // let twin_id = - // pallet_tfgrid::TwinIdByAccountID::::get(&source).ok_or(Error::::TwinNotExists)?; - // ensure!( - // pallet_tfgrid::NodeIdByTwinID::::contains_key(twin_id), - // Error::::NodeNotExists - // ); - // let node_id = pallet_tfgrid::NodeIdByTwinID::::get(twin_id); - - // for contract_resource in contract_resources { - // // we know contract exists, fetch it - // // if the node is trying to send garbage data we can throw an error here - // if let Some(contract) = Contracts::::get(contract_resource.contract_id) { - // let node_contract = Self::get_node_contract(&contract)?; - // ensure!( - // node_contract.node_id == node_id, - // Error::::NodeNotAuthorizedToComputeReport - // ); - - // // Do insert - // NodeContractResources::::insert( - // contract_resource.contract_id, - // &contract_resource, - // ); - // // deposit event - // Self::deposit_event(Event::UpdatedUsedResources(contract_resource)); - // } - // } - - // Ok(Pays::No.into()) - // } - pub fn _compute_reports( source: T::AccountId, reports: Vec, diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index c64009ff5..8741c7953 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -207,7 +207,6 @@ fn test_create_deployment_contract_no_node_in_farm_with_enough_resources() { }); } -// todo test creating deployment contract using rent contract id (+ all possible failures) // todo test billing // todo test grouping contracts @@ -320,6 +319,124 @@ fn test_create_deployment_contract_finding_a_node_failure() { }); } +#[test] +fn test_create_rent_contract_then_deployment_contract_checking_power_target() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_with_three_nodes(); + // node 2 should be down and when we create the rent contract the node should be woken up + // we do not yet change the used resources until deployment contracts are created + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Down + ); + assert_ok!(SmartContractModule::create_rent_contract( + Origin::signed(bob()), + 2, + None + )); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Up + ); + assert_eq!( + TfgridModule::nodes(2).unwrap().used_resources, + Resources::empty() + ); + // creating the deployment contract should claim resources from the node + let resources = get_resources(); + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(bob()), + 1, + generate_deployment_hash(), + get_deployment_data(), + resources, + 1, + None, + Some(1) + )); + assert_eq!(TfgridModule::nodes(2).unwrap().used_resources, resources); + // canceling the deployment contract should not shutdown the node (because of the created + // rent contract) but it should unclaim the resources on that node + assert_ok!(SmartContractModule::cancel_contract( + Origin::signed(bob()), + 2 + )); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Up + ); + // canceling rent contract should shut down the node (as it is not the first in the list + // of nodes from that farm) + assert_ok!(SmartContractModule::cancel_contract( + Origin::signed(bob()), + 1 + )); + assert_eq!( + TfgridModule::nodes(2).unwrap().power_target, + PowerTarget::Down + ); + + let our_events = System::events(); + for event in our_events.clone().iter() { + log::info!("Event: {:?}", event); + } + // should have emitted one power up event and one power down + assert_eq!( + our_events.contains(&record(MockEvent::SmartContractModule( + SmartContractEvent::::PowerTargetChanged { + farm_id: 1, + node_id: 2, + power_target: PowerTarget::Up, + } + ))), + true + ); + assert_eq!( + our_events.contains(&record(MockEvent::SmartContractModule( + SmartContractEvent::::PowerTargetChanged { + farm_id: 1, + node_id: 2, + power_target: PowerTarget::Down, + } + ))), + true + ); + + }); +} + +#[test] +fn test_cancel_rent_contract_should_not_shutdown_first_node() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_with_three_nodes(); + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + assert_ok!(SmartContractModule::create_rent_contract( + Origin::signed(bob()), + 1, + None + )); + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + assert_ok!(SmartContractModule::cancel_contract( + Origin::signed(bob()), + 1 + )); + // node should still be up as it is the first in the list of nodes of that farm + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); + }); +} + + #[test] fn test_cancel_deployment_contract_shutdown_node() { new_test_ext().execute_with(|| { @@ -1078,7 +1195,7 @@ fn test_cancel_rent_contract_with_active_deployment_contracts_fails() { node_id, None )); - + // set rent contract id to 1 to use node from rent contract with id 1 assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1094,6 +1211,11 @@ fn test_cancel_rent_contract_with_active_deployment_contracts_fails() { SmartContractModule::cancel_contract(Origin::signed(bob()), 1,), Error::::NodeHasActiveContracts ); + // node 1 should still be up after failed attempt to cancel rent contract + assert_eq!( + TfgridModule::nodes(1).unwrap().power_target, + PowerTarget::Up + ); }); } From 0a9e165325148596f4401552ab4e52a43237d1fd Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 11 Oct 2022 11:13:06 +0200 Subject: [PATCH 52/87] feat: fixed mergeconflicts #473 --- .../pallets/pallet-smart-contract/src/lib.rs | 1 + .../pallets/pallet-smart-contract/src/tests.rs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 5bde6433d..5d87d11cd 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -11,6 +11,7 @@ use frame_support::{ Currency, EnsureOrigin, ExistenceRequirement, ExistenceRequirement::KeepAlive, Get, LockableCurrency, OnUnbalanced, WithdrawReasons, }, + transactional, weights::Pays, BoundedVec, }; diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 8741c7953..89b0e731a 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -2327,7 +2327,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_cont // Event 17: Node contract canceled // Event 18: Rent contract Canceled // => no Node Contract billed event - assert_eq!(our_events.len(), 10); + assert_eq!(our_events.len(), 9); for e in our_events.clone().iter() { info!("event: {:?}", e); @@ -2357,7 +2357,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_cont ); assert_eq!( - our_events[17], + our_events[7], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::NodeContractCanceled { @@ -2367,7 +2367,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_cont })) ); assert_eq!( - our_events[18], + our_events[8], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::RentContractCanceled { @@ -2639,7 +2639,7 @@ fn test_restore_rent_contract_and_deployment_contracts_in_grace_works() { let our_events = System::events(); assert_eq!( - our_events[10], + our_events[7], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodEnded { @@ -2649,7 +2649,7 @@ fn test_restore_rent_contract_and_deployment_contracts_in_grace_works() { })) ); assert_eq!( - our_events[11], + our_events[8], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ContractGracePeriodEnded { From 611d6ff87df4587b839066088c2cd935600b3be6 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 11 Oct 2022 17:56:22 +0200 Subject: [PATCH 53/87] feat: implementation of contract policy (aka grouping of contracts) #473 #475 --- .../pallets/pallet-smart-contract/src/lib.rs | 147 +++++++++++++++--- .../pallet-smart-contract/src/tests.rs | 21 +-- .../pallet-smart-contract/src/types.rs | 15 ++ substrate-node/support/src/types.rs | 20 ++- 4 files changed, 171 insertions(+), 32 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 5d87d11cd..b01a8ee36 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -32,7 +32,7 @@ use sp_runtime::{ use substrate_fixed::types::U64F64; use tfchain_support::{ traits::ChangeNode, - types::{Node, PowerTarget, Resources}, + types::{ContractPolicy, Node, PowerTarget, Resources}, }; pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"smct"); @@ -155,6 +155,18 @@ pub mod pallet { ValueQuery, >; + #[pallet::storage] + #[pallet::getter(fn group_by_node)] + pub type GroupIDByNodeId = StorageDoubleMap< + _, + Blake2_128Concat, + u32, + Blake2_128Concat, + DeploymentHash, + u32, + ValueQuery, + >; + #[pallet::storage] #[pallet::getter(fn active_node_contracts)] // A list of Contract ID's for a given node. @@ -188,6 +200,19 @@ pub mod pallet { #[pallet::getter(fn contract_id)] pub type ContractID = StorageValue<_, u64, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn group_id)] + pub type GroupID = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn groups)] + pub type Groups = StorageMap<_, Blake2_128Concat, u32, Group, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn contract_id_by_node_group_config)] + pub type ContractIDByNodeGroupConfig = + StorageMap<_, Blake2_128Concat, types::NodeGroupConfig, u64, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn solution_providers)] pub type SolutionProviders = @@ -321,6 +346,7 @@ pub mod pallet { node_id: u32, power_target: PowerTarget, }, + GroupCreated(types::Group), } #[pallet::error] @@ -361,6 +387,8 @@ pub mod pallet { NoSuitableNodeInFarm, NotAuthorizedToCreateDeploymentContract, InvalidResources, + GroupNotExists, + InvalidContractPolicy, } #[pallet::genesis_config] @@ -387,6 +415,12 @@ pub mod pallet { #[pallet::call] impl Pallet { + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn create_group(origin: OriginFor) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_create_group(account_id) + } + // DEPRECATED #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn create_node_contract( @@ -448,7 +482,7 @@ pub mod pallet { resources: Resources, public_ips: u32, solution_provider_id: Option, - rent_contract_id: Option, + contract_policy: Option, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; Self::_create_deployment_contract( @@ -459,7 +493,7 @@ pub mod pallet { resources, public_ips, solution_provider_id, - rent_contract_id, + contract_policy, ) } @@ -577,6 +611,25 @@ use sp_std::convert::{TryFrom, TryInto}; use tfchain_support::types::PublicIP; // Internal functions of the pallet impl Pallet { + pub fn _create_group(account_id: T::AccountId) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + + let mut id = GroupID::::get(); + id = id + 1; + + let new_group = types::Group { + id: id, + twin_id: twin_id, + }; + + Groups::::insert(id, &new_group); + + Self::deposit_event(Event::GroupCreated(new_group)); + + Ok(().into()) + } + #[transactional] pub fn _create_deployment_contract( account_id: T::AccountId, @@ -586,26 +639,35 @@ impl Pallet { resources: Resources, public_ips: u32, solution_provider_id: Option, - rent_contract_id: Option, + contract_policy: Option, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - // if rent_contract_id is an actual rent contract let's use that node to create deployment contract and - // only allow the user who created the rent contract to actually deploy a deployment contract on it - // else find a suitable node in the provided farm - let node_id = if let Some(contract_id) = rent_contract_id { - let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - ensure!( - contract.twin_id == twin_id, - Error::::NotAuthorizedToCreateDeploymentContract - ); - Self::get_rent_contract(&contract)?.node_id - } else { - // the farm cannot be a dedicated farm unless you provide a rent contract id - let farm = pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; - ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); - Self::_find_suitable_node_in_farm(farm_id, resources)? + let mut group_id = None; + let node_id = match contract_policy { + Some(ContractPolicy::Join(contract_id)) => { + let contract = + Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + ensure!( + contract.twin_id == twin_id, + Error::::NotAuthorizedToCreateDeploymentContract + ); + Self::_get_node_id_from_contract(&contract)? + } + Some(ContractPolicy::Exclusive(g_id)) => { + let farm = + pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; + ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); + group_id = Some(g_id); + Self::_find_suitable_node_in_farm(farm_id, resources, group_id)? + } + None | Some(ContractPolicy::Any) => { + let farm = + pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; + ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); + Self::_find_suitable_node_in_farm(farm_id, resources, None)? + } }; // If the contract with hash and node id exists and it's in any other state then @@ -635,6 +697,7 @@ impl Pallet { public_ips: public_ips, public_ips_list: public_ips_list, resources: resources, + group_id: group_id, }; // Create contract @@ -644,6 +707,17 @@ impl Pallet { solution_provider_id, )?; + // insert NodeGroup configuration + if let Some(group_id) = group_id { + ContractIDByNodeGroupConfig::::insert( + types::NodeGroupConfig { + group_id: group_id, + node_id: node_id, + }, + contract.contract_id, + ); + } + let now = >::get().saturated_into::() / 1000; let contract_billing_information = types::ContractBillingInformation { last_updated: now, @@ -668,6 +742,21 @@ impl Pallet { Ok(().into()) } + pub fn _get_node_id_from_contract( + contract: &types::Contract, + ) -> Result { + let node_id = match contract.contract_type { + types::ContractData::RentContract(ref rent_contract) => rent_contract.node_id, + types::ContractData::DeploymentContract(ref deployment_contract) => { + deployment_contract.node_id + } + _ => 0, + }; + ensure!(node_id != 0, Error::::InvalidContractType); + + Ok(node_id.into()) + } + pub fn _create_rent_contract( account_id: T::AccountId, node_id: u32, @@ -821,6 +910,7 @@ impl Pallet { fn _find_suitable_node_in_farm( farm_id: u32, resources: Resources, + group_id: Option, ) -> Result { ensure!( pallet_tfgrid::Farms::::contains_key(farm_id), @@ -829,12 +919,22 @@ impl Pallet { let nodes_in_farm = pallet_tfgrid::NodesByFarmID::::get(farm_id); let mut suitable_nodes = Vec::new(); + for node_id in nodes_in_farm { let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; if node.can_claim_resources(resources) && !ActiveRentContractForNode::::contains_key(node_id) { - suitable_nodes.push(node); + if let Some(id) = group_id { + if ContractIDByNodeGroupConfig::::contains_key(types::NodeGroupConfig { + node_id: node_id, + group_id: id, + }) { + suitable_nodes.push(node); + } + } else { + suitable_nodes.push(node); + } } } @@ -980,6 +1080,13 @@ impl Pallet { Self::_change_power_target_node(rent_contract.node_id, PowerTarget::Down)?; } types::ContractData::DeploymentContract(ref deployment_contract) => { + // remove NodeGroup config + if let Some(group_id) = deployment_contract.group_id { + ContractIDByNodeGroupConfig::::remove(types::NodeGroupConfig { + group_id: group_id, + node_id: deployment_contract.node_id, + }); + } Self::_un_claim_resources_on_node( deployment_contract.node_id, deployment_contract.resources, diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 89b0e731a..342f43912 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -17,7 +17,7 @@ use crate::cost; use log::info; use pallet_tfgrid::types as pallet_tfgrid_types; use tfchain_support::types::{ - FarmCertification, Location, NodeCertification, PowerTarget, PublicIP, Resources, + ContractPolicy, FarmCertification, Location, NodeCertification, PowerTarget, PublicIP, Resources, }; const GIGABYTE: u64 = 1024 * 1024 * 1024; @@ -353,7 +353,7 @@ fn test_create_rent_contract_then_deployment_contract_checking_power_target() { resources, 1, None, - Some(1) + Some(ContractPolicy::Join(1)) )); assert_eq!(TfgridModule::nodes(2).unwrap().used_resources, resources); // canceling the deployment contract should not shutdown the node (because of the created @@ -576,6 +576,7 @@ fn test_update_deployment_contract_works() { public_ips: 0, public_ips_list: Vec::new().try_into().unwrap(), resources: updated_resources, + group_id: None, }; let contract_type = types::ContractData::DeploymentContract(deployment_contract); @@ -1145,7 +1146,7 @@ fn test_create_deployment_contract_when_having_a_rentcontract_works() { get_resources(), 1, None, - Some(1) + Some(ContractPolicy::Join(1)) )); }) } @@ -1174,7 +1175,7 @@ fn test_create_deployment_contract_when_someone_else_has_rent_contract_fails() { get_resources(), 1, None, - Some(1), + Some(ContractPolicy::Join(1)), ), Error::::NotAuthorizedToCreateDeploymentContract ); @@ -1204,7 +1205,7 @@ fn test_cancel_rent_contract_with_active_deployment_contracts_fails() { get_resources(), 1, None, - Some(1) + Some(ContractPolicy::Join(1)) )); assert_noop!( @@ -2248,7 +2249,7 @@ fn test_create_rent_contract_and_deployment_contract_excludes_deployment_contrac get_resources(), 0, None, - Some(1) + Some(ContractPolicy::Join(1)) )); pool_state .write() @@ -2298,7 +2299,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_cont get_resources(), 0, None, - Some(1) + Some(ContractPolicy::Join(1)) )); // run 12 cycles, contracts should cancel after 11 due to lack of funds @@ -2400,7 +2401,7 @@ fn test_create_rent_contract_and_deployment_contract_with_ip_billing_works() { get_resources(), 1, None, - Some(1) + Some(ContractPolicy::Join(1)) )); // 2 contracts => we expect 2 calls to bill_contract @@ -2559,7 +2560,7 @@ fn test_restore_rent_contract_and_deployment_contracts_in_grace_works() { get_resources(), 0, None, - Some(1) + Some(ContractPolicy::Join(1)) )); // cycle 1 @@ -2737,7 +2738,7 @@ fn test_rent_contract_and_deployment_contract_canceled_when_node_is_deleted_work get_resources(), 0, None, - Some(1) + Some(ContractPolicy::Join(1)) )); // 2 contracts => 2 calls to bill_contract diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index 42e1585e8..647cab3ad 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -28,6 +28,20 @@ impl Default for StorageVersion { } } + + +#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo)] +pub struct Group { + pub id: u32, + pub twin_id: u32, +} + +#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo)] +pub struct NodeGroupConfig { + pub node_id: u32, + pub group_id: u32, +} + #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] @@ -66,6 +80,7 @@ pub struct DeploymentContract { pub public_ips: u32, pub public_ips_list: BoundedVec, MaxNodeContractPublicIPs>, pub resources: Resources, + pub group_id: Option, } #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] diff --git a/substrate-node/support/src/types.rs b/substrate-node/support/src/types.rs index 9c2bad611..946b3a952 100644 --- a/substrate-node/support/src/types.rs +++ b/substrate-node/support/src/types.rs @@ -68,6 +68,18 @@ pub struct FarmingPolicyLimit { pub node_certification: bool, } +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +pub enum ContractPolicy { + Any, + Join(u64), + Exclusive(u32), +} +impl Default for ContractPolicy { + fn default() -> ContractPolicy { + ContractPolicy::Any + } +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo)] pub enum PowerTarget { Up, @@ -106,7 +118,6 @@ pub struct Node { impl Node { pub fn can_claim_resources(&self, resources: Resources) -> bool { - (self.resources.hru - self.used_resources.hru) >= resources.hru && (self.resources.sru - self.used_resources.sru) >= resources.sru && (self.resources.cru - self.used_resources.cru) >= resources.cru @@ -164,7 +175,12 @@ pub struct Resources { impl Resources { pub fn empty() -> Resources { - Resources { hru: 0, sru: 0, cru: 0, mru: 0 } + Resources { + hru: 0, + sru: 0, + cru: 0, + mru: 0, + } } pub fn add(mut self, other: &Resources) -> Resources { From 4aad91fb524068038cfe6e5fe041f80b96d0e1ae Mon Sep 17 00:00:00 2001 From: brandonpille Date: Wed, 12 Oct 2022 10:02:36 +0200 Subject: [PATCH 54/87] feat: fixed merge conflicts #473 --- .../pallets/pallet-smart-contract/src/mock.rs | 3 ++- .../pallets/pallet-smart-contract/src/tests.rs | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index daac582ff..d767097ae 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -346,9 +346,10 @@ pub struct PoolState { impl PoolState { pub fn should_call_bill_contract( + &mut self, + contract_id: u64, expected_result: ExtrinsicResult, block_number: u64, - expected_result: ExtrinsicResult, ) { self.expected_calls.push(( crate::Call::bill_contract_for_block { contract_id }, diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 342f43912..0707d6d9c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1545,7 +1545,7 @@ fn test_deployment_contract_billing_cycles_delete_node_cancels_contract() { let contract_id = 1; let twin_id = 2; - for _ in 0..5 { + for i in 0..5 { pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); @@ -1641,7 +1641,7 @@ fn test_deployment_contract_only_public_ip_billing_cycles() { let contract_id = 1; let twin_id = 2; - for _ in 0..5 { + for i in 0..5 { pool_state.write().should_call_bill_contract( contract_id, Ok(Pays::Yes.into()), @@ -1695,7 +1695,7 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_works() let twin_id = 2; // 2 cycles for billing - for _ in 0..2 { + for i in 0..2 { pool_state.write().should_call_bill_contract( contract_id, Ok(Pays::Yes.into()), @@ -1921,7 +1921,7 @@ fn test_restore_deployment_contract_in_grace_works() { None )); - for _ in 0..6 { + for i in 0..6 { pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); @@ -2303,7 +2303,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_cont )); // run 12 cycles, contracts should cancel after 11 due to lack of funds - for _ in 0..11 { + for i in 0..11 { pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); From aceb1760daf5c68e98c3037609f535f06e81ec1b Mon Sep 17 00:00:00 2001 From: brandonpille Date: Wed, 12 Oct 2022 17:21:15 +0200 Subject: [PATCH 55/87] feat: fixed integration tests #473 --- substrate-node/tests/TfChainClient.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate-node/tests/TfChainClient.py b/substrate-node/tests/TfChainClient.py index e0c262e84..b81b3c075 100644 --- a/substrate-node/tests/TfChainClient.py +++ b/substrate-node/tests/TfChainClient.py @@ -370,7 +370,7 @@ def get_node(self, id: int = 1, port: int = DEFAULT_PORT): def create_deployment_contract(self, farm_id: int = 1, deployment_data: bytes = randbytes(32), deployment_hash: bytes = randbytes(32), public_ips: int = 0, hru: int = 0, sru: int = 0, cru: int = 0, mru: int = 0, solution_provider_id: int | None = None, - rent_contract_id: int | None = None, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + contract_policy: int | None = None, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") params = { @@ -385,7 +385,7 @@ def create_deployment_contract(self, farm_id: int = 1, deployment_data: bytes = "mru": mru * GIGABYTE }, "solution_provider_id": solution_provider_id, - "rent_contract_id": rent_contract_id + "contract_policy": contract_policy } call = substrate.compose_call( "SmartContractModule", "create_deployment_contract", params) From 7e536d18242eebbe82862ab52a884a185af76940 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 18 Oct 2022 09:52:05 +0200 Subject: [PATCH 56/87] feat: implementation done and code can be build --- substrate-node/pallets/pallet-dao/src/lib.rs | 6 +- .../pallets/pallet-smart-contract/src/cost.rs | 95 +- .../pallets/pallet-smart-contract/src/lib.rs | 849 ++++++++++++------ .../pallet-smart-contract/src/tests.rs | 801 ++++++++--------- .../pallet-smart-contract/src/types.rs | 50 +- .../pallets/pallet-tfgrid/src/lib.rs | 17 +- .../pallets/pallet-tfgrid/src/tests.rs | 8 +- substrate-node/support/src/types.rs | 67 +- 8 files changed, 1118 insertions(+), 775 deletions(-) diff --git a/substrate-node/pallets/pallet-dao/src/lib.rs b/substrate-node/pallets/pallet-dao/src/lib.rs index 4a168faad..fbfac0cb0 100644 --- a/substrate-node/pallets/pallet-dao/src/lib.rs +++ b/substrate-node/pallets/pallet-dao/src/lib.rs @@ -523,10 +523,10 @@ impl ChangeNode, InterfaceOf> for Pallet { old_node: Option<&Node, InterfaceOf>>, new_node: &Node, InterfaceOf>, ) { - let new_node_weight = Self::get_node_weight(new_node.resources); + let new_node_weight = Self::get_node_weight(new_node.resources.total_resources); match old_node { Some(node) => { - let old_node_weight = Self::get_node_weight(node.resources); + let old_node_weight = Self::get_node_weight(node.resources.total_resources); if node.farm_id != new_node.farm_id { let mut old_farm_weight = FarmWeight::::get(node.farm_id); @@ -554,7 +554,7 @@ impl ChangeNode, InterfaceOf> for Pallet { } fn node_deleted(node: &Node, InterfaceOf>) { - let node_weight = Self::get_node_weight(node.resources); + let node_weight = Self::get_node_weight(node.resources.total_resources); let mut farm_weight = FarmWeight::::get(node.farm_id); farm_weight = farm_weight.checked_sub(node_weight).unwrap_or(0); FarmWeight::::insert(node.farm_id, farm_weight); diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index c95af3118..b7dd4d7b5 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -53,50 +53,33 @@ impl Contract { seconds_elapsed: u64, ) -> Result { let total_cost = match &self.contract_type { - // Calculate total cost for a node contract - types::ContractData::DeploymentContract(node_contract) => { + types::ContractData::DeploymentContract(_) => { + // cost is calculated in capacity reservation contracts + 0 + } + types::ContractData::CapacityReservationContract(capacity_reservation_contract) => { // Get the contract billing info to view the amount unbilled for NRU (network resource units) let contract_billing_info = self.get_billing_info(); // Get the node - if !pallet_tfgrid::Nodes::::contains_key(node_contract.node_id) { - return Err(DispatchErrorWithPostInfo::from(Error::::NodeNotExists)); - } - - // If this node contract is deployed on a node which has a rent contract - // We can ignore billing for the resources used by this node contract - let bill_resources = if pallet::ActiveRentContractForNode::::contains_key( - node_contract.node_id, - ) { - false - } else { - true - }; + let node = pallet_tfgrid::Nodes::::get(capacity_reservation_contract.node_id) + .ok_or(Error::::NodeNotExists)?; let contract_cost = calculate_resources_cost::( - node_contract.resources, - node_contract.public_ips, + capacity_reservation_contract.resources.total_resources, + capacity_reservation_contract.public_ips, seconds_elapsed, &pricing_policy, - bill_resources, ); - contract_cost + contract_billing_info.amount_unbilled - } - types::ContractData::RentContract(rent_contract) => { - if !pallet_tfgrid::Nodes::::contains_key(rent_contract.node_id) { - return Err(DispatchErrorWithPostInfo::from(Error::::NodeNotExists)); + if node.resources.total_resources + == capacity_reservation_contract.resources.total_resources + { + Percent::from_percent(pricing_policy.discount_for_dedication_nodes) + * contract_cost + + contract_billing_info.amount_unbilled + } else { + contract_cost + contract_billing_info.amount_unbilled } - let node = pallet_tfgrid::Nodes::::get(rent_contract.node_id).unwrap(); - - let contract_cost = calculate_resources_cost::( - node.resources, - 0, - seconds_elapsed, - &pricing_policy, - true, - ); - Percent::from_percent(pricing_policy.discount_for_dedication_nodes) * contract_cost } - // Calculate total cost for a name contract types::ContractData::NameContract(_) => { // bill user for name usage for number of seconds elapsed let total_cost_u64f64 = (U64F64::from_num(pricing_policy.unique_name.value) / 3600) @@ -115,32 +98,26 @@ pub fn calculate_resources_cost( ipu: u32, seconds_elapsed: u64, pricing_policy: &pallet_tfgrid_types::PricingPolicy, - bill_resources: bool, ) -> u64 { - let mut total_cost = U64F64::from_num(0); - - if bill_resources { - let hru = U64F64::from_num(resources.hru) / pricing_policy.su.factor_base_1024(); - let sru = U64F64::from_num(resources.sru) / pricing_policy.su.factor_base_1024(); - let mru = U64F64::from_num(resources.mru) / pricing_policy.cu.factor_base_1024(); - let cru = U64F64::from_num(resources.cru); - - let su_used = hru / 1200 + sru / 200; - // the pricing policy su cost value is expressed in 1 hours or 3600 seconds. - // we bill every 3600 seconds but here we need to calculate the cost per second and multiply it by the seconds elapsed. - let su_cost = (U64F64::from_num(pricing_policy.su.value) / 3600) - * U64F64::from_num(seconds_elapsed) - * su_used; - log::debug!("su cost: {:?}", su_cost); - - let cu = calculate_cu(cru, mru); - - let cu_cost = (U64F64::from_num(pricing_policy.cu.value) / 3600) - * U64F64::from_num(seconds_elapsed) - * cu; - log::debug!("cu cost: {:?}", cu_cost); - total_cost = su_cost + cu_cost; - } + let hru = U64F64::from_num(resources.hru) / pricing_policy.su.factor_base_1024(); + let sru = U64F64::from_num(resources.sru) / pricing_policy.su.factor_base_1024(); + let mru = U64F64::from_num(resources.mru) / pricing_policy.cu.factor_base_1024(); + let cru = U64F64::from_num(resources.cru); + + let su_used = hru / 1200 + sru / 200; + // the pricing policy su cost value is expressed in 1 hours or 3600 seconds. + // we bill every 3600 seconds but here we need to calculate the cost per second and multiply it by the seconds elapsed. + let su_cost = (U64F64::from_num(pricing_policy.su.value) / 3600) + * U64F64::from_num(seconds_elapsed) + * su_used; + log::debug!("su cost: {:?}", su_cost); + + let cu = calculate_cu(cru, mru); + + let cu_cost = + (U64F64::from_num(pricing_policy.cu.value) / 3600) * U64F64::from_num(seconds_elapsed) * cu; + log::debug!("cu cost: {:?}", cu_cost); + let mut total_cost = su_cost + cu_cost; if ipu > 0 { let total_ip_cost = U64F64::from_num(ipu) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index b01a8ee36..72ba601d5 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -32,7 +32,7 @@ use sp_runtime::{ use substrate_fixed::types::U64F64; use tfchain_support::{ traits::ChangeNode, - types::{ContractPolicy, Node, PowerTarget, Resources}, + types::{CapacityReservationPolicy, ConsumableResources, Node, PowerTarget, Resources}, }; pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"smct"); @@ -174,11 +174,22 @@ pub mod pallet { pub type ActiveNodeContracts = StorageMap<_, Blake2_128Concat, u32, Vec, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn contracts_by_capacity_reservation)] + // A list of Contract ID's for a given capacity reservation. + pub type ContractsByCapacityReservation = + StorageMap<_, Blake2_128Concat, u32, Vec, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn contract_to_bill_at_block)] pub type ContractsToBillAt = StorageMap<_, Blake2_128Concat, u64, Vec, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn capacity_reservations_to_bill_at_block)] + pub type CapacityReservationsToBillAt = + StorageMap<_, Blake2_128Concat, u64, Vec, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn contract_number_of_cylces_billed)] pub type ContractLock = @@ -209,8 +220,8 @@ pub mod pallet { pub type Groups = StorageMap<_, Blake2_128Concat, u32, Group, OptionQuery>; #[pallet::storage] - #[pallet::getter(fn contract_id_by_node_group_config)] - pub type ContractIDByNodeGroupConfig = + #[pallet::getter(fn capacity_reservation_id_by_node_group_config)] + pub type CapacityReservationIDByNodeGroupConfig = StorageMap<_, Blake2_128Concat, types::NodeGroupConfig, u64, ValueQuery>; #[pallet::storage] @@ -286,7 +297,7 @@ pub mod pallet { ContractCreated(types::Contract), /// A contract was updated ContractUpdated(types::Contract), - /// A Node contract is canceled + /// deprecated event NodeContractCanceled { contract_id: u64, node_id: u32, @@ -321,7 +332,7 @@ pub mod pallet { UpdatedUsedResources(types::ContractResources), /// Network resources report received for contract NruConsumptionReportReceived(types::NruConsumption), - /// a Rent contract is canceled + /// Deprecated event RentContractCanceled { contract_id: u64, }, @@ -347,6 +358,16 @@ pub mod pallet { power_target: PowerTarget, }, GroupCreated(types::Group), + CapacityReservationContractCanceled { + contract_id: u64, + node_id: u32, + twin_id: u32, + }, + DeploymentContractCanceled { + contract_id: u64, + capacity_reservation_contract_id: u64, + twin_id: u32, + }, } #[pallet::error] @@ -389,6 +410,9 @@ pub mod pallet { InvalidResources, GroupNotExists, InvalidContractPolicy, + CapacityReservationNotExists, + CapacityReservationHasActiveContracts, + ResourcesUsedByActiveContracts, } #[pallet::genesis_config] @@ -474,26 +498,53 @@ pub mod pallet { } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn create_deployment_contract( + pub fn create_capacity_reservation_contract( origin: OriginFor, farm_id: u32, + policy: CapacityReservationPolicy, + node_id: Option, + resources: Option, + features: Option>, + ) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_create_capacity_reservation_contract( + account_id, farm_id, policy, node_id, resources, features, + ) + } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn update_capacity_reservation_contract( + origin: OriginFor, + capacity_reservation_id: u64, + resources: Resources, + ) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_update_capacity_reservation_contract( + account_id, + capacity_reservation_id, + resources, + ) + } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn create_deployment_contract( + origin: OriginFor, + capacity_reservation_id: u64, deployment_hash: DeploymentHash, deployment_data: DeploymentDataInput, resources: Resources, public_ips: u32, solution_provider_id: Option, - contract_policy: Option, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; Self::_create_deployment_contract( account_id, - farm_id, + capacity_reservation_id, deployment_hash, deployment_data, resources, public_ips, solution_provider_id, - contract_policy, ) } @@ -532,14 +583,15 @@ pub mod pallet { Err(DispatchErrorWithPostInfo::from(Error::::MethodIsDeprecated).into()) } + // DEPRECATED #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn create_rent_contract( - origin: OriginFor, - node_id: u32, - solution_provider_id: Option, + _origin: OriginFor, + _node_id: u32, + _solution_provider_id: Option, ) -> DispatchResultWithPostInfo { - let account_id = ensure_signed(origin)?; - Self::_create_rent_contract(account_id, node_id, solution_provider_id) + // return error + Err(DispatchErrorWithPostInfo::from(Error::::MethodIsDeprecated).into()) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] @@ -630,52 +682,179 @@ impl Pallet { Ok(().into()) } + pub fn _create_capacity_reservation_contract( + account_id: T::AccountId, + farm_id: u32, + policy: CapacityReservationPolicy, + node_id: Option, + resources: Option, + _features: Option>, + ) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + ensure!( + pallet_tfgrid::Farms::::contains_key(farm_id), + Error::::FarmNotExists + ); + let mut resources = resources.unwrap_or(Resources::empty()); + let mut group_id = None; + let node_id = if let Some(n_id) = node_id { + let node = pallet_tfgrid::Nodes::::get(n_id).ok_or(Error::::NodeNotExists)?; + ensure!( + node.resources.used_resources == Resources::empty(), + Error::::NodeNotAvailableToDeploy + ); + // if node_id was passed the user wants to reserve all resources of a specific node + resources = node.resources.total_resources; + n_id + } else { + group_id = match policy { + CapacityReservationPolicy::Exclusive(g_id) => Some(g_id), + CapacityReservationPolicy::Any => None, + }; + Self::_find_suitable_node_in_farm(farm_id, resources, group_id)? + }; + + Self::_claim_resources_on_node(node_id, resources)?; + + let new_capacity_reservation = types::CapacityReservationContract { + node_id: node_id, + resources: ConsumableResources { + total_resources: resources, + used_resources: Resources::empty(), + }, + group_id: group_id, + public_ips: 0, + deployment_contracts: vec![], + }; + + // Create contract + let contract = Self::_create_contract( + twin_id, + types::ContractData::CapacityReservationContract(new_capacity_reservation.clone()), + None, + )?; + + let now = >::get().saturated_into::() / 1000; + let contract_billing_information = types::ContractBillingInformation { + last_updated: now, + amount_unbilled: 0, + previous_nu_reported: 0, + }; + ContractBillingInformationByID::::insert( + contract.contract_id, + contract_billing_information, + ); + + // insert NodeGroup configuration + if let Some(group_id) = group_id { + CapacityReservationIDByNodeGroupConfig::::insert( + types::NodeGroupConfig { + group_id: group_id, + node_id: node_id, + }, + contract.contract_id, + ); + } + + // Insert contract into active contracts map + let mut capacity_reservation_contracts = + ActiveNodeContracts::::get(&new_capacity_reservation.node_id); + capacity_reservation_contracts.push(contract.contract_id); + ActiveNodeContracts::::insert( + &new_capacity_reservation.node_id, + &capacity_reservation_contracts, + ); + + Self::deposit_event(Event::ContractCreated(contract)); + + Ok(().into()) + } + + #[transactional] + pub fn _update_capacity_reservation_contract( + account_id: T::AccountId, + capacity_reservation_id: u64, + resources: Resources, + ) -> DispatchResultWithPostInfo { + let mut contract = Contracts::::get(capacity_reservation_id) + .ok_or(Error::::CapacityReservationNotExists)?; + let twin = + pallet_tfgrid::Twins::::get(contract.twin_id).ok_or(Error::::TwinNotExists)?; + ensure!( + twin.account_id == account_id, + Error::::TwinNotAuthorizedToCancelContract + ); + let mut capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; + + let resources_increase = capacity_reservation_contract + .resources + .calculate_increase_in_resources(&resources); + let resources_reduction = capacity_reservation_contract + .resources + .calculate_reduction_in_resources(&resources); + + // we can only reduce as much as we have free resources in our reservation + ensure!( + capacity_reservation_contract + .resources + .can_consume_resources(&resources_reduction), + Error::::ResourcesUsedByActiveContracts + ); + if !resources_increase.is_empty() { + Self::_claim_resources_on_node( + capacity_reservation_contract.node_id, + resources_increase, + )?; + } + if !resources_reduction.is_empty() { + Self::_unclaim_resources_on_node( + capacity_reservation_contract.node_id, + resources_reduction, + )?; + } + + capacity_reservation_contract.resources.total_resources = resources; + + contract.contract_type = + types::ContractData::CapacityReservationContract(capacity_reservation_contract); + + contract.state = contract.state.clone(); + Contracts::::insert(&contract.contract_id, contract.clone()); + + Self::deposit_event(Event::ContractUpdated(contract)); + + Ok(().into()) + } + #[transactional] pub fn _create_deployment_contract( account_id: T::AccountId, - farm_id: u32, + capacity_reservation_id: u64, deployment_hash: DeploymentHash, deployment_data: DeploymentDataInput, resources: Resources, public_ips: u32, solution_provider_id: Option, - contract_policy: Option, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let mut group_id = None; - let node_id = match contract_policy { - Some(ContractPolicy::Join(contract_id)) => { - let contract = - Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - ensure!( - contract.twin_id == twin_id, - Error::::NotAuthorizedToCreateDeploymentContract - ); - Self::_get_node_id_from_contract(&contract)? - } - Some(ContractPolicy::Exclusive(g_id)) => { - let farm = - pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; - ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); - group_id = Some(g_id); - Self::_find_suitable_node_in_farm(farm_id, resources, group_id)? - } - None | Some(ContractPolicy::Any) => { - let farm = - pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; - ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); - Self::_find_suitable_node_in_farm(farm_id, resources, None)? - } - }; + let contract = Contracts::::get(capacity_reservation_id) + .ok_or(Error::::CapacityReservationNotExists)?; + let capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; // If the contract with hash and node id exists and it's in any other state then // contractState::Deleted then we don't allow the creation of it. // If it exists we allow the user to "restore" this contract - if ContractIDByNodeIDAndHash::::contains_key(node_id, &deployment_hash) { - //TODO decide what to do with this - let contract_id = ContractIDByNodeIDAndHash::::get(node_id, &deployment_hash); + if ContractIDByNodeIDAndHash::::contains_key( + capacity_reservation_contract.node_id, + &deployment_hash, + ) { + let contract_id = ContractIDByNodeIDAndHash::::get( + capacity_reservation_contract.node_id, + &deployment_hash, + ); let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; if !contract.is_state_delete() { return Err(Error::::ContractIsNotUnique.into()); @@ -690,34 +869,22 @@ impl Pallet { MaxNodeContractPublicIPs, > = vec![].try_into().unwrap(); // Prepare DeploymentContract struct - let node_contract = types::DeploymentContract { - node_id: node_id, + let deployment_contract = types::DeploymentContract { + capacity_reservation_id: capacity_reservation_id, deployment_hash: deployment_hash.clone(), deployment_data: deployment_data, public_ips: public_ips, public_ips_list: public_ips_list, resources: resources, - group_id: group_id, }; // Create contract let contract = Self::_create_contract( twin_id, - types::ContractData::DeploymentContract(node_contract.clone()), + types::ContractData::DeploymentContract(deployment_contract.clone()), solution_provider_id, )?; - // insert NodeGroup configuration - if let Some(group_id) = group_id { - ContractIDByNodeGroupConfig::::insert( - types::NodeGroupConfig { - group_id: group_id, - node_id: node_id, - }, - contract.contract_id, - ); - } - let now = >::get().saturated_into::() / 1000; let contract_billing_information = types::ContractBillingInformation { last_updated: now, @@ -730,25 +897,25 @@ impl Pallet { ); // Insert contract id by (node_id, hash) - ContractIDByNodeIDAndHash::::insert(node_id, deployment_hash, contract.contract_id); - - // Insert contract into active contracts map - let mut node_contracts = ActiveNodeContracts::::get(&node_contract.node_id); - node_contracts.push(contract.contract_id); - ActiveNodeContracts::::insert(&node_contract.node_id, &node_contracts); + ContractIDByNodeIDAndHash::::insert( + capacity_reservation_contract.node_id, + deployment_hash, + contract.contract_id, + ); Self::deposit_event(Event::ContractCreated(contract)); Ok(().into()) } - pub fn _get_node_id_from_contract( - contract: &types::Contract, - ) -> Result { + pub fn _get_node_id_from_contract(contract_id: u64) -> Result { + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; let node_id = match contract.contract_type { - types::ContractData::RentContract(ref rent_contract) => rent_contract.node_id, + types::ContractData::CapacityReservationContract(ref capacity_reservation_contract) => { + capacity_reservation_contract.node_id + } types::ContractData::DeploymentContract(ref deployment_contract) => { - deployment_contract.node_id + Self::_get_node_id_from_contract(deployment_contract.capacity_reservation_id)? } _ => 0, }; @@ -757,45 +924,45 @@ impl Pallet { Ok(node_id.into()) } - pub fn _create_rent_contract( - account_id: T::AccountId, - node_id: u32, - solution_provider_id: Option, - ) -> DispatchResultWithPostInfo { - ensure!( - !ActiveRentContractForNode::::contains_key(node_id), - Error::::NodeHasRentContract - ); - - let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - ensure!( - pallet_tfgrid::Farms::::contains_key(node.farm_id), - Error::::FarmNotExists - ); - - let active_node_contracts = ActiveNodeContracts::::get(node_id); - let farm = pallet_tfgrid::Farms::::get(node.farm_id).ok_or(Error::::FarmNotExists)?; - ensure!( - farm.dedicated_farm || active_node_contracts.is_empty(), - Error::::NodeNotAvailableToDeploy - ); - - // Create contract - let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) - .ok_or(Error::::TwinNotExists)?; - let contract = Self::_create_contract( - twin_id, - types::ContractData::RentContract(types::RentContract { node_id }), - solution_provider_id, - )?; - - // Insert active rent contract for node - ActiveRentContractForNode::::insert(node_id, contract.contract_id); - - Self::deposit_event(Event::ContractCreated(contract)); - - Ok(().into()) - } + // pub fn _create_rent_contract( + // account_id: T::AccountId, + // node_id: u32, + // solution_provider_id: Option, + // ) -> DispatchResultWithPostInfo { + // ensure!( + // !ActiveRentContractForNode::::contains_key(node_id), + // Error::::NodeHasRentContract + // ); + + // let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + // ensure!( + // pallet_tfgrid::Farms::::contains_key(node.farm_id), + // Error::::FarmNotExists + // ); + + // let active_node_contracts = ActiveNodeContracts::::get(node_id); + // let farm = pallet_tfgrid::Farms::::get(node.farm_id).ok_or(Error::::FarmNotExists)?; + // ensure!( + // farm.dedicated_farm || active_node_contracts.is_empty(), + // Error::::NodeNotAvailableToDeploy + // ); + + // // Create contract + // let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + // .ok_or(Error::::TwinNotExists)?; + // let contract = Self::_create_contract( + // twin_id, + // types::ContractData::RentContract(types::RentContract { node_id }), + // solution_provider_id, + // )?; + + // // Insert active rent contract for node + // ActiveRentContractForNode::::insert(node_id, contract.contract_id); + + // Self::deposit_event(Event::ContractCreated(contract)); + + // Ok(().into()) + // } // Registers a DNS name for a Twin // Ensures uniqueness and also checks if it's a valid DNS name @@ -871,37 +1038,148 @@ impl Pallet { let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; ensure!( - node.can_claim_resources(resources), + node.resources.can_consume_resources(&resources), Error::::NotEnoughResourcesOnNode ); + // if the power_target is down wake the node up and emit event + if matches!(node.power_target, PowerTarget::Down) { + Self::deposit_event(Event::PowerTargetChanged { + farm_id: node.farm_id, + node_id: node_id, + power_target: PowerTarget::Up, + }); + node.power_target = PowerTarget::Up; + } + //update the available resources - node.used_resources = node.used_resources.add(&resources); + node.resources.consume(&resources); pallet_tfgrid::Nodes::::insert(node.id, &node); - Self::_change_power_target_node(node_id, PowerTarget::Up)?; - Ok(().into()) } - fn _un_claim_resources_on_node( + fn _unclaim_resources_on_node( node_id: u32, resources: Resources, ) -> DispatchResultWithPostInfo { let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; //update the available resources - node.used_resources = node.used_resources.substract(&resources); + node.resources.free(&resources); + + // if the power_target is down wake the node up and emit event + // do not power down if it is the first node in the list of nodes from that farm + if node.can_be_shutdown() + && matches!(node.power_target, PowerTarget::Up) + && pallet_tfgrid::NodesByFarmID::::get(node.farm_id)[0] != node_id + { + Self::deposit_event(Event::PowerTargetChanged { + farm_id: node.farm_id, + node_id: node_id, + power_target: PowerTarget::Down, + }); + node.power_target = PowerTarget::Down; + } pallet_tfgrid::Nodes::::insert(node.id, &node); - // we don't shutdown the node if: - // some of the resources are still being used - // it is the first node in the farm (one must stay up) - // there is a rent contract (canceling that rent contract will trigger the shutdown) - if node.can_be_shutdown() && !ActiveRentContractForNode::::contains_key(node_id) { - Self::_change_power_target_node(node_id, PowerTarget::Down)?; + Ok(().into()) + } + + fn _claim_resources_on_capacity_reservation( + capacity_reservation_id: u64, + resources: Resources, + ) -> DispatchResultWithPostInfo { + let mut contract = Contracts::::get(capacity_reservation_id) + .ok_or(Error::::CapacityReservationNotExists)?; + let mut capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; + + ensure!( + capacity_reservation_contract + .resources + .can_consume_resources(&resources), + Error::::NotEnoughResourcesOnNode + ); + + //update the available resources + capacity_reservation_contract.resources.consume(&resources); + + contract.contract_type = + types::ContractData::CapacityReservationContract(capacity_reservation_contract); + + Contracts::::insert(capacity_reservation_id, contract); + + Ok(().into()) + } + + fn _unclaim_resources_on_capacity_reservation( + capacity_reservation_id: u64, + resources: Resources, + ) -> DispatchResultWithPostInfo { + let mut contract = Contracts::::get(capacity_reservation_id) + .ok_or(Error::::CapacityReservationNotExists)?; + let mut capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; + + //update the available resources + capacity_reservation_contract.resources.free(&resources); + + contract.contract_type = + types::ContractData::CapacityReservationContract(capacity_reservation_contract); + + Contracts::::insert(capacity_reservation_id, contract); + + Ok(().into()) + } + + fn _add_deployment_contract_to_capacity_reservation_contract( + capacity_reservation_id: u64, + deployment_contract_id: u64, + ) -> DispatchResultWithPostInfo { + let mut contract = Contracts::::get(capacity_reservation_id) + .ok_or(Error::::CapacityReservationNotExists)?; + let mut capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; + + if !capacity_reservation_contract + .deployment_contracts + .contains(&deployment_contract_id) + { + //update the available resources + capacity_reservation_contract + .deployment_contracts + .push(deployment_contract_id); + + contract.contract_type = + types::ContractData::CapacityReservationContract(capacity_reservation_contract); + + Contracts::::insert(capacity_reservation_id, contract); + } + + Ok(().into()) + } + + fn _remove_deployment_contract_from_capacity_reservation_contract( + capacity_reservation_id: u64, + deployment_contract_id: u64, + ) -> DispatchResultWithPostInfo { + let mut contract = Contracts::::get(capacity_reservation_id) + .ok_or(Error::::CapacityReservationNotExists)?; + let mut capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; + + if capacity_reservation_contract + .deployment_contracts + .contains(&deployment_contract_id) + { + //update the available resources + capacity_reservation_contract + .deployment_contracts + .retain(|&id| id != deployment_contract_id); + + contract.contract_type = + types::ContractData::CapacityReservationContract(capacity_reservation_contract); + + Contracts::::insert(capacity_reservation_id, contract); } Ok(().into()) @@ -917,19 +1195,24 @@ impl Pallet { Error::::FarmNotExists ); + if let Some(g_id) = group_id { + ensure!(Groups::::contains_key(g_id), Error::::GroupNotExists); + } + let nodes_in_farm = pallet_tfgrid::NodesByFarmID::::get(farm_id); let mut suitable_nodes = Vec::new(); for node_id in nodes_in_farm { let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - if node.can_claim_resources(resources) - && !ActiveRentContractForNode::::contains_key(node_id) - { - if let Some(id) = group_id { - if ContractIDByNodeGroupConfig::::contains_key(types::NodeGroupConfig { - node_id: node_id, - group_id: id, - }) { + if node.resources.can_consume_resources(&resources) { + + if let Some(g_id) = group_id { + if !CapacityReservationIDByNodeGroupConfig::::contains_key( + types::NodeGroupConfig { + node_id: node_id, + group_id: g_id, + }, + ) { suitable_nodes.push(node); } } else { @@ -956,12 +1239,19 @@ impl Pallet { id = id + 1; match contract_type { - types::ContractData::DeploymentContract(ref mut nc) => { - Self::_reserve_ip(id, nc)?; - Self::_claim_resources_on_node(nc.node_id, nc.resources)?; + types::ContractData::DeploymentContract(ref mut c) => { + Self::_reserve_ip(id, c)?; + Self::_claim_resources_on_capacity_reservation( + c.capacity_reservation_id, + c.resources, + )?; + Self::_add_deployment_contract_to_capacity_reservation_contract( + c.capacity_reservation_id, + id, + )?; } - types::ContractData::RentContract(ref mut nc) => { - Self::_change_power_target_node(nc.node_id, PowerTarget::Up)?; + types::ContractData::CapacityReservationContract(ref mut c) => { + Self::_claim_resources_on_node(c.node_id, c.resources.total_resources)?; } _ => {} } @@ -1018,37 +1308,43 @@ impl Pallet { Error::::CannotUpdateContractInGraceState ); - let mut node_contract = Self::get_node_contract(&contract.clone())?; + let mut deployment_contract = Self::get_node_contract(&contract.clone())?; + let cr_contract = Contracts::::get(deployment_contract.capacity_reservation_id) + .ok_or(Error::::ContractNotExists)?; + let capacity_reservation_contract = Self::get_capacity_reservation_contract(&cr_contract)?; // remove and reinsert contract id by node id and hash because that hash can have changed ContractIDByNodeIDAndHash::::remove( - node_contract.node_id, - node_contract.deployment_hash, + capacity_reservation_contract.node_id, + deployment_contract.deployment_hash, ); ContractIDByNodeIDAndHash::::insert( - node_contract.node_id, + capacity_reservation_contract.node_id, &deployment_hash, contract_id, ); - node_contract.deployment_hash = deployment_hash; - node_contract.deployment_data = deployment_data; + deployment_contract.deployment_hash = deployment_hash; + deployment_contract.deployment_data = deployment_data; + // update the resources with the extra resources if let Some(resources) = resources { - ensure!( - resources > node_contract.resources, - Error::::InvalidResources - ); - let extra_resources = resources.substract(&node_contract.resources); - Self::_claim_resources_on_node(node_contract.node_id, extra_resources)?; - node_contract.resources = resources; + if deployment_contract.resources != resources { + // first unclaim all resources from deployment contract + Self::_unclaim_resources_on_capacity_reservation( + cr_contract.contract_id, + deployment_contract.resources, + )?; + // then claim the new required resources + Self::_claim_resources_on_capacity_reservation(cr_contract.contract_id, resources)?; + deployment_contract.resources = resources; + } } // override values - contract.contract_type = types::ContractData::DeploymentContract(node_contract); - - let state = contract.state.clone(); - Self::_update_contract_state(&mut contract, &state)?; + contract.contract_type = types::ContractData::DeploymentContract(deployment_contract); + contract.state = contract.state.clone(); + Contracts::::insert(&contract.contract_id, contract.clone()); Self::deposit_event(Event::ContractUpdated(contract)); @@ -1069,33 +1365,33 @@ impl Pallet { Error::::TwinNotAuthorizedToCancelContract ); - // If it's a rent contract and it still has active workloads, don't allow cancellation. + // If it's a capacity reservation contract and it still has active workloads, don't allow cancellation. match contract.contract_type { - types::ContractData::RentContract(ref rent_contract) => { - let active_node_contracts = ActiveNodeContracts::::get(rent_contract.node_id); + types::ContractData::CapacityReservationContract(ref capacity_reservation_contract) => { ensure!( - active_node_contracts.len() == 0, - Error::::NodeHasActiveContracts + capacity_reservation_contract.deployment_contracts.len() == 0, + Error::::CapacityReservationHasActiveContracts ); - Self::_change_power_target_node(rent_contract.node_id, PowerTarget::Down)?; + Self::_unclaim_resources_on_node( + capacity_reservation_contract.node_id, + capacity_reservation_contract.resources.total_resources, + )?; } types::ContractData::DeploymentContract(ref deployment_contract) => { - // remove NodeGroup config - if let Some(group_id) = deployment_contract.group_id { - ContractIDByNodeGroupConfig::::remove(types::NodeGroupConfig { - group_id: group_id, - node_id: deployment_contract.node_id, - }); - } - Self::_un_claim_resources_on_node( - deployment_contract.node_id, + Self::_remove_deployment_contract_from_capacity_reservation_contract( + deployment_contract.capacity_reservation_id, + contract_id, + )?; + Self::_unclaim_resources_on_capacity_reservation( + deployment_contract.capacity_reservation_id, deployment_contract.resources, )?; } _ => {} } - - Self::_update_contract_state(&mut contract, &types::ContractState::Deleted(cause))?; + contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + contract.state = types::ContractState::Deleted(cause); + Contracts::::insert(&contract.contract_id, contract.clone()); Self::bill_contract(contract.contract_id)?; // Remove all associated storage Self::remove_contract(contract.contract_id); @@ -1131,9 +1427,9 @@ impl Pallet { // if the node is trying to send garbage data we can throw an error here let contract = Contracts::::get(report.contract_id).ok_or(Error::::ContractNotExists)?; - let node_contract = Self::get_node_contract(&contract)?; + let capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; ensure!( - node_contract.node_id == node_id, + capacity_reservation_contract.node_id == node_id, Error::::NodeNotAuthorizedToComputeReport ); @@ -1178,7 +1474,6 @@ impl Pallet { ContractBillingInformationByID::::insert(report.contract_id, &contract_billing_info); } - fn bill_contract_using_signed_transaction(contract_id: u64) -> Result<(), Error> { let signer = Signer::::AuthorityId>::any_account(); if !signer.can_sign() { @@ -1312,7 +1607,10 @@ impl Pallet { twin_id: contract.twin_id, }); // If the contract is a rent contract, also move state on associated node contracts - Self::handle_grace_rent_contract(contract, types::ContractState::Created)?; + Self::handle_grace_capacity_reservation_contract( + contract, + types::ContractState::Created, + )?; } else { let diff = current_block - grace_start; // If the contract grace period ran out, we can decomission the contract @@ -1346,7 +1644,7 @@ impl Pallet { block_number: current_block.saturated_into(), }); // If the contract is a rent contract, also move associated node contract to grace period - Self::handle_grace_rent_contract( + Self::handle_grace_capacity_reservation_contract( contract, types::ContractState::GracePeriod(current_block), )?; @@ -1358,14 +1656,13 @@ impl Pallet { Ok(contract) } - fn handle_grace_rent_contract( + fn handle_grace_capacity_reservation_contract( contract: &mut types::Contract, state: types::ContractState, ) -> DispatchResultWithPostInfo { match &contract.contract_type { - types::ContractData::RentContract(rc) => { - let active_node_contracts = ActiveNodeContracts::::get(rc.node_id); - for ctr_id in active_node_contracts { + types::ContractData::CapacityReservationContract(c) => { + for &ctr_id in &c.deployment_contracts { let mut ctr = Contracts::::get(ctr_id).ok_or(Error::::ContractNotExists)?; Self::_update_contract_state(&mut ctr, &state)?; @@ -1374,14 +1671,14 @@ impl Pallet { types::ContractState::Created => { Self::deposit_event(Event::ContractGracePeriodEnded { contract_id: ctr_id, - node_id: rc.node_id, + node_id: c.node_id, twin_id: ctr.twin_id, }); } types::ContractState::GracePeriod(block_number) => { Self::deposit_event(Event::ContractGracePeriodStarted { contract_id: ctr_id, - node_id: rc.node_id, + node_id: c.node_id, twin_id: ctr.twin_id, block_number, }); @@ -1494,28 +1791,64 @@ impl Pallet { if let Some(contract) = contract { match contract.contract_type.clone() { - types::ContractData::DeploymentContract(mut node_contract) => { - Self::remove_active_node_contract(node_contract.node_id, contract_id); - if node_contract.public_ips > 0 { - match Self::_free_ip(contract_id, &mut node_contract) { + types::ContractData::DeploymentContract(mut deployment_contract) => { + let node_id = match Self::_get_node_id_from_contract(contract_id) { + Ok(n_id) => n_id, + Err(e) => { + log::error!("error while getting the node id from the deployment contract: {:?}", e); + 0 + } + }; + if node_id == 0 { + return; + } + // free the public ips requested by the contract + if deployment_contract.public_ips > 0 { + match Self::_free_ip(contract_id, &mut deployment_contract) { Ok(_) => (), Err(e) => { log::info!("error while freeing ips: {:?}", e); } } } - + // unclaim all resources used by this contract on the capacity reservation contract + match Self::_unclaim_resources_on_capacity_reservation( + deployment_contract.capacity_reservation_id, + deployment_contract.resources, + ) { + Ok(_) => (), + Err(e) => { + log::error!( + "error while freeing resources from deployment contract: {:?}", + e + ); + } + } + // remove the deployment contract from the capacity reservation contract + match Self::_remove_deployment_contract_from_capacity_reservation_contract( + deployment_contract.capacity_reservation_id, + contract_id, + ) { + Ok(_) => (), + Err(e) => { + log::error!( + "error while removing the deployment contract from the capacity reservation contract: {:?}", + e + ); + } + } // remove the contract by hash from storage ContractIDByNodeIDAndHash::::remove( - node_contract.node_id, - &node_contract.deployment_hash, + node_id, + &deployment_contract.deployment_hash, ); //NodeContractResources::::remove(contract_id); ContractBillingInformationByID::::remove(contract_id); - Self::deposit_event(Event::NodeContractCanceled { + Self::deposit_event(Event::DeploymentContractCanceled { contract_id, - node_id: node_contract.node_id, + capacity_reservation_contract_id: deployment_contract + .capacity_reservation_id, twin_id: contract.twin_id, }); } @@ -1523,15 +1856,35 @@ impl Pallet { ContractIDByNameRegistration::::remove(name_contract.name); Self::deposit_event(Event::NameContractCanceled { contract_id }); } - types::ContractData::RentContract(rent_contract) => { - ActiveRentContractForNode::::remove(rent_contract.node_id); - // Remove all associated active node contracts - let active_node_contracts = - ActiveNodeContracts::::get(rent_contract.node_id); - for node_contract in active_node_contracts { - Self::remove_contract(node_contract); + types::ContractData::CapacityReservationContract(capacity_reservation_contract) => { + // first remove all deployment contracts created with that reserved capacity + for deployment_contract_id in capacity_reservation_contract.deployment_contracts + { + Self::remove_contract(deployment_contract_id); + } + // unclaim the resources on the node + match Self::_unclaim_resources_on_node( + capacity_reservation_contract.node_id, + capacity_reservation_contract.resources.total_resources, + ) { + Ok(_) => (), + Err(e) => { + log::error!( + "error while freeing resources from capacity reservation contract: {:?}", + e + ); + } } - Self::deposit_event(Event::RentContractCanceled { contract_id }); + // remove the contract id from the active contracts on that node + Self::remove_active_node_contract( + capacity_reservation_contract.node_id, + contract_id, + ); + Self::deposit_event(Event::CapacityReservationContractCanceled { + contract_id: contract_id, + node_id: capacity_reservation_contract.node_id, + twin_id: contract.twin_id, + }); } }; info!("removing contract"); @@ -1683,7 +2036,6 @@ impl Pallet { let index = Self::get_contract_index().checked_sub(1).unwrap_or(0); let mut contracts = ContractsToBillAt::::get(index); - if !contracts.contains(&contract_id) { contracts.push(contract_id); ContractsToBillAt::::insert(index, &contracts); @@ -1704,60 +2056,28 @@ impl Pallet { contract.state = state.clone(); Contracts::::insert(&contract.contract_id, contract.clone()); - // if the contract is a name contract, nothing to do left here - match contract.contract_type { - types::ContractData::NameContract(_) => return Ok(().into()), - types::ContractData::RentContract(_) => return Ok(().into()), - _ => (), - }; - - // if the contract is a node contract - // manage the ActiveNodeContracts map accordingly - let node_contract = Self::get_node_contract(contract)?; - - let mut contracts = ActiveNodeContracts::::get(&node_contract.node_id); - - match contracts.iter().position(|id| id == &contract.contract_id) { - Some(index) => { - // if the new contract state is delete, remove the contract id from the map - if contract.is_state_delete() { - contracts.remove(index); - } - } - None => { - // if the contract is not present add it to the active contracts map - if state == &types::ContractState::Created { - contracts.push(contract.contract_id); - } - } - }; - - ActiveNodeContracts::::insert(&node_contract.node_id, &contracts); - Ok(().into()) } fn remove_active_node_contract(node_id: u32, contract_id: u64) { let mut contracts = ActiveNodeContracts::::get(&node_id); - match contracts.iter().position(|id| id == &contract_id) { - Some(index) => { - contracts.remove(index); - } - None => (), - }; + contracts.retain(|&id| id != contract_id); ActiveNodeContracts::::insert(&node_id, &contracts); } pub fn _reserve_ip( contract_id: u64, - node_contract: &mut types::DeploymentContract, + deployment_contract: &mut types::DeploymentContract, ) -> DispatchResultWithPostInfo { - if node_contract.public_ips == 0 { + let mut contract = Contracts::::get(deployment_contract.capacity_reservation_id) + .ok_or(Error::::CapacityReservationNotExists)?; + let mut capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; + if deployment_contract.public_ips == 0 { return Ok(().into()); } - let node = pallet_tfgrid::Nodes::::get(node_contract.node_id) + let node = pallet_tfgrid::Nodes::::get(capacity_reservation_contract.node_id) .ok_or(Error::::NodeNotExists)?; let mut farm = @@ -1766,10 +2086,10 @@ impl Pallet { log::info!( "Number of farm ips {:?}, number of ips to reserve: {:?}", farm.public_ips.len(), - node_contract.public_ips as usize + capacity_reservation_contract.public_ips as usize ); ensure!( - farm.public_ips.len() >= node_contract.public_ips as usize, + farm.public_ips.len() >= deployment_contract.public_ips as usize, Error::::FarmHasNotEnoughPublicIPs ); @@ -1782,7 +2102,7 @@ impl Pallet { > = vec![].try_into().unwrap(); for i in 0..farm.public_ips.len() { - if ips.len() == node_contract.public_ips as usize { + if ips.len() == deployment_contract.public_ips as usize { break; } @@ -1802,11 +2122,11 @@ impl Pallet { // Safeguard check if we actually have the amount of ips we wanted to reserve ensure!( - ips.len() == node_contract.public_ips as usize, + ips.len() == deployment_contract.public_ips as usize, Error::::FarmHasNotEnoughPublicIPsFree ); - node_contract.public_ips_list = ips.try_into().or_else(|_| { + deployment_contract.public_ips_list = ips.try_into().or_else(|_| { return Err(DispatchErrorWithPostInfo::from( Error::::FailedToReserveIP, )); @@ -1815,14 +2135,23 @@ impl Pallet { // Update the farm with the reserved ips pallet_tfgrid::Farms::::insert(farm.id, farm); + // Update the capacity reservation contract for billing pub ips + capacity_reservation_contract.public_ips += deployment_contract.public_ips; + contract.contract_type = + types::ContractData::CapacityReservationContract(capacity_reservation_contract); + Contracts::::insert(contract.contract_id, &contract); + Ok(().into()) } pub fn _free_ip( contract_id: u64, - node_contract: &mut types::DeploymentContract, + deployment_contract: &mut types::DeploymentContract, ) -> DispatchResultWithPostInfo { - let node = pallet_tfgrid::Nodes::::get(node_contract.node_id) + let mut contract = Contracts::::get(deployment_contract.capacity_reservation_id) + .ok_or(Error::::CapacityReservationNotExists)?; + let mut capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; + let node = pallet_tfgrid::Nodes::::get(capacity_reservation_contract.node_id) .ok_or(Error::::NodeNotExists)?; let mut farm = @@ -1850,6 +2179,12 @@ impl Pallet { pallet_tfgrid::Farms::::insert(farm.id, farm); + // Update the capacity reservation contract for billing pub ips + capacity_reservation_contract.public_ips -= deployment_contract.public_ips; + contract.contract_type = + types::ContractData::CapacityReservationContract(capacity_reservation_contract); + Contracts::::insert(contract.contract_id, &contract); + // Emit an event containing the IP's freed for this contract Self::deposit_event(Event::IPsFreed { contract_id, @@ -1872,11 +2207,11 @@ impl Pallet { } } - pub fn get_rent_contract( + pub fn get_capacity_reservation_contract( contract: &types::Contract, - ) -> Result { + ) -> Result { match contract.contract_type.clone() { - types::ContractData::RentContract(c) => Ok(c), + types::ContractData::CapacityReservationContract(c) => Ok(c), _ => { return Err(DispatchErrorWithPostInfo::from( Error::::InvalidContractType, @@ -1985,29 +2320,15 @@ impl ChangeNode, InterfaceOf> for Pallet { fn node_deleted(node: &Node, InterfaceOf>) { // Clean up all active contracts - let active_node_contracts = ActiveNodeContracts::::get(node.id); - for node_contract_id in active_node_contracts { - if let Some(mut contract) = Contracts::::get(node_contract_id) { - // Bill contract - let _ = Self::_update_contract_state( - &mut contract, - &types::ContractState::Deleted(types::Cause::CanceledByUser), - ); - let _ = Self::bill_contract(node_contract_id); - Self::remove_contract(node_contract_id); - } - } - - // First clean up rent contract if it exists - if let Some(rc_id) = ActiveRentContractForNode::::get(node.id) { - if let Some(mut contract) = Contracts::::get(rc_id) { + for contract_id in ActiveNodeContracts::::get(node.id) { + if let Some(mut contract) = Contracts::::get(contract_id) { // Bill contract let _ = Self::_update_contract_state( &mut contract, &types::ContractState::Deleted(types::Cause::CanceledByUser), ); - let _ = Self::bill_contract(contract.contract_id); - Self::remove_contract(contract.contract_id); + let _ = Self::bill_contract(contract_id); + Self::remove_contract(contract_id); } } } diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 0707d6d9c..d256f0a46 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -17,7 +17,8 @@ use crate::cost; use log::info; use pallet_tfgrid::types as pallet_tfgrid_types; use tfchain_support::types::{ - ContractPolicy, FarmCertification, Location, NodeCertification, PowerTarget, PublicIP, Resources, + CapacityReservationPolicy, ConsumableResources, FarmCertification, Location, NodeCertification, + PowerTarget, PublicIP, Resources, }; const GIGABYTE: u64 = 1024 * 1024 * 1024; @@ -26,26 +27,25 @@ const GIGABYTE: u64 = 1024 * 1024 * 1024; // -------------------- // #[test] -fn test_create_deployment_contract_works() { +fn test_create_capacity_reservation_contract_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 0, - None, + CapacityReservationPolicy::Any, None, + Some(get_resources()), + None )); }); } #[test] fn test_create_deployment_contract_with_public_ips_works() { + // todo fix this new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); @@ -58,7 +58,6 @@ fn test_create_deployment_contract_with_public_ips_works() { get_resources(), 1, None, - None, )); let deployment_contract = SmartContractModule::contracts(1).unwrap(); @@ -84,20 +83,18 @@ fn test_create_deployment_contract_with_public_ips_works() { } #[test] -fn test_create_deployment_contract_with_undefined_node_fails() { +fn test_create_capacity_reservation_contract_with_nonexisting_farm_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); assert_noop!( - SmartContractModule::create_deployment_contract( + SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 2, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 0, + CapacityReservationPolicy::Any, None, + Some(get_resources()), None, ), Error::::FarmNotExists @@ -107,6 +104,7 @@ fn test_create_deployment_contract_with_undefined_node_fails() { #[test] fn test_create_deployment_contract_with_same_hash_and_node_fails() { + // TODO reorganize new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); @@ -120,7 +118,6 @@ fn test_create_deployment_contract_with_same_hash_and_node_fails() { get_resources(), 0, None, - None, )); assert_noop!( @@ -132,7 +129,6 @@ fn test_create_deployment_contract_with_same_hash_and_node_fails() { get_resources(), 0, None, - None ), Error::::ContractIsNotUnique ); @@ -141,6 +137,7 @@ fn test_create_deployment_contract_with_same_hash_and_node_fails() { #[test] fn test_create_deployment_contract_which_was_canceled_before_works() { + //todo reorganize new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); @@ -154,7 +151,6 @@ fn test_create_deployment_contract_which_was_canceled_before_works() { get_resources(), 0, None, - None, )); let contract_id = SmartContractModule::node_contract_by_hash(1, h); assert_eq!(contract_id, 1); @@ -173,7 +169,6 @@ fn test_create_deployment_contract_which_was_canceled_before_works() { get_resources(), 0, None, - None )); let contract_id = SmartContractModule::node_contract_by_hash(1, h); assert_eq!(contract_id, 2); @@ -181,25 +176,23 @@ fn test_create_deployment_contract_which_was_canceled_before_works() { } #[test] -fn test_create_deployment_contract_no_node_in_farm_with_enough_resources() { +fn test_create_capacity_reservation_contract_no_node_in_farm_with_enough_resources() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); assert_noop!( - SmartContractModule::create_deployment_contract( + SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - Resources { + CapacityReservationPolicy::Any, + None, + Some(Resources { cru: 10, hru: 0, mru: 2 * GIGABYTE, sru: 60 * GIGABYTE - }, - 0, - None, + }), None, ), Error::::NoSuitableNodeInFarm @@ -211,18 +204,18 @@ fn test_create_deployment_contract_no_node_in_farm_with_enough_resources() { // todo test grouping contracts #[test] -fn test_create_deployment_contract_finding_a_node() { +fn test_create_capacity_reservation_contract_finding_a_node() { new_test_ext().execute_with(|| { run_to_block(1, None); - prepare_farm_three_nodes_three_deployment_contracts(); + prepare_farm_three_nodes_three_capacity_reservation_contracts(); // first contract should go to node 1 match SmartContractModule::contracts(1).unwrap().contract_type { - types::ContractData::DeploymentContract(c) => { + types::ContractData::CapacityReservationContract(c) => { assert_eq!(c.node_id, 1); assert_eq!( - c.resources, + c.resources.total_resources, Resources { cru: 4, hru: 0, @@ -238,10 +231,10 @@ fn test_create_deployment_contract_finding_a_node() { // second contract will take most resources but can still go to node 1 match SmartContractModule::contracts(2).unwrap().contract_type { - types::ContractData::DeploymentContract(c) => { + types::ContractData::CapacityReservationContract(c) => { assert_eq!(c.node_id, 1); assert_eq!( - c.resources, + c.resources.total_resources, Resources { cru: 4, hru: 1000 * GIGABYTE, @@ -257,10 +250,10 @@ fn test_create_deployment_contract_finding_a_node() { // third contract can no longer go to node 1 => node 2 should be started match SmartContractModule::contracts(3).unwrap().contract_type { - types::ContractData::DeploymentContract(c) => { + types::ContractData::CapacityReservationContract(c) => { assert_eq!(c.node_id, 2); assert_eq!( - c.resources, + c.resources.total_resources, Resources { cru: 2, hru: 1024 * GIGABYTE, @@ -293,25 +286,23 @@ fn test_create_deployment_contract_finding_a_node() { } #[test] -fn test_create_deployment_contract_finding_a_node_failure() { +fn test_create_capacity_reservation_contract_finding_a_node_failure() { new_test_ext().execute_with(|| { run_to_block(1, None); - prepare_farm_three_nodes_three_deployment_contracts(); - + prepare_farm_three_nodes_three_capacity_reservation_contracts(); + // no available nodes anymore that meet the required resources assert_noop!( - SmartContractModule::create_deployment_contract( + SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - Resources { + CapacityReservationPolicy::Any, + None, + Some(Resources { hru: 4096 * GIGABYTE, sru: 2048 * GIGABYTE, cru: 32, mru: 48 * GIGABYTE, - }, - 0, - None, + }), None, ), Error::::NoSuitableNodeInFarm @@ -320,52 +311,84 @@ fn test_create_deployment_contract_finding_a_node_failure() { } #[test] -fn test_create_rent_contract_then_deployment_contract_checking_power_target() { +fn test_create_capacity_reservation_contract_full_node_then_deployment_contract() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_with_three_nodes(); // node 2 should be down and when we create the rent contract the node should be woken up // we do not yet change the used resources until deployment contracts are created + let node_id = 2; assert_eq!( - TfgridModule::nodes(2).unwrap().power_target, + TfgridModule::nodes(node_id).unwrap().power_target, PowerTarget::Down ); - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - 2, - None + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); assert_eq!( - TfgridModule::nodes(2).unwrap().power_target, + TfgridModule::nodes(node_id).unwrap().power_target, PowerTarget::Up ); assert_eq!( - TfgridModule::nodes(2).unwrap().used_resources, - Resources::empty() + TfgridModule::nodes(node_id) + .unwrap() + .resources + .used_resources, + TfgridModule::nodes(node_id) + .unwrap() + .resources + .total_resources ); // creating the deployment contract should claim resources from the node let resources = get_resources(); + let hash = generate_deployment_hash(); + let data = get_deployment_data(); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, - generate_deployment_hash(), - get_deployment_data(), + hash, + data.clone(), resources, - 1, + 0, None, - Some(ContractPolicy::Join(1)) )); - assert_eq!(TfgridModule::nodes(2).unwrap().used_resources, resources); + // we expect the reservation contract to look like this: + assert_eq!( + SmartContractModule::contracts(1).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: resources_n2(), + used_resources: resources, + }, + node_id: 2, + deployment_contracts: vec![2] + }) + ); + // we expect the deployment contract to look like this: + assert_eq!( + SmartContractModule::contracts(2).unwrap().contract_type, + types::ContractData::DeploymentContract(types::DeploymentContract { + capacity_reservation_id: 1, + deployment_data: data, + deployment_hash: hash, + public_ips: 0, + public_ips_list: Vec::new().try_into().unwrap(), + resources: resources, + }) + ); // canceling the deployment contract should not shutdown the node (because of the created // rent contract) but it should unclaim the resources on that node assert_ok!(SmartContractModule::cancel_contract( Origin::signed(bob()), 2 )); - assert_eq!( - TfgridModule::nodes(2).unwrap().power_target, - PowerTarget::Up - ); // canceling rent contract should shut down the node (as it is not the first in the list // of nodes from that farm) assert_ok!(SmartContractModule::cancel_contract( @@ -373,7 +396,7 @@ fn test_create_rent_contract_then_deployment_contract_checking_power_target() { 1 )); assert_eq!( - TfgridModule::nodes(2).unwrap().power_target, + TfgridModule::nodes(node_id).unwrap().power_target, PowerTarget::Down ); @@ -402,12 +425,11 @@ fn test_create_rent_contract_then_deployment_contract_checking_power_target() { ))), true ); - }); } #[test] -fn test_cancel_rent_contract_should_not_shutdown_first_node() { +fn test_cancel_capacity_reservation_contract_should_not_shutdown_first_node() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_with_three_nodes(); @@ -415,9 +437,12 @@ fn test_cancel_rent_contract_should_not_shutdown_first_node() { TfgridModule::nodes(1).unwrap().power_target, PowerTarget::Up ); - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), None )); assert_eq!( @@ -436,12 +461,12 @@ fn test_cancel_rent_contract_should_not_shutdown_first_node() { }); } - #[test] -fn test_cancel_deployment_contract_shutdown_node() { +fn test_cancel_capacity_reservation_contract_shutdown_node() { new_test_ext().execute_with(|| { run_to_block(1, None); - prepare_farm_three_nodes_three_deployment_contracts(); + prepare_farm_three_nodes_three_capacity_reservation_contracts(); + // node 1 => capacity contract 1 and 2 // cancel contract 2 = nothing should change assert_ok!(SmartContractModule::cancel_contract( @@ -462,13 +487,8 @@ fn test_cancel_deployment_contract_shutdown_node() { ); // on node 1 there is only one contract left => used resources of node 1 should equal resources of contract 1 assert_eq!( - TfgridModule::nodes(1).unwrap().used_resources, - Resources { - cru: 4, - hru: 0, - mru: 2 * GIGABYTE, - sru: 60 * GIGABYTE, - } + TfgridModule::nodes(1).unwrap().resources.used_resources, + resources_c1() ); // cancel contract 3 = node 2 should shutdown @@ -490,7 +510,7 @@ fn test_cancel_deployment_contract_shutdown_node() { ); // nothing else running on node 2 => used resources should be 0 assert_eq!( - TfgridModule::nodes(2).unwrap().used_resources, + TfgridModule::nodes(2).unwrap().resources.used_resources, Resources::empty() ); @@ -514,7 +534,7 @@ fn test_cancel_deployment_contract_shutdown_node() { ); // nothing else running on node 1 => used resources should be 0 assert_eq!( - TfgridModule::nodes(1).unwrap().used_resources, + TfgridModule::nodes(1).unwrap().resources.used_resources, Resources::empty() ); @@ -537,49 +557,43 @@ fn test_cancel_deployment_contract_shutdown_node() { } #[test] -fn test_update_deployment_contract_works() { +fn test_update_capacity_reservation_contract_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - let resources = get_resources(); - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - resources.clone(), - 0, + CapacityReservationPolicy::Any, None, + Some(get_resources()), None, )); - let new_hash = generate_deployment_hash(); - let deployment_data = get_deployment_data(); - let updated_resources = resources.clone().add(&Resources { + let updated_resources = get_resources().clone().add(&Resources { cru: 1, hru: 1 * GIGABYTE, mru: 2 * GIGABYTE, sru: 30 * GIGABYTE, }); - assert_ok!(SmartContractModule::update_deployment_contract( + assert_ok!(SmartContractModule::update_capacity_reservation_contract( Origin::signed(alice()), 1, - new_hash, - get_deployment_data(), - Some(updated_resources), + updated_resources, )); - let deployment_contract = types::DeploymentContract { + let capacity_reservation_contract = types::CapacityReservationContract { node_id: 1, - deployment_hash: new_hash, - deployment_data, - public_ips: 0, - public_ips_list: Vec::new().try_into().unwrap(), - resources: updated_resources, group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: updated_resources, + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], }; - let contract_type = types::ContractData::DeploymentContract(deployment_contract); - + let contract_type = + types::ContractData::CapacityReservationContract(capacity_reservation_contract); let expected_contract_value = types::Contract { contract_id: 1, state: types::ContractState::Created, @@ -596,46 +610,33 @@ fn test_update_deployment_contract_works() { assert_eq!(contracts.len(), 1); assert_eq!(contracts[0], 1); - - let deployment_contract_id_by_hash = - SmartContractModule::node_contract_by_hash(1, new_hash); - assert_eq!(deployment_contract_id_by_hash, 1); }); } #[test] -fn test_update_deployment_contract_too_much_resources() { +fn test_update_capacity_reservation_contract_too_much_resources() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - Resources { - cru: 2, - hru: 0, - mru: 2 * GIGABYTE, - sru: 60 * GIGABYTE - }, - 0, - None, + CapacityReservationPolicy::Any, None, + Some(get_resources()), + None )); // asking for too much resources assert_noop!( - SmartContractModule::update_deployment_contract( + SmartContractModule::update_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - Some(Resources { + Resources { hru: 1024 * GIGABYTE, sru: 512 * GIGABYTE, cru: 10, mru: 16 * GIGABYTE - }), + }, ), Error::::NotEnoughResourcesOnNode ); @@ -643,58 +644,64 @@ fn test_update_deployment_contract_too_much_resources() { } #[test] -fn test_update_deployment_contract_invalid_resources() { +fn test_capacity_reservation_contract_decrease_resources_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - Resources { - cru: 2, - hru: 0, - mru: 2 * GIGABYTE, - sru: 60 * GIGABYTE - }, - 0, + CapacityReservationPolicy::Any, None, + Some(get_resources()), None, )); - // resources should increase - assert_noop!( - SmartContractModule::update_deployment_contract( - Origin::signed(alice()), - 1, - generate_deployment_hash(), - get_deployment_data(), - Some(Resources { - cru: 1, - hru: 0, - mru: 1 * GIGABYTE, - sru: 30 * GIGABYTE - }), - ), - Error::::InvalidResources + let updated_resources = Resources { + cru: 1, + hru: 0, + mru: 1 * GIGABYTE, + sru: 80 * GIGABYTE, + }; + assert_ok!(SmartContractModule::update_capacity_reservation_contract( + Origin::signed(alice()), + 1, + updated_resources, + )); + // validation + assert_eq!( + TfgridModule::nodes(1).unwrap().resources.used_resources, + updated_resources + ); + assert_eq!( + SmartContractModule::contracts(1).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: 1, + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: updated_resources, + used_resources: Resources::empty(), + }, + deployment_contracts: vec![] + }) ); }); } +// todo test decrease resources fails due to resources being used by some contract + #[test] -fn test_update_deployment_contract_not_exists_fails() { +fn test_update_capacity_reservation_contract_not_exists_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); assert_noop!( - SmartContractModule::update_deployment_contract( + SmartContractModule::update_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - None, + get_resources() ), Error::::ContractNotExists ); @@ -702,34 +709,30 @@ fn test_update_deployment_contract_not_exists_fails() { } #[test] -fn test_update_deployment_contract_wrong_twins_fails() { +fn test_update_capacity_reservation_contract_wrong_twins_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 0, + CapacityReservationPolicy::Any, None, + Some(get_resources()), None, )); assert_noop!( - SmartContractModule::update_deployment_contract( + SmartContractModule::update_capacity_reservation_contract( Origin::signed(bob()), 1, - generate_deployment_hash(), - get_deployment_data(), - Some(Resources { + Resources { cru: 1, hru: 0, mru: 1 * GIGABYTE, sru: 10 * GIGABYTE - }), + }, ), Error::::TwinNotAuthorizedToUpdateContract ); @@ -737,19 +740,17 @@ fn test_update_deployment_contract_wrong_twins_fails() { } #[test] -fn test_cancel_deployment_contract_works() { +fn test_cancel_capacity_reservation_contract_contract_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 0, + CapacityReservationPolicy::Any, None, + Some(get_resources()), None, )); @@ -766,59 +767,6 @@ fn test_cancel_deployment_contract_works() { }); } -#[test] -fn test_create_multiple_deployment_contracts_works() { - new_test_ext().execute_with(|| { - run_to_block(1, None); - prepare_farm_and_node(); - - assert_ok!(SmartContractModule::create_deployment_contract( - Origin::signed(alice()), - 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 0, - None, - None - )); - - assert_ok!(SmartContractModule::create_deployment_contract( - Origin::signed(alice()), - 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 0, - None, - None - )); - - assert_ok!(SmartContractModule::create_deployment_contract( - Origin::signed(alice()), - 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 0, - None, - None - )); - - let deployment_contracts = SmartContractModule::active_node_contracts(1); - assert_eq!(deployment_contracts.len(), 3); - - // now cancel 1 and check if the storage maps are updated correctly - assert_ok!(SmartContractModule::cancel_contract( - Origin::signed(alice()), - 1 - )); - - let deployment_contracts = SmartContractModule::active_node_contracts(1); - assert_eq!(deployment_contracts.len(), 2); - }); -} - #[test] fn test_cancel_deployment_contract_frees_public_ips_works() { new_test_ext().execute_with(|| { @@ -833,7 +781,6 @@ fn test_cancel_deployment_contract_frees_public_ips_works() { get_resources(), 1, None, - None )); let farm = TfgridModule::farms(1).unwrap(); @@ -875,7 +822,6 @@ fn test_cancel_deployment_contract_wrong_twins_fails() { get_resources(), 0, None, - None )); assert_noop!( @@ -1014,45 +960,71 @@ fn test_create_name_contract_with_invalid_dns_name_fails() { // -------------------- // #[test] -fn test_create_rent_contract_works() { +fn test_create_capacity_reservation_contract_full_node_reservation_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - node_id, - None + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); let contract = SmartContractModule::contracts(1).unwrap(); - let rent_contract = types::RentContract { node_id }; + let rent_contract = types::CapacityReservationContract { + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: resources_n1(), + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], + node_id: 1, + }; + assert_eq!( + TfgridModule::nodes(1).unwrap().resources.used_resources, + resources_n1() + ); assert_eq!( contract.contract_type, - types::ContractData::RentContract(rent_contract) + types::ContractData::CapacityReservationContract(rent_contract) ); }); } #[test] -fn test_cancel_rent_contract_works() { +fn test_cancel_capacity_reservation_contract_of_full_node_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - node_id, + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, None )); - let contract = SmartContractModule::contracts(1).unwrap(); - let rent_contract = types::RentContract { node_id }; assert_eq!( - contract.contract_type, - types::ContractData::RentContract(rent_contract) + SmartContractModule::contracts(1).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: node_id, + group_id: None, + public_ips: 0, + resources: ConsumableResources{ + total_resources: resources_n1(), + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], + }) ); assert_ok!(SmartContractModule::cancel_contract( @@ -1066,46 +1038,55 @@ fn test_cancel_rent_contract_works() { } #[test] -fn test_create_rent_contract_on_node_in_use_fails() { +fn test_create_capacity_reservation_contract_on_node_in_use_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 1, + CapacityReservationPolicy::Any, + Some(1), None, None, )); assert_noop!( - SmartContractModule::create_rent_contract(Origin::signed(bob()), 1, None), + SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + Some(1), + None, + None, + ), Error::::NodeNotAvailableToDeploy ); }) } #[test] -fn test_create_rent_contract_non_dedicated_empty_node_works() { +fn test_capacity_reservation_contract_non_dedicated_empty_node_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( - Origin::signed(bob()), - node_id, - None + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); }) } #[test] fn test_create_deployment_contract_on_dedicated_node_without_rent_contract_fails() { + // todo fix this new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); @@ -1119,7 +1100,6 @@ fn test_create_deployment_contract_on_dedicated_node_without_rent_contract_fails get_resources(), 1, None, - None ), Error::::NodeNotAvailableToDeploy ); @@ -1128,6 +1108,7 @@ fn test_create_deployment_contract_on_dedicated_node_without_rent_contract_fails #[test] fn test_create_deployment_contract_when_having_a_rentcontract_works() { + // todo fix test new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); @@ -1146,7 +1127,6 @@ fn test_create_deployment_contract_when_having_a_rentcontract_works() { get_resources(), 1, None, - Some(ContractPolicy::Join(1)) )); }) } @@ -1175,7 +1155,6 @@ fn test_create_deployment_contract_when_someone_else_has_rent_contract_fails() { get_resources(), 1, None, - Some(ContractPolicy::Join(1)), ), Error::::NotAuthorizedToCreateDeploymentContract ); @@ -1183,7 +1162,7 @@ fn test_create_deployment_contract_when_someone_else_has_rent_contract_fails() { } #[test] -fn test_cancel_rent_contract_with_active_deployment_contracts_fails() { +fn test_cancel_capacity_reservation_contract_with_active_deployment_contracts_fails() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -1191,9 +1170,12 @@ fn test_cancel_rent_contract_with_active_deployment_contracts_fails() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - node_id, + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, None )); // set rent contract id to 1 to use node from rent contract with id 1 @@ -1203,14 +1185,13 @@ fn test_cancel_rent_contract_with_active_deployment_contracts_fails() { generate_deployment_hash(), get_deployment_data(), get_resources(), - 1, + 0, None, - Some(ContractPolicy::Join(1)) )); assert_noop!( SmartContractModule::cancel_contract(Origin::signed(bob()), 1,), - Error::::NodeHasActiveContracts + Error::::CapacityReservationHasActiveContracts ); // node 1 should still be up after failed attempt to cancel rent contract assert_eq!( @@ -1242,7 +1223,6 @@ fn test_deployment_contract_billing_details() { get_resources(), 1, None, - None )); push_nru_report_for_contract(1, 10); @@ -1330,7 +1310,6 @@ fn test_deployment_contract_billing_details_with_solution_provider() { get_resources(), 1, Some(1), - None, )); push_nru_report_for_contract(1, 10); @@ -1373,7 +1352,6 @@ fn test_multiple_contracts_billing_loop_works() { get_resources(), 1, None, - None )); assert_ok!(SmartContractModule::create_name_contract( Origin::signed(bob()), @@ -1419,7 +1397,6 @@ fn test_deployment_contract_billing_cycles() { get_resources(), 0, None, - None )); let contract_id = 1; let twin_id = 2; @@ -1483,7 +1460,6 @@ fn test_node_multiple_contract_billing_cycles() { get_resources(), 0, None, - None )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), @@ -1493,7 +1469,6 @@ fn test_node_multiple_contract_billing_cycles() { get_resources(), 0, None, - None )); let twin_id = 2; @@ -1540,7 +1515,6 @@ fn test_deployment_contract_billing_cycles_delete_node_cancels_contract() { get_resources(), 1, None, - None )); let contract_id = 1; let twin_id = 2; @@ -1636,7 +1610,6 @@ fn test_deployment_contract_only_public_ip_billing_cycles() { get_resources(), 1, None, - None )); let contract_id = 1; let twin_id = 2; @@ -1688,7 +1661,6 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_works() get_resources(), 0, None, - None )); let contract_id = 1; @@ -1745,7 +1717,6 @@ fn test_deployment_contract_billing_fails() { get_resources(), 1, None, - None )); let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(1); @@ -1793,7 +1764,6 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_without_ get_resources(), 0, None, - None, )); let contract_id = 1; @@ -1868,7 +1838,6 @@ fn test_deployment_contract_out_of_funds_should_move_state_to_graceperiod_works( get_resources(), 0, None, - None )); // cycle 1 @@ -1918,7 +1887,6 @@ fn test_restore_deployment_contract_in_grace_works() { get_resources(), 0, None, - None )); for i in 0..6 { @@ -1978,7 +1946,6 @@ fn test_deployment_contract_grace_period_cancels_contract_when_grace_period_ends get_resources(), 0, None, - None )); // cycle 1 @@ -2075,7 +2042,7 @@ fn test_name_contract_billing() { } #[test] -fn test_rent_contract_billing() { +fn test_capacity_reservation_contract_full_node_billing() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2083,17 +2050,27 @@ fn test_rent_contract_billing() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - node_id, - None + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); - let contract = SmartContractModule::contracts(1).unwrap(); - let rent_contract = types::RentContract { node_id }; assert_eq!( - contract.contract_type, - types::ContractData::RentContract(rent_contract) + SmartContractModule::contracts(1).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: node_id, + public_ips: 0, + deployment_contracts: vec![], + group_id: None, + resources: ConsumableResources { + total_resources: resources_n1(), + used_resources: Resources::empty() + }, + }) ); pool_state @@ -2108,7 +2085,7 @@ fn test_rent_contract_billing() { } #[test] -fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { +fn test_capacity_reservation_contract_full_node_billing_cancel_should_bill_reserved_balance() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2116,19 +2093,15 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - node_id, + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, None )); - let contract = SmartContractModule::contracts(1).unwrap(); - let rent_contract = types::RentContract { node_id }; - assert_eq!( - contract.contract_type, - types::ContractData::RentContract(rent_contract) - ); - pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); @@ -2178,7 +2151,7 @@ fn test_rent_contract_billing_cancel_should_bill_reserved_balance() { } #[test] -fn test_rent_contract_canceled_mid_cycle_should_bill_for_remainder() { +fn test_capacity_reservation_contract_full_node_canceled_mid_cycle_should_bill_for_remainder() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2186,19 +2159,15 @@ fn test_rent_contract_canceled_mid_cycle_should_bill_for_remainder() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - node_id, - None + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); - let contract = SmartContractModule::contracts(1).unwrap(); - let rent_contract = types::RentContract { node_id }; - assert_eq!( - contract.contract_type, - types::ContractData::RentContract(rent_contract) - ); - let twin = TfgridModule::twins(2).unwrap(); let usable_balance = Balances::usable_balance(&twin.account_id); let free_balance = Balances::free_balance(&twin.account_id); @@ -2226,7 +2195,7 @@ fn test_rent_contract_canceled_mid_cycle_should_bill_for_remainder() { } #[test] -fn test_create_rent_contract_and_deployment_contract_excludes_deployment_contract_from_billing_works( +fn test_create_capacity_contract_full_node_and_deployment_contract_should_bill_full_node_works( ) { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { @@ -2235,10 +2204,13 @@ fn test_create_rent_contract_and_deployment_contract_excludes_deployment_contrac TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - node_id, - None + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2249,7 +2221,6 @@ fn test_create_rent_contract_and_deployment_contract_excludes_deployment_contrac get_resources(), 0, None, - Some(ContractPolicy::Join(1)) )); pool_state .write() @@ -2277,7 +2248,7 @@ fn test_create_rent_contract_and_deployment_contract_excludes_deployment_contrac } #[test] -fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_contracts_works() { +fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_cancel_deployment_contracts_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2285,9 +2256,12 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_cont TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), - node_id, + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, None )); @@ -2299,7 +2273,6 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_cont get_resources(), 0, None, - Some(ContractPolicy::Join(1)) )); // run 12 cycles, contracts should cancel after 11 due to lack of funds @@ -2379,7 +2352,7 @@ fn test_rent_contract_canceled_due_to_out_of_funds_should_cancel_deployment_cont } #[test] -fn test_create_rent_contract_and_deployment_contract_with_ip_billing_works() { +fn test_create_capacity_reservation_contract_contract_and_deployment_contract_with_ip_billing_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2387,9 +2360,12 @@ fn test_create_rent_contract_and_deployment_contract_with_ip_billing_works() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - node_id, + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, None )); @@ -2401,7 +2377,6 @@ fn test_create_rent_contract_and_deployment_contract_with_ip_billing_works() { get_resources(), 1, None, - Some(ContractPolicy::Join(1)) )); // 2 contracts => we expect 2 calls to bill_contract @@ -2434,7 +2409,7 @@ fn test_create_rent_contract_and_deployment_contract_with_ip_billing_works() { } #[test] -fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { +fn test_capacity_reservation_contract_full_node_out_of_funds_should_move_state_to_graceperiod_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2442,10 +2417,13 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), - node_id, - None + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); // cycle 1 @@ -2474,7 +2452,7 @@ fn test_rent_contract_out_of_funds_should_move_state_to_graceperiod_works() { } #[test] -fn test_restore_rent_contract_in_grace_works() { +fn test_restore_capacity_reservation_contract_in_grace_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2482,10 +2460,13 @@ fn test_restore_rent_contract_in_grace_works() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), - node_id, - None + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); // cycle 1 @@ -2539,7 +2520,7 @@ fn test_restore_rent_contract_in_grace_works() { } #[test] -fn test_restore_rent_contract_and_deployment_contracts_in_grace_works() { +fn test_restore_capacity_reservation_contract_and_deployment_contracts_in_grace_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2547,9 +2528,12 @@ fn test_restore_rent_contract_and_deployment_contracts_in_grace_works() { TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), - node_id, + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, None )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2560,7 +2544,6 @@ fn test_restore_rent_contract_and_deployment_contracts_in_grace_works() { get_resources(), 0, None, - Some(ContractPolicy::Join(1)) )); // cycle 1 @@ -2663,7 +2646,7 @@ fn test_restore_rent_contract_and_deployment_contracts_in_grace_works() { } #[test] -fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works() { +fn test_capacity_reservation_contract_grace_period_cancels_contract_when_grace_period_ends_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2671,10 +2654,13 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), - node_id, - None + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); // cycle 1 @@ -2716,7 +2702,7 @@ fn test_rent_contract_grace_period_cancels_contract_when_grace_period_ends_works } #[test] -fn test_rent_contract_and_deployment_contract_canceled_when_node_is_deleted_works() { +fn test_capacity_reservation_contract_and_deployment_contract_canceled_when_node_is_deleted_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2724,10 +2710,13 @@ fn test_rent_contract_and_deployment_contract_canceled_when_node_is_deleted_work TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); let node_id = 1; - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), - node_id, - None + 1, + CapacityReservationPolicy::Any, + Some(node_id), + None, + None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2738,7 +2727,6 @@ fn test_rent_contract_and_deployment_contract_canceled_when_node_is_deleted_work get_resources(), 0, None, - Some(ContractPolicy::Join(1)) )); // 2 contracts => 2 calls to bill_contract @@ -2847,7 +2835,6 @@ fn test_create_deployment_contract_with_solution_provider_works() { get_resources(), 0, Some(1), - None )); }); } @@ -2879,7 +2866,6 @@ fn test_create_deployment_contract_with_solution_provider_fails_if_not_approved( get_resources(), 0, Some(1), - None ), Error::::SolutionProviderNotApproved ); @@ -3154,19 +3140,12 @@ pub fn prepare_farm_and_node() { latitude: "32.323112123".as_bytes().to_vec(), }; - let resources = Resources { - hru: 1024 * GIGABYTE, - sru: 512 * GIGABYTE, - cru: 8, - mru: 16 * GIGABYTE, - }; - let country = "Belgium".as_bytes().to_vec(); let city = "Ghent".as_bytes().to_vec(); TfgridModule::create_node( Origin::signed(alice()), 1, - resources, + resources_n1(), location, country, city, @@ -3188,19 +3167,12 @@ pub fn prepare_farm_with_three_nodes() { latitude: "241.323112123".as_bytes().to_vec(), }; - let resources = Resources { - hru: 2048 * GIGABYTE, - sru: 1024 * GIGABYTE, - cru: 16, - mru: 32 * GIGABYTE, - }; - let country = "Belgium".as_bytes().to_vec(); let city = "Ghent".as_bytes().to_vec(); TfgridModule::create_node( Origin::signed(bob()), 1, - resources, + resources_c2(), location, country, city, @@ -3211,26 +3183,19 @@ pub fn prepare_farm_with_three_nodes() { ) .unwrap(); - // SECOND NODE + // THIRD NODE // random location let location = Location { longitude: "6514.233213231".as_bytes().to_vec(), latitude: "324.323112123".as_bytes().to_vec(), }; - let resources = Resources { - hru: 512 * GIGABYTE, - sru: 256 * GIGABYTE, - cru: 4, - mru: 8 * GIGABYTE, - }; - let country = "Belgium".as_bytes().to_vec(); let city = "Ghent".as_bytes().to_vec(); TfgridModule::create_node( Origin::signed(charlie()), 1, - resources, + resources_n3(), location, country, city, @@ -3271,19 +3236,12 @@ pub fn prepare_dedicated_farm_and_node() { latitude: "32.323112123".as_bytes().to_vec(), }; - let resources = Resources { - hru: 1024 * GIGABYTE, - sru: 512 * GIGABYTE, - cru: 8, - mru: 16 * GIGABYTE, - }; - let country = "Belgium".as_bytes().to_vec(); let city = "Ghent".as_bytes().to_vec(); TfgridModule::create_node( Origin::signed(alice()), 1, - resources, + resources_n1(), location, country, city, @@ -3295,6 +3253,33 @@ pub fn prepare_dedicated_farm_and_node() { .unwrap(); } +pub fn resources_n1() -> Resources { + Resources { + hru: 1024 * GIGABYTE, + sru: 512 * GIGABYTE, + cru: 8, + mru: 16 * GIGABYTE, + } +} + +pub fn resources_n2() -> Resources { + Resources { + hru: 2048 * GIGABYTE, + sru: 1024 * GIGABYTE, + cru: 16, + mru: 32 * GIGABYTE, + } +} + +pub fn resources_n3() -> Resources { + Resources { + hru: 512 * GIGABYTE, + sru: 256 * GIGABYTE, + cru: 4, + mru: 8 * GIGABYTE, + } +} + pub fn create_twin(origin: AccountId) { let document = "some_link".as_bytes().to_vec(); let hash = "some_hash".as_bytes().to_vec(); @@ -3426,7 +3411,34 @@ fn get_resources() -> Resources { } } -fn prepare_farm_three_nodes_three_deployment_contracts() { +fn resources_c1() -> Resources { + Resources { + cru: 4, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE, + } +} + +fn resources_c2() -> Resources { + Resources { + cru: 4, + hru: 1000 * GIGABYTE, + mru: 10 * GIGABYTE, + sru: 100 * GIGABYTE, + } +} + +fn resources_c3() -> Resources { + Resources { + cru: 2, + hru: 1024 * GIGABYTE, + mru: 4 * GIGABYTE, + sru: 50 * GIGABYTE, + } +} + +fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { prepare_farm_with_three_nodes(); assert_eq!( @@ -3443,20 +3455,12 @@ fn prepare_farm_three_nodes_three_deployment_contracts() { ); // first contract should go to node 1 - let resources_c1 = Resources { - cru: 4, - hru: 0, - mru: 2 * GIGABYTE, - sru: 60 * GIGABYTE, - }; - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - resources_c1, - 0, + CapacityReservationPolicy::Any, None, + Some(resources_c1()), None, )); @@ -3474,38 +3478,22 @@ fn prepare_farm_three_nodes_three_deployment_contracts() { ); // second contract will take most resources but can still go to node 1 - let resources_c2 = Resources { - cru: 4, - hru: 1000 * GIGABYTE, - mru: 10 * GIGABYTE, - sru: 100 * GIGABYTE, - }; - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - resources_c2, - 0, + CapacityReservationPolicy::Any, None, + Some(resources_c2()), None, ),); - // third contract will take most resources but can still go to node 1 - let resources_c3 = Resources { - cru: 2, - hru: 1024 * GIGABYTE, - mru: 4 * GIGABYTE, - sru: 50 * GIGABYTE, - }; - assert_ok!(SmartContractModule::create_deployment_contract( + // third can no longer go on node 1 so should start node 2 up + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - resources_c3, - 0, + CapacityReservationPolicy::Any, None, + Some(resources_c3()), None, ),); @@ -3521,4 +3509,7 @@ fn prepare_farm_three_nodes_three_deployment_contracts() { TfgridModule::nodes(3).unwrap().power_target, PowerTarget::Down ); + + assert_eq!(SmartContractModule::active_node_contracts(1).len(), 2); + assert_eq!(SmartContractModule::active_node_contracts(2).len(), 1); } diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index 647cab3ad..b5befa545 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -7,7 +7,9 @@ use frame_support::{BoundedVec, RuntimeDebugNoBound}; use scale_info::TypeInfo; use sp_std::prelude::*; use substrate_fixed::types::U64F64; -use tfchain_support::types::Resources; +use tfchain_support::types::{ + ConsumableResources, Resources +}; pub type BlockNumber = u64; @@ -28,8 +30,6 @@ impl Default for StorageVersion { } } - - #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo)] pub struct Group { pub id: u32, @@ -42,7 +42,7 @@ pub struct NodeGroupConfig { pub group_id: u32, } -#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct Contract { @@ -61,18 +61,29 @@ impl Contract { pub fn get_node_id(&self) -> u32 { match self.contract_type.clone() { - ContractData::RentContract(c) => c.node_id, - ContractData::DeploymentContract(c) => c.node_id, + ContractData::CapacityReservationContract(c) => c.node_id, + ContractData::DeploymentContract(_) => 0, ContractData::NameContract(_) => 0, } } } +#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo)] +#[scale_info(skip_type_params(T))] +#[codec(mel_bound())] +pub struct CapacityReservationContract { + pub node_id: u32, + pub resources: ConsumableResources, + pub group_id: Option, + pub public_ips: u32, + pub deployment_contracts: Vec, +} + #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct DeploymentContract { - pub node_id: u32, + pub capacity_reservation_id: u64, // Hash of the deployment, set by the user // Max 32 bytes pub deployment_hash: DeploymentHash, @@ -80,7 +91,6 @@ pub struct DeploymentContract { pub public_ips: u32, pub public_ips_list: BoundedVec, MaxNodeContractPublicIPs>, pub resources: Resources, - pub group_id: Option, } #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] @@ -107,20 +117,20 @@ pub struct RentContract { pub node_id: u32, } -#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub enum ContractData { DeploymentContract(DeploymentContract), NameContract(NameContract), - RentContract(RentContract), + CapacityReservationContract(CapacityReservationContract), } -impl Default for ContractData { - fn default() -> ContractData { - ContractData::RentContract(RentContract::default()) - } -} +// impl Default for ContractData { +// fn default() -> ContractData { +// ContractData::CapacityReservationContract(CapacityReservationContract::default()) +// } +// } #[derive( PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen, @@ -218,6 +228,7 @@ pub struct ContractResources { pub used: Resources, } +// DEPRECATED #[derive( PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen, )] @@ -227,6 +238,15 @@ pub struct ContractLock { pub cycles: u16, } +#[derive( + PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen, +)] +pub struct CapacityReservationLock { + pub amount_locked: BalanceOf, + pub lock_updated: u64, + pub cycles: u16, +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo)] pub struct SolutionProvider { pub solution_provider_id: u64, diff --git a/substrate-node/pallets/pallet-tfgrid/src/lib.rs b/substrate-node/pallets/pallet-tfgrid/src/lib.rs index 1f854ad63..ce78fb77b 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/lib.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/lib.rs @@ -52,7 +52,7 @@ pub mod pallet { use tfchain_support::{ traits::ChangeNode, types::{ - Farm, FarmCertification, FarmingPolicyLimit, Interface, Location, Node, + ConsumableResources, Farm, FarmCertification, FarmingPolicyLimit, Interface, Location, Node, NodeCertification, PublicConfig, PublicIP, Resources, IP, }, }; @@ -980,8 +980,10 @@ pub mod pallet { id, farm_id, twin_id, - resources, - used_resources: Resources::empty(), + resources: ConsumableResources { + total_resources: resources, + used_resources: Resources::empty(), + }, location, country, city, @@ -1062,7 +1064,7 @@ pub mod pallet { }; // If the resources on a certified node changed, reset the certification level to DIY - if stored_node.resources != resources + if stored_node.resources.total_resources != resources && stored_node.certification == NodeCertification::Certified { stored_node.certification = NodeCertification::Diy; @@ -1073,7 +1075,8 @@ pub mod pallet { } stored_node.farm_id = farm_id; - stored_node.resources = resources; + //TODO check that updating resources is possible! + stored_node.resources.total_resources = resources; stored_node.location = location; stored_node.country = country; stored_node.city = city; @@ -2045,7 +2048,7 @@ impl Pallet { match limits.cu { Some(cu_limit) => { - let cu = resources::get_cu(node.resources); + let cu = resources::get_cu(node.resources.total_resources); if cu > cu_limit { return Self::get_default_farming_policy(); } @@ -2056,7 +2059,7 @@ impl Pallet { match limits.su { Some(su_limit) => { - let su = resources::get_su(node.resources); + let su = resources::get_su(node.resources.total_resources); if su > su_limit { return Self::get_default_farming_policy(); } diff --git a/substrate-node/pallets/pallet-tfgrid/src/tests.rs b/substrate-node/pallets/pallet-tfgrid/src/tests.rs index abac00313..dd14915e6 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/tests.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/tests.rs @@ -728,7 +728,7 @@ fn update_certified_node_resources_loses_certification_works() { assert_eq!(node.certification, NodeCertification::Certified); // Change cores to 2 - let mut node_resources = node.resources; + let mut node_resources = node.resources.total_resources; node_resources.cru = 2; assert_ok!(TfgridModule::update_node( @@ -785,7 +785,7 @@ fn update_certified_node_same_resources_keeps_certification_works() { Origin::signed(alice()), 1, 1, - node.resources, + node.resources.total_resources, node.location, node.country, node.city, @@ -2327,8 +2327,8 @@ fn test_attach_farming_policy_flow(farming_policy_id: u32) { // Provide enough CU and SU limits to avoid attaching default policy to node // For node: [CU = 20; SU = 2] - assert_eq!(resources::get_cu(node.resources) <= limit.cu.unwrap(), true); - assert_eq!(resources::get_su(node.resources) <= limit.su.unwrap(), true); + assert_eq!(resources::get_cu(node.resources.total_resources) <= limit.cu.unwrap(), true); + assert_eq!(resources::get_su(node.resources.total_resources) <= limit.su.unwrap(), true); // Link farming policy to farm assert_ok!(TfgridModule::attach_policy_to_farm( diff --git a/substrate-node/support/src/types.rs b/substrate-node/support/src/types.rs index 946b3a952..a5ec322e1 100644 --- a/substrate-node/support/src/types.rs +++ b/substrate-node/support/src/types.rs @@ -69,14 +69,13 @@ pub struct FarmingPolicyLimit { } #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -pub enum ContractPolicy { +pub enum CapacityReservationPolicy { Any, - Join(u64), Exclusive(u32), } -impl Default for ContractPolicy { - fn default() -> ContractPolicy { - ContractPolicy::Any +impl Default for CapacityReservationPolicy { + fn default() -> CapacityReservationPolicy { + CapacityReservationPolicy::Any } } @@ -92,14 +91,44 @@ impl Default for PowerTarget { } } +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen)] +pub struct ConsumableResources { + pub total_resources: Resources, + pub used_resources: Resources, +} + +impl ConsumableResources { + pub fn can_consume_resources(&self, resources: &Resources) -> bool { + (self.total_resources.hru - self.used_resources.hru) >= resources.hru + && (self.total_resources.sru - self.used_resources.sru) >= resources.sru + && (self.total_resources.cru - self.used_resources.cru) >= resources.cru + && (self.total_resources.mru - self.used_resources.mru) >= resources.mru + } + + pub fn consume(&mut self, resources: &Resources) { + self.used_resources = self.used_resources.add(&resources); + } + + pub fn free(&mut self, resources: &Resources) { + self.used_resources = self.used_resources.substract(&resources); + } + + pub fn calculate_increase_in_resources(&self, resources: &Resources) -> Resources { + resources.substract(&self.total_resources) + } + + pub fn calculate_reduction_in_resources(&self, resources: &Resources) -> Resources { + self.total_resources.substract(&resources) + } +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo)] pub struct Node { pub version: u32, pub id: u32, pub farm_id: u32, pub twin_id: u32, - pub resources: Resources, - pub used_resources: Resources, + pub resources: ConsumableResources, pub location: Location, pub country: Vec, pub city: Vec, @@ -117,18 +146,8 @@ pub struct Node { } impl Node { - pub fn can_claim_resources(&self, resources: Resources) -> bool { - (self.resources.hru - self.used_resources.hru) >= resources.hru - && (self.resources.sru - self.used_resources.sru) >= resources.sru - && (self.resources.cru - self.used_resources.cru) >= resources.cru - && (self.resources.mru - self.used_resources.mru) >= resources.mru - } - pub fn can_be_shutdown(&self) -> bool { - self.used_resources.hru == 0 - && self.used_resources.sru == 0 - && self.used_resources.cru == 0 - && self.used_resources.mru == 0 + self.resources.used_resources.is_empty() } } @@ -183,6 +202,10 @@ impl Resources { } } + pub fn is_empty(self) -> bool { + self.cru == 0 && self.sru == 0 && self.hru == 0 && self.mru == 0 + } + pub fn add(mut self, other: &Resources) -> Resources { self.cru += other.cru; self.sru += other.sru; @@ -190,6 +213,14 @@ impl Resources { self.mru += other.mru; self } + + pub fn can_substract(self, other: &Resources) -> bool { + self.cru >= other.cru + && self.sru >= other.sru + && self.hru >= other.hru + && self.mru >= other.mru + } + pub fn substract(mut self, other: &Resources) -> Resources { self.cru = if self.cru < other.cru { 0 From 8cbd6a9aef6f8d3099de522e7fdd1541f1de6afd Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 18 Oct 2022 18:11:05 +0200 Subject: [PATCH 57/87] fixed most tests --- .../pallets/pallet-smart-contract/src/lib.rs | 54 +- .../pallet-smart-contract/src/tests.rs | 508 ++++++++++++------ .../pallets/pallet-tfgrid/src/lib.rs | 9 +- 3 files changed, 371 insertions(+), 200 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 72ba601d5..1c6c16f28 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -505,10 +505,11 @@ pub mod pallet { node_id: Option, resources: Option, features: Option>, + solution_provider_id: Option, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; Self::_create_capacity_reservation_contract( - account_id, farm_id, policy, node_id, resources, features, + account_id, farm_id, policy, node_id, resources, features, solution_provider_id ) } @@ -534,7 +535,6 @@ pub mod pallet { deployment_data: DeploymentDataInput, resources: Resources, public_ips: u32, - solution_provider_id: Option, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; Self::_create_deployment_contract( @@ -544,7 +544,6 @@ pub mod pallet { deployment_data, resources, public_ips, - solution_provider_id, ) } @@ -682,6 +681,7 @@ impl Pallet { Ok(().into()) } + #[transactional] pub fn _create_capacity_reservation_contract( account_id: T::AccountId, farm_id: u32, @@ -689,6 +689,7 @@ impl Pallet { node_id: Option, resources: Option, _features: Option>, + solution_provider_id: Option, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; @@ -712,11 +713,11 @@ impl Pallet { CapacityReservationPolicy::Exclusive(g_id) => Some(g_id), CapacityReservationPolicy::Any => None, }; + let farm = pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; + ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); Self::_find_suitable_node_in_farm(farm_id, resources, group_id)? }; - Self::_claim_resources_on_node(node_id, resources)?; - let new_capacity_reservation = types::CapacityReservationContract { node_id: node_id, resources: ConsumableResources { @@ -732,7 +733,7 @@ impl Pallet { let contract = Self::_create_contract( twin_id, types::ContractData::CapacityReservationContract(new_capacity_reservation.clone()), - None, + solution_provider_id, )?; let now = >::get().saturated_into::() / 1000; @@ -835,14 +836,14 @@ impl Pallet { deployment_data: DeploymentDataInput, resources: Resources, public_ips: u32, - solution_provider_id: Option, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let contract = Contracts::::get(capacity_reservation_id) + let cr_contract = Contracts::::get(capacity_reservation_id) .ok_or(Error::::CapacityReservationNotExists)?; - let capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; + let capacity_reservation_contract = Self::get_capacity_reservation_contract(&cr_contract)?; + ensure!(cr_contract.twin_id == twin_id, Error::::NotAuthorizedToCreateDeploymentContract); // If the contract with hash and node id exists and it's in any other state then // contractState::Deleted then we don't allow the creation of it. @@ -882,7 +883,7 @@ impl Pallet { let contract = Self::_create_contract( twin_id, types::ContractData::DeploymentContract(deployment_contract.clone()), - solution_provider_id, + None, )?; let now = >::get().saturated_into::() / 1000; @@ -1066,8 +1067,11 @@ impl Pallet { ) -> DispatchResultWithPostInfo { let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + log::info!("Before freeing: {:?}", node.resources.used_resources); //update the available resources node.resources.free(&resources); + log::info!("After freeing: {:?}", node.resources.used_resources); + // if the power_target is down wake the node up and emit event // do not power down if it is the first node in the list of nodes from that farm @@ -1204,8 +1208,14 @@ impl Pallet { for node_id in nodes_in_farm { let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + log::info!("total {:?}", node.resources.total_resources); + log::info!("used {:?}", node.resources.used_resources); + log::info!("required {:?}", resources); + log::info!("HRU: {:?}", (node.resources.total_resources.hru - node.resources.used_resources.hru) >= resources.hru); + log::info!("SRU: {:?}", (node.resources.total_resources.sru - node.resources.used_resources.sru) >= resources.sru); + log::info!("CRU: {:?}", (node.resources.total_resources.cru - node.resources.used_resources.cru) >= resources.cru); + log::info!("MRU: {:?}", (node.resources.total_resources.mru - node.resources.used_resources.mru) >= resources.mru); if node.resources.can_consume_resources(&resources) { - if let Some(g_id) = group_id { if !CapacityReservationIDByNodeGroupConfig::::contains_key( types::NodeGroupConfig { @@ -1263,13 +1273,15 @@ impl Pallet { twin_id, contract_id: id, state: types::ContractState::Created, - contract_type, + contract_type: contract_type.clone(), solution_provider_id, }; // Start billing frequency loop // Will always be block now + frequency - Self::insert_contract_to_bill(id); + if !matches!(contract_type, types::ContractData::DeploymentContract(_)) { + Self::insert_contract_to_bill(id); + } // insert into contracts map Contracts::::insert(id, &contract); @@ -1372,20 +1384,6 @@ impl Pallet { capacity_reservation_contract.deployment_contracts.len() == 0, Error::::CapacityReservationHasActiveContracts ); - Self::_unclaim_resources_on_node( - capacity_reservation_contract.node_id, - capacity_reservation_contract.resources.total_resources, - )?; - } - types::ContractData::DeploymentContract(ref deployment_contract) => { - Self::_remove_deployment_contract_from_capacity_reservation_contract( - deployment_contract.capacity_reservation_id, - contract_id, - )?; - Self::_unclaim_resources_on_capacity_reservation( - deployment_contract.capacity_reservation_id, - deployment_contract.resources, - )?; } _ => {} } @@ -1807,7 +1805,7 @@ impl Pallet { match Self::_free_ip(contract_id, &mut deployment_contract) { Ok(_) => (), Err(e) => { - log::info!("error while freeing ips: {:?}", e); + log::error!("error while freeing ips: {:?}", e); } } } diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index d256f0a46..7b2fc46cb 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -38,17 +38,17 @@ fn test_create_capacity_reservation_contract_works() { CapacityReservationPolicy::Any, None, Some(get_resources()), - None + None, + None, )); }); } #[test] fn test_create_deployment_contract_with_public_ips_works() { - // todo fix this new_test_ext().execute_with(|| { run_to_block(1, None); - prepare_farm_and_node(); + prepare_farm_node_and_capacity_reservation(); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), @@ -57,7 +57,6 @@ fn test_create_deployment_contract_with_public_ips_works() { get_deployment_data(), get_resources(), 1, - None, )); let deployment_contract = SmartContractModule::contracts(1).unwrap(); @@ -96,6 +95,7 @@ fn test_create_capacity_reservation_contract_with_nonexisting_farm_fails() { None, Some(get_resources()), None, + None, ), Error::::FarmNotExists ); @@ -104,10 +104,9 @@ fn test_create_capacity_reservation_contract_with_nonexisting_farm_fails() { #[test] fn test_create_deployment_contract_with_same_hash_and_node_fails() { - // TODO reorganize new_test_ext().execute_with(|| { run_to_block(1, None); - prepare_farm_and_node(); + prepare_farm_node_and_capacity_reservation(); let h = generate_deployment_hash(); assert_ok!(SmartContractModule::create_deployment_contract( @@ -117,7 +116,6 @@ fn test_create_deployment_contract_with_same_hash_and_node_fails() { get_deployment_data(), get_resources(), 0, - None, )); assert_noop!( @@ -128,7 +126,6 @@ fn test_create_deployment_contract_with_same_hash_and_node_fails() { get_deployment_data(), get_resources(), 0, - None, ), Error::::ContractIsNotUnique ); @@ -137,10 +134,9 @@ fn test_create_deployment_contract_with_same_hash_and_node_fails() { #[test] fn test_create_deployment_contract_which_was_canceled_before_works() { - //todo reorganize new_test_ext().execute_with(|| { run_to_block(1, None); - prepare_farm_and_node(); + prepare_farm_node_and_capacity_reservation(); let h = generate_deployment_hash(); assert_ok!(SmartContractModule::create_deployment_contract( @@ -150,14 +146,13 @@ fn test_create_deployment_contract_which_was_canceled_before_works() { get_deployment_data(), get_resources(), 0, - None, )); let contract_id = SmartContractModule::node_contract_by_hash(1, h); - assert_eq!(contract_id, 1); + assert_eq!(contract_id, 2); assert_ok!(SmartContractModule::cancel_contract( Origin::signed(alice()), - 1 + 2 )); let h = generate_deployment_hash(); @@ -168,10 +163,9 @@ fn test_create_deployment_contract_which_was_canceled_before_works() { get_deployment_data(), get_resources(), 0, - None, )); let contract_id = SmartContractModule::node_contract_by_hash(1, h); - assert_eq!(contract_id, 2); + assert_eq!(contract_id, 3); }); } @@ -194,6 +188,7 @@ fn test_create_capacity_reservation_contract_no_node_in_farm_with_enough_resourc sru: 60 * GIGABYTE }), None, + None, ), Error::::NoSuitableNodeInFarm ); @@ -304,6 +299,7 @@ fn test_create_capacity_reservation_contract_finding_a_node_failure() { mru: 48 * GIGABYTE, }), None, + None, ), Error::::NoSuitableNodeInFarm ); @@ -329,6 +325,7 @@ fn test_create_capacity_reservation_contract_full_node_then_deployment_contract( Some(node_id), None, None, + None, )); assert_eq!( TfgridModule::nodes(node_id).unwrap().power_target, @@ -355,7 +352,6 @@ fn test_create_capacity_reservation_contract_full_node_then_deployment_contract( data.clone(), resources, 0, - None, )); // we expect the reservation contract to look like this: assert_eq!( @@ -443,7 +439,8 @@ fn test_cancel_capacity_reservation_contract_should_not_shutdown_first_node() { CapacityReservationPolicy::Any, None, Some(resources_c1()), - None + None, + None, )); assert_eq!( TfgridModule::nodes(1).unwrap().power_target, @@ -568,6 +565,7 @@ fn test_update_capacity_reservation_contract_works() { None, Some(get_resources()), None, + None, )); let updated_resources = get_resources().clone().add(&Resources { @@ -624,7 +622,8 @@ fn test_update_capacity_reservation_contract_too_much_resources() { CapacityReservationPolicy::Any, None, Some(get_resources()), - None + None, + None, )); // asking for too much resources assert_noop!( @@ -656,6 +655,7 @@ fn test_capacity_reservation_contract_decrease_resources_works() { None, Some(get_resources()), None, + None, )); let updated_resources = Resources { cru: 1, @@ -721,6 +721,7 @@ fn test_update_capacity_reservation_contract_wrong_twins_fails() { None, Some(get_resources()), None, + None, )); assert_noop!( @@ -752,6 +753,7 @@ fn test_cancel_capacity_reservation_contract_contract_works() { None, Some(get_resources()), None, + None, )); assert_ok!(SmartContractModule::cancel_contract( @@ -771,24 +773,23 @@ fn test_cancel_capacity_reservation_contract_contract_works() { fn test_cancel_deployment_contract_frees_public_ips_works() { new_test_ext().execute_with(|| { run_to_block(1, None); - prepare_farm_and_node(); + prepare_farm_node_and_capacity_reservation(); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), - get_resources(), + resources_c1(), 1, - None, )); let farm = TfgridModule::farms(1).unwrap(); - assert_eq!(farm.public_ips[0].contract_id, 1); + assert_eq!(farm.public_ips[0].contract_id, 2); assert_ok!(SmartContractModule::cancel_contract( Origin::signed(alice()), - 1 + 2 )); let farm = TfgridModule::farms(1).unwrap(); @@ -812,16 +813,15 @@ fn test_cancel_deployment_contract_not_exists_fails() { fn test_cancel_deployment_contract_wrong_twins_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); - prepare_farm_and_node(); + prepare_farm_node_and_capacity_reservation(); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(alice()), 1, generate_deployment_hash(), get_deployment_data(), - get_resources(), + resources_c1(), 0, - None, )); assert_noop!( @@ -973,6 +973,7 @@ fn test_create_capacity_reservation_contract_full_node_reservation_works() { Some(node_id), None, None, + None, )); let contract = SmartContractModule::contracts(1).unwrap(); @@ -1010,7 +1011,8 @@ fn test_cancel_capacity_reservation_contract_of_full_node_works() { CapacityReservationPolicy::Any, Some(node_id), None, - None + None, + None, )); assert_eq!( @@ -1019,7 +1021,7 @@ fn test_cancel_capacity_reservation_contract_of_full_node_works() { node_id: node_id, group_id: None, public_ips: 0, - resources: ConsumableResources{ + resources: ConsumableResources { total_resources: resources_n1(), used_resources: Resources::empty(), }, @@ -1050,6 +1052,7 @@ fn test_create_capacity_reservation_contract_on_node_in_use_fails() { Some(1), None, None, + None, )); assert_noop!( @@ -1060,6 +1063,7 @@ fn test_create_capacity_reservation_contract_on_node_in_use_fails() { Some(1), None, None, + None, ), Error::::NodeNotAvailableToDeploy ); @@ -1080,25 +1084,27 @@ fn test_capacity_reservation_contract_non_dedicated_empty_node_works() { Some(node_id), None, None, + None, )); }) } #[test] -fn test_create_deployment_contract_on_dedicated_node_without_rent_contract_fails() { +fn test_create_capacity_reservation_contract_on_dedicated_farm_withouth_reserving_full_node_fails() +{ // todo fix this new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); assert_noop!( - SmartContractModule::create_deployment_contract( + SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), // not requesting the the full node should not be possible for dedicated farms! + None, None, ), Error::::NodeNotAvailableToDeploy @@ -1107,16 +1113,20 @@ fn test_create_deployment_contract_on_dedicated_node_without_rent_contract_fails } #[test] -fn test_create_deployment_contract_when_having_a_rentcontract_works() { +fn test_create_deployment_contract_when_having_a_capacity_reservation_for_full_node_works() { // todo fix test new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - None + CapacityReservationPolicy::Any, + Some(1), + None, + None, + None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -1126,26 +1136,28 @@ fn test_create_deployment_contract_when_having_a_rentcontract_works() { get_deployment_data(), get_resources(), 1, - None, )); }) } #[test] -fn test_create_deployment_contract_when_someone_else_has_rent_contract_fails() { +fn test_create_deployment_contract_using_someone_elses_capacity_reservation_contract() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); // create rent contract with bob - assert_ok!(SmartContractModule::create_rent_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - None + CapacityReservationPolicy::Any, + Some(1), + None, + None, + None, )); - // try to create node contract with Alice - // Alice not the owner of the rent contract so she is unauthorized to deploy a deployment contract + // Alice not the owner of the capacity reservation contract so she is unauthorized to deploy a deployment contract assert_noop!( SmartContractModule::create_deployment_contract( Origin::signed(alice()), @@ -1154,7 +1166,6 @@ fn test_create_deployment_contract_when_someone_else_has_rent_contract_fails() { get_deployment_data(), get_resources(), 1, - None, ), Error::::NotAuthorizedToCreateDeploymentContract ); @@ -1176,7 +1187,8 @@ fn test_cancel_capacity_reservation_contract_with_active_deployment_contracts_fa CapacityReservationPolicy::Any, Some(node_id), None, - None + None, + None, )); // set rent contract id to 1 to use node from rent contract with id 1 assert_ok!(SmartContractModule::create_deployment_contract( @@ -1186,7 +1198,6 @@ fn test_cancel_capacity_reservation_contract_with_active_deployment_contracts_fa get_deployment_data(), get_resources(), 0, - None, )); assert_noop!( @@ -1215,6 +1226,15 @@ fn test_deployment_contract_billing_details() { let twin = TfgridModule::twins(2).unwrap(); let initial_twin_balance = Balances::free_balance(&twin.account_id); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1222,7 +1242,6 @@ fn test_deployment_contract_billing_details() { get_deployment_data(), get_resources(), 1, - None, )); push_nru_report_for_contract(1, 10); @@ -1302,6 +1321,15 @@ fn test_deployment_contract_billing_details_with_solution_provider() { let initial_twin_balance = Balances::free_balance(&twin.account_id); let initial_total_issuance = Balances::total_issuance(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + Some(1), + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1309,7 +1337,6 @@ fn test_deployment_contract_billing_details_with_solution_provider() { get_deployment_data(), get_resources(), 1, - Some(1), )); push_nru_report_for_contract(1, 10); @@ -1344,6 +1371,15 @@ fn test_multiple_contracts_billing_loop_works() { run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1351,7 +1387,6 @@ fn test_multiple_contracts_billing_loop_works() { get_deployment_data(), get_resources(), 1, - None, )); assert_ok!(SmartContractModule::create_name_contract( Origin::signed(bob()), @@ -1361,23 +1396,28 @@ fn test_multiple_contracts_billing_loop_works() { let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(1); assert_eq!(contracts_to_bill_at_block.len(), 2); - // 2 contracts => 2 billings + // 3 contracts => 2 billings (capacity reservation and name contract) pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); pool_state .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); + .should_call_bill_contract(3, Ok(Pays::Yes.into()), 11); run_to_block(11, Some(&mut pool_state)); // Test that the expected events were emitted let our_events = System::events(); - - // 1: Contract Created (node contract) - // 2: Contract created (name contract) - // 3: Contract Billed (node contract) - // 4: Contract Billed (name contract) - assert_eq!(our_events.len(), 6); + for event in our_events.clone().iter() { + info!("{:?}", event); + } + // PriceStored + // AveragePriceStored + // Contract Created (capacity contract) + // Contract Created (deployment contract) + // Contract created (name contract) + // Contract Billed (capacity contract) + // Contract Billed (name contract) + assert_eq!(our_events.len(), 7); }) } @@ -1389,6 +1429,16 @@ fn test_deployment_contract_billing_cycles() { run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); + assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1396,7 +1446,6 @@ fn test_deployment_contract_billing_cycles() { get_deployment_data(), get_resources(), 0, - None, )); let contract_id = 1; let twin_id = 2; @@ -1451,42 +1500,43 @@ fn test_node_multiple_contract_billing_cycles() { prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + // todo add other capacity reservation contract!! + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), - get_resources(), + half_resources_c1(), 0, - None, )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, generate_deployment_hash(), get_deployment_data(), - get_resources(), + half_resources_c1(), 0, - None, )); let twin_id = 2; pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); let (amount_due_contract_1, discount_received) = calculate_tft_cost(1, twin_id, 11); run_to_block(12, Some(&mut pool_state)); check_report_cost(1, amount_due_contract_1, 12, discount_received); - let (amount_due_contract_2, discount_received) = calculate_tft_cost(2, twin_id, 11); - run_to_block(12, Some(&mut pool_state)); - check_report_cost(2, amount_due_contract_2, 12, discount_received); - let twin = TfgridModule::twins(twin_id).unwrap(); let usable_balance = Balances::usable_balance(&twin.account_id); let free_balance = Balances::free_balance(&twin.account_id); @@ -1494,7 +1544,7 @@ fn test_node_multiple_contract_billing_cycles() { let locked_balance = free_balance - usable_balance; assert_eq!( locked_balance.saturated_into::(), - amount_due_contract_1 as u128 + amount_due_contract_2 as u128 + amount_due_contract_1 as u128 ); }); } @@ -1507,6 +1557,15 @@ fn test_deployment_contract_billing_cycles_delete_node_cancels_contract() { run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1514,7 +1573,6 @@ fn test_deployment_contract_billing_cycles_delete_node_cancels_contract() { get_deployment_data(), get_resources(), 1, - None, )); let contract_id = 1; let twin_id = 2; @@ -1575,7 +1633,7 @@ fn test_deployment_contract_billing_cycles_delete_node_cancels_contract() { assert_eq!( our_events.contains(&record(MockEvent::SmartContractModule( SmartContractEvent::::IPsFreed { - contract_id: 1, + contract_id: 2, public_ips: ips } ))), @@ -1583,7 +1641,17 @@ fn test_deployment_contract_billing_cycles_delete_node_cancels_contract() { ); assert_eq!( our_events.contains(&record(MockEvent::SmartContractModule( - SmartContractEvent::::NodeContractCanceled { + SmartContractEvent::::DeploymentContractCanceled { + contract_id: 2, + capacity_reservation_contract_id: 1, + twin_id: 2 + } + ))), + true + ); + assert_eq!( + our_events.contains(&record(MockEvent::SmartContractModule( + SmartContractEvent::::CapacityReservationContractCanceled { contract_id: 1, node_id: 1, twin_id: 2 @@ -1602,6 +1670,15 @@ fn test_deployment_contract_only_public_ip_billing_cycles() { run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1609,7 +1686,6 @@ fn test_deployment_contract_only_public_ip_billing_cycles() { get_deployment_data(), get_resources(), 1, - None, )); let contract_id = 1; let twin_id = 2; @@ -1653,6 +1729,15 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_works() run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1660,9 +1745,8 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_works() get_deployment_data(), get_resources(), 0, - None, )); - + // only capacity reservation contract should be billed let contract_id = 1; let twin_id = 2; @@ -1685,6 +1769,11 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_works() run_to_block(28, Some(&mut pool_state)); let (amount_due_as_u128, discount_received) = calculate_tft_cost(contract_id, twin_id, 7); + // cancel deployment contract then capacity reservation + assert_ok!(SmartContractModule::cancel_contract( + Origin::signed(bob()), + 2 + )); assert_ok!(SmartContractModule::cancel_contract( Origin::signed(bob()), 1 @@ -1709,6 +1798,15 @@ fn test_deployment_contract_billing_fails() { // Creates a farm and node and sets the price of tft to 0 which raises an error later prepare_farm_and_node(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1716,7 +1814,6 @@ fn test_deployment_contract_billing_fails() { get_deployment_data(), get_resources(), 1, - None, )); let contracts_to_bill_at_block = SmartContractModule::contract_to_bill_at_block(1); @@ -1755,6 +1852,15 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_without_ let initial_twin_balance = Balances::free_balance(&twin.account_id); info!("initial twin balance: {:?}", initial_twin_balance); let initial_total_issuance = Balances::total_issuance(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), @@ -1763,9 +1869,9 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_without_ get_deployment_data(), get_resources(), 0, - None, )); + // contract id 1 is our capacity reservation contract let contract_id = 1; let twin_id = 2; @@ -1800,6 +1906,12 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_without_ let usable_balance_before_canceling = Balances::usable_balance(&twin.account_id); assert_ne!(usable_balance_before_canceling, 0); + // cancel deployment contract + assert_ok!(SmartContractModule::cancel_contract( + Origin::signed(bob()), + 2 + )); + // cancel capacity reservation assert_ok!(SmartContractModule::cancel_contract( Origin::signed(bob()), 1 @@ -1830,6 +1942,15 @@ fn test_deployment_contract_out_of_funds_should_move_state_to_graceperiod_works( run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(charlie()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), 1, @@ -1837,7 +1958,6 @@ fn test_deployment_contract_out_of_funds_should_move_state_to_graceperiod_works( get_deployment_data(), get_resources(), 0, - None, )); // cycle 1 @@ -1879,6 +1999,15 @@ fn test_restore_deployment_contract_in_grace_works() { run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(charlie()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), 1, @@ -1886,7 +2015,6 @@ fn test_restore_deployment_contract_in_grace_works() { get_deployment_data(), get_resources(), 0, - None, )); for i in 0..6 { @@ -1905,6 +2033,7 @@ fn test_restore_deployment_contract_in_grace_works() { let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); + // todo testing the used resources (should still be in used resoures) let our_events = System::events(); assert_eq!( our_events.contains(&record(MockEvent::SmartContractModule( @@ -1917,6 +2046,17 @@ fn test_restore_deployment_contract_in_grace_works() { ))), true ); + assert_eq!( + our_events.contains(&record(MockEvent::SmartContractModule( + SmartContractEvent::::ContractGracePeriodStarted { + contract_id: 2, + node_id: 1, + twin_id: 3, + block_number: 21 + } + ))), + true + ); run_to_block(31, Some(&mut pool_state)); run_to_block(41, Some(&mut pool_state)); @@ -1937,7 +2077,16 @@ fn test_deployment_contract_grace_period_cancels_contract_when_grace_period_ends prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - + + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(charlie()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), 1, @@ -1945,7 +2094,6 @@ fn test_deployment_contract_grace_period_cancels_contract_when_grace_period_ends get_deployment_data(), get_resources(), 0, - None, )); // cycle 1 @@ -2057,6 +2205,7 @@ fn test_capacity_reservation_contract_full_node_billing() { Some(node_id), None, None, + None, )); assert_eq!( @@ -2099,7 +2248,8 @@ fn test_capacity_reservation_contract_full_node_billing_cancel_should_bill_reser CapacityReservationPolicy::Any, Some(node_id), None, - None + None, + None, )); pool_state @@ -2166,6 +2316,7 @@ fn test_capacity_reservation_contract_full_node_canceled_mid_cycle_should_bill_f Some(node_id), None, None, + None, )); let twin = TfgridModule::twins(2).unwrap(); @@ -2195,8 +2346,7 @@ fn test_capacity_reservation_contract_full_node_canceled_mid_cycle_should_bill_f } #[test] -fn test_create_capacity_contract_full_node_and_deployment_contract_should_bill_full_node_works( -) { +fn test_create_capacity_contract_full_node_and_deployment_contract_should_bill_full_node_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2211,6 +2361,7 @@ fn test_create_capacity_contract_full_node_and_deployment_contract_should_bill_f Some(node_id), None, None, + None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2220,14 +2371,10 @@ fn test_create_capacity_contract_full_node_and_deployment_contract_should_bill_f get_deployment_data(), get_resources(), 0, - None, )); pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); run_to_block(11, Some(&mut pool_state)); let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 10); @@ -2248,7 +2395,8 @@ fn test_create_capacity_contract_full_node_and_deployment_contract_should_bill_f } #[test] -fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_cancel_deployment_contracts_works() { +fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_cancel_deployment_contracts_works( +) { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2262,7 +2410,8 @@ fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_c CapacityReservationPolicy::Any, Some(node_id), None, - None + None, + None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2272,7 +2421,6 @@ fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_c get_deployment_data(), get_resources(), 0, - None, )); // run 12 cycles, contracts should cancel after 11 due to lack of funds @@ -2280,9 +2428,6 @@ fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_c pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11 + i * 10); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11 + i * 10); } for i in 0..11 { run_to_block(12 + 10 * i, Some(&mut pool_state)); @@ -2293,14 +2438,15 @@ fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_c // check_report_cost(1, 3, amount_due_as_u128, 12, discount_received); let our_events = System::events(); - // Event 1: Rent contract created - // Event 2: Node Contract created - // Event 3: Grace period started rent contract - // Event 4: Grace period started node contract - // Event 5-16: Rent contract billed - // Event 17: Node contract canceled - // Event 18: Rent contract Canceled - // => no Node Contract billed event + // Event 1: Price stored + // Event 2: Average Price stored + // Event 3: Capacity Reservation contract created + // Event 4: Deployment Contract created + // Event 5: Grace period started capacity reservation contract + // Event 6: Grace period started deployment contract + // Event 7: Capacity contract billed + // Event 7: Deployment contract canceled + // Event 8: Capacity reservation contract Canceled assert_eq!(our_events.len(), 9); for e in our_events.clone().iter() { @@ -2334,25 +2480,28 @@ fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_c our_events[7], record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, - >::NodeContractCanceled { + >::DeploymentContractCanceled { contract_id: 2, - node_id: 1, + capacity_reservation_contract_id: 1, twin_id: 3 })) ); assert_eq!( our_events[8], - record(MockEvent::SmartContractModule(SmartContractEvent::< - TestRuntime, - >::RentContractCanceled { - contract_id: 1 - })) + record(MockEvent::SmartContractModule( + SmartContractEvent::::CapacityReservationContractCanceled { + contract_id: 1, + node_id: 1, + twin_id: 3 + } + )) ); }); } #[test] -fn test_create_capacity_reservation_contract_contract_and_deployment_contract_with_ip_billing_works() { +fn test_create_capacity_reservation_contract_contract_and_deployment_contract_with_ip_billing_works( +) { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2366,7 +2515,8 @@ fn test_create_capacity_reservation_contract_contract_and_deployment_contract_wi CapacityReservationPolicy::Any, Some(node_id), None, - None + None, + None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2376,40 +2526,34 @@ fn test_create_capacity_reservation_contract_contract_and_deployment_contract_wi get_deployment_data(), get_resources(), 1, - None, )); // 2 contracts => we expect 2 calls to bill_contract pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); run_to_block(11, Some(&mut pool_state)); - // check contract 1 costs (Rent Contract) + // check contract 1 costs (Capacity Reservation Contract) let (amount_due_as_u128, discount_received) = calculate_tft_cost(1, 2, 10); assert_ne!(amount_due_as_u128, 0); check_report_cost(1, amount_due_as_u128, 11, discount_received); - // check contract 2 costs (Node Contract) - let (amount_due_as_u128, discount_received) = calculate_tft_cost(2, 2, 10); - assert_ne!(amount_due_as_u128, 0); - check_report_cost(2, amount_due_as_u128, 11, discount_received); - let our_events = System::events(); + for event in our_events.clone().iter() { + info!("{:?}", event); + } // Event 1: Price Stored // Event 2: Avg price stored - // Event 2: Rent contract created - // Event 3: Node Contract created - // Event 4: Rent contract billed - // Event 5: Node Contract billed - assert_eq!(our_events.len(), 6); + // Event 2: Capacity Reservation contract created + // Event 3: Deployment Contract created + // Event 4: Capacity Reservation cntract billed + assert_eq!(our_events.len(), 5); }); } #[test] -fn test_capacity_reservation_contract_full_node_out_of_funds_should_move_state_to_graceperiod_works() { +fn test_capacity_reservation_contract_full_node_out_of_funds_should_move_state_to_graceperiod_works( +) { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2424,6 +2568,7 @@ fn test_capacity_reservation_contract_full_node_out_of_funds_should_move_state_t Some(node_id), None, None, + None, )); // cycle 1 @@ -2467,6 +2612,7 @@ fn test_restore_capacity_reservation_contract_in_grace_works() { Some(node_id), None, None, + None, )); // cycle 1 @@ -2534,7 +2680,8 @@ fn test_restore_capacity_reservation_contract_and_deployment_contracts_in_grace_ CapacityReservationPolicy::Any, Some(node_id), None, - None + None, + None, )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), @@ -2543,16 +2690,12 @@ fn test_restore_capacity_reservation_contract_and_deployment_contracts_in_grace_ get_deployment_data(), get_resources(), 0, - None, )); // cycle 1 pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); run_to_block(11, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); @@ -2585,17 +2728,11 @@ fn test_restore_capacity_reservation_contract_and_deployment_contracts_in_grace_ pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 21); run_to_block(22, Some(&mut pool_state)); pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 31); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 31); run_to_block(32, Some(&mut pool_state)); // Transfer some balance to the owner of the contract to trigger the grace period to stop @@ -2604,17 +2741,11 @@ fn test_restore_capacity_reservation_contract_and_deployment_contracts_in_grace_ pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 41); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 41); run_to_block(42, Some(&mut pool_state)); pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 51); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 51); run_to_block(52, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1).unwrap(); @@ -2661,6 +2792,7 @@ fn test_capacity_reservation_contract_grace_period_cancels_contract_when_grace_p Some(node_id), None, None, + None, )); // cycle 1 @@ -2702,7 +2834,8 @@ fn test_capacity_reservation_contract_grace_period_cancels_contract_when_grace_p } #[test] -fn test_capacity_reservation_contract_and_deployment_contract_canceled_when_node_is_deleted_works() { +fn test_capacity_reservation_contract_and_deployment_contract_canceled_when_node_is_deleted_works() +{ let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -2717,6 +2850,7 @@ fn test_capacity_reservation_contract_and_deployment_contract_canceled_when_node Some(node_id), None, None, + None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2726,16 +2860,16 @@ fn test_capacity_reservation_contract_and_deployment_contract_canceled_when_node get_deployment_data(), get_resources(), 0, - None, )); // 2 contracts => 2 calls to bill_contract pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); - pool_state - .write() - .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); + // TODO: decide whether to bill deployment contracts + // pool_state + // .write() + // .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); run_to_block(11, Some(&mut pool_state)); run_to_block(16, Some(&mut pool_state)); @@ -2751,8 +2885,8 @@ fn test_capacity_reservation_contract_and_deployment_contract_canceled_when_node assert_eq!( our_events.contains(&record(MockEvent::SmartContractModule( - SmartContractEvent::::NodeContractCanceled { - contract_id: 2, + SmartContractEvent::::CapacityReservationContractCanceled { + contract_id: 1, node_id: 1, twin_id: 2 } @@ -2761,7 +2895,11 @@ fn test_capacity_reservation_contract_and_deployment_contract_canceled_when_node ); assert_eq!( our_events.contains(&record(MockEvent::SmartContractModule( - SmartContractEvent::::RentContractCanceled { contract_id: 1 } + SmartContractEvent::::DeploymentContractCanceled { + contract_id: 2, + capacity_reservation_contract_id: 1, + twin_id: 2 + } ))), true ); @@ -2820,27 +2958,27 @@ fn test_create_solution_provider_fails_if_take_to_high() { } #[test] -fn test_create_deployment_contract_with_solution_provider_works() { +fn test_create_capacity_reservation_contract_with_solution_provider_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); prepare_solution_provider(); - assert_ok!(SmartContractModule::create_deployment_contract( + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 0, + CapacityReservationPolicy::Any, + Some(1), + None, + None, Some(1), )); }); } #[test] -fn test_create_deployment_contract_with_solution_provider_fails_if_not_approved() { +fn test_create_capacity_reservation_contract_with_solution_provider_fails_if_not_approved() { new_test_ext().execute_with(|| { prepare_farm_and_node(); @@ -2858,13 +2996,13 @@ fn test_create_deployment_contract_with_solution_provider_fails_if_not_approved( )); assert_noop!( - SmartContractModule::create_deployment_contract( + SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - generate_deployment_hash(), - get_deployment_data(), - get_resources(), - 0, + CapacityReservationPolicy::Any, + Some(1), + None, + None, Some(1), ), Error::::SolutionProviderNotApproved @@ -3157,6 +3295,20 @@ pub fn prepare_farm_and_node() { .unwrap(); } +pub fn prepare_farm_node_and_capacity_reservation() { + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); +} + pub fn prepare_farm_with_three_nodes() { prepare_farm_and_node(); @@ -3172,7 +3324,7 @@ pub fn prepare_farm_with_three_nodes() { TfgridModule::create_node( Origin::signed(bob()), 1, - resources_c2(), + resources_n2(), location, country, city, @@ -3420,6 +3572,15 @@ fn resources_c1() -> Resources { } } +fn half_resources_c1() -> Resources { + Resources { + cru: 2, + hru: 0, + mru: 1 * GIGABYTE, + sru: 30 * GIGABYTE, + } +} + fn resources_c2() -> Resources { Resources { cru: 4, @@ -3462,6 +3623,7 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { None, Some(resources_c1()), None, + None, )); assert_eq!( @@ -3476,6 +3638,10 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { TfgridModule::nodes(3).unwrap().power_target, PowerTarget::Down ); + assert_eq!( + TfgridModule::nodes(1).unwrap().resources.used_resources, + resources_c1() + ); // second contract will take most resources but can still go to node 1 assert_ok!(SmartContractModule::create_capacity_reservation_contract( @@ -3485,7 +3651,12 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { None, Some(resources_c2()), None, + None, ),); + assert_eq!( + TfgridModule::nodes(1).unwrap().resources.used_resources, + resources_c1().add(&resources_c2()) + ); // third can no longer go on node 1 so should start node 2 up assert_ok!(SmartContractModule::create_capacity_reservation_contract( @@ -3495,6 +3666,7 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { None, Some(resources_c3()), None, + None, ),); assert_eq!( @@ -3509,6 +3681,10 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { TfgridModule::nodes(3).unwrap().power_target, PowerTarget::Down ); + assert_eq!( + TfgridModule::nodes(2).unwrap().resources.used_resources, + resources_c3() + ); assert_eq!(SmartContractModule::active_node_contracts(1).len(), 2); assert_eq!(SmartContractModule::active_node_contracts(2).len(), 1); diff --git a/substrate-node/pallets/pallet-tfgrid/src/lib.rs b/substrate-node/pallets/pallet-tfgrid/src/lib.rs index ce78fb77b..c596967d1 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/lib.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/lib.rs @@ -1727,6 +1727,9 @@ pub mod pallet { farm_twin_id == farm_twin.id, Error::::FarmerNotAuthorized ); + + // Call node deleted + T::NodeChanged::node_deleted(&node); let mut nodes_by_farm = NodesByFarmID::::get(node.farm_id); let location = nodes_by_farm @@ -1735,15 +1738,9 @@ pub mod pallet { nodes_by_farm.remove(location); NodesByFarmID::::insert(node.farm_id, nodes_by_farm); - // Call node deleted - T::NodeChanged::node_deleted(&node); - Nodes::::remove(node_id); NodeIdByTwinID::::remove(node.twin_id); - // Call node deleted - T::NodeChanged::node_deleted(&node); - Self::deposit_event(Event::NodeDeleted(node_id)); Ok(().into()) From e22f18caa3786d70be771a9d47aca73caacb9764 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 18 Oct 2022 18:14:55 +0200 Subject: [PATCH 58/87] fixed all tests --- substrate-node/pallets/pallet-smart-contract/src/lib.rs | 2 +- substrate-node/pallets/pallet-smart-contract/src/tests.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 1c6c16f28..1a5562f0e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -784,7 +784,7 @@ impl Pallet { pallet_tfgrid::Twins::::get(contract.twin_id).ok_or(Error::::TwinNotExists)?; ensure!( twin.account_id == account_id, - Error::::TwinNotAuthorizedToCancelContract + Error::::TwinNotAuthorizedToUpdateContract ); let mut capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 7b2fc46cb..ca95713da 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -690,7 +690,7 @@ fn test_capacity_reservation_contract_decrease_resources_works() { } // todo test decrease resources fails due to resources being used by some contract - +// todo more tests resources update #[test] fn test_update_capacity_reservation_contract_not_exists_fails() { new_test_ext().execute_with(|| { @@ -703,7 +703,7 @@ fn test_update_capacity_reservation_contract_not_exists_fails() { 1, get_resources() ), - Error::::ContractNotExists + Error::::CapacityReservationNotExists ); }); } From bedc58cb3fd9cdf1454e8e7b36c78cf4ed7e6113 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Thu, 20 Oct 2022 15:58:02 +0200 Subject: [PATCH 59/87] improved tests --- .../pallets/pallet-smart-contract/src/lib.rs | 1 + .../pallet-smart-contract/src/tests.rs | 402 ++++++++++-------- 2 files changed, 236 insertions(+), 167 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 1a5562f0e..5536c8491 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -574,6 +574,7 @@ pub mod pallet { Self::_compute_reports(account_id, reports) } + // DEPRECATED #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn report_contract_resources( _origin: OriginFor, diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index ca95713da..c7b3b605f 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -41,6 +41,11 @@ fn test_create_capacity_reservation_contract_works() { None, None, )); + + assert_eq!( + TfgridModule::nodes(1).unwrap().resources.used_resources, + get_resources() + ); }); } @@ -195,7 +200,6 @@ fn test_create_capacity_reservation_contract_no_node_in_farm_with_enough_resourc }); } -// todo test billing // todo test grouping contracts #[test] @@ -204,79 +208,6 @@ fn test_create_capacity_reservation_contract_finding_a_node() { run_to_block(1, None); prepare_farm_three_nodes_three_capacity_reservation_contracts(); - - // first contract should go to node 1 - match SmartContractModule::contracts(1).unwrap().contract_type { - types::ContractData::CapacityReservationContract(c) => { - assert_eq!(c.node_id, 1); - assert_eq!( - c.resources.total_resources, - Resources { - cru: 4, - hru: 0, - mru: 2 * GIGABYTE, - sru: 60 * GIGABYTE, - } - ); - } - _ => { - panic!("Expecting a deployment contract!"); - } - } - - // second contract will take most resources but can still go to node 1 - match SmartContractModule::contracts(2).unwrap().contract_type { - types::ContractData::CapacityReservationContract(c) => { - assert_eq!(c.node_id, 1); - assert_eq!( - c.resources.total_resources, - Resources { - cru: 4, - hru: 1000 * GIGABYTE, - mru: 10 * GIGABYTE, - sru: 100 * GIGABYTE, - } - ); - } - _ => { - panic!("Expecting a deployment contract!"); - } - } - - // third contract can no longer go to node 1 => node 2 should be started - match SmartContractModule::contracts(3).unwrap().contract_type { - types::ContractData::CapacityReservationContract(c) => { - assert_eq!(c.node_id, 2); - assert_eq!( - c.resources.total_resources, - Resources { - cru: 2, - hru: 1024 * GIGABYTE, - mru: 4 * GIGABYTE, - sru: 50 * GIGABYTE, - } - ); - } - _ => { - panic!("Expecting a deployment contract!"); - } - } - - let our_events = System::events(); - for event in our_events.clone().iter() { - log::info!("Event: {:?}", event); - } - // node 2 should be started and event should be emitted - assert_eq!( - our_events.contains(&record(MockEvent::SmartContractModule( - SmartContractEvent::::PowerTargetChanged { - farm_id: 1, - node_id: 2, - power_target: PowerTarget::Up, - } - ))), - true - ); }); } @@ -307,17 +238,14 @@ fn test_create_capacity_reservation_contract_finding_a_node_failure() { } #[test] -fn test_create_capacity_reservation_contract_full_node_then_deployment_contract() { +fn test_create_capacity_reservation_contract_reserving_full_node_then_deployment_contract_then_cancel_everything( +) { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_with_three_nodes(); - // node 2 should be down and when we create the rent contract the node should be woken up + // node 2 should be down and when we create the capacity reservation contract the node should be woken up // we do not yet change the used resources until deployment contracts are created let node_id = 2; - assert_eq!( - TfgridModule::nodes(node_id).unwrap().power_target, - PowerTarget::Down - ); assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, @@ -342,7 +270,6 @@ fn test_create_capacity_reservation_contract_full_node_then_deployment_contract( .total_resources ); // creating the deployment contract should claim resources from the node - let resources = get_resources(); let hash = generate_deployment_hash(); let data = get_deployment_data(); assert_ok!(SmartContractModule::create_deployment_contract( @@ -350,10 +277,10 @@ fn test_create_capacity_reservation_contract_full_node_then_deployment_contract( 1, hash, data.clone(), - resources, + get_resources(), 0, )); - // we expect the reservation contract to look like this: + // we expect the capacity reservation contract to look like this: assert_eq!( SmartContractModule::contracts(1).unwrap().contract_type, types::ContractData::CapacityReservationContract(types::CapacityReservationContract { @@ -361,9 +288,9 @@ fn test_create_capacity_reservation_contract_full_node_then_deployment_contract( public_ips: 0, resources: ConsumableResources { total_resources: resources_n2(), - used_resources: resources, + used_resources: get_resources(), }, - node_id: 2, + node_id: node_id, deployment_contracts: vec![2] }) ); @@ -376,16 +303,29 @@ fn test_create_capacity_reservation_contract_full_node_then_deployment_contract( deployment_hash: hash, public_ips: 0, public_ips_list: Vec::new().try_into().unwrap(), - resources: resources, + resources: get_resources(), }) ); - // canceling the deployment contract should not shutdown the node (because of the created - // rent contract) but it should unclaim the resources on that node + // canceling the deployment contract should unclaim the resources on that node and + // remove the contract from the list of deployment contracts assert_ok!(SmartContractModule::cancel_contract( Origin::signed(bob()), 2 )); - // canceling rent contract should shut down the node (as it is not the first in the list + assert_eq!( + SmartContractModule::contracts(1).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: resources_n2(), + used_resources: Resources::empty(), + }, + node_id: node_id, + deployment_contracts: vec![] + }) + ); + // canceling capacity reservation contract should shut down the node (as it is not the first in the list // of nodes from that farm) assert_ok!(SmartContractModule::cancel_contract( Origin::signed(bob()), @@ -395,6 +335,13 @@ fn test_create_capacity_reservation_contract_full_node_then_deployment_contract( TfgridModule::nodes(node_id).unwrap().power_target, PowerTarget::Down ); + assert_eq!( + TfgridModule::nodes(node_id).unwrap().resources, + ConsumableResources { + total_resources: resources_n2(), + used_resources: Resources::empty(), + } + ); let our_events = System::events(); for event in our_events.clone().iter() { @@ -464,7 +411,6 @@ fn test_cancel_capacity_reservation_contract_shutdown_node() { run_to_block(1, None); prepare_farm_three_nodes_three_capacity_reservation_contracts(); // node 1 => capacity contract 1 and 2 - // cancel contract 2 = nothing should change assert_ok!(SmartContractModule::cancel_contract( Origin::signed(alice()), @@ -535,11 +481,11 @@ fn test_cancel_capacity_reservation_contract_shutdown_node() { Resources::empty() ); + // check the power target events let our_events = System::events(); for event in our_events.clone().iter() { log::info!("Event: {:?}", event); } - assert_eq!( our_events.contains(&record(MockEvent::SmartContractModule( SmartContractEvent::::PowerTargetChanged { @@ -553,6 +499,8 @@ fn test_cancel_capacity_reservation_contract_shutdown_node() { }); } +// tests update_deployment_reservation_contract + #[test] fn test_update_capacity_reservation_contract_works() { new_test_ext().execute_with(|| { @@ -568,18 +516,23 @@ fn test_update_capacity_reservation_contract_works() { None, )); - let updated_resources = get_resources().clone().add(&Resources { - cru: 1, - hru: 1 * GIGABYTE, - mru: 2 * GIGABYTE, - sru: 30 * GIGABYTE, - }); + let updated_resources = Resources { + cru: 1, // decrease + hru: 1 * GIGABYTE, // increase + mru: 2 * GIGABYTE, // unmodified + sru: 90 * GIGABYTE, // increase + }; assert_ok!(SmartContractModule::update_capacity_reservation_contract( Origin::signed(alice()), 1, updated_resources, )); - + // Used resources on node should be updated! + assert_eq!( + TfgridModule::nodes(1).unwrap().resources.used_resources, + updated_resources + ); + // contract should look like this: let capacity_reservation_contract = types::CapacityReservationContract { node_id: 1, group_id: None, @@ -643,7 +596,8 @@ fn test_update_capacity_reservation_contract_too_much_resources() { } #[test] -fn test_capacity_reservation_contract_decrease_resources_works() { +fn test_capacity_reservation_contract_decrease_resources_fails_resources_used_by_active_contracts() +{ new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); @@ -657,34 +611,34 @@ fn test_capacity_reservation_contract_decrease_resources_works() { None, None, )); + // deployment contract using half of the resources + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + Resources { + cru: 1, + hru: 0, + mru: 1 * GIGABYTE, + sru: 30 * GIGABYTE, + }, + 0 + )); + // update the resources: sru is lower then what the deployment contract is using => failure let updated_resources = Resources { cru: 1, hru: 0, mru: 1 * GIGABYTE, - sru: 80 * GIGABYTE, + sru: 20 * GIGABYTE, }; - assert_ok!(SmartContractModule::update_capacity_reservation_contract( - Origin::signed(alice()), - 1, - updated_resources, - )); - // validation - assert_eq!( - TfgridModule::nodes(1).unwrap().resources.used_resources, - updated_resources - ); - assert_eq!( - SmartContractModule::contracts(1).unwrap().contract_type, - types::ContractData::CapacityReservationContract(types::CapacityReservationContract { - node_id: 1, - group_id: None, - public_ips: 0, - resources: ConsumableResources { - total_resources: updated_resources, - used_resources: Resources::empty(), - }, - deployment_contracts: vec![] - }) + assert_noop!( + SmartContractModule::update_capacity_reservation_contract( + Origin::signed(alice()), + 1, + updated_resources, + ), + Error::::ResourcesUsedByActiveContracts ); }); } @@ -741,7 +695,7 @@ fn test_update_capacity_reservation_contract_wrong_twins_fails() { } #[test] -fn test_cancel_capacity_reservation_contract_contract_works() { +fn test_cancel_capacity_reservation_contract_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); @@ -761,6 +715,11 @@ fn test_cancel_capacity_reservation_contract_contract_works() { 1 )); + assert_eq!( + TfgridModule::nodes(1).unwrap().resources.used_resources, + Resources::empty() + ); + let deployment_contract = SmartContractModule::contracts(1); assert_eq!(deployment_contract, None); @@ -768,6 +727,43 @@ fn test_cancel_capacity_reservation_contract_contract_works() { assert_eq!(contracts.len(), 0); }); } +// todo test multiple deployment contracts on the same capacity reservation contract + +#[test] +fn test_cancel_deployment_contract_free_resources_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_node_and_capacity_reservation(); + + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + generate_deployment_hash(), + get_deployment_data(), + resources_c1(), + 0, + )); + + assert_ok!(SmartContractModule::cancel_contract( + Origin::signed(alice()), + 2 + )); + // used resources should be empty and deployment contracts should be an empty list + assert_eq!( + SmartContractModule::contracts(1).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: 1, + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: resources_n1(), + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], + }) + ); + }); +} #[test] fn test_cancel_deployment_contract_frees_public_ips_works() { @@ -956,11 +952,11 @@ fn test_create_name_contract_with_invalid_dns_name_fails() { }); } -// RENT CONTRACT TESTS // -// -------------------- // +// CAPACITY CONTRACT RESERVING ALL RESOURCES OF NODE TESTS // +// -------------------------------------------- // #[test] -fn test_create_capacity_reservation_contract_full_node_reservation_works() { +fn test_create_capacity_reservation_contract_reserving_all_resources_node_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); @@ -999,7 +995,7 @@ fn test_create_capacity_reservation_contract_full_node_reservation_works() { } #[test] -fn test_cancel_capacity_reservation_contract_of_full_node_works() { +fn test_cancel_capacity_reservation_contract_all_resources_of_node_works() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); @@ -1040,11 +1036,12 @@ fn test_cancel_capacity_reservation_contract_of_full_node_works() { } #[test] -fn test_create_capacity_reservation_contract_on_node_in_use_fails() { +fn test_create_capacity_reservation_contract_reserving_all_resources_on_node_in_use_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_farm_and_node(); + // Alice is reserving the node 1 for herself assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, @@ -1090,9 +1087,8 @@ fn test_capacity_reservation_contract_non_dedicated_empty_node_works() { } #[test] -fn test_create_capacity_reservation_contract_on_dedicated_farm_withouth_reserving_full_node_fails() -{ - // todo fix this +fn test_create_capacity_reservation_contract_on_dedicated_farm_without_reserving_all_resources_of_node_fails( +) { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); @@ -1103,7 +1099,7 @@ fn test_create_capacity_reservation_contract_on_dedicated_farm_withouth_reservin 1, CapacityReservationPolicy::Any, None, - Some(resources_c1()), // not requesting the the full node should not be possible for dedicated farms! + Some(resources_c1()), // not requesting the all the resources of the node should not be possible for dedicated farms! None, None, ), @@ -1113,8 +1109,8 @@ fn test_create_capacity_reservation_contract_on_dedicated_farm_withouth_reservin } #[test] -fn test_create_deployment_contract_when_having_a_capacity_reservation_for_full_node_works() { - // todo fix test +fn test_create_deployment_contract_when_having_a_capacity_reservation_reserving_all_resources_of_node_works( +) { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); @@ -1141,12 +1137,12 @@ fn test_create_deployment_contract_when_having_a_capacity_reservation_for_full_n } #[test] -fn test_create_deployment_contract_using_someone_elses_capacity_reservation_contract() { +fn test_create_deployment_contract_using_someone_elses_capacity_reservation_contract_fails() { new_test_ext().execute_with(|| { run_to_block(1, None); prepare_dedicated_farm_and_node(); - // create rent contract with bob + // create capacity reservation contract with bob assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, @@ -1156,7 +1152,6 @@ fn test_create_deployment_contract_using_someone_elses_capacity_reservation_cont None, None, )); - // Alice not the owner of the capacity reservation contract so she is unauthorized to deploy a deployment contract assert_noop!( SmartContractModule::create_deployment_contract( @@ -1190,7 +1185,6 @@ fn test_cancel_capacity_reservation_contract_with_active_deployment_contracts_fa None, None, )); - // set rent contract id to 1 to use node from rent contract with id 1 assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1204,7 +1198,7 @@ fn test_cancel_capacity_reservation_contract_with_active_deployment_contracts_fa SmartContractModule::cancel_contract(Origin::signed(bob()), 1,), Error::::CapacityReservationHasActiveContracts ); - // node 1 should still be up after failed attempt to cancel rent contract + // node 1 should still be up after failed attempt to cancel capacity contract assert_eq!( TfgridModule::nodes(1).unwrap().power_target, PowerTarget::Up @@ -1500,7 +1494,7 @@ fn test_node_multiple_contract_billing_cycles() { prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - // todo add other capacity reservation contract!! + // CAPACITY RESERVATION 1 with 2 deployment contracts assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, @@ -1510,7 +1504,6 @@ fn test_node_multiple_contract_billing_cycles() { None, None, )); - assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(bob()), 1, @@ -1527,15 +1520,39 @@ fn test_node_multiple_contract_billing_cycles() { half_resources_c1(), 0, )); - let twin_id = 2; + // CAPACITY RESERVATION 2 with 1 deployment contract + let rest_of_the_resources_on_node_1 = resources_n1().substract(&resources_c1()); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(bob()), + 1, + CapacityReservationPolicy::Any, + None, + Some(rest_of_the_resources_on_node_1.clone()), + None, + None, + )); + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(bob()), + 4, + generate_deployment_hash(), + get_deployment_data(), + rest_of_the_resources_on_node_1, + 0, + )); + let twin_id = 2; pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + pool_state + .write() + .should_call_bill_contract(4, Ok(Pays::Yes.into()), 11); - let (amount_due_contract_1, discount_received) = calculate_tft_cost(1, twin_id, 11); + let (cost_1st_capacity_reservation, discount_1) = calculate_tft_cost(1, twin_id, 11); + let (cost_2nd_capacity_reservation, discount_2) = calculate_tft_cost(4, twin_id, 11); run_to_block(12, Some(&mut pool_state)); - check_report_cost(1, amount_due_contract_1, 12, discount_received); + check_report_cost(1, cost_1st_capacity_reservation, 12, discount_1); + check_report_cost(4, cost_2nd_capacity_reservation, 12, discount_2); let twin = TfgridModule::twins(twin_id).unwrap(); let usable_balance = Balances::usable_balance(&twin.account_id); @@ -1544,7 +1561,7 @@ fn test_node_multiple_contract_billing_cycles() { let locked_balance = free_balance - usable_balance; assert_eq!( locked_balance.saturated_into::(), - amount_due_contract_1 as u128 + cost_1st_capacity_reservation as u128 + cost_2nd_capacity_reservation as u128 ); }); } @@ -1641,7 +1658,7 @@ fn test_deployment_contract_billing_cycles_delete_node_cancels_contract() { ); assert_eq!( our_events.contains(&record(MockEvent::SmartContractModule( - SmartContractEvent::::DeploymentContractCanceled { + SmartContractEvent::::DeploymentContractCanceled { contract_id: 2, capacity_reservation_contract_id: 1, twin_id: 2 @@ -1675,7 +1692,7 @@ fn test_deployment_contract_only_public_ip_billing_cycles() { 1, CapacityReservationPolicy::Any, None, - Some(resources_c1()), + None, // no resources required None, None )); @@ -1684,7 +1701,7 @@ fn test_deployment_contract_only_public_ip_billing_cycles() { 1, generate_deployment_hash(), get_deployment_data(), - get_resources(), + Resources::empty(), 1, )); let contract_id = 1; @@ -2077,7 +2094,7 @@ fn test_deployment_contract_grace_period_cancels_contract_when_grace_period_ends prepare_farm_and_node(); run_to_block(1, Some(&mut pool_state)); TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); - + assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, @@ -2500,8 +2517,7 @@ fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_c } #[test] -fn test_create_capacity_reservation_contract_contract_and_deployment_contract_with_ip_billing_works( -) { +fn test_create_capacity_reservation_contract_and_deployment_contract_with_ip_billing_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); ext.execute_with(|| { prepare_dedicated_farm_and_node(); @@ -3293,6 +3309,13 @@ pub fn prepare_farm_and_node() { "some_serial".as_bytes().to_vec(), ) .unwrap(); + assert_eq!( + TfgridModule::nodes(1).unwrap().resources, + ConsumableResources { + total_resources: resources_n1(), + used_resources: Resources::empty(), + } + ); } pub fn prepare_farm_node_and_capacity_reservation() { @@ -3307,13 +3330,19 @@ pub fn prepare_farm_node_and_capacity_reservation() { None, None, )); + assert_eq!( + TfgridModule::nodes(1).unwrap().resources, + ConsumableResources { + total_resources: resources_n1(), + used_resources: resources_c1(), + } + ); } pub fn prepare_farm_with_three_nodes() { prepare_farm_and_node(); // SECOND NODE - // random location let location = Location { longitude: "45.233213231".as_bytes().to_vec(), latitude: "241.323112123".as_bytes().to_vec(), @@ -3334,9 +3363,15 @@ pub fn prepare_farm_with_three_nodes() { "some_serial".as_bytes().to_vec(), ) .unwrap(); + assert_eq!( + TfgridModule::nodes(2).unwrap().resources, + ConsumableResources { + total_resources: resources_n2(), + used_resources: Resources::empty(), + } + ); // THIRD NODE - // random location let location = Location { longitude: "6514.233213231".as_bytes().to_vec(), latitude: "324.323112123".as_bytes().to_vec(), @@ -3357,6 +3392,13 @@ pub fn prepare_farm_with_three_nodes() { "some_serial".as_bytes().to_vec(), ) .unwrap(); + assert_eq!( + TfgridModule::nodes(3).unwrap().resources, + ConsumableResources { + total_resources: resources_n3(), + used_resources: Resources::empty(), + } + ); let nodes_from_farm = TfgridModule::nodes_by_farm_id(1); assert_eq!(nodes_from_farm.len(), 3); @@ -3602,19 +3644,6 @@ fn resources_c3() -> Resources { fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { prepare_farm_with_three_nodes(); - assert_eq!( - TfgridModule::nodes(1).unwrap().power_target, - PowerTarget::Up - ); - assert_eq!( - TfgridModule::nodes(2).unwrap().power_target, - PowerTarget::Down - ); - assert_eq!( - TfgridModule::nodes(3).unwrap().power_target, - PowerTarget::Down - ); - // first contract should go to node 1 assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), @@ -3642,6 +3671,19 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { TfgridModule::nodes(1).unwrap().resources.used_resources, resources_c1() ); + assert_eq!( + SmartContractModule::contracts(1).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: 1, + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: resources_c1(), + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], + }) + ); // second contract will take most resources but can still go to node 1 assert_ok!(SmartContractModule::create_capacity_reservation_contract( @@ -3652,11 +3694,24 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { Some(resources_c2()), None, None, - ),); + )); assert_eq!( TfgridModule::nodes(1).unwrap().resources.used_resources, resources_c1().add(&resources_c2()) ); + assert_eq!( + SmartContractModule::contracts(2).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: 1, + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: resources_c2(), + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], + }) + ); // third can no longer go on node 1 so should start node 2 up assert_ok!(SmartContractModule::create_capacity_reservation_contract( @@ -3685,6 +3740,19 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { TfgridModule::nodes(2).unwrap().resources.used_resources, resources_c3() ); + assert_eq!( + SmartContractModule::contracts(3).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: 2, + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: resources_c3(), + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], + }) + ); assert_eq!(SmartContractModule::active_node_contracts(1).len(), 2); assert_eq!(SmartContractModule::active_node_contracts(2).len(), 1); From d45fe281b7980afde216e79b0213e779b3225887 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 21 Oct 2022 13:38:28 +0200 Subject: [PATCH 60/87] fixed unit tests --- .../pallets/pallet-smart-contract/src/lib.rs | 48 ++- .../pallet-smart-contract/src/tests.rs | 309 +++++++++++++++++- 2 files changed, 339 insertions(+), 18 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 5536c8491..a88bdda7a 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -413,6 +413,7 @@ pub mod pallet { CapacityReservationNotExists, CapacityReservationHasActiveContracts, ResourcesUsedByActiveContracts, + NotEnoughResourcesInCapacityReservation, } #[pallet::genesis_config] @@ -509,7 +510,13 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; Self::_create_capacity_reservation_contract( - account_id, farm_id, policy, node_id, resources, features, solution_provider_id + account_id, + farm_id, + policy, + node_id, + resources, + features, + solution_provider_id, ) } @@ -787,6 +794,13 @@ impl Pallet { twin.account_id == account_id, Error::::TwinNotAuthorizedToUpdateContract ); + + // Don't allow updates for contracts that are in grace state + ensure!( + !matches!(contract.state, types::ContractState::GracePeriod(_)), + Error::::CannotUpdateContractInGraceState + ); + let mut capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; let resources_increase = capacity_reservation_contract @@ -844,7 +858,10 @@ impl Pallet { let cr_contract = Contracts::::get(capacity_reservation_id) .ok_or(Error::::CapacityReservationNotExists)?; let capacity_reservation_contract = Self::get_capacity_reservation_contract(&cr_contract)?; - ensure!(cr_contract.twin_id == twin_id, Error::::NotAuthorizedToCreateDeploymentContract); + ensure!( + cr_contract.twin_id == twin_id, + Error::::NotAuthorizedToCreateDeploymentContract + ); // If the contract with hash and node id exists and it's in any other state then // contractState::Deleted then we don't allow the creation of it. @@ -1073,7 +1090,6 @@ impl Pallet { node.resources.free(&resources); log::info!("After freeing: {:?}", node.resources.used_resources); - // if the power_target is down wake the node up and emit event // do not power down if it is the first node in the list of nodes from that farm if node.can_be_shutdown() @@ -1105,7 +1121,7 @@ impl Pallet { capacity_reservation_contract .resources .can_consume_resources(&resources), - Error::::NotEnoughResourcesOnNode + Error::::NotEnoughResourcesInCapacityReservation ); //update the available resources @@ -1212,10 +1228,26 @@ impl Pallet { log::info!("total {:?}", node.resources.total_resources); log::info!("used {:?}", node.resources.used_resources); log::info!("required {:?}", resources); - log::info!("HRU: {:?}", (node.resources.total_resources.hru - node.resources.used_resources.hru) >= resources.hru); - log::info!("SRU: {:?}", (node.resources.total_resources.sru - node.resources.used_resources.sru) >= resources.sru); - log::info!("CRU: {:?}", (node.resources.total_resources.cru - node.resources.used_resources.cru) >= resources.cru); - log::info!("MRU: {:?}", (node.resources.total_resources.mru - node.resources.used_resources.mru) >= resources.mru); + log::info!( + "HRU: {:?}", + (node.resources.total_resources.hru - node.resources.used_resources.hru) + >= resources.hru + ); + log::info!( + "SRU: {:?}", + (node.resources.total_resources.sru - node.resources.used_resources.sru) + >= resources.sru + ); + log::info!( + "CRU: {:?}", + (node.resources.total_resources.cru - node.resources.used_resources.cru) + >= resources.cru + ); + log::info!( + "MRU: {:?}", + (node.resources.total_resources.mru - node.resources.used_resources.mru) + >= resources.mru + ); if node.resources.can_consume_resources(&resources) { if let Some(g_id) = group_id { if !CapacityReservationIDByNodeGroupConfig::::contains_key( diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index c7b3b605f..d7ab0324a 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -499,8 +499,6 @@ fn test_cancel_capacity_reservation_contract_shutdown_node() { }); } -// tests update_deployment_reservation_contract - #[test] fn test_update_capacity_reservation_contract_works() { new_test_ext().execute_with(|| { @@ -643,8 +641,6 @@ fn test_capacity_reservation_contract_decrease_resources_fails_resources_used_by }); } -// todo test decrease resources fails due to resources being used by some contract -// todo more tests resources update #[test] fn test_update_capacity_reservation_contract_not_exists_fails() { new_test_ext().execute_with(|| { @@ -756,7 +752,7 @@ fn test_cancel_deployment_contract_free_resources_works() { group_id: None, public_ips: 0, resources: ConsumableResources { - total_resources: resources_n1(), + total_resources: resources_c1(), used_resources: Resources::empty(), }, deployment_contracts: vec![], @@ -827,6 +823,279 @@ fn test_cancel_deployment_contract_wrong_twins_fails() { }); } +#[test] +fn test_update_deployment_contract_increase_resources_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); + let data = get_deployment_data(); + let hash = generate_deployment_hash(); + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + hash, + data.clone(), + half_resources_c1(), + 0 + )); + assert_eq!( + SmartContractModule::contracts(2).unwrap().contract_type, + types::ContractData::DeploymentContract(types::DeploymentContract { + capacity_reservation_id: 1, + deployment_data: data, + deployment_hash: hash, + public_ips: 0, + resources: half_resources_c1(), + public_ips_list: vec![].try_into().unwrap(), + }) + ); + let updated_data = get_updated_deployment_data(); + let updated_hash = generate_deployment_hash(); + assert_ok!(SmartContractModule::update_deployment_contract( + Origin::signed(alice()), + 2, + updated_hash, + updated_data.clone(), + Some(resources_c1()), + )); + assert_eq!( + SmartContractModule::contracts(2).unwrap().contract_type, + types::ContractData::DeploymentContract(types::DeploymentContract { + capacity_reservation_id: 1, + deployment_data: updated_data, + deployment_hash: updated_hash, + public_ips: 0, + resources: resources_c1(), + public_ips_list: vec![].try_into().unwrap(), + }) + ); + }); +} + +#[test] +fn test_update_deployment_contract_decrease_resources_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); + let data = get_deployment_data(); + let hash = generate_deployment_hash(); + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + hash, + data.clone(), + resources_c1(), + 0 + )); + assert_eq!( + SmartContractModule::contracts(2).unwrap().contract_type, + types::ContractData::DeploymentContract(types::DeploymentContract { + capacity_reservation_id: 1, + deployment_data: data.clone(), + deployment_hash: hash, + public_ips: 0, + resources: resources_c1(), + public_ips_list: vec![].try_into().unwrap(), + }) + ); + assert_ok!(SmartContractModule::update_deployment_contract( + Origin::signed(alice()), + 2, + hash, + data.clone(), + Some(half_resources_c1()), + )); + assert_eq!( + SmartContractModule::contracts(2).unwrap().contract_type, + types::ContractData::DeploymentContract(types::DeploymentContract { + capacity_reservation_id: 1, + deployment_data: data, + deployment_hash: hash, + public_ips: 0, + resources: half_resources_c1(), + public_ips_list: vec![].try_into().unwrap(), + }) + ); + }); +} + +#[test] +fn test_update_deployment_contract_unauthorized_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); + let data = get_deployment_data(); + let hash = generate_deployment_hash(); + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + hash, + data.clone(), + half_resources_c1(), + 0 + )); + assert_noop!( + SmartContractModule::update_deployment_contract( + Origin::signed(bob()), + 2, + generate_deployment_hash(), + get_updated_deployment_data(), + Some(resources_c1()), + ), + Error::::TwinNotAuthorizedToUpdateContract + ); + }); +} + +#[test] +fn test_update_deployment_contract_notenoughresourcesonnode_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); + let data = get_deployment_data(); + let hash = generate_deployment_hash(); + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(alice()), + 1, + hash, + data.clone(), + half_resources_c1(), + 0 + )); + assert_eq!( + SmartContractModule::contracts(2).unwrap().contract_type, + types::ContractData::DeploymentContract(types::DeploymentContract { + capacity_reservation_id: 1, + deployment_data: data, + deployment_hash: hash, + public_ips: 0, + resources: half_resources_c1(), + public_ips_list: vec![].try_into().unwrap(), + }) + ); + let updated_data = get_updated_deployment_data(); + let updated_hash = generate_deployment_hash(); + assert_noop!( + SmartContractModule::update_deployment_contract( + Origin::signed(alice()), + 2, + updated_hash, + updated_data, + Some(resources_n1()), + ), + Error::::NotEnoughResourcesInCapacityReservation + ); + }); +} + +#[test] +fn test_update_contract_in_grace_state_fails() { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + TFTPriceModule::set_prices(Origin::signed(bob()), 50, 101).unwrap(); + + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(charlie()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); + assert_ok!(SmartContractModule::create_deployment_contract( + Origin::signed(charlie()), + 1, + generate_deployment_hash(), + get_deployment_data(), + half_resources_c1(), + 0 + )); + + // cycle 1 + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); + run_to_block(11, Some(&mut pool_state)); + + // cycle 2 + // user does not have enough funds to pay for 2 cycles + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21); + run_to_block(21, Some(&mut pool_state)); + + assert_eq!( + SmartContractModule::contracts(1).unwrap().state, + types::ContractState::GracePeriod(21) + ); + assert_eq!( + SmartContractModule::contracts(2).unwrap().state, + types::ContractState::GracePeriod(21) + ); + assert_noop!( + SmartContractModule::update_capacity_reservation_contract( + Origin::signed(charlie()), + 1, + resources_c1() + ), + Error::::CannotUpdateContractInGraceState + ); + assert_noop!( + SmartContractModule::update_deployment_contract( + Origin::signed(charlie()), + 2, + generate_deployment_hash(), + get_updated_deployment_data(), + Some(resources_n1()), + ), + Error::::CannotUpdateContractInGraceState + ); + }); +} + // NAME CONTRACT TESTS // // -------------------- // @@ -2050,7 +2319,24 @@ fn test_restore_deployment_contract_in_grace_works() { let c1 = SmartContractModule::contracts(1).unwrap(); assert_eq!(c1.state, types::ContractState::GracePeriod(21)); - // todo testing the used resources (should still be in used resoures) + // resources should still be reserved + assert_eq!( + SmartContractModule::contracts(1).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: 1, + public_ips: 0, + deployment_contracts: vec![2], + group_id: None, + resources: ConsumableResources { + total_resources: resources_c1(), + used_resources: get_resources(), + }, + }) + ); + assert_eq!( + TfgridModule::nodes(1).unwrap().resources.used_resources, + resources_c1() + ); let our_events = System::events(); assert_eq!( our_events.contains(&record(MockEvent::SmartContractModule( @@ -2882,10 +3168,6 @@ fn test_capacity_reservation_contract_and_deployment_contract_canceled_when_node pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 11); - // TODO: decide whether to bill deployment contracts - // pool_state - // .write() - // .should_call_bill_contract(2, Ok(Pays::Yes.into()), 11); run_to_block(11, Some(&mut pool_state)); run_to_block(16, Some(&mut pool_state)); @@ -3596,6 +3878,13 @@ fn get_deployment_data() -> crate::DeploymentDataInput { .unwrap() } +fn get_updated_deployment_data() -> crate::DeploymentDataInput { + BoundedVec::>::try_from( + "changedthedata".as_bytes().to_vec(), + ) + .unwrap() +} + fn get_resources() -> Resources { Resources { cru: 2, From 793e67cbab99990185128942f2dc9b75b2af56de Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 21 Oct 2022 17:11:28 +0200 Subject: [PATCH 61/87] tests on grouping --- .../pallets/pallet-smart-contract/src/lib.rs | 134 +++++++--- .../pallet-smart-contract/src/tests.rs | 238 +++++++++++++++++- .../pallet-smart-contract/src/types.rs | 1 + 3 files changed, 340 insertions(+), 33 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index a88bdda7a..498639591 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -357,7 +357,13 @@ pub mod pallet { node_id: u32, power_target: PowerTarget, }, - GroupCreated(types::Group), + GroupCreated { + group_id: u32, + twin_id: u32, + }, + GroupDeleted { + group_id: u32, + }, CapacityReservationContractCanceled { contract_id: u64, node_id: u32, @@ -409,6 +415,8 @@ pub mod pallet { NotAuthorizedToCreateDeploymentContract, InvalidResources, GroupNotExists, + TwinNotAuthorizedToDeleteGroup, + GroupHasActiveMembers, InvalidContractPolicy, CapacityReservationNotExists, CapacityReservationHasActiveContracts, @@ -446,6 +454,12 @@ pub mod pallet { Self::_create_group(account_id) } + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn delete_group(origin: OriginFor, group_id: u32) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_delete_group(account_id, group_id) + } + // DEPRECATED #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn create_node_contract( @@ -680,11 +694,78 @@ impl Pallet { let new_group = types::Group { id: id, twin_id: twin_id, + capacity_reservation_contract_ids: vec![], }; Groups::::insert(id, &new_group); - Self::deposit_event(Event::GroupCreated(new_group)); + Self::deposit_event(Event::GroupCreated { + group_id: id, + twin_id: twin_id, + }); + + Ok(().into()) + } + + pub fn _delete_group(account_id: T::AccountId, group_id: u32) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + + let group = Groups::::get(group_id).ok_or(Error::::GroupNotExists)?; + + ensure!( + twin_id == group.twin_id, + Error::::TwinNotAuthorizedToDeleteGroup + ); + ensure!( + group.capacity_reservation_contract_ids.is_empty(), + Error::::GroupHasActiveMembers + ); + + Groups::::remove(group_id); + + Self::deposit_event(Event::GroupDeleted { group_id: group_id }); + + Ok(().into()) + } + + pub fn _add_capacity_reservation_contract_to_group( + group_id: u32, + capacity_rservation_id: u64, + node_id: u32, + ) -> DispatchResultWithPostInfo { + let mut group = Groups::::get(group_id).ok_or(Error::::GroupNotExists)?; + group + .capacity_reservation_contract_ids + .push(capacity_rservation_id); + Groups::::insert(group_id, &group); + + CapacityReservationIDByNodeGroupConfig::::insert( + types::NodeGroupConfig { + group_id: group_id, + node_id: node_id, + }, + capacity_rservation_id, + ); + + Ok(().into()) + } + + pub fn _remove_capacity_reservation_contract_from_group( + group_id: u32, + capacity_reservation_id: u64, + node_id: u32, + ) -> DispatchResultWithPostInfo { + let mut group = Groups::::get(group_id).ok_or(Error::::GroupNotExists)?; + group + .capacity_reservation_contract_ids + .retain(|&id| id != capacity_reservation_id); + Groups::::insert(group_id, &group); + + CapacityReservationIDByNodeGroupConfig::::remove(types::NodeGroupConfig { + node_id: node_id, + group_id: group_id, + }); Ok(().into()) } @@ -744,6 +825,7 @@ impl Pallet { solution_provider_id, )?; + // insert billing information let now = >::get().saturated_into::() / 1000; let contract_billing_information = types::ContractBillingInformation { last_updated: now, @@ -757,13 +839,11 @@ impl Pallet { // insert NodeGroup configuration if let Some(group_id) = group_id { - CapacityReservationIDByNodeGroupConfig::::insert( - types::NodeGroupConfig { - group_id: group_id, - node_id: node_id, - }, + Self::_add_capacity_reservation_contract_to_group( + group_id, contract.contract_id, - ); + node_id, + )?; } // Insert contract into active contracts map @@ -1225,29 +1305,6 @@ impl Pallet { for node_id in nodes_in_farm { let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - log::info!("total {:?}", node.resources.total_resources); - log::info!("used {:?}", node.resources.used_resources); - log::info!("required {:?}", resources); - log::info!( - "HRU: {:?}", - (node.resources.total_resources.hru - node.resources.used_resources.hru) - >= resources.hru - ); - log::info!( - "SRU: {:?}", - (node.resources.total_resources.sru - node.resources.used_resources.sru) - >= resources.sru - ); - log::info!( - "CRU: {:?}", - (node.resources.total_resources.cru - node.resources.used_resources.cru) - >= resources.cru - ); - log::info!( - "MRU: {:?}", - (node.resources.total_resources.mru - node.resources.used_resources.mru) - >= resources.mru - ); if node.resources.can_consume_resources(&resources) { if let Some(g_id) = group_id { if !CapacityReservationIDByNodeGroupConfig::::contains_key( @@ -1893,6 +1950,21 @@ impl Pallet { { Self::remove_contract(deployment_contract_id); } + + // remove groups + if let Some(group_id) = capacity_reservation_contract.group_id { + match Self::_remove_capacity_reservation_contract_from_group( + group_id, + contract_id, + capacity_reservation_contract.node_id, + ) { + Ok(_) => (), + Err(e) => { + log::error!("error while removing the capacity reservation contract fromt its group: {:?}", e); + } + } + } + // unclaim the resources on the node match Self::_unclaim_resources_on_node( capacity_reservation_contract.node_id, diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index d7ab0324a..fae93da2e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -23,6 +23,152 @@ use tfchain_support::types::{ const GIGABYTE: u64 = 1024 * 1024 * 1024; +// GROUP TESTS // +// -------------------- // + +#[test] +fn test_create_group_then_capacity_reservation_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_group(Origin::signed(alice()))); + let group = SmartContractModule::groups(1).unwrap(); + assert_eq!(group.twin_id, 1); + assert_eq!(group.capacity_reservation_contract_ids.len(), 0); + + create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 1); + + let our_events = System::events(); + assert_eq!( + our_events.contains(&record(MockEvent::SmartContractModule( + SmartContractEvent::::GroupCreated { + group_id: 1, + twin_id: 1, + } + ))), + true + ); + }); +} + +#[test] +fn test_remove_group_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_group(Origin::signed(alice()))); + + assert_ok!(SmartContractModule::delete_group( + Origin::signed(alice()), + 1 + )); + + assert_eq!(SmartContractModule::groups(1), None); + + let our_events = System::events(); + assert_eq!( + our_events.contains(&record(MockEvent::SmartContractModule( + SmartContractEvent::::GroupDeleted { group_id: 1 } + ))), + true + ); + }); +} + +#[test] +fn test_remove_group_unauthorized_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_group(Origin::signed(alice()))); + + assert_noop!( + SmartContractModule::delete_group(Origin::signed(bob()), 1), + Error::::TwinNotAuthorizedToDeleteGroup + ); + }); +} + +#[test] +fn test_remove_group_active_capacity_reservation_contracts_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_and_node(); + + assert_ok!(SmartContractModule::create_group(Origin::signed(alice()))); + let group = SmartContractModule::groups(1).unwrap(); + assert_eq!(group.twin_id, 1); + assert_eq!(group.capacity_reservation_contract_ids.len(), 0); + + create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 1); + + assert_noop!( + SmartContractModule::delete_group(Origin::signed(alice()), 1), + Error::::GroupHasActiveMembers + ); + }); +} + +#[test] +fn test_create_capacity_contract_reservation_finding_node_using_group() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_with_three_nodes(); + + assert_ok!(SmartContractModule::create_group(Origin::signed(alice()))); + + // although there is still place to add the contract on node 1 all contracts are in the same group + // so they should not go on the same node + create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 1); + create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 2); + create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 3); + + // only three nodes so no more node left that doesn't contain a capacity reservation contract that is in the same group + assert_noop!( + SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Exclusive(1), + None, + Some(resources_c1()), + None, + None, + ), + Error::::NoSuitableNodeInFarm + ); + // let's add it without a group + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Any, + None, + Some(resources_c1()), + None, + None, + )); + assert_eq!( + SmartContractModule::contracts(4).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: 1, + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: resources_c1(), + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], + }) + ); + assert_eq!( + TfgridModule::nodes(1).unwrap().resources.used_resources, + resources_c1().add(&resources_c1()) + ); + }); +} + // NODE CONTRACT TESTS // // -------------------- // @@ -200,8 +346,6 @@ fn test_create_capacity_reservation_contract_no_node_in_farm_with_enough_resourc }); } -// todo test grouping contracts - #[test] fn test_create_capacity_reservation_contract_finding_a_node() { new_test_ext().execute_with(|| { @@ -237,6 +381,35 @@ fn test_create_capacity_reservation_contract_finding_a_node_failure() { }); } +// todo test grouping contracts + +#[test] +fn test_create_capacity_reservation_contract_grouping_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_with_three_nodes(); + + // todo + assert_noop!( + SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Any, + None, + Some(Resources { + hru: 4096 * GIGABYTE, + sru: 2048 * GIGABYTE, + cru: 32, + mru: 48 * GIGABYTE, + }), + None, + None, + ), + Error::::NoSuitableNodeInFarm + ); + }); +} + #[test] fn test_create_capacity_reservation_contract_reserving_full_node_then_deployment_contract_then_cancel_everything( ) { @@ -3729,6 +3902,67 @@ pub fn prepare_dedicated_farm_and_node() { .unwrap(); } +pub fn create_capacity_reservation_and_add_to_group( + farm_id: u32, + resources: Resources, + group_id: u32, + expected_node_id: u32, +) { + let cnt_members_before = SmartContractModule::groups(group_id) + .unwrap() + .capacity_reservation_contract_ids + .len(); + let cnt_contracts = SmartContractModule::contract_id(); + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + farm_id, + CapacityReservationPolicy::Exclusive(group_id), + None, + Some(resources), + None, + None, + )); + + assert_eq!( + SmartContractModule::contracts(cnt_contracts + 1) + .unwrap() + .contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: expected_node_id, + group_id: Some(group_id), + public_ips: 0, + resources: ConsumableResources { + total_resources: resources, + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], + }) + ); + + assert_eq!( + TfgridModule::nodes(expected_node_id).unwrap().power_target, + PowerTarget::Up + ); + + let group = SmartContractModule::groups(group_id).unwrap(); + assert_eq!( + group.capacity_reservation_contract_ids.len(), + cnt_members_before + 1 + ); + assert_eq!( + group.capacity_reservation_contract_ids[cnt_members_before], + cnt_contracts + 1 + ); + + assert_eq!( + SmartContractModule::capacity_reservation_id_by_node_group_config(types::NodeGroupConfig { + group_id: group_id, + node_id: expected_node_id + }), + cnt_contracts + 1 + ); +} + pub fn resources_n1() -> Resources { Resources { hru: 1024 * GIGABYTE, diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index b5befa545..fb72a2e07 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -34,6 +34,7 @@ impl Default for StorageVersion { pub struct Group { pub id: u32, pub twin_id: u32, + pub capacity_reservation_contract_ids: Vec } #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo)] From b0aa44a955af31a28e6195a34fc21bf8fea25e75 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Mon, 24 Oct 2022 16:03:25 +0200 Subject: [PATCH 62/87] added unit tests on public config filtering --- .../pallets/pallet-smart-contract/src/lib.rs | 108 +-- .../pallets/pallet-smart-contract/src/mock.rs | 16 + .../pallet-smart-contract/src/tests.rs | 673 ++++++++++-------- substrate-node/support/src/types.rs | 30 +- substrate-node/tests/TfChainClient.py | 36 +- 5 files changed, 496 insertions(+), 367 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 498639591..78a6daaad 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -32,7 +32,9 @@ use sp_runtime::{ use substrate_fixed::types::U64F64; use tfchain_support::{ traits::ChangeNode, - types::{CapacityReservationPolicy, ConsumableResources, Node, PowerTarget, Resources}, + types::{ + CapacityReservationPolicy, ConsumableResources, Node, NodeFeatures, PowerTarget, Resources, + }, }; pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"smct"); @@ -517,9 +519,6 @@ pub mod pallet { origin: OriginFor, farm_id: u32, policy: CapacityReservationPolicy, - node_id: Option, - resources: Option, - features: Option>, solution_provider_id: Option, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; @@ -527,9 +526,6 @@ pub mod pallet { account_id, farm_id, policy, - node_id, - resources, - features, solution_provider_id, ) } @@ -775,9 +771,6 @@ impl Pallet { account_id: T::AccountId, farm_id: u32, policy: CapacityReservationPolicy, - node_id: Option, - resources: Option, - _features: Option>, solution_provider_id: Option, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) @@ -786,25 +779,43 @@ impl Pallet { pallet_tfgrid::Farms::::contains_key(farm_id), Error::::FarmNotExists ); - let mut resources = resources.unwrap_or(Resources::empty()); - let mut group_id = None; - let node_id = if let Some(n_id) = node_id { - let node = pallet_tfgrid::Nodes::::get(n_id).ok_or(Error::::NodeNotExists)?; - ensure!( - node.resources.used_resources == Resources::empty(), - Error::::NodeNotAvailableToDeploy - ); - // if node_id was passed the user wants to reserve all resources of a specific node - resources = node.resources.total_resources; - n_id - } else { - group_id = match policy { - CapacityReservationPolicy::Exclusive(g_id) => Some(g_id), - CapacityReservationPolicy::Any => None, - }; - let farm = pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; - ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); - Self::_find_suitable_node_in_farm(farm_id, resources, group_id)? + + let (node_id, resources, group_id) = match policy { + CapacityReservationPolicy::Any { + resources, + features, + } => { + let farm = + pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; + ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); + let n_id = Self::_find_suitable_node_in_farm(farm_id, resources, None, features)?; + (n_id, resources, None) + } + CapacityReservationPolicy::Exclusive { + group_id, + resources, + features, + } => { + let farm = + pallet_tfgrid::Farms::::get(farm_id).ok_or(Error::::FarmNotExists)?; + ensure!(!farm.dedicated_farm, Error::::NodeNotAvailableToDeploy); + let n_id = Self::_find_suitable_node_in_farm( + farm_id, + resources, + Some(group_id), + features, + )?; + (n_id, resources, Some(group_id)) + } + CapacityReservationPolicy::Node { node_id } => { + let node = + pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + ensure!( + node.resources.used_resources == Resources::empty(), + Error::::NodeNotAvailableToDeploy + ); + (node_id, node.resources.total_resources, None) + } }; let new_capacity_reservation = types::CapacityReservationContract { @@ -1290,33 +1301,40 @@ impl Pallet { farm_id: u32, resources: Resources, group_id: Option, + features: Option>, ) -> Result { ensure!( pallet_tfgrid::Farms::::contains_key(farm_id), Error::::FarmNotExists ); + let nodes_in_farm = pallet_tfgrid::NodesByFarmID::::get(farm_id); + let mut suitable_nodes = Vec::new(); + for node_id in nodes_in_farm { + let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; + suitable_nodes.push(node); + } + + // only keep nodes with enough resources + suitable_nodes.retain_mut(|node| node.resources.can_consume_resources(&resources)); + // only keep nodes that DON'T have a contract configured on them that belong to the same group if let Some(g_id) = group_id { ensure!(Groups::::contains_key(g_id), Error::::GroupNotExists); + suitable_nodes.retain_mut(|node| { + !CapacityReservationIDByNodeGroupConfig::::contains_key(types::NodeGroupConfig { + node_id: node.id, + group_id: g_id, + }) + }); } - let nodes_in_farm = pallet_tfgrid::NodesByFarmID::::get(farm_id); - let mut suitable_nodes = Vec::new(); - - for node_id in nodes_in_farm { - let node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - if node.resources.can_consume_resources(&resources) { - if let Some(g_id) = group_id { - if !CapacityReservationIDByNodeGroupConfig::::contains_key( - types::NodeGroupConfig { - node_id: node_id, - group_id: g_id, - }, - ) { - suitable_nodes.push(node); + // only keep nodes with the required features + if let Some(features) = features { + for feature in features { + match feature { + NodeFeatures::PublicNode => { + suitable_nodes.retain_mut(|node| node.public_config.is_some()) } - } else { - suitable_nodes.push(node); } } } diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index d767097ae..475b6cc12 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -236,6 +236,22 @@ pub(crate) fn get_twin_ip(twin_ip_input: &[u8]) -> TestTwinIp { TwinIp::try_from(twin_ip_input.to_vec()).expect("Invalid twin ip input.") } +pub(crate) fn get_pub_config_ip4(ip4: &[u8]) -> TestIP4 { + IP4::try_from(ip4.to_vec()).expect("Invalid ip4 input") +} + +pub(crate) fn get_pub_config_gw4(gw4: &[u8]) -> TestGW4 { + GW4::try_from(gw4.to_vec()).expect("Invalid gw4 input") +} + +pub(crate) fn get_pub_config_ip6(ip6: &[u8]) -> TestIP6 { + IP6::try_from(ip6.to_vec()).expect("Invalid ip6 input") +} + +pub(crate) fn get_pub_config_gw6(gw6: &[u8]) -> TestGW6 { + GW6::try_from(gw6.to_vec()).expect("Invalid gw6 input") +} + /// Helper function to generate a crypto pair from seed fn get_from_seed(seed: &str) -> ::Public { TPublic::Pair::from_string(&format!("//{}", seed), None) diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index fae93da2e..2ea565c28 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -18,7 +18,7 @@ use log::info; use pallet_tfgrid::types as pallet_tfgrid_types; use tfchain_support::types::{ CapacityReservationPolicy, ConsumableResources, FarmCertification, Location, NodeCertification, - PowerTarget, PublicIP, Resources, + NodeFeatures, PowerTarget, PublicConfig, PublicIP, Resources, IP, }; const GIGABYTE: u64 = 1024 * 1024 * 1024; @@ -37,7 +37,7 @@ fn test_create_group_then_capacity_reservation_works() { assert_eq!(group.twin_id, 1); assert_eq!(group.capacity_reservation_contract_ids.len(), 0); - create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 1); + create_capacity_reservation_and_add_to_group(1, resources_c1(), None, 1, 1); let our_events = System::events(); assert_eq!( @@ -103,7 +103,7 @@ fn test_remove_group_active_capacity_reservation_contracts_fails() { assert_eq!(group.twin_id, 1); assert_eq!(group.capacity_reservation_contract_ids.len(), 0); - create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 1); + create_capacity_reservation_and_add_to_group(1, resources_c1(), None, 1, 1); assert_noop!( SmartContractModule::delete_group(Origin::signed(alice()), 1), @@ -122,19 +122,20 @@ fn test_create_capacity_contract_reservation_finding_node_using_group() { // although there is still place to add the contract on node 1 all contracts are in the same group // so they should not go on the same node - create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 1); - create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 2); - create_capacity_reservation_and_add_to_group(1, resources_c1(), 1, 3); + create_capacity_reservation_and_add_to_group(1, resources_c1(), None, 1, 1); + create_capacity_reservation_and_add_to_group(1, resources_c1(), None, 1, 2); + create_capacity_reservation_and_add_to_group(1, resources_c1(), None, 1, 3); // only three nodes so no more node left that doesn't contain a capacity reservation contract that is in the same group assert_noop!( SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Exclusive(1), - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Exclusive { + group_id: 1, + resources: resources_c1(), + features: None, + }, None, ), Error::::NoSuitableNodeInFarm @@ -143,10 +144,10 @@ fn test_create_capacity_contract_reservation_finding_node_using_group() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_eq!( @@ -169,8 +170,117 @@ fn test_create_capacity_contract_reservation_finding_node_using_group() { }); } -// NODE CONTRACT TESTS // -// -------------------- // +#[test] +fn test_capacity_reservation_contract_with_policy_any_and_features_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_with_three_nodes(); + add_public_config(1, 3, alice()); + + // Contract should go to node 3 (the only node with a public config) and thus bring it up + assert_ok!(SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Any { + resources: resources_c3(), + features: Some(vec![NodeFeatures::PublicNode]), + }, + None, + )); + + assert_eq!( + SmartContractModule::contracts(1).unwrap().contract_type, + types::ContractData::CapacityReservationContract(types::CapacityReservationContract { + node_id: 3, + group_id: None, + public_ips: 0, + resources: ConsumableResources { + total_resources: resources_c3(), + used_resources: Resources::empty(), + }, + deployment_contracts: vec![], + }) + ); + + assert_eq!( + TfgridModule::nodes(3).unwrap().power_target, + PowerTarget::Up + ); + }); +} + +#[test] +fn test_capacity_reservation_contract_with_policy_exclusive_and_features_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_with_three_nodes(); + // node 2 and 3 have public config + add_public_config(1, 2, alice()); + add_public_config(1, 3, alice()); + + assert_ok!(SmartContractModule::create_group(Origin::signed(alice()))); + + // Contract should go to node 2 (enough resources + has public config) and thus bring it up + let expected_node = 2; + create_capacity_reservation_and_add_to_group( + 1, + resources_c2(), + Some(vec![NodeFeatures::PublicNode]), + 1, + expected_node, + ); + + // Contract could go to node 2 but there is already a contract on that node that belongs to the same group + // so the contract will go to node 3 which also has a public config + let expected_node = 3; + create_capacity_reservation_and_add_to_group( + 1, + resources_c3(), + Some(vec![NodeFeatures::PublicNode]), + 1, + expected_node, + ); + }); +} + +#[test] +fn test_capacity_reservation_contract_with_policy_exclusive_and_features_fails() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_farm_with_three_nodes(); + // node 2 has public config + add_public_config(1, 2, alice()); + + assert_ok!(SmartContractModule::create_group(Origin::signed(alice()))); + + // Contract should go to node 2 (enough resources + has public config) and thus bring it up + let expected_node = 2; + create_capacity_reservation_and_add_to_group( + 1, + resources_c2(), + Some(vec![NodeFeatures::PublicNode]), + 1, + expected_node, + ); + + // Contract could go to node 2 (if we only look at resources) but the contract we want to create + // belongs to the same group as prior contract so we can't add it on node 2. As node 2 is the only + // node with a public config this call shoul fail + assert_noop!( + SmartContractModule::create_capacity_reservation_contract( + Origin::signed(alice()), + 1, + CapacityReservationPolicy::Exclusive { + group_id: 1, + resources: resources_c3(), + features: Some(vec![NodeFeatures::PublicNode]), + }, + None, + ), + Error::::NoSuitableNodeInFarm + ); + }); +} #[test] fn test_create_capacity_reservation_contract_works() { @@ -181,10 +291,10 @@ fn test_create_capacity_reservation_contract_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(get_resources()), - None, + CapacityReservationPolicy::Any { + resources: get_resources(), + features: None, + }, None, )); @@ -242,10 +352,10 @@ fn test_create_capacity_reservation_contract_with_nonexisting_farm_fails() { SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 2, - CapacityReservationPolicy::Any, - None, - Some(get_resources()), - None, + CapacityReservationPolicy::Any { + resources: get_resources(), + features: None, + }, None, ), Error::::FarmNotExists @@ -330,15 +440,15 @@ fn test_create_capacity_reservation_contract_no_node_in_farm_with_enough_resourc SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(Resources { - cru: 10, - hru: 0, - mru: 2 * GIGABYTE, - sru: 60 * GIGABYTE - }), - None, + CapacityReservationPolicy::Any { + resources: Resources { + cru: 10, + hru: 0, + mru: 2 * GIGABYTE, + sru: 60 * GIGABYTE + }, + features: None, + }, None, ), Error::::NoSuitableNodeInFarm @@ -365,44 +475,15 @@ fn test_create_capacity_reservation_contract_finding_a_node_failure() { SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(Resources { - hru: 4096 * GIGABYTE, - sru: 2048 * GIGABYTE, - cru: 32, - mru: 48 * GIGABYTE, - }), - None, - None, - ), - Error::::NoSuitableNodeInFarm - ); - }); -} - -// todo test grouping contracts - -#[test] -fn test_create_capacity_reservation_contract_grouping_works() { - new_test_ext().execute_with(|| { - run_to_block(1, None); - prepare_farm_with_three_nodes(); - - // todo - assert_noop!( - SmartContractModule::create_capacity_reservation_contract( - Origin::signed(alice()), - 1, - CapacityReservationPolicy::Any, - None, - Some(Resources { - hru: 4096 * GIGABYTE, - sru: 2048 * GIGABYTE, - cru: 32, - mru: 48 * GIGABYTE, - }), - None, + CapacityReservationPolicy::Any { + resources: Resources { + hru: 4096 * GIGABYTE, + sru: 2048 * GIGABYTE, + cru: 32, + mru: 48 * GIGABYTE, + }, + features: None + }, None, ), Error::::NoSuitableNodeInFarm @@ -422,10 +503,7 @@ fn test_create_capacity_reservation_contract_reserving_full_node_then_deployment assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); assert_eq!( @@ -556,10 +634,10 @@ fn test_cancel_capacity_reservation_contract_should_not_shutdown_first_node() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_eq!( @@ -680,10 +758,10 @@ fn test_update_capacity_reservation_contract_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(get_resources()), - None, + CapacityReservationPolicy::Any { + resources: get_resources(), + features: None, + }, None, )); @@ -743,10 +821,10 @@ fn test_update_capacity_reservation_contract_too_much_resources() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(get_resources()), - None, + CapacityReservationPolicy::Any { + resources: get_resources(), + features: None, + }, None, )); // asking for too much resources @@ -776,10 +854,10 @@ fn test_capacity_reservation_contract_decrease_resources_fails_resources_used_by assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(get_resources()), - None, + CapacityReservationPolicy::Any { + resources: get_resources(), + features: None, + }, None, )); // deployment contract using half of the resources @@ -840,10 +918,10 @@ fn test_update_capacity_reservation_contract_wrong_twins_fails() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(get_resources()), - None, + CapacityReservationPolicy::Any { + resources: get_resources(), + features: None, + }, None, )); @@ -872,10 +950,10 @@ fn test_cancel_capacity_reservation_contract_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(get_resources()), - None, + CapacityReservationPolicy::Any { + resources: get_resources(), + features: None, + }, None, )); @@ -896,7 +974,6 @@ fn test_cancel_capacity_reservation_contract_works() { assert_eq!(contracts.len(), 0); }); } -// todo test multiple deployment contracts on the same capacity reservation contract #[test] fn test_cancel_deployment_contract_free_resources_works() { @@ -1005,10 +1082,10 @@ fn test_update_deployment_contract_increase_resources_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); let data = get_deployment_data(); @@ -1064,10 +1141,10 @@ fn test_update_deployment_contract_decrease_resources_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); let data = get_deployment_data(); @@ -1121,10 +1198,10 @@ fn test_update_deployment_contract_unauthorized_fails() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); let data = get_deployment_data(); @@ -1159,10 +1236,10 @@ fn test_update_deployment_contract_notenoughresourcesonnode_fails() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); let data = get_deployment_data(); @@ -1212,10 +1289,10 @@ fn test_update_contract_in_grace_state_fails() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -1407,10 +1484,7 @@ fn test_create_capacity_reservation_contract_reserving_all_resources_node_works( assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -1446,10 +1520,7 @@ fn test_cancel_capacity_reservation_contract_all_resources_of_node_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -1487,10 +1558,7 @@ fn test_create_capacity_reservation_contract_reserving_all_resources_on_node_in_ assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - Some(1), - None, - None, + CapacityReservationPolicy::Node { node_id: 1 }, None, )); @@ -1498,10 +1566,7 @@ fn test_create_capacity_reservation_contract_reserving_all_resources_on_node_in_ SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(1), - None, - None, + CapacityReservationPolicy::Node { node_id: 1 }, None, ), Error::::NodeNotAvailableToDeploy @@ -1519,10 +1584,7 @@ fn test_capacity_reservation_contract_non_dedicated_empty_node_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); }) @@ -1539,10 +1601,10 @@ fn test_create_capacity_reservation_contract_on_dedicated_farm_without_reserving SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), // not requesting the all the resources of the node should not be possible for dedicated farms! - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), // not requesting the all the resources of the node should not be possible for dedicated farms! + features: None, + }, None, ), Error::::NodeNotAvailableToDeploy @@ -1560,10 +1622,7 @@ fn test_create_deployment_contract_when_having_a_capacity_reservation_reserving_ assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(1), - None, - None, + CapacityReservationPolicy::Node { node_id: 1 }, None, )); @@ -1588,10 +1647,7 @@ fn test_create_deployment_contract_using_someone_elses_capacity_reservation_cont assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(1), - None, - None, + CapacityReservationPolicy::Node { node_id: 1 }, None, )); // Alice not the owner of the capacity reservation contract so she is unauthorized to deploy a deployment contract @@ -1621,10 +1677,7 @@ fn test_cancel_capacity_reservation_contract_with_active_deployment_contracts_fa assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -1665,10 +1718,10 @@ fn test_deployment_contract_billing_details() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -1760,10 +1813,10 @@ fn test_deployment_contract_billing_details_with_solution_provider() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, Some(1), )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -1810,10 +1863,10 @@ fn test_multiple_contracts_billing_loop_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -1868,10 +1921,10 @@ fn test_deployment_contract_billing_cycles() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); @@ -1940,10 +1993,10 @@ fn test_node_multiple_contract_billing_cycles() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -1967,10 +2020,10 @@ fn test_node_multiple_contract_billing_cycles() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(rest_of_the_resources_on_node_1.clone()), - None, + CapacityReservationPolicy::Any { + resources: rest_of_the_resources_on_node_1.clone(), + features: None, + }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2019,10 +2072,10 @@ fn test_deployment_contract_billing_cycles_delete_node_cancels_contract() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2132,10 +2185,10 @@ fn test_deployment_contract_only_public_ip_billing_cycles() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - None, // no resources required - None, + CapacityReservationPolicy::Any { + resources: Resources::empty(), // no resources required + features: None, + }, None )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2191,10 +2244,10 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_works() assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2260,10 +2313,10 @@ fn test_deployment_contract_billing_fails() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2314,10 +2367,10 @@ fn test_deployment_contract_billing_cycles_cancel_contract_during_cycle_without_ assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); @@ -2404,10 +2457,10 @@ fn test_deployment_contract_out_of_funds_should_move_state_to_graceperiod_works( assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2461,11 +2514,11 @@ fn test_restore_deployment_contract_in_grace_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, - None )); assert_ok!(SmartContractModule::create_deployment_contract( Origin::signed(charlie()), @@ -2557,10 +2610,10 @@ fn test_deployment_contract_grace_period_cancels_contract_when_grace_period_ends assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -2677,10 +2730,7 @@ fn test_capacity_reservation_contract_full_node_billing() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -2721,10 +2771,7 @@ fn test_capacity_reservation_contract_full_node_billing_cancel_should_bill_reser assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -2788,10 +2835,7 @@ fn test_capacity_reservation_contract_full_node_canceled_mid_cycle_should_bill_f assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -2833,10 +2877,7 @@ fn test_create_capacity_contract_full_node_and_deployment_contract_should_bill_f assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -2883,10 +2924,7 @@ fn test_create_capacity_contract_full_node_canceled_due_to_out_of_funds_should_c assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -2987,10 +3025,7 @@ fn test_create_capacity_reservation_contract_and_deployment_contract_with_ip_bil assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -3039,10 +3074,7 @@ fn test_capacity_reservation_contract_full_node_out_of_funds_should_move_state_t assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -3083,10 +3115,7 @@ fn test_restore_capacity_reservation_contract_in_grace_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -3152,10 +3181,7 @@ fn test_restore_capacity_reservation_contract_and_deployment_contracts_in_grace_ assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); assert_ok!(SmartContractModule::create_deployment_contract( @@ -3263,10 +3289,7 @@ fn test_capacity_reservation_contract_grace_period_cancels_contract_when_grace_p assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(charlie()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -3321,10 +3344,7 @@ fn test_capacity_reservation_contract_and_deployment_contract_canceled_when_node assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(bob()), 1, - CapacityReservationPolicy::Any, - Some(node_id), - None, - None, + CapacityReservationPolicy::Node { node_id: node_id }, None, )); @@ -3439,10 +3459,7 @@ fn test_create_capacity_reservation_contract_with_solution_provider_works() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - Some(1), - None, - None, + CapacityReservationPolicy::Node { node_id: 1 }, Some(1), )); }); @@ -3470,10 +3487,7 @@ fn test_create_capacity_reservation_contract_with_solution_provider_fails_if_not SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - Some(1), - None, - None, + CapacityReservationPolicy::Node { node_id: 1 }, Some(1), ), Error::::SolutionProviderNotApproved @@ -3779,10 +3793,10 @@ pub fn prepare_farm_node_and_capacity_reservation() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); assert_eq!( @@ -3794,6 +3808,39 @@ pub fn prepare_farm_node_and_capacity_reservation() { ); } +pub fn add_public_config(farm_id: u32, node_id: u32, account_id: AccountId) { + let ipv4 = get_pub_config_ip4(&"185.206.122.33/24".as_bytes().to_vec()); + let ipv6 = get_pub_config_ip6(&"2a10:b600:1::0cc4:7a30:65b5/64".as_bytes().to_vec()); + let gw4 = get_pub_config_gw4(&"185.206.122.1".as_bytes().to_vec()); + let gw6 = get_pub_config_gw6(&"2a10:b600:1::1".as_bytes().to_vec()); + + let pub_config = PublicConfig { + ip4: IP { + ip: ipv4.clone().0, + gw: gw4.clone().0, + }, + ip6: Some(IP { + ip: ipv6.clone().0, + gw: gw6.clone().0, + }), + domain: Some("some-domain".as_bytes().to_vec().try_into().unwrap()), + }; + + assert_ok!(TfgridModule::add_node_public_config( + Origin::signed(account_id), + farm_id, + node_id, + Some(pub_config.clone()) + )); + assert_eq!( + TfgridModule::nodes(node_id) + .unwrap() + .public_config + .is_some(), + true + ); +} + pub fn prepare_farm_with_three_nodes() { prepare_farm_and_node(); @@ -3905,6 +3952,7 @@ pub fn prepare_dedicated_farm_and_node() { pub fn create_capacity_reservation_and_add_to_group( farm_id: u32, resources: Resources, + features: Option>, group_id: u32, expected_node_id: u32, ) { @@ -3916,10 +3964,11 @@ pub fn create_capacity_reservation_and_add_to_group( assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), farm_id, - CapacityReservationPolicy::Exclusive(group_id), - None, - Some(resources), - None, + CapacityReservationPolicy::Exclusive { + group_id: group_id, + resources: resources, + features: features, + }, None, )); @@ -3963,33 +4012,6 @@ pub fn create_capacity_reservation_and_add_to_group( ); } -pub fn resources_n1() -> Resources { - Resources { - hru: 1024 * GIGABYTE, - sru: 512 * GIGABYTE, - cru: 8, - mru: 16 * GIGABYTE, - } -} - -pub fn resources_n2() -> Resources { - Resources { - hru: 2048 * GIGABYTE, - sru: 1024 * GIGABYTE, - cru: 16, - mru: 32 * GIGABYTE, - } -} - -pub fn resources_n3() -> Resources { - Resources { - hru: 512 * GIGABYTE, - sru: 256 * GIGABYTE, - cru: 4, - mru: 8 * GIGABYTE, - } -} - pub fn create_twin(origin: AccountId) { let document = "some_link".as_bytes().to_vec(); let hash = "some_hash".as_bytes().to_vec(); @@ -4128,6 +4150,33 @@ fn get_resources() -> Resources { } } +pub fn resources_n1() -> Resources { + Resources { + hru: 1024 * GIGABYTE, + sru: 512 * GIGABYTE, + cru: 8, + mru: 16 * GIGABYTE, + } +} + +pub fn resources_n2() -> Resources { + Resources { + hru: 2048 * GIGABYTE, + sru: 1024 * GIGABYTE, + cru: 16, + mru: 32 * GIGABYTE, + } +} + +pub fn resources_n3() -> Resources { + Resources { + hru: 512 * GIGABYTE, + sru: 256 * GIGABYTE, + cru: 4, + mru: 8 * GIGABYTE, + } +} + fn resources_c1() -> Resources { Resources { cru: 4, @@ -4158,7 +4207,7 @@ fn resources_c2() -> Resources { fn resources_c3() -> Resources { Resources { cru: 2, - hru: 1024 * GIGABYTE, + hru: 512 * GIGABYTE, mru: 4 * GIGABYTE, sru: 50 * GIGABYTE, } @@ -4171,10 +4220,10 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c1()), - None, + CapacityReservationPolicy::Any { + resources: resources_c1(), + features: None, + }, None, )); @@ -4212,10 +4261,10 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c2()), - None, + CapacityReservationPolicy::Any { + resources: resources_c2(), + features: None, + }, None, )); assert_eq!( @@ -4240,10 +4289,10 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { assert_ok!(SmartContractModule::create_capacity_reservation_contract( Origin::signed(alice()), 1, - CapacityReservationPolicy::Any, - None, - Some(resources_c3()), - None, + CapacityReservationPolicy::Any { + resources: resources_c3(), + features: None, + }, None, ),); diff --git a/substrate-node/support/src/types.rs b/substrate-node/support/src/types.rs index a5ec322e1..8012f4642 100644 --- a/substrate-node/support/src/types.rs +++ b/substrate-node/support/src/types.rs @@ -68,17 +68,35 @@ pub struct FarmingPolicyLimit { pub node_certification: bool, } -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo)] pub enum CapacityReservationPolicy { - Any, - Exclusive(u32), + Any { + resources: Resources, + features: Option>, + }, + Exclusive { + group_id: u32, + resources: Resources, + features: Option>, + }, + Node { + node_id: u32, + }, } impl Default for CapacityReservationPolicy { fn default() -> CapacityReservationPolicy { - CapacityReservationPolicy::Any + CapacityReservationPolicy::Any { + resources: Resources::empty(), + features: None, + } } } +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo)] +pub enum NodeFeatures { + PublicNode, +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo)] pub enum PowerTarget { Up, @@ -91,7 +109,9 @@ impl Default for PowerTarget { } } -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen)] +#[derive( + PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen, +)] pub struct ConsumableResources { pub total_resources: Resources, pub used_resources: Resources, diff --git a/substrate-node/tests/TfChainClient.py b/substrate-node/tests/TfChainClient.py index b81b3c075..69d527425 100644 --- a/substrate-node/tests/TfChainClient.py +++ b/substrate-node/tests/TfChainClient.py @@ -181,6 +181,32 @@ def wait_till_block(self, x: int = 1, port: int = DEFAULT_PORT): raise Exception(f"Timeout on waiting for {x} blocks") time.sleep(6) + def create_group(self, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") + + call = substrate.compose_call( + "SmartContractModule", "create_group", {}) + expected_events = [{ + "module_id": "SmartContractModule", + "event_id": "GroupCreated" + }] + self._sign_extrinsic_submit_check_response( + substrate, call, who, expected_events=expected_events) + + def delete_group(self, id: int = 1, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") + + call = substrate.compose_call("SmartContractModule", "delete_group", + { + "group_id": id + }) + expected_events = [{ + "module_id": "SmartContractModule", + "event_id": "GroupDeleted" + }] + self._sign_extrinsic_submit_check_response( + substrate, call, who, expected_events=expected_events) + def create_farm(self, name: str = "myfarm", public_ips: list = [], port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") @@ -368,9 +394,9 @@ def get_node(self, id: int = 1, port: int = DEFAULT_PORT): return q.value def create_deployment_contract(self, farm_id: int = 1, deployment_data: bytes = randbytes(32), - deployment_hash: bytes = randbytes(32), public_ips: int = 0, hru: int = 0, sru: int = 0, - cru: int = 0, mru: int = 0, solution_provider_id: int | None = None, - contract_policy: int | None = None, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + deployment_hash: bytes = randbytes(32), public_ips: int = 0, hru: int = 0, sru: int = 0, + cru: int = 0, mru: int = 0, solution_provider_id: int | None = None, + contract_policy: int | None = None, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") params = { @@ -397,8 +423,8 @@ def create_deployment_contract(self, farm_id: int = 1, deployment_data: bytes = substrate, call, who, expected_events=expected_events) def update_deployment_contract(self, contract_id: int = 1, deployment_data: bytes = randbytes(32), - deployment_hash: bytes = randbytes(32), hru: int = 0, sru: int = 0, cru: int = 0, - mru: int = 0, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + deployment_hash: bytes = randbytes(32), hru: int = 0, sru: int = 0, cru: int = 0, + mru: int = 0, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") call = substrate.compose_call("SmartContractModule", "update_deployment_contract", { From 57b62bb365ac75728d79f1c97582e230aaa56545 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 25 Oct 2022 18:13:52 +0200 Subject: [PATCH 63/87] fixed integration tests --- .../pallets/pallet-smart-contract/src/lib.rs | 18 ++- substrate-node/tests/TfChainClient.py | 117 +++++++------- substrate-node/tests/integration_tests.robot | 151 +++++++++++++----- 3 files changed, 188 insertions(+), 98 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 78a6daaad..ceeff0642 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1428,7 +1428,7 @@ impl Pallet { Error::::CannotUpdateContractInGraceState ); - let mut deployment_contract = Self::get_node_contract(&contract.clone())?; + let mut deployment_contract = Self::get_deployment_contract(&contract.clone())?; let cr_contract = Contracts::::get(deployment_contract.capacity_reservation_id) .ok_or(Error::::ContractNotExists)?; let capacity_reservation_contract = Self::get_capacity_reservation_contract(&cr_contract)?; @@ -1533,13 +1533,17 @@ impl Pallet { // if the node is trying to send garbage data we can throw an error here let contract = Contracts::::get(report.contract_id).ok_or(Error::::ContractNotExists)?; - let capacity_reservation_contract = Self::get_capacity_reservation_contract(&contract)?; + let deployment_contract = Self::get_deployment_contract(&contract)?; + let contract_cr = Contracts::::get(deployment_contract.capacity_reservation_id) + .ok_or(Error::::CapacityReservationNotExists)?; + let capacity_reservation_contract = + Self::get_capacity_reservation_contract(&contract_cr)?; ensure!( capacity_reservation_contract.node_id == node_id, Error::::NodeNotAuthorizedToComputeReport ); - Self::_calculate_report_cost(&report, &pricing_policy); + Self::_calculate_report_cost(contract_cr.contract_id, &report, &pricing_policy); Self::deposit_event(Event::NruConsumptionReportReceived(report.clone())); } @@ -1550,11 +1554,11 @@ impl Pallet { // Takes in a report for NRU (network resource units) // Updates the contract's billing information in storage pub fn _calculate_report_cost( + contract_id: u64, report: &types::NruConsumption, pricing_policy: &pallet_tfgrid_types::PricingPolicy, ) { - let mut contract_billing_info = - ContractBillingInformationByID::::get(report.contract_id); + let mut contract_billing_info = ContractBillingInformationByID::::get(contract_id); if report.timestamp < contract_billing_info.last_updated { return; } @@ -1577,7 +1581,7 @@ impl Pallet { // update contract billing info contract_billing_info.amount_unbilled += total; contract_billing_info.last_updated = report.timestamp; - ContractBillingInformationByID::::insert(report.contract_id, &contract_billing_info); + ContractBillingInformationByID::::insert(contract_id, &contract_billing_info); } fn bill_contract_using_signed_transaction(contract_id: u64) -> Result<(), Error> { @@ -2315,7 +2319,7 @@ impl Pallet { Ok(().into()) } - pub fn get_node_contract( + pub fn get_deployment_contract( contract: &types::Contract, ) -> Result, DispatchErrorWithPostInfo> { match contract.contract_type.clone() { diff --git a/substrate-node/tests/TfChainClient.py b/substrate-node/tests/TfChainClient.py index 69d527425..1c8479a39 100644 --- a/substrate-node/tests/TfChainClient.py +++ b/substrate-node/tests/TfChainClient.py @@ -32,6 +32,8 @@ UNIT_TYPES = [UNIT_BYTES, UNIT_KILOBYTES, UNIT_MEGABYTES, UNIT_GIGABYTES, UNIT_TERRABYTES] +EMPTY_RESOURCES = { "hru": 0, "mru": 0, "cru": 0, "sru": 0} + class TfChainClient: def __init__(self): @@ -88,6 +90,12 @@ def _sign_extrinsic_submit_check_response(self, substrate, call, who: str, expec self._check_events([event.value["event"] for event in response.triggered_events], expected_events) + + def _gigabytify_resources(self, resources: dict): + resources["hru"] *= GIGABYTE + resources["sru"] *= GIGABYTE + resources["mru"] *= GIGABYTE + return dict(resources) def setup_predefined_account(self, who: str, port: int = DEFAULT_PORT): logging.info("Setting up predefined account %s (%s)", who, @@ -275,7 +283,7 @@ def remove_farm_ip(self, id: int = 1, ip: str = "", port: int = DEFAULT_PORT, wh self._sign_extrinsic_submit_check_response( substrate, call, who, expected_events=expected_events) - def create_node(self, farm_id: int = 1, hru: int = 0, sru: int = 0, cru: int = 0, mru: int = 0, + def create_node(self, farm_id: int = 1, resources: dict = EMPTY_RESOURCES, longitude: str = "", latitude: str = "", country: str = "", city: str = "", interfaces: list = [], secure_boot: bool = False, virtualized: bool = False, serial_number: str = "", port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): @@ -283,12 +291,7 @@ def create_node(self, farm_id: int = 1, hru: int = 0, sru: int = 0, cru: int = 0 params = { "farm_id": farm_id, - "resources": { - "hru": hru * GIGABYTE, - "sru": sru * GIGABYTE, - "cru": cru, - "mru": mru * GIGABYTE - }, + "resources": self._gigabytify_resources(resources), "location": { "longitude": f"{longitude}", "latitude": f"{latitude}" @@ -309,7 +312,7 @@ def create_node(self, farm_id: int = 1, hru: int = 0, sru: int = 0, cru: int = 0 self._sign_extrinsic_submit_check_response( substrate, call, who, expected_events=expected_events) - def update_node(self, node_id: int = 1, farm_id: int = 1, hru: int = 0, sru: int = 0, cru: int = 0, mru: int = 0, + def update_node(self, node_id: int = 1, farm_id: int = 1, resources: dict = EMPTY_RESOURCES, longitude: str = "", latitude: str = "", country: str = "", city: str = "", secure_boot: bool = False, virtualized: bool = False, serial_number: str = "", port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): @@ -318,12 +321,7 @@ def update_node(self, node_id: int = 1, farm_id: int = 1, hru: int = 0, sru: int params = { "node_id": node_id, "farm_id": farm_id, - "resources": { - "hru": hru * GIGABYTE, - "sru": sru * GIGABYTE, - "cru": cru, - "mru": mru * GIGABYTE - }, + "resources": self._gigabytify_resources(resources), "location": { "longitude": f"{longitude}", "latitude": f"{latitude}" @@ -392,25 +390,57 @@ def get_node(self, id: int = 1, port: int = DEFAULT_PORT): q = substrate.query("TfgridModule", "Nodes", [id]) return q.value + + def get_contract(self, id: int = 1, port: int = DEFAULT_PORT): + substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") - def create_deployment_contract(self, farm_id: int = 1, deployment_data: bytes = randbytes(32), - deployment_hash: bytes = randbytes(32), public_ips: int = 0, hru: int = 0, sru: int = 0, - cru: int = 0, mru: int = 0, solution_provider_id: int | None = None, - contract_policy: int | None = None, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + q = substrate.query("SmartContractModule", "Contracts", [id]) + + return q.value + + def get_node_id_from_capacity_reservation_contract(self, contract_id: int = 1, port: int = DEFAULT_PORT): + contract = self.get_contract(id=contract_id, port=port) + assert contract is not None, "Contract doesn't exist" + assert "CapacityReservationContract" in contract["contract_type"], "Contract is not a Capacity Reservation Contract" + return contract["contract_type"]["CapacityReservationContract"]["node_id"] + + def create_capacity_reservation_contract(self, farm_id: int = 1, policy: dict = {}, + solution_provider_id: int | None = None, port: int = DEFAULT_PORT, + who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") + call_function = substrate.get_metadata_call_function( + "SmartContractModule", "create_capacity_reservation_contract") + + call = substrate.compose_call("SmartContractModule", "create_capacity_reservation_contract", + { + "farm_id": farm_id, + "policy": dict(policy), + "solution_provider_id": solution_provider_id + }) + expected_events = [{ + "module_id": "SmartContractModule", + "event_id": "ContractCreated" + }] + self._sign_extrinsic_submit_check_response( + substrate, call, who, expected_events=expected_events) + + def create_deployment_contract(self, capacity_reservation_id: int = 1, deployment_data: None | bytes = None, + deployment_hash: None | bytes = None, public_ips: int = 0, + resources: dict = EMPTY_RESOURCES, contract_policy: int | None = None, + port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") + if deployment_data is None: + deployment_data = randbytes(32) + if deployment_hash is None: + deployment_hash = randbytes(32) + params = { - "farm_id": farm_id, + "capacity_reservation_id": capacity_reservation_id, "deployment_data": deployment_data, "deployment_hash": deployment_hash, "public_ips": public_ips, - "resources": { - "hru": hru * GIGABYTE, - "sru": sru * GIGABYTE, - "cru": cru, - "mru": mru * GIGABYTE - }, - "solution_provider_id": solution_provider_id, + "resources": self._gigabytify_resources(resources), "contract_policy": contract_policy } call = substrate.compose_call( @@ -423,20 +453,15 @@ def create_deployment_contract(self, farm_id: int = 1, deployment_data: bytes = substrate, call, who, expected_events=expected_events) def update_deployment_contract(self, contract_id: int = 1, deployment_data: bytes = randbytes(32), - deployment_hash: bytes = randbytes(32), hru: int = 0, sru: int = 0, cru: int = 0, - mru: int = 0, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + deployment_hash: bytes = randbytes(32), resources: None | dict = None, + port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") call = substrate.compose_call("SmartContractModule", "update_deployment_contract", { "contract_id": contract_id, "deployment_data": deployment_data, "deployment_hash": deployment_hash, - "resources": None if hru == 0 and sru == 0 and cru == 0 and mru == 0 else { - "hru": hru * GIGABYTE, - "sru": sru * GIGABYTE, - "cru": cru, - "mru": mru * GIGABYTE - } + "resources": None if resources is None else self._gigabytify_resources(resources), }) expected_events = [{ "module_id": "SmartContractModule", @@ -445,22 +470,6 @@ def update_deployment_contract(self, contract_id: int = 1, deployment_data: byte self._sign_extrinsic_submit_check_response( substrate, call, who, expected_events=expected_events) - def create_rent_contract(self, node_id: int = 1, solution_provider_id: int | None = None, port: int = DEFAULT_PORT, - who: str = DEFAULT_SIGNER): - substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") - - call = substrate.compose_call("SmartContractModule", "create_rent_contract", - { - "node_id": node_id, - "solution_provider_id": solution_provider_id - }) - expected_events = [{ - "module_id": "SmartContractModule", - "event_id": "ContractCreated" - }] - self._sign_extrinsic_submit_check_response( - substrate, call, who, expected_events=expected_events) - def create_name_contract(self, name: str = "", port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): substrate = self._connect_to_server(f"ws://127.0.0.1:{port}") @@ -493,13 +502,13 @@ def cancel_name_contract(self, contract_id: int = 1, port: int = DEFAULT_PORT, w self._cancel_contract(contract_id=contract_id, type="Name", port=port, who=who) - def cancel_rent_contract(self, contract_id: int = 1, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + def cancel_deployment_contract(self, contract_id: int = 1, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): self._cancel_contract(contract_id=contract_id, - type="Rent", port=port, who=who) + type="Deployment", port=port, who=who) - def cancel_node_contract(self, contract_id: int = 1, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): + def cancel_capacity_reservation_contract(self, contract_id: int = 1, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): self._cancel_contract(contract_id=contract_id, - type="Node", port=port, who=who) + type="CapacityReservation", port=port, who=who) def add_nru_reports(self, contract_id: int = 1, nru: int = 1, port: int = DEFAULT_PORT, who: str = DEFAULT_SIGNER): block_number = self.get_block_number(port=port) diff --git a/substrate-node/tests/integration_tests.robot b/substrate-node/tests/integration_tests.robot index 50b6e707c..5f7b94b4c 100644 --- a/substrate-node/tests/integration_tests.robot +++ b/substrate-node/tests/integration_tests.robot @@ -6,6 +6,11 @@ Library TfChainClient.py Library OperatingSystem +*** Variables *** +&{RESOURCES_N1} hru=${1024} sru=${512} cru=${8} mru=${16} +&{RESOURCES_N2} hru=${512} sru=${256} cru=${4} mru=${8} +&{RESOURCES_N3} hru=${2048} sru=${1024} cru=${16} mru=${32} + *** Keywords *** Public Ips Should Contain Ip [Arguments] ${list} ${ip} @@ -33,13 +38,44 @@ Setup Network And Create Farm Setup Network And Create Node [Documentation] Helper function to quickly create a network with 2 nodes and creating a farm and a node using Alice's key Setup Network And Create Farm - Create Node farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent + Create Node farm_id=${1} resources=&{RESOURCES_N1} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent + +Setup Network And Create Three nodes + [Documentation] Helper function that sets up a network, creates a farm and creates three nodes + Setup Network And Create Farm + Setup Predefined Account who=Charlie + + Create Node farm_id=${1} resources=&{RESOURCES_N1} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent who=Alice + Create Node farm_id=${1} resources=&{RESOURCES_N2} longitude=872.15414 latitude=86748.6587 country=Belgium city=Ghent who=Bob + Create Node farm_id=${1} resources=&{RESOURCES_N3} longitude=254.54545 latitude=3213.6987 country=Belgium city=Ghent who=Charlie Create Interface [Arguments] ${name} ${mac} ${ips} ${dict} = Create Dictionary name ${name} mac ${mac} ips ${ips} [Return] ${dict} +Resources + [Arguments] ${hru}=${0} ${sru}=${0} ${cru}=${0} ${mru}=${0} + ${dict} = Create Dictionary hru ${hru} sru ${sru} cru ${cru} mru ${mru} + [Return] ${dict} + +Capacity Reservation Policy Any + [Arguments] ${resources} ${features}=${None} + ${args} = Create List ${resources} ${features} + ${dict} = Create Dictionary Any ${args} + [Return] ${dict} + +Capacity Reservation Policy Node + [Arguments] ${node_id} + ${dict} = Create Dictionary Node ${node_id} + [Return] ${dict} + +Capacity Reservation Policy Exclusive + [Arguments] ${group_id} ${resources} ${features}=${None} + ${args} = Create List ${group_id} ${resources} ${features} + ${dict} = Create Dictionary Exclusive ${args} + [Return] ${dict} + Ensure Account Balance Increased [Arguments] ${balance_before} ${balance_after} IF ${balance_before}[free] >= ${balance_after}[free]-${balance_after}[fee_frozen] @@ -188,12 +224,12 @@ Test Create Update Delete Node Setup Multi Node Network log_name=test_create_update_delet_node amt=${3} Setup Network And Create Farm - Create Node farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent + Create Node farm_id=${1} resources=&{RESOURCES_N1} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent ${node} = Get Node ${1} Should Not Be Equal ${node} ${None} Should Be Equal ${node}[city] Ghent - Update Node node_id=${1} farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Celles + Update Node node_id=${1} farm_id=${1} resources=&{RESOURCES_N1} longitude=2.17403 latitude=41.40338 country=Belgium city=Celles ${node} = Get Node ${1} Should Not Be Equal ${node} ${None} Should Be Equal ${node}[city] Celles @@ -217,7 +253,7 @@ Test Reporting Uptime ... Report Uptime ${500} Create Farm name=alice_farm - Create Node farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent + Create Node farm_id=${1} resources=&{RESOURCES_N1} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent Report Uptime ${500} @@ -275,7 +311,10 @@ Test Add Public Config On Node: Failure InvalidDomain Tear Down Multi Node Network -Test Create Update Cancel Node Contract: Success +Test Create Update Cancel Capacity Reservation Contract: Success + + +Test Create Update Cancel Deployment Contract: Success [Documentation] Testing api calls (create, update, cancel) for managing a node contract Setup Multi Node Network log_name=test_create_node_contract @@ -289,10 +328,14 @@ Test Create Update Cancel Node Contract: Success ${interface_ips} = Create List 10.2.3.3 ${interface_1} = Create Interface name=zos mac=00:00:5e:00:53:af ips=${interface_ips} ${interfaces} = Create List ${interface_1} - Create Node farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent interfaces=${interfaces} + Create Node farm_id=${1} resources=&{RESOURCES_N1} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent interfaces=${interfaces} # Bob is the one creating the contract and thus being billed - Create Deployment Contract farm_id=${1} public_ips=${1} who=Bob port=9946 + ${resources_cr} = Resources hru=${512} sru=${256} cru=${4} mru=${8} + ${policy} = Capacity Reservation Policy Any ${resources_cr} + Create Capacity Reservation Contract farm_id=${1} policy=${policy} who=Bob port=9946 + ${resources_dc} = Resources hru=${256} sru=${256} cru=${2} mru=${4} + Create Deployment Contract capacity_reservation_id=${1} resources=${resources_dc} public_ips=${1} who=Bob port=9946 ${farm} = Get Farm ${1} Should Not Be Equal ${farm} ${None} msg=Farm with id 1 doesn't exist @@ -300,35 +343,28 @@ Test Create Update Cancel Node Contract: Success Length Should Be ${farm}[public_ips] 1 msg=There should only be one public ip in public_ips Should Be Equal ${farm}[public_ips][0][ip] 185.206.122.33/24 msg=The public ip address should be 185.206.122.33/24 Should Be Equal ${farm}[public_ips][0][gateway] 185.206.122.1 msg=The gateway should be 185.206.122.1 - Should Be Equal ${farm}[public_ips][0][contract_id] ${1} msg=The public ip was claimed in contract with id 1 while the farm contains a different contract id for it + Should Be Equal ${farm}[public_ips][0][contract_id] ${2} msg=The public ip was claimed in contract with id 1 while the farm contains a different contract id for it - Update Deployment Contract contract_id=${1} who=Bob port=9946 + ${updated_resources} = Resources hru=${512} sru=${128} cru=${1} mru=${6} + Update Deployment Contract contract_id=${2} resources=${updated_resources} who=Bob port=9946 - Cancel Node Contract contract_id=${1} who=Bob port=9946 + Cancel Deployment Contract contract_id=${2} who=Bob port=9946 Tear Down Multi Node Network -Test Create Node Contract: Failure Not Enough Public Ips +Test Create Deployment Contract: Failure Not Enough Public Ips [Documentation] Testing creating a node contract and requesting too much pub ips Setup Multi Node Network log_name=test_create_node_contract_failure_notenoughpubips # the function below creates a farm containing 0 public ips and a node with 0 configured interfaces Setup Network And Create Node + + ${policy} = Capacity Reservation Policy Node ${1} + Create Capacity Reservation Contract farm_id=${1} policy=${policy} # let's request 2 public ips which should result in an error + ${resources} = Resources Run Keyword And Expect Error *'FarmHasNotEnoughPublicIPs'* - ... Create Deployment Contract farm_id=${1} public_ips=${2} - - Tear Down Multi Node Network - -Test Create Rent Contract: Success - [Documentation] Testing api calls (create, cancel) for managing a rent contract - Setup Multi Node Network log_name=test_create_rent_contract - - Setup Network And Create Node - - Create Rent Contract node_id=${1} - - Cancel Rent Contract contract_id=${1} + ... Create Deployment Contract capacity_reservation_id=${1} public_ips=${2} resources=${resources} Tear Down Multi Node Network @@ -421,19 +457,31 @@ Test Billing # Setup Setup Predefined Account who=Alice - Setup Predefined Account who=Bob - Create Farm name=alice_farm - Create Node farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent + Setup Predefined Account who=Bob + + ${ip_1} = Create Dictionary ip 185.206.122.33/24 gw 185.206.122.1 + ${public_ips} = Create List ${ip_1} + Create Farm name=alice_farm public_ips=${public_ips} + Create Node farm_id=${1} resources=&{RESOURCES_N1} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent ${balance_alice} = Balance Data who=Alice ${balance_bob} = Balance Data who=Bob port=${9946} - # Bob will be using the node: let's create a node contract in his name - Create Deployment Contract farm_id=${1} hru=${20} sru=${20} cru=${2} mru=${4} port=${9946} who=Bob - Add Nru Reports contract_id=${1} nru=${3} + + # Bob will be using a node with a specific amount of resources. Let's look for such a node using the policy Any (any node that meets the requirements) + ${resources_cr} = Resources hru=${512} sru=${256} cru=${4} mru=${8} + ${policy} = Capacity Reservation Policy Any ${resources_cr} + Create Capacity Reservation Contract farm_id=${1} policy=${policy} port=${9946} who=Bob + # Let's create two deployment contracts (simulating two vms on the node) both taking half of the resources of the capacity reservation contract + ${resources_dc} = Resources hru=${256} sru=${128} cru=${2} mru=${4} + Create Deployment Contract capacity_reservation_id=${1} resources=${resources_dc} port=${9946} who=Bob + Create Deployment Contract capacity_reservation_id=${1} resources=${resources_dc} public_ips=1 port=${9946} who=Bob + Add Nru Reports contract_id=${2} nru=${3} # Let it run 6 blocks so that the user will be billed 1 time Wait X Blocks ${6} - Cancel Node Contract contract_id=${1} who=Bob + Cancel Deployment Contract contract_id=${3} who=Bob + Cancel Deployment Contract contract_id=${2} who=Bob + Cancel Capacity Reservation Contract contract_id=${1} who=Bob # Balance should have decreased ${balance_alice_after} = Balance Data who=Alice @@ -450,7 +498,7 @@ Test Solution Provider Setup Predefined Account who=Alice Setup Predefined Account who=Bob Create Farm name=alice_farm - Create Node farm_id=${1} hru=${1024} sru=${512} cru=${8} mru=${16} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent + Create Node farm_id=${1} resources=${RESOURCES_N1} longitude=2.17403 latitude=41.40338 country=Belgium city=Ghent # lets add two providers: charlie gets 30% and Dave 10% ${providers} = Create Dictionary Charlie ${30} Dave ${10} @@ -469,13 +517,17 @@ Test Solution Provider ${balance_charlie_before} = Balance Data who=Charlie ${balance_dave_before} = Balance Data who=Dave - # Bob will be using the node: let's create a node contract in his name - Create Deployment Contract farm_id=${1} hru=${20} sru=${20} cru=${2} mru=${4} port=9946 who=Bob solution_provider_id=${1} - Add Nru Reports contract_id=${1} nru=${3} + # Bob will be using the node + ${policy} = Capacity Reservation Policy Node ${1} + Create Capacity Reservation Contract farm_id=${1} policy=${policy} solution_provider_id=${1} port=9946 who=Bob + ${resources} = Resources hru=${20} sru=${20} cru=${2} mru=${4} + Create Deployment Contract resources=${resources} port=9946 who=Bob + Add Nru Reports contract_id=${2} nru=${3} # Wait 6 blocks: after 5 blocks Bob should be billed Wait X Blocks ${6} # Cancel the contract so that the bill is distributed and so that the providers get their part - Cancel Node Contract contract_id=${1} who=Bob + Cancel Deployment Contract contract_id=${2} who=Bob + Cancel Capacity Reservation Contract contract_id=${1} who=Bob # Verification: both providers should have received their part ${balance_charlie_after} = Balance Data who=Charlie @@ -483,4 +535,29 @@ Test Solution Provider Ensure Account Balance Increased ${balance_charlie_before} ${balance_charlie_after} Ensure Account Balance Increased ${balance_dave_before} ${balance_dave_after} - Tear Down Multi Node Network \ No newline at end of file + Tear Down Multi Node Network + +Test Grouping Contracts + [Documentation] Testing creating a group for contracts and finding a node using the exclusive policy + Setup Multi Node Network log_name=test_grouping_contracts + + Setup Network And Create Three nodes + + Create Group + + ${resources} = Resources hru=${256} sru=${128} cru=${2} mru=${4} + ${policy} = Capacity Reservation Policy Exclusive ${1} ${resources} + # create three capacity reservation contracts, all with the same policy, same group, thus all should go to a different node + Create Capacity Reservation Contract farm_id=${1} policy=${policy} + Create Capacity Reservation Contract farm_id=${1} policy=${policy} + Create Capacity Reservation Contract farm_id=${1} policy=${policy} + + ${node_c1} = Get Node Id From Capacity Reservation Contract contract_id=${1} + Should Be Equal ${node_c1} ${1} msg=Contract 1 should be deployed on node 1! + ${node_c2} = Get Node Id From Capacity Reservation Contract contract_id=${2} + Should Be Equal ${node_c2} ${2} msg=Contract 2 should be deployed on node 2! + ${node_c3} = Get Node Id From Capacity Reservation Contract contract_id=${3} + Should Be Equal ${node_c3} ${3} msg=Contract 3 should be deployed on node 3! + + + From 8758e06960b26c0eb88e33a8614c908d9bf6e571 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 25 Oct 2022 18:30:12 +0200 Subject: [PATCH 64/87] fixed integration tests --- substrate-node/tests/TfChainClient.py | 14 ++++---------- substrate-node/tests/integration_tests.robot | 3 ++- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/substrate-node/tests/TfChainClient.py b/substrate-node/tests/TfChainClient.py index 1c8479a39..2719a091b 100644 --- a/substrate-node/tests/TfChainClient.py +++ b/substrate-node/tests/TfChainClient.py @@ -90,12 +90,6 @@ def _sign_extrinsic_submit_check_response(self, substrate, call, who: str, expec self._check_events([event.value["event"] for event in response.triggered_events], expected_events) - - def _gigabytify_resources(self, resources: dict): - resources["hru"] *= GIGABYTE - resources["sru"] *= GIGABYTE - resources["mru"] *= GIGABYTE - return dict(resources) def setup_predefined_account(self, who: str, port: int = DEFAULT_PORT): logging.info("Setting up predefined account %s (%s)", who, @@ -291,7 +285,7 @@ def create_node(self, farm_id: int = 1, resources: dict = EMPTY_RESOURCES, params = { "farm_id": farm_id, - "resources": self._gigabytify_resources(resources), + "resources": dict(resources), "location": { "longitude": f"{longitude}", "latitude": f"{latitude}" @@ -321,7 +315,7 @@ def update_node(self, node_id: int = 1, farm_id: int = 1, resources: dict = EMPT params = { "node_id": node_id, "farm_id": farm_id, - "resources": self._gigabytify_resources(resources), + "resources": dict(resources), "location": { "longitude": f"{longitude}", "latitude": f"{latitude}" @@ -440,7 +434,7 @@ def create_deployment_contract(self, capacity_reservation_id: int = 1, deploymen "deployment_data": deployment_data, "deployment_hash": deployment_hash, "public_ips": public_ips, - "resources": self._gigabytify_resources(resources), + "resources": dict(resources), "contract_policy": contract_policy } call = substrate.compose_call( @@ -461,7 +455,7 @@ def update_deployment_contract(self, contract_id: int = 1, deployment_data: byte "contract_id": contract_id, "deployment_data": deployment_data, "deployment_hash": deployment_hash, - "resources": None if resources is None else self._gigabytify_resources(resources), + "resources": None if resources is None else dict(resources), }) expected_events = [{ "module_id": "SmartContractModule", diff --git a/substrate-node/tests/integration_tests.robot b/substrate-node/tests/integration_tests.robot index 5f7b94b4c..eed9933e2 100644 --- a/substrate-node/tests/integration_tests.robot +++ b/substrate-node/tests/integration_tests.robot @@ -311,7 +311,8 @@ Test Add Public Config On Node: Failure InvalidDomain Tear Down Multi Node Network -Test Create Update Cancel Capacity Reservation Contract: Success +#Test Create Update Cancel Capacity Reservation Contract: Success + Test Create Update Cancel Deployment Contract: Success From 75bb96eb855b2fb1531af963476d51516363a52c Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 25 Oct 2022 18:43:49 +0200 Subject: [PATCH 65/87] fixed unit tests --- substrate-node/pallets/pallet-smart-contract/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 2ea565c28..ea8f7370e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1733,7 +1733,7 @@ fn test_deployment_contract_billing_details() { 1, )); - push_nru_report_for_contract(1, 10); + push_nru_report_for_contract(2, 10); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(1); assert_eq!(contract_to_bill, [1]); @@ -1828,7 +1828,7 @@ fn test_deployment_contract_billing_details_with_solution_provider() { 1, )); - push_nru_report_for_contract(1, 10); + push_nru_report_for_contract(2, 10); let contract_to_bill = SmartContractModule::contract_to_bill_at_block(1); assert_eq!(contract_to_bill, [1]); From 251f38772636c7a049968c91590b85da3bb534a8 Mon Sep 17 00:00:00 2001 From: renauter Date: Wed, 26 Oct 2022 14:06:23 -0300 Subject: [PATCH 66/87] feat: add new service contract type --- .../pallets/pallet-smart-contract/src/cost.rs | 8 ++++ .../pallets/pallet-smart-contract/src/lib.rs | 44 +++++++++++++++++++ .../pallet-smart-contract/src/types.rs | 26 ++++++++--- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index b7dd4d7b5..05f236d54 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -86,6 +86,14 @@ impl Contract { * U64F64::from_num(seconds_elapsed); total_cost_u64f64.to_num::() } + types::ContractData::ServiceContract(service_contract) => { + // bill user for service usage for number of seconds elapsed + let bill_window = 0; // TODO + let contract_cost = (U64F64::from_num(service_contract.base_fee * bill_window) + / 3600) + + U64F64::from_num(service_contract.variable_fee); + contract_cost.to_num::() + } }; Ok(total_cost) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index ceeff0642..b8dd2b253 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -309,6 +309,10 @@ pub mod pallet { NameContractCanceled { contract_id: u64, }, + /// A Service contract is canceled + ServiceContractCanceled { + contract_id: u64, + }, /// IP got reserved by a Node contract IPsReserved { contract_id: u64, @@ -640,6 +644,12 @@ pub mod pallet { let _account_id = ensure_signed(origin)?; Self::bill_contract(contract_id) } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn create_service_contract(origin: OriginFor) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_create_service_contract(account_id) + } } #[pallet::hooks] @@ -1113,6 +1123,37 @@ impl Pallet { Ok(().into()) } + #[transactional] + pub fn _create_service_contract(source: T::AccountId) -> DispatchResultWithPostInfo { + ensure!( + pallet_tfgrid::TwinIdByAccountID::::contains_key(&source), + Error::::TwinNotExists + ); + let twin_id = + pallet_tfgrid::TwinIdByAccountID::::get(&source).ok_or(Error::::TwinNotExists)?; + + let service_contract = types::ServiceContract { + service_twin_id: 0, // TODO + consumer_twin_id: 0, // TODO + base_fee: 0, // OK + variable_fee: 0, // OK + metadata: vec![].try_into().unwrap(), // TODO + accepted_by_service: false, // TODO + accepted_by_consumer: false, // TODO + last_bill_received: 0, // TODO + }; + + let contract = Self::_create_contract( + twin_id, + types::ContractData::ServiceContract(service_contract), + None, + )?; + + Self::deposit_event(Event::ContractCreated(contract)); + + Ok(().into()) + } + fn _change_power_target_node( node_id: u32, power_target: PowerTarget, @@ -2011,6 +2052,9 @@ impl Pallet { twin_id: contract.twin_id, }); } + types::ContractData::ServiceContract(_) => { + Self::deposit_event(Event::ServiceContractCanceled { contract_id }); + } }; info!("removing contract"); Contracts::::remove(contract_id); diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index fb72a2e07..61aaf67ce 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -3,13 +3,11 @@ use crate::pallet::{ }; use crate::Config; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{BoundedVec, RuntimeDebugNoBound}; +use frame_support::{pallet_prelude::ConstU32, BoundedVec, RuntimeDebugNoBound}; use scale_info::TypeInfo; use sp_std::prelude::*; use substrate_fixed::types::U64F64; -use tfchain_support::types::{ - ConsumableResources, Resources -}; +use tfchain_support::types::{ConsumableResources, Resources}; pub type BlockNumber = u64; @@ -34,7 +32,7 @@ impl Default for StorageVersion { pub struct Group { pub id: u32, pub twin_id: u32, - pub capacity_reservation_contract_ids: Vec + pub capacity_reservation_contract_ids: Vec, } #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo)] @@ -65,6 +63,7 @@ impl Contract { ContractData::CapacityReservationContract(c) => c.node_id, ContractData::DeploymentContract(_) => 0, ContractData::NameContract(_) => 0, + ContractData::ServiceContract(_) => 0, } } } @@ -101,6 +100,20 @@ pub struct NameContract { pub name: T::NameContractName, } +#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +#[codec(mel_bound())] +pub struct ServiceContract { + pub service_twin_id: u32, + pub consumer_twin_id: u32, + pub base_fee: u64, + pub variable_fee: u64, + pub metadata: BoundedVec>, + pub accepted_by_service: bool, + pub accepted_by_consumer: bool, + pub last_bill_received: u32, +} + #[derive( PartialEq, Eq, @@ -122,9 +135,10 @@ pub struct RentContract { #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub enum ContractData { + CapacityReservationContract(CapacityReservationContract), DeploymentContract(DeploymentContract), NameContract(NameContract), - CapacityReservationContract(CapacityReservationContract), + ServiceContract(ServiceContract), } // impl Default for ContractData { From f62819d7e8fe50869384e76579366b05e24c5f04 Mon Sep 17 00:00:00 2001 From: renauter Date: Thu, 27 Oct 2022 18:36:53 -0300 Subject: [PATCH 67/87] feat: evolving on service contract approval flow --- .../pallets/pallet-smart-contract/src/lib.rs | 222 ++++++++++++++++-- .../pallet-smart-contract/src/types.rs | 33 ++- 2 files changed, 233 insertions(+), 22 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index b8dd2b253..aa7c436c9 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1,11 +1,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -use sp_std::prelude::*; - use frame_support::{ dispatch::DispatchErrorWithPostInfo, ensure, log::info, + pallet_prelude::ConstU32, pallet_prelude::DispatchResult, traits::{ Currency, EnsureOrigin, ExistenceRequirement, ExistenceRequirement::KeepAlive, Get, @@ -29,6 +28,7 @@ use sp_runtime::{ traits::{CheckedSub, SaturatedConversion}, Perbill, }; +use sp_std::prelude::*; use substrate_fixed::types::U64F64; use tfchain_support::{ traits::ChangeNode, @@ -309,9 +309,15 @@ pub mod pallet { NameContractCanceled { contract_id: u64, }, + /// A Service contract is approved + ServiceContractApproved { + contract_id: u64, + }, /// A Service contract is canceled ServiceContractCanceled { contract_id: u64, + service_twin_id: u32, + consumer_twin_id: u32, }, /// IP got reserved by a Node contract IPsReserved { @@ -428,6 +434,12 @@ pub mod pallet { CapacityReservationHasActiveContracts, ResourcesUsedByActiveContracts, NotEnoughResourcesInCapacityReservation, + TwinNotAuthorizedToCreateServiceContract, + TwinNotAuthorizedToSetMetadata, + TwinNotAuthorizedToSetFees, + TwinNotAuthorizedToApproveServiceContract, + NoServiceContractModificationAllowed, + NoServiceContractApprovalAllowed, } #[pallet::genesis_config] @@ -646,9 +658,43 @@ pub mod pallet { } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn create_service_contract(origin: OriginFor) -> DispatchResultWithPostInfo { + pub fn create_service_contract( + origin: OriginFor, + service_account: T::AccountId, + consumer_account: T::AccountId, + ) -> DispatchResultWithPostInfo { + let caller_account = ensure_signed(origin)?; + Self::_create_service_contract(caller_account, service_account, consumer_account) + } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn service_contract_set_metadata( + origin: OriginFor, + contract_id: u64, + metadata: BoundedVec>, + ) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_service_contract_set_metadata(account_id, contract_id, metadata) + } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn service_contract_set_fees( + origin: OriginFor, + contract_id: u64, + base_fee: u64, + variable_fee: u64, + ) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_service_contract_set_fees(account_id, contract_id, base_fee, variable_fee) + } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn service_contract_approve( + origin: OriginFor, + contract_id: u64, + ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_create_service_contract(account_id) + Self::_service_contract_approve(account_id, contract_id) } } @@ -1124,27 +1170,40 @@ impl Pallet { } #[transactional] - pub fn _create_service_contract(source: T::AccountId) -> DispatchResultWithPostInfo { + pub fn _create_service_contract( + caller: T::AccountId, + service: T::AccountId, + consumer: T::AccountId, + ) -> DispatchResultWithPostInfo { + let caller_twin_id = + pallet_tfgrid::TwinIdByAccountID::::get(&caller).ok_or(Error::::TwinNotExists)?; + + let service_twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&service) + .ok_or(Error::::TwinNotExists)?; + + let consumer_twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&consumer) + .ok_or(Error::::TwinNotExists)?; + + // Only service or consumer can create contract ensure!( - pallet_tfgrid::TwinIdByAccountID::::contains_key(&source), - Error::::TwinNotExists + caller_twin_id == service_twin_id || caller_twin_id == service_twin_id, + Error::::TwinNotAuthorizedToCreateServiceContract, ); - let twin_id = - pallet_tfgrid::TwinIdByAccountID::::get(&source).ok_or(Error::::TwinNotExists)?; let service_contract = types::ServiceContract { - service_twin_id: 0, // TODO - consumer_twin_id: 0, // TODO - base_fee: 0, // OK - variable_fee: 0, // OK - metadata: vec![].try_into().unwrap(), // TODO - accepted_by_service: false, // TODO - accepted_by_consumer: false, // TODO - last_bill_received: 0, // TODO + service_twin_id, // OK + consumer_twin_id, // OK + base_fee: 0, // OK + variable_fee: 0, // OK + metadata: vec![].try_into().unwrap(), // OK + accepted_by_service: false, // OK + accepted_by_consumer: false, // OK + last_bill: 0, // TODO + state: types::ServiceContractState::Created, // OK }; let contract = Self::_create_contract( - twin_id, + caller_twin_id, types::ContractData::ServiceContract(service_contract), None, )?; @@ -1154,6 +1213,112 @@ impl Pallet { Ok(().into()) } + pub fn _service_contract_set_metadata( + account_id: T::AccountId, + contract_id: u64, + metadata: BoundedVec>, + ) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + + let mut service_contract = Self::get_service_contract(&contract)?; + + // Only service or consumer can set metadata + ensure!( + twin_id == service_contract.service_twin_id | service_contract.consumer_twin_id, + Error::::TwinNotAuthorizedToSetMetadata, + ); + + // Only allow to modify metadata if contract still not approved by both parties + ensure!( + !matches!( + service_contract.state, + types::ServiceContractState::ApprovedByBoth + ), + Error::::NoServiceContractModificationAllowed, + ); + + service_contract.metadata = metadata; + + Ok(().into()) + } + + pub fn _service_contract_set_fees( + account_id: T::AccountId, + contract_id: u64, + base_fee: u64, + variable_fee: u64, + ) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + + let mut service_contract = Self::get_service_contract(&contract)?; + + // Only service can set fees + ensure!( + twin_id == service_contract.service_twin_id, + Error::::TwinNotAuthorizedToSetFees, + ); + + // Only allow to modify fees if contract still not approved by both parties + ensure!( + !matches!( + service_contract.state, + types::ServiceContractState::ApprovedByBoth + ), + Error::::NoServiceContractModificationAllowed, + ); + + service_contract.base_fee = base_fee; + service_contract.variable_fee = variable_fee; + + Ok(().into()) + } + + pub fn _service_contract_approve( + account_id: T::AccountId, + contract_id: u64, + ) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + + let mut service_contract = Self::get_service_contract(&contract)?; + + // Allow to approve contract only if agreement is ready + ensure!( + matches!( + service_contract.state, + types::ServiceContractState::AgreementReady + ), + Error::::NoServiceContractApprovalAllowed, + ); + + // Only service or consumer can accept agreement + if twin_id == service_contract.service_twin_id { + service_contract.accepted_by_service = true; + } else if twin_id == service_contract.consumer_twin_id { + service_contract.accepted_by_consumer = true + } else { + return Err(DispatchErrorWithPostInfo::from( + Error::::TwinNotAuthorizedToApproveServiceContract, + )); + } + + // If both parties (service and consumer) accepted then contract is approved + if service_contract.accepted_by_service && service_contract.accepted_by_consumer { + service_contract.state = types::ServiceContractState::ApprovedByBoth; + Self::deposit_event(Event::ServiceContractApproved { contract_id }); + } + + Ok(().into()) + } + fn _change_power_target_node( node_id: u32, power_target: PowerTarget, @@ -2052,8 +2217,12 @@ impl Pallet { twin_id: contract.twin_id, }); } - types::ContractData::ServiceContract(_) => { - Self::deposit_event(Event::ServiceContractCanceled { contract_id }); + types::ContractData::ServiceContract(service_contract) => { + Self::deposit_event(Event::ServiceContractCanceled { + contract_id: contract_id, + service_twin_id: service_contract.service_twin_id, + consumer_twin_id: service_contract.consumer_twin_id, + }); } }; info!("removing contract"); @@ -2389,6 +2558,19 @@ impl Pallet { } } + pub fn get_service_contract( + contract: &types::Contract, + ) -> Result { + match contract.contract_type.clone() { + types::ContractData::ServiceContract(c) => Ok(c), + _ => { + return Err(DispatchErrorWithPostInfo::from( + Error::::InvalidContractType, + )) + } + } + } + fn get_usable_balance(account_id: &T::AccountId) -> BalanceOf { let balance = pallet_balances::pallet::Pallet::::usable_balance(account_id); let b = balance.saturated_into::(); diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index 61aaf67ce..f1a4f6e21 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -108,10 +108,39 @@ pub struct ServiceContract { pub consumer_twin_id: u32, pub base_fee: u64, pub variable_fee: u64, - pub metadata: BoundedVec>, + pub metadata: BoundedVec>, // limited to 64 bytes (2 public keys) pub accepted_by_service: bool, pub accepted_by_consumer: bool, - pub last_bill_received: u32, + pub last_bill: u64, + pub state: ServiceContractState, +} + +// impl ServiceContract { +// pub fn get_state(&self) -> ServiceContractState { +// if self.accepted_by_service && self.accepted_by_consumer { +// return ServiceContractState::ApprovedByBoth; +// } else if self.base_fee.is_some() && self.metadata.is_some() { +// return ServiceContractState::AgreementReady; +// } +// ServiceContractState::Created +// } +// } + +#[derive( + PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen, +)] +pub struct ServiceContractBillReport { + pub bill_id: u64, + pub amount: u64, + pub window: u64, + pub metadata: BoundedVec>, // limited to 50 bytes for now +} + +#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum ServiceContractState { + Created, + AgreementReady, + ApprovedByBoth, } #[derive( From 0ea8829c9cb1f9876dd706edb6b6f91d11ee8edd Mon Sep 17 00:00:00 2001 From: renauter Date: Fri, 28 Oct 2022 16:28:26 -0300 Subject: [PATCH 68/87] test: add service contract basic tests and samll improvements --- .../pallets/pallet-smart-contract/src/lib.rs | 43 ++++++-- .../pallet-smart-contract/src/tests.rs | 97 +++++++++++++++++++ .../pallet-smart-contract/src/types.rs | 5 +- 3 files changed, 135 insertions(+), 10 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index aa7c436c9..24cb9be80 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -4,7 +4,6 @@ use frame_support::{ dispatch::DispatchErrorWithPostInfo, ensure, log::info, - pallet_prelude::ConstU32, pallet_prelude::DispatchResult, traits::{ Currency, EnsureOrigin, ExistenceRequirement, ExistenceRequirement::KeepAlive, Get, @@ -440,6 +439,7 @@ pub mod pallet { TwinNotAuthorizedToApproveServiceContract, NoServiceContractModificationAllowed, NoServiceContractApprovalAllowed, + MetadataTooLong, } #[pallet::genesis_config] @@ -671,7 +671,7 @@ pub mod pallet { pub fn service_contract_set_metadata( origin: OriginFor, contract_id: u64, - metadata: BoundedVec>, + metadata: Vec, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; Self::_service_contract_set_metadata(account_id, contract_id, metadata) @@ -1186,7 +1186,7 @@ impl Pallet { // Only service or consumer can create contract ensure!( - caller_twin_id == service_twin_id || caller_twin_id == service_twin_id, + caller_twin_id == service_twin_id || caller_twin_id == consumer_twin_id, Error::::TwinNotAuthorizedToCreateServiceContract, ); @@ -1216,18 +1216,19 @@ impl Pallet { pub fn _service_contract_set_metadata( account_id: T::AccountId, contract_id: u64, - metadata: BoundedVec>, + metadata: Vec, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; let mut service_contract = Self::get_service_contract(&contract)?; // Only service or consumer can set metadata ensure!( - twin_id == service_contract.service_twin_id | service_contract.consumer_twin_id, + twin_id == service_contract.service_twin_id + || twin_id == service_contract.consumer_twin_id, Error::::TwinNotAuthorizedToSetMetadata, ); @@ -1240,7 +1241,18 @@ impl Pallet { Error::::NoServiceContractModificationAllowed, ); - service_contract.metadata = metadata; + // TODO create metadata input type to control size + service_contract.metadata = + BoundedVec::try_from(metadata).map_err(|_| Error::::MetadataTooLong)?; + + // If base_fee is set and non-zero (mandatory) + if !service_contract.base_fee != 0 { + service_contract.state = types::ServiceContractState::AgreementReady; + } + + // Update contract in list after modification + contract.contract_type = types::ContractData::ServiceContract(service_contract); + Contracts::::insert(contract_id, contract); Ok(().into()) } @@ -1254,7 +1266,7 @@ impl Pallet { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; let mut service_contract = Self::get_service_contract(&contract)?; @@ -1276,6 +1288,15 @@ impl Pallet { service_contract.base_fee = base_fee; service_contract.variable_fee = variable_fee; + // If metadata is filled and not empty (mandatory) + if !service_contract.metadata.is_empty() { + service_contract.state = types::ServiceContractState::AgreementReady; + } + + // Update contract in list after modification + contract.contract_type = types::ContractData::ServiceContract(service_contract); + Contracts::::insert(contract_id, contract); + Ok(().into()) } @@ -1286,7 +1307,7 @@ impl Pallet { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; let mut service_contract = Self::get_service_contract(&contract)?; @@ -1316,6 +1337,10 @@ impl Pallet { Self::deposit_event(Event::ServiceContractApproved { contract_id }); } + // Update contract in list after modification + contract.contract_type = types::ContractData::ServiceContract(service_contract); + Contracts::::insert(contract_id, contract); + Ok(().into()) } diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index ea8f7370e..834abc3bc 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1471,6 +1471,74 @@ fn test_create_name_contract_with_invalid_dns_name_fails() { }); } +// SERVICE CONTRACT TESTS // +// ---------------------- // + +#[test] +fn test_create_service_contract_works() { + new_test_ext().execute_with(|| { + create_service_consumer_contract(); + + // check data + + // check event + }); +} + +#[test] +fn test_service_contract_set_metadata_works() { + new_test_ext().execute_with(|| { + create_service_consumer_contract(); + + assert_ok!(SmartContractModule::service_contract_set_metadata( + Origin::signed(alice()), + 1, + b"some_metadata".to_vec(), + )); + + // check data + }); +} + +#[test] +fn test_service_contract_set_fees_works() { + new_test_ext().execute_with(|| { + create_service_consumer_contract(); + + assert_ok!(SmartContractModule::service_contract_set_fees( + Origin::signed(alice()), + 1, + 10, + 10, + )); + + // check data + }); +} + +#[test] +fn test_service_contract_approve_works() { + new_test_ext().execute_with(|| { + prepare_service_consumer_contract(); + + assert_ok!(SmartContractModule::service_contract_approve( + Origin::signed(alice()), + 1, + )); + + // check state + + assert_ok!(SmartContractModule::service_contract_approve( + Origin::signed(bob()), + 1, + )); + + // check data + + // check event + }); +} + // CAPACITY CONTRACT RESERVING ALL RESOURCES OF NODE TESTS // // -------------------------------------------- // @@ -4329,3 +4397,32 @@ fn prepare_farm_three_nodes_three_capacity_reservation_contracts() { assert_eq!(SmartContractModule::active_node_contracts(1).len(), 2); assert_eq!(SmartContractModule::active_node_contracts(2).len(), 1); } + +fn create_service_consumer_contract() { + create_twin(alice()); + create_twin(bob()); + + // create contract between service (Alice) and consumer (Bob) + assert_ok!(SmartContractModule::create_service_contract( + Origin::signed(alice()), + alice(), + bob(), + )); +} + +fn prepare_service_consumer_contract() { + create_service_consumer_contract(); + + assert_ok!(SmartContractModule::service_contract_set_metadata( + Origin::signed(alice()), + 1, + b"some_metadata".to_vec(), + )); + + assert_ok!(SmartContractModule::service_contract_set_fees( + Origin::signed(alice()), + 1, + 10, + 10, + )); +} diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index f1a4f6e21..35f755f6b 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -100,6 +100,9 @@ pub struct NameContract { pub name: T::NameContractName, } +// metadata length limited to 64 bytes (2 public keys) +pub const MAX_METADATA_LENGTH: u32 = 64; + #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] @@ -108,7 +111,7 @@ pub struct ServiceContract { pub consumer_twin_id: u32, pub base_fee: u64, pub variable_fee: u64, - pub metadata: BoundedVec>, // limited to 64 bytes (2 public keys) + pub metadata: BoundedVec>, pub accepted_by_service: bool, pub accepted_by_consumer: bool, pub last_bill: u64, From 986304a3f29ee497354b5f0cdebc7d81c89811dd Mon Sep 17 00:00:00 2001 From: renauter Date: Thu, 3 Nov 2022 18:14:48 -0300 Subject: [PATCH 69/87] feat: contract approval complete cycle and test improvements --- .../pallets/pallet-smart-contract/src/lib.rs | 60 +++++++-- .../pallet-smart-contract/src/tests.rs | 119 ++++++++++++++++-- 2 files changed, 161 insertions(+), 18 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 24cb9be80..70a38245e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -315,8 +315,6 @@ pub mod pallet { /// A Service contract is canceled ServiceContractCanceled { contract_id: u64, - service_twin_id: u32, - consumer_twin_id: u32, }, /// IP got reserved by a Node contract IPsReserved { @@ -437,6 +435,7 @@ pub mod pallet { TwinNotAuthorizedToSetMetadata, TwinNotAuthorizedToSetFees, TwinNotAuthorizedToApproveServiceContract, + TwinNotAuthorizedToRejectServiceContract, NoServiceContractModificationAllowed, NoServiceContractApprovalAllowed, MetadataTooLong, @@ -696,6 +695,15 @@ pub mod pallet { let account_id = ensure_signed(origin)?; Self::_service_contract_approve(account_id, contract_id) } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn service_contract_reject( + origin: OriginFor, + contract_id: u64, + ) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_service_contract_reject(account_id, contract_id) + } } #[pallet::hooks] @@ -1246,7 +1254,7 @@ impl Pallet { BoundedVec::try_from(metadata).map_err(|_| Error::::MetadataTooLong)?; // If base_fee is set and non-zero (mandatory) - if !service_contract.base_fee != 0 { + if service_contract.base_fee != 0 { service_contract.state = types::ServiceContractState::AgreementReady; } @@ -1331,7 +1339,7 @@ impl Pallet { )); } - // If both parties (service and consumer) accepted then contract is approved + // If both parties (service and consumer) accept then contract is approved if service_contract.accepted_by_service && service_contract.accepted_by_consumer { service_contract.state = types::ServiceContractState::ApprovedByBoth; Self::deposit_event(Event::ServiceContractApproved { contract_id }); @@ -1344,6 +1352,42 @@ impl Pallet { Ok(().into()) } + pub fn _service_contract_reject( + account_id: T::AccountId, + contract_id: u64, + ) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + + let service_contract = Self::get_service_contract(&contract)?; + + // Allow to reject contract only if agreement is ready + ensure!( + matches!( + service_contract.state, + types::ServiceContractState::AgreementReady + ), + Error::::NoServiceContractApprovalAllowed, + ); + + // Only service or consumer can reject agreement + if twin_id != service_contract.service_twin_id + && twin_id != service_contract.consumer_twin_id + { + return Err(DispatchErrorWithPostInfo::from( + Error::::TwinNotAuthorizedToRejectServiceContract, + )); + } + + // If one party (service or consumer) rejects then contract + // is canceled and removed from contract list + Self::remove_contract(contract_id); + + Ok(().into()) + } + fn _change_power_target_node( node_id: u32, power_target: PowerTarget, @@ -2242,12 +2286,8 @@ impl Pallet { twin_id: contract.twin_id, }); } - types::ContractData::ServiceContract(service_contract) => { - Self::deposit_event(Event::ServiceContractCanceled { - contract_id: contract_id, - service_twin_id: service_contract.service_twin_id, - consumer_twin_id: service_contract.consumer_twin_id, - }); + types::ContractData::ServiceContract(_service_contract) => { + Self::deposit_event(Event::ServiceContractCanceled { contract_id }); } }; info!("removing contract"); diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 834abc3bc..0c3d281cf 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1477,11 +1477,26 @@ fn test_create_name_contract_with_invalid_dns_name_fails() { #[test] fn test_create_service_contract_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); create_service_consumer_contract(); - // check data + let contract = SmartContractModule::contracts(1).unwrap(); + let service_contrat = get_service_contract(); + assert_eq!( + contract.contract_type, + types::ContractData::ServiceContract(service_contrat), + ); - // check event + let our_events = System::events(); + assert_eq!(!our_events.is_empty(), true); + assert_eq!( + our_events.last().unwrap(), + &record(MockEvent::SmartContractModule(SmartContractEvent::< + TestRuntime, + >::ContractCreated( + contract + ))), + ); }); } @@ -1496,7 +1511,13 @@ fn test_service_contract_set_metadata_works() { b"some_metadata".to_vec(), )); - // check data + let contract = SmartContractModule::contracts(1).unwrap(); + let mut service_contrat = get_service_contract(); + service_contrat.metadata = BoundedVec::try_from(b"some_metadata".to_vec()).unwrap(); + assert_eq!( + contract.contract_type, + types::ContractData::ServiceContract(service_contrat), + ); }); } @@ -1508,34 +1529,102 @@ fn test_service_contract_set_fees_works() { assert_ok!(SmartContractModule::service_contract_set_fees( Origin::signed(alice()), 1, - 10, + 100, 10, )); - // check data + let contract = SmartContractModule::contracts(1).unwrap(); + let mut service_contrat = get_service_contract(); + service_contrat.base_fee = 100; + service_contrat.variable_fee = 10; + assert_eq!( + contract.contract_type, + types::ContractData::ServiceContract(service_contrat), + ); }); } #[test] fn test_service_contract_approve_works() { new_test_ext().execute_with(|| { + run_to_block(1, None); prepare_service_consumer_contract(); + let contract = SmartContractModule::contracts(1).unwrap(); + let mut service_contrat = get_service_contract(); + service_contrat.base_fee = 100; + service_contrat.variable_fee = 10; + service_contrat.metadata = BoundedVec::try_from(b"some_metadata".to_vec()).unwrap(); + service_contrat.state = types::ServiceContractState::AgreementReady; + assert_eq!( + contract.contract_type, + types::ContractData::ServiceContract(service_contrat.clone()), + ); + + // Service approves assert_ok!(SmartContractModule::service_contract_approve( Origin::signed(alice()), 1, )); - // check state + let contract = SmartContractModule::contracts(1).unwrap(); + service_contrat.accepted_by_service = true; + assert_eq!( + contract.contract_type, + types::ContractData::ServiceContract(service_contrat.clone()), + ); + // Consumer approves assert_ok!(SmartContractModule::service_contract_approve( Origin::signed(bob()), 1, )); - // check data + let contract = SmartContractModule::contracts(1).unwrap(); + service_contrat.accepted_by_consumer = true; + service_contrat.state = types::ServiceContractState::ApprovedByBoth; + assert_eq!( + contract.contract_type, + types::ContractData::ServiceContract(service_contrat), + ); // check event + let our_events = System::events(); + assert_eq!(!our_events.is_empty(), true); + assert_eq!( + our_events.last().unwrap(), + &record(MockEvent::SmartContractModule(SmartContractEvent::< + TestRuntime, + >::ServiceContractApproved { + contract_id: 1, + })), + ); + }); +} + +#[test] +fn test_service_contract_reject_works() { + new_test_ext().execute_with(|| { + run_to_block(1, None); + prepare_service_consumer_contract(); + + assert_ok!(SmartContractModule::service_contract_reject( + Origin::signed(alice()), + 1, + )); + + assert_eq!(SmartContractModule::contracts(1).is_none(), true); + + let our_events = System::events(); + assert_eq!(!our_events.is_empty(), true); + assert_eq!( + our_events.last().unwrap(), + &record(MockEvent::SmartContractModule(SmartContractEvent::< + TestRuntime, + >::ServiceContractCanceled { + contract_id: 1, + })), + ); }); } @@ -4422,7 +4511,21 @@ fn prepare_service_consumer_contract() { assert_ok!(SmartContractModule::service_contract_set_fees( Origin::signed(alice()), 1, - 10, + 100, 10, )); } + +fn get_service_contract() -> types::ServiceContract { + types::ServiceContract { + service_twin_id: 1, //Alice + consumer_twin_id: 2, //Bob + base_fee: 0, + variable_fee: 0, + metadata: bounded_vec![], + accepted_by_service: false, + accepted_by_consumer: false, + last_bill: 0, + state: types::ServiceContractState::Created, + } +} From 189f0e1e6c15300cbbf78787cefddf4269955d2e Mon Sep 17 00:00:00 2001 From: renauter Date: Mon, 7 Nov 2022 02:58:06 -0300 Subject: [PATCH 70/87] feat: wip service send bill report --- .../pallets/pallet-smart-contract/src/lib.rs | 76 +++++++++++++++++-- .../pallet-smart-contract/src/tests.rs | 6 +- .../pallet-smart-contract/src/types.rs | 5 +- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 70a38245e..c99b521b0 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -139,6 +139,11 @@ pub mod pallet { pub type ContractBillingInformationByID = StorageMap<_, Blake2_128Concat, u64, ContractBillingInformation, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn service_contract_bill_by_id)] + pub type ServiceContractBillByID = + StorageMap<_, Blake2_128Concat, u64, ServiceContractBill, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn node_contract_resources)] pub type NodeContractResources = @@ -436,8 +441,10 @@ pub mod pallet { TwinNotAuthorizedToSetFees, TwinNotAuthorizedToApproveServiceContract, TwinNotAuthorizedToRejectServiceContract, + TwinNotAuthorizedToBillServiceContract, NoServiceContractModificationAllowed, NoServiceContractApprovalAllowed, + NoServiceContractBillingAllowed, MetadataTooLong, } @@ -657,13 +664,13 @@ pub mod pallet { } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn create_service_contract( + pub fn service_contract_create( origin: OriginFor, service_account: T::AccountId, consumer_account: T::AccountId, ) -> DispatchResultWithPostInfo { let caller_account = ensure_signed(origin)?; - Self::_create_service_contract(caller_account, service_account, consumer_account) + Self::_service_contract_create(caller_account, service_account, consumer_account) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] @@ -704,6 +711,15 @@ pub mod pallet { let account_id = ensure_signed(origin)?; Self::_service_contract_reject(account_id, contract_id) } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn service_contract_bill( + origin: OriginFor, + contract_id: u64, + ) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_service_contract_bill(account_id, contract_id) + } } #[pallet::hooks] @@ -1178,7 +1194,7 @@ impl Pallet { } #[transactional] - pub fn _create_service_contract( + pub fn _service_contract_create( caller: T::AccountId, service: T::AccountId, consumer: T::AccountId, @@ -1206,7 +1222,7 @@ impl Pallet { metadata: vec![].try_into().unwrap(), // OK accepted_by_service: false, // OK accepted_by_consumer: false, // OK - last_bill: 0, // TODO + last_bill: 0, // OK state: types::ServiceContractState::Created, // OK }; @@ -1339,9 +1355,11 @@ impl Pallet { )); } - // If both parties (service and consumer) accept then contract is approved + // If both parties (service and consumer) accept then contract is approved and can be billed if service_contract.accepted_by_service && service_contract.accepted_by_consumer { service_contract.state = types::ServiceContractState::ApprovedByBoth; + let now = >::get().saturated_into::() / 1000; + service_contract.last_bill = now; // Initialize billing period Self::deposit_event(Event::ServiceContractApproved { contract_id }); } @@ -1388,6 +1406,52 @@ impl Pallet { Ok(().into()) } + pub fn _service_contract_bill( + account_id: T::AccountId, + contract_id: u64, + ) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + + let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + + let mut service_contract = Self::get_service_contract(&contract)?; + + // Only service can bill consumer for service contract + ensure!( + twin_id == service_contract.service_twin_id, + Error::::TwinNotAuthorizedToBillServiceContract, + ); + + // Allow to bill contract only if approved by both + ensure!( + matches!( + service_contract.state, + types::ServiceContractState::ApprovedByBoth + ), + Error::::NoServiceContractBillingAllowed, + ); + + // Get amount of time to bill for + let now = >::get().saturated_into::() / 1000; + let window = now - service_contract.last_bill; + + // Create service contract bill and insert to list + let service_contract_bill = types::ServiceContractBill { + amount: 0, // TODO + window, // OK + metadata: vec![].try_into().unwrap(), // TODO + }; + ServiceContractBillByID::::insert(contract.contract_id, service_contract_bill); + + // Update contract in list after modification + service_contract.last_bill = now; + contract.contract_type = types::ContractData::ServiceContract(service_contract); + Contracts::::insert(contract_id, contract); + + Ok(().into()) + } + fn _change_power_target_node( node_id: u32, power_target: PowerTarget, @@ -1893,7 +1957,7 @@ impl Pallet { // Bills a contract (DeploymentContract or NameContract) // Calculates how much TFT is due by the user and distributes the rewards fn bill_contract(contract_id: u64) -> DispatchResultWithPostInfo { - // Clean up contract from blling loop if it not exists anymore + // Clean up contract from billing loop if it does not exist anymore if !Contracts::::contains_key(contract_id) { log::debug!("cleaning up deleted contract from storage"); diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 0c3d281cf..05184319c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1475,7 +1475,7 @@ fn test_create_name_contract_with_invalid_dns_name_fails() { // ---------------------- // #[test] -fn test_create_service_contract_works() { +fn test_service_contract_create_works() { new_test_ext().execute_with(|| { run_to_block(1, None); create_service_consumer_contract(); @@ -1582,13 +1582,13 @@ fn test_service_contract_approve_works() { let contract = SmartContractModule::contracts(1).unwrap(); service_contrat.accepted_by_consumer = true; + service_contrat.last_bill = 1628082006; service_contrat.state = types::ServiceContractState::ApprovedByBoth; assert_eq!( contract.contract_type, types::ContractData::ServiceContract(service_contrat), ); - // check event let our_events = System::events(); assert_eq!(!our_events.is_empty(), true); assert_eq!( @@ -4492,7 +4492,7 @@ fn create_service_consumer_contract() { create_twin(bob()); // create contract between service (Alice) and consumer (Bob) - assert_ok!(SmartContractModule::create_service_contract( + assert_ok!(SmartContractModule::service_contract_create( Origin::signed(alice()), alice(), bob(), diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index 35f755f6b..a99220d71 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -132,10 +132,9 @@ pub struct ServiceContract { #[derive( PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen, )] -pub struct ServiceContractBillReport { - pub bill_id: u64, +pub struct ServiceContractBill { pub amount: u64, - pub window: u64, + pub window: u64, // amount of time (in seconds) covered since last bill pub metadata: BoundedVec>, // limited to 50 bytes for now } From d5ae226a045a97cfbd13032169ba6ab27fd0dc21 Mon Sep 17 00:00:00 2001 From: renauter Date: Mon, 7 Nov 2022 11:58:57 -0300 Subject: [PATCH 71/87] test: some updates after changing base branch --- .../pallet-smart-contract/src/tests.rs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 05184319c..72e8bf637 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -327,7 +327,7 @@ fn test_create_deployment_contract_with_public_ips_works() { let farm = TfgridModule::farms(1).unwrap(); assert_eq!(farm.public_ips[0].contract_id, 1); - assert_eq!(c.public_ips, 1); + assert_eq!(c.public_ips, 2); let pub_ip = PublicIP { ip: "185.206.122.33/24".as_bytes().to_vec().try_into().unwrap(), @@ -335,7 +335,13 @@ fn test_create_deployment_contract_with_public_ips_works() { contract_id: 1, }; + let pub_ip_2 = PublicIP { + ip: "185.206.122.34/24".as_bytes().to_vec().try_into().unwrap(), + gateway: "185.206.122.1".as_bytes().to_vec().try_into().unwrap(), + contract_id: 1, + }; assert_eq!(c.public_ips_list[0], pub_ip); + assert_eq!(c.public_ips_list[1], pub_ip_2); } _ => (), } @@ -2812,20 +2818,26 @@ fn test_deployment_contract_grace_period_cancels_contract_when_grace_period_ends ); // grace period stops after 100 blocknumbers, so after 121 - for i in 1..10 { + for i in 1..11 { pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21 + i * 10); } - for i in 1..10 { + for i in 1..11 { run_to_block(21 + i * 10, Some(&mut pool_state)); } - pool_state - .write() - .should_call_bill_contract(1, Ok(Pays::Yes.into()), 121); - run_to_block(121, Some(&mut pool_state)); + // pool_state + // .write() + // .should_call_bill_contract(1, Ok(Pays::Yes.into()), 131); + // run_to_block(131, Some(&mut pool_state)); + + // The user's total free balance should be distributed + let free_balance = Balances::free_balance(&twin.account_id); + let total_amount_billed = initial_twin_balance - free_balance; + + validate_distribution_rewards(initial_total_issuance, total_amount_billed, false); let c1 = SmartContractModule::contracts(1); assert_eq!(c1, None); @@ -3850,6 +3862,10 @@ pub fn prepare_farm(source: AccountId, dedicated: bool) { ip: "185.206.122.33/24".as_bytes().to_vec().try_into().unwrap(), gw: "185.206.122.1".as_bytes().to_vec().try_into().unwrap(), }); + pub_ips.push(pallet_tfgrid_types::PublicIpInput { + ip: "185.206.122.34/24".as_bytes().to_vec().try_into().unwrap(), + gw: "185.206.122.1".as_bytes().to_vec().try_into().unwrap(), + }); let su_policy = pallet_tfgrid_types::Policy { value: 194400, From 01e02a0c40b83969facc75108200f9b5438a86f6 Mon Sep 17 00:00:00 2001 From: renauter Date: Mon, 7 Nov 2022 12:01:10 -0300 Subject: [PATCH 72/87] test: typo correction --- .../pallet-smart-contract/src/tests.rs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 72e8bf637..a89b048ec 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1487,10 +1487,10 @@ fn test_service_contract_create_works() { create_service_consumer_contract(); let contract = SmartContractModule::contracts(1).unwrap(); - let service_contrat = get_service_contract(); + let service_contract = get_service_contract(); assert_eq!( contract.contract_type, - types::ContractData::ServiceContract(service_contrat), + types::ContractData::ServiceContract(service_contract), ); let our_events = System::events(); @@ -1518,11 +1518,11 @@ fn test_service_contract_set_metadata_works() { )); let contract = SmartContractModule::contracts(1).unwrap(); - let mut service_contrat = get_service_contract(); - service_contrat.metadata = BoundedVec::try_from(b"some_metadata".to_vec()).unwrap(); + let mut service_contract = get_service_contract(); + service_contract.metadata = BoundedVec::try_from(b"some_metadata".to_vec()).unwrap(); assert_eq!( contract.contract_type, - types::ContractData::ServiceContract(service_contrat), + types::ContractData::ServiceContract(service_contract), ); }); } @@ -1540,12 +1540,12 @@ fn test_service_contract_set_fees_works() { )); let contract = SmartContractModule::contracts(1).unwrap(); - let mut service_contrat = get_service_contract(); - service_contrat.base_fee = 100; - service_contrat.variable_fee = 10; + let mut service_contract = get_service_contract(); + service_contract.base_fee = 100; + service_contract.variable_fee = 10; assert_eq!( contract.contract_type, - types::ContractData::ServiceContract(service_contrat), + types::ContractData::ServiceContract(service_contract), ); }); } @@ -1557,14 +1557,14 @@ fn test_service_contract_approve_works() { prepare_service_consumer_contract(); let contract = SmartContractModule::contracts(1).unwrap(); - let mut service_contrat = get_service_contract(); - service_contrat.base_fee = 100; - service_contrat.variable_fee = 10; - service_contrat.metadata = BoundedVec::try_from(b"some_metadata".to_vec()).unwrap(); - service_contrat.state = types::ServiceContractState::AgreementReady; + let mut service_contract = get_service_contract(); + service_contract.base_fee = 100; + service_contract.variable_fee = 10; + service_contract.metadata = BoundedVec::try_from(b"some_metadata".to_vec()).unwrap(); + service_contract.state = types::ServiceContractState::AgreementReady; assert_eq!( contract.contract_type, - types::ContractData::ServiceContract(service_contrat.clone()), + types::ContractData::ServiceContract(service_contract.clone()), ); // Service approves @@ -1574,10 +1574,10 @@ fn test_service_contract_approve_works() { )); let contract = SmartContractModule::contracts(1).unwrap(); - service_contrat.accepted_by_service = true; + service_contract.accepted_by_service = true; assert_eq!( contract.contract_type, - types::ContractData::ServiceContract(service_contrat.clone()), + types::ContractData::ServiceContract(service_contract.clone()), ); // Consumer approves @@ -1587,12 +1587,12 @@ fn test_service_contract_approve_works() { )); let contract = SmartContractModule::contracts(1).unwrap(); - service_contrat.accepted_by_consumer = true; - service_contrat.last_bill = 1628082006; - service_contrat.state = types::ServiceContractState::ApprovedByBoth; + service_contract.accepted_by_consumer = true; + service_contract.last_bill = 1628082006; + service_contract.state = types::ServiceContractState::ApprovedByBoth; assert_eq!( contract.contract_type, - types::ContractData::ServiceContract(service_contrat), + types::ContractData::ServiceContract(service_contract), ); let our_events = System::events(); From 1e46023ae1a64cbaebe35faaed8ea57e3b6f83d3 Mon Sep 17 00:00:00 2001 From: renauter Date: Mon, 7 Nov 2022 23:47:48 -0300 Subject: [PATCH 73/87] feat service contract billing extrinsic --- .../pallets/pallet-smart-contract/src/lib.rs | 85 ++++++++++++------- .../pallet-smart-contract/src/types.rs | 4 +- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index c99b521b0..dc885237c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -442,10 +442,11 @@ pub mod pallet { TwinNotAuthorizedToApproveServiceContract, TwinNotAuthorizedToRejectServiceContract, TwinNotAuthorizedToBillServiceContract, - NoServiceContractModificationAllowed, - NoServiceContractApprovalAllowed, - NoServiceContractBillingAllowed, - MetadataTooLong, + ServiceContractModificationNotAllowed, + ServiceContractApprovalNotAllowed, + ServiceContractBillingNotAllowed, + ServiceContractBillMetadataTooLong, + ServiceContractMetadataTooLong, } #[pallet::genesis_config] @@ -716,9 +717,11 @@ pub mod pallet { pub fn service_contract_bill( origin: OriginFor, contract_id: u64, + variable_amount: u64, + metadata: Vec, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_service_contract_bill(account_id, contract_id) + Self::_service_contract_bill(account_id, contract_id, variable_amount, metadata) } } @@ -1215,15 +1218,15 @@ impl Pallet { ); let service_contract = types::ServiceContract { - service_twin_id, // OK - consumer_twin_id, // OK - base_fee: 0, // OK - variable_fee: 0, // OK - metadata: vec![].try_into().unwrap(), // OK - accepted_by_service: false, // OK - accepted_by_consumer: false, // OK - last_bill: 0, // OK - state: types::ServiceContractState::Created, // OK + service_twin_id, + consumer_twin_id, + base_fee: 0, + variable_fee: 0, + metadata: vec![].try_into().unwrap(), + accepted_by_service: false, + accepted_by_consumer: false, + last_bill: 0, + state: types::ServiceContractState::Created, }; let contract = Self::_create_contract( @@ -1262,12 +1265,12 @@ impl Pallet { service_contract.state, types::ServiceContractState::ApprovedByBoth ), - Error::::NoServiceContractModificationAllowed, + Error::::ServiceContractModificationNotAllowed, ); // TODO create metadata input type to control size - service_contract.metadata = - BoundedVec::try_from(metadata).map_err(|_| Error::::MetadataTooLong)?; + service_contract.metadata = BoundedVec::try_from(metadata) + .map_err(|_| Error::::ServiceContractMetadataTooLong)?; // If base_fee is set and non-zero (mandatory) if service_contract.base_fee != 0 { @@ -1306,7 +1309,7 @@ impl Pallet { service_contract.state, types::ServiceContractState::ApprovedByBoth ), - Error::::NoServiceContractModificationAllowed, + Error::::ServiceContractModificationNotAllowed, ); service_contract.base_fee = base_fee; @@ -1341,7 +1344,7 @@ impl Pallet { service_contract.state, types::ServiceContractState::AgreementReady ), - Error::::NoServiceContractApprovalAllowed, + Error::::ServiceContractApprovalNotAllowed, ); // Only service or consumer can accept agreement @@ -1387,7 +1390,7 @@ impl Pallet { service_contract.state, types::ServiceContractState::AgreementReady ), - Error::::NoServiceContractApprovalAllowed, + Error::::ServiceContractApprovalNotAllowed, ); // Only service or consumer can reject agreement @@ -1409,6 +1412,8 @@ impl Pallet { pub fn _service_contract_bill( account_id: T::AccountId, contract_id: u64, + variable_amount: u64, + metadata: Vec, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; @@ -1429,26 +1434,46 @@ impl Pallet { service_contract.state, types::ServiceContractState::ApprovedByBoth ), - Error::::NoServiceContractBillingAllowed, + Error::::ServiceContractBillingNotAllowed, ); - // Get amount of time to bill for + // Get elapsed time (in seconds) to bill for service let now = >::get().saturated_into::() / 1000; let window = now - service_contract.last_bill; + // Update contract in list after modification + // Do it here to allow future billing if this one fails + // because max window size is reached + // TOCHECK: specs not clear on this point + service_contract.last_bill = now; + contract.contract_type = types::ContractData::ServiceContract(service_contract.clone()); + Contracts::::insert(contract_id, contract.clone()); + + // Billing window max size is 1 hour + ensure!(window <= 3600, Error::::ServiceContractBillingNotAllowed,); + + // Billing variable amount is bounded by contract variable fee + ensure!( + variable_amount + <= ((U64F64::from_num(window) / 3600) + * U64F64::from_num(service_contract.variable_fee)) + .round() + .to_num::(), + Error::::ServiceContractBillingNotAllowed, + ); + + // TODO: create metadata input type to control size + let bill_metadata = BoundedVec::try_from(metadata) + .map_err(|_| Error::::ServiceContractBillMetadataTooLong)?; + // Create service contract bill and insert to list let service_contract_bill = types::ServiceContractBill { - amount: 0, // TODO - window, // OK - metadata: vec![].try_into().unwrap(), // TODO + variable_amount, + window, + metadata: bill_metadata, }; ServiceContractBillByID::::insert(contract.contract_id, service_contract_bill); - // Update contract in list after modification - service_contract.last_bill = now; - contract.contract_type = types::ContractData::ServiceContract(service_contract); - Contracts::::insert(contract_id, contract); - Ok(().into()) } diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index a99220d71..bc587724c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -133,8 +133,8 @@ pub struct ServiceContract { PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen, )] pub struct ServiceContractBill { - pub amount: u64, - pub window: u64, // amount of time (in seconds) covered since last bill + pub variable_amount: u64, // variable amount which is billed + pub window: u64, // amount of time (in seconds) covered since last bill pub metadata: BoundedVec>, // limited to 50 bytes for now } From a9af8f7370e30b0630f9d5f63a96a59ed6b81fd8 Mon Sep 17 00:00:00 2001 From: renauter Date: Tue, 8 Nov 2022 00:12:10 -0300 Subject: [PATCH 74/87] test: fix after changing base branch --- .../pallet-smart-contract/src/tests.rs | 30 +++++-------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index a89b048ec..6389afc9e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -327,7 +327,7 @@ fn test_create_deployment_contract_with_public_ips_works() { let farm = TfgridModule::farms(1).unwrap(); assert_eq!(farm.public_ips[0].contract_id, 1); - assert_eq!(c.public_ips, 2); + assert_eq!(c.public_ips, 1); let pub_ip = PublicIP { ip: "185.206.122.33/24".as_bytes().to_vec().try_into().unwrap(), @@ -335,13 +335,7 @@ fn test_create_deployment_contract_with_public_ips_works() { contract_id: 1, }; - let pub_ip_2 = PublicIP { - ip: "185.206.122.34/24".as_bytes().to_vec().try_into().unwrap(), - gateway: "185.206.122.1".as_bytes().to_vec().try_into().unwrap(), - contract_id: 1, - }; assert_eq!(c.public_ips_list[0], pub_ip); - assert_eq!(c.public_ips_list[1], pub_ip_2); } _ => (), } @@ -2818,26 +2812,20 @@ fn test_deployment_contract_grace_period_cancels_contract_when_grace_period_ends ); // grace period stops after 100 blocknumbers, so after 121 - for i in 1..11 { + for i in 1..10 { pool_state .write() .should_call_bill_contract(1, Ok(Pays::Yes.into()), 21 + i * 10); } - for i in 1..11 { + for i in 1..10 { run_to_block(21 + i * 10, Some(&mut pool_state)); } - // pool_state - // .write() - // .should_call_bill_contract(1, Ok(Pays::Yes.into()), 131); - // run_to_block(131, Some(&mut pool_state)); - - // The user's total free balance should be distributed - let free_balance = Balances::free_balance(&twin.account_id); - let total_amount_billed = initial_twin_balance - free_balance; - - validate_distribution_rewards(initial_total_issuance, total_amount_billed, false); + pool_state + .write() + .should_call_bill_contract(1, Ok(Pays::Yes.into()), 121); + run_to_block(121, Some(&mut pool_state)); let c1 = SmartContractModule::contracts(1); assert_eq!(c1, None); @@ -3862,10 +3850,6 @@ pub fn prepare_farm(source: AccountId, dedicated: bool) { ip: "185.206.122.33/24".as_bytes().to_vec().try_into().unwrap(), gw: "185.206.122.1".as_bytes().to_vec().try_into().unwrap(), }); - pub_ips.push(pallet_tfgrid_types::PublicIpInput { - ip: "185.206.122.34/24".as_bytes().to_vec().try_into().unwrap(), - gw: "185.206.122.1".as_bytes().to_vec().try_into().unwrap(), - }); let su_policy = pallet_tfgrid_types::Policy { value: 194400, From 98dbab4c36bf15d828e59247a7d7e2fd6f24d71e Mon Sep 17 00:00:00 2001 From: renauter Date: Tue, 8 Nov 2022 13:18:25 -0300 Subject: [PATCH 75/87] feat: update cost of service contract regarding bill --- .../pallets/pallet-smart-contract/src/cost.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index 05f236d54..add873ded 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -2,7 +2,7 @@ use crate::pallet; use crate::pallet::BalanceOf; use crate::pallet::Error; use crate::types; -use crate::types::{Contract, ContractBillingInformation}; +use crate::types::{Contract, ContractBillingInformation, ServiceContractBill}; use crate::Config; use frame_support::dispatch::DispatchErrorWithPostInfo; use pallet_tfgrid::types as pallet_tfgrid_types; @@ -16,6 +16,10 @@ impl Contract { pallet::ContractBillingInformationByID::::get(self.contract_id) } + pub fn get_service_contract_bill(&self) -> ServiceContractBill { + pallet::ServiceContractBillByID::::get(self.contract_id) + } + pub fn calculate_contract_cost_tft( &self, balance: BalanceOf, @@ -29,6 +33,7 @@ impl Contract { // - NodeContract // - RentContract // - NameContract + // - ServiceContract let total_cost = self.calculate_contract_cost(&pricing_policy, seconds_elapsed)?; // If cost is 0, reinsert to be billed at next interval if total_cost == 0 { @@ -88,11 +93,11 @@ impl Contract { } types::ContractData::ServiceContract(service_contract) => { // bill user for service usage for number of seconds elapsed - let bill_window = 0; // TODO - let contract_cost = (U64F64::from_num(service_contract.base_fee * bill_window) - / 3600) - + U64F64::from_num(service_contract.variable_fee); - contract_cost.to_num::() + let service_contract_bill = self.get_service_contract_bill(); + let contract_cost = U64F64::from_num(service_contract.base_fee) + * U64F64::from_num(service_contract_bill.window / 3600) + + U64F64::from_num(service_contract_bill.variable_amount); + contract_cost.round().to_num::() } }; From d9d2ca266e99747cde6306b04333d5eec6ab3467 Mon Sep 17 00:00:00 2001 From: renauter Date: Tue, 8 Nov 2022 15:44:01 -0300 Subject: [PATCH 76/87] feat: update time strategy for billing --- .../pallets/pallet-smart-contract/src/lib.rs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index dc885237c..62523a10f 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1439,18 +1439,12 @@ impl Pallet { // Get elapsed time (in seconds) to bill for service let now = >::get().saturated_into::() / 1000; - let window = now - service_contract.last_bill; + let elapsed_time = now - service_contract.last_bill; - // Update contract in list after modification - // Do it here to allow future billing if this one fails - // because max window size is reached - // TOCHECK: specs not clear on this point - service_contract.last_bill = now; - contract.contract_type = types::ContractData::ServiceContract(service_contract.clone()); - Contracts::::insert(contract_id, contract.clone()); - - // Billing window max size is 1 hour - ensure!(window <= 3600, Error::::ServiceContractBillingNotAllowed,); + // Billing time (window) is max 1h by design + // So extra time will not be billed + // It is service responsability to bill on rigth frequency + let window = elapsed_time.min(3600); // Billing variable amount is bounded by contract variable fee ensure!( @@ -1474,6 +1468,11 @@ impl Pallet { }; ServiceContractBillByID::::insert(contract.contract_id, service_contract_bill); + // Update contract in list after modification + service_contract.last_bill = now; + contract.contract_type = types::ContractData::ServiceContract(service_contract); + Contracts::::insert(contract_id, contract); + Ok(().into()) } From bbd3ce8ddfa6c236118619a35d22c8cd48ac41b9 Mon Sep 17 00:00:00 2001 From: renauter Date: Wed, 16 Nov 2022 01:55:39 -0300 Subject: [PATCH 77/87] feat: wip rework service billing --- .../pallets/pallet-smart-contract/src/cost.rs | 41 ++++++-- .../pallets/pallet-smart-contract/src/lib.rs | 94 ++++++++++++++++--- .../pallet-smart-contract/src/tests.rs | 14 ++- .../pallet-smart-contract/src/types.rs | 17 +--- 4 files changed, 131 insertions(+), 35 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index add873ded..9c44470aa 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -2,7 +2,7 @@ use crate::pallet; use crate::pallet::BalanceOf; use crate::pallet::Error; use crate::types; -use crate::types::{Contract, ContractBillingInformation, ServiceContractBill}; +use crate::types::{Contract, ContractBillingInformation, ServiceContract, ServiceContractBill}; use crate::Config; use frame_support::dispatch::DispatchErrorWithPostInfo; use pallet_tfgrid::types as pallet_tfgrid_types; @@ -33,7 +33,6 @@ impl Contract { // - NodeContract // - RentContract // - NameContract - // - ServiceContract let total_cost = self.calculate_contract_cost(&pricing_policy, seconds_elapsed)?; // If cost is 0, reinsert to be billed at next interval if total_cost == 0 { @@ -92,12 +91,8 @@ impl Contract { total_cost_u64f64.to_num::() } types::ContractData::ServiceContract(service_contract) => { - // bill user for service usage for number of seconds elapsed - let service_contract_bill = self.get_service_contract_bill(); - let contract_cost = U64F64::from_num(service_contract.base_fee) - * U64F64::from_num(service_contract_bill.window / 3600) - + U64F64::from_num(service_contract_bill.variable_amount); - contract_cost.round().to_num::() + let service_bill = self.get_service_contract_bill(); + service_contract.calculate_bill_cost(service_bill) } }; @@ -105,6 +100,36 @@ impl Contract { } } +impl ServiceContract { + pub fn calculate_bill_cost_tft( + &self, + service_bill: ServiceContractBill, + ) -> Result, DispatchErrorWithPostInfo> { + // Calculate the cost in mUSD for service contract bill + let total_cost = self.calculate_bill_cost(service_bill); + + if total_cost == 0 { + return Ok(BalanceOf::::saturated_from(0 as u128)); + } + + // Calculate the cost in TFT for service contract + let total_cost_tft_64 = calculate_cost_in_tft_from_musd::(total_cost)?; + + // convert to balance object + let amount_due: BalanceOf = BalanceOf::::saturated_from(total_cost_tft_64); + + return Ok(amount_due); + } + + pub fn calculate_bill_cost(&self, service_bill: ServiceContractBill) -> u64 { + // bill user for service usage for elpased usage (window) in seconds + let contract_cost = U64F64::from_num(self.base_fee) + * U64F64::from_num(service_bill.window / 3600) + + U64F64::from_num(service_bill.variable_amount); + contract_cost.round().to_num::() + } +} + // Calculates the total cost of a node contract. pub fn calculate_resources_cost( resources: Resources, diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 62523a10f..dfe2e2e06 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] +use core::marker::PhantomData; use frame_support::{ dispatch::DispatchErrorWithPostInfo, ensure, @@ -714,14 +715,14 @@ pub mod pallet { } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn service_contract_bill( + pub fn service_contract_send_bill( origin: OriginFor, contract_id: u64, variable_amount: u64, metadata: Vec, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_service_contract_bill(account_id, contract_id, variable_amount, metadata) + Self::_service_contract_send_bill(account_id, contract_id, variable_amount, metadata) } } @@ -1227,6 +1228,7 @@ impl Pallet { accepted_by_consumer: false, last_bill: 0, state: types::ServiceContractState::Created, + phantom: PhantomData, }; let contract = Self::_create_contract( @@ -1360,10 +1362,14 @@ impl Pallet { // If both parties (service and consumer) accept then contract is approved and can be billed if service_contract.accepted_by_service && service_contract.accepted_by_consumer { + // Change contract state to approved and emit event service_contract.state = types::ServiceContractState::ApprovedByBoth; - let now = >::get().saturated_into::() / 1000; - service_contract.last_bill = now; // Initialize billing period Self::deposit_event(Event::ServiceContractApproved { contract_id }); + // Initialize billing time + let now = >::get().saturated_into::() / 1000; + service_contract.last_bill = now; + // Start billing inserting contract in billing loop + Self::insert_contract_to_bill(contract_id); } // Update contract in list after modification @@ -1409,7 +1415,7 @@ impl Pallet { Ok(().into()) } - pub fn _service_contract_bill( + pub fn _service_contract_send_bill( account_id: T::AccountId, contract_id: u64, variable_amount: u64, @@ -1439,12 +1445,12 @@ impl Pallet { // Get elapsed time (in seconds) to bill for service let now = >::get().saturated_into::() / 1000; - let elapsed_time = now - service_contract.last_bill; + let elapsed_seconds_since_last_bill = now - service_contract.last_bill; // Billing time (window) is max 1h by design // So extra time will not be billed // It is service responsability to bill on rigth frequency - let window = elapsed_time.min(3600); + let window = elapsed_seconds_since_last_bill.min(3600); // Billing variable amount is bounded by contract variable fee ensure!( @@ -1978,10 +1984,21 @@ impl Pallet { return Err(>::OffchainSignedTxError); } - // Bills a contract (DeploymentContract or NameContract) - // Calculates how much TFT is due by the user and distributes the rewards - fn bill_contract(contract_id: u64) -> DispatchResultWithPostInfo { - // Clean up contract from billing loop if it does not exist anymore + fn _handle_bill_contract_by_type(contract_id: u64) -> DispatchResultWithPostInfo { + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + + match contract.contract_type { + types::ContractData::ServiceContract(_) => { + return Self::_service_contract_pay_bill(contract_id); + } + _ => (), + }; + + Ok(().into()) + } + + // Remove contract from billing loop if it does not exist anymore + fn is_contract_billing_active(contract_id: u64) -> bool { if !Contracts::::contains_key(contract_id) { log::debug!("cleaning up deleted contract from storage"); @@ -1992,6 +2009,59 @@ impl Pallet { contracts.retain(|&c| c != contract_id); ContractsToBillAt::::insert(index, contracts); + return false; + } + true + } + + // Pay a bill for a service contract + // Calculates how much TFT is due by the consumer and pay the amount to the service + fn _service_contract_pay_bill(contract_id: u64) -> DispatchResultWithPostInfo { + if !Self::is_contract_billing_active(contract_id) { + return Ok(().into()); + } + + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + + let service_contract = Self::get_service_contract(&contract)?; + + let service_contract_bill = contract.get_service_contract_bill(); // TODO: handle bill not exists + + let amount_due = service_contract.calculate_bill_cost_tft(service_contract_bill)?; + + // If there is nothing to be paid and the contract is not in state delete, return + // Can be that the users cancels the contract in the same block that it's getting billed + // where elapsed seconds would be 0, but we still have to distribute rewards + if amount_due == BalanceOf::::saturated_from(0 as u128) && !contract.is_state_delete() { + log::debug!("amount to be billed is 0, nothing to do"); + return Ok(().into()); + }; + + let _service_twin = pallet_tfgrid::Twins::::get(service_contract.service_twin_id) + .ok_or(Error::::TwinNotExists)?; + + let consumer_twin = pallet_tfgrid::Twins::::get(service_contract.consumer_twin_id) + .ok_or(Error::::TwinNotExists)?; + + let _usable_balance = Self::get_usable_balance(&consumer_twin.account_id); + + // If the contract is in delete state, remove all associated storage + if matches!(contract.state, types::ContractState::Deleted(_)) { + Self::remove_contract(contract.contract_id); + } + + log::info!( + "bill successfully payed by consumer for service contract with id {:?}", + contract_id, + ); + + Ok(().into()) + } + + // Bills a contract (DeploymentContract or NameContract) + // Calculates how much TFT is due by the user and distributes the rewards + fn bill_contract(contract_id: u64) -> DispatchResultWithPostInfo { + if !Self::is_contract_billing_active(contract_id) { return Ok(().into()); } @@ -2713,7 +2783,7 @@ impl Pallet { pub fn get_service_contract( contract: &types::Contract, - ) -> Result { + ) -> Result, DispatchErrorWithPostInfo> { match contract.contract_type.clone() { types::ContractData::ServiceContract(c) => Ok(c), _ => { diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 6389afc9e..9b2937eeb 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1,3 +1,5 @@ +use core::marker::PhantomData; + use super::{types, Event as SmartContractEvent}; use crate::{mock::Event as MockEvent, mock::*, test_utils::*, Error}; @@ -1628,6 +1630,13 @@ fn test_service_contract_reject_works() { }); } +#[test] +fn test_service_contract_send_bill_works() { + new_test_ext().execute_with(|| { + // TODO + }); +} + // CAPACITY CONTRACT RESERVING ALL RESOURCES OF NODE TESTS // // -------------------------------------------- // @@ -4516,8 +4525,8 @@ fn prepare_service_consumer_contract() { )); } -fn get_service_contract() -> types::ServiceContract { - types::ServiceContract { +fn get_service_contract() -> types::ServiceContract { + types::ServiceContract:: { service_twin_id: 1, //Alice consumer_twin_id: 2, //Bob base_fee: 0, @@ -4527,5 +4536,6 @@ fn get_service_contract() -> types::ServiceContract { accepted_by_consumer: false, last_bill: 0, state: types::ServiceContractState::Created, + phantom: PhantomData, } } diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index bc587724c..999e2b595 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -3,6 +3,7 @@ use crate::pallet::{ }; use crate::Config; use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; use frame_support::{pallet_prelude::ConstU32, BoundedVec, RuntimeDebugNoBound}; use scale_info::TypeInfo; use sp_std::prelude::*; @@ -106,7 +107,7 @@ pub const MAX_METADATA_LENGTH: u32 = 64; #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] -pub struct ServiceContract { +pub struct ServiceContract { pub service_twin_id: u32, pub consumer_twin_id: u32, pub base_fee: u64, @@ -116,19 +117,9 @@ pub struct ServiceContract { pub accepted_by_consumer: bool, pub last_bill: u64, pub state: ServiceContractState, + pub phantom: PhantomData, } -// impl ServiceContract { -// pub fn get_state(&self) -> ServiceContractState { -// if self.accepted_by_service && self.accepted_by_consumer { -// return ServiceContractState::ApprovedByBoth; -// } else if self.base_fee.is_some() && self.metadata.is_some() { -// return ServiceContractState::AgreementReady; -// } -// ServiceContractState::Created -// } -// } - #[derive( PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, Debug, TypeInfo, MaxEncodedLen, )] @@ -169,7 +160,7 @@ pub enum ContractData { CapacityReservationContract(CapacityReservationContract), DeploymentContract(DeploymentContract), NameContract(NameContract), - ServiceContract(ServiceContract), + ServiceContract(ServiceContract), } // impl Default for ContractData { From 2b0088ebf555702f7a1da0f06789b393b0ce2d49 Mon Sep 17 00:00:00 2001 From: renauter Date: Wed, 16 Nov 2022 14:56:41 -0300 Subject: [PATCH 78/87] feat: simplifying service contract billing --- .../pallets/pallet-smart-contract/src/cost.rs | 10 +- .../pallets/pallet-smart-contract/src/lib.rs | 111 +++++++----------- 2 files changed, 47 insertions(+), 74 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index 9c44470aa..cef8ce22a 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -16,10 +16,6 @@ impl Contract { pallet::ContractBillingInformationByID::::get(self.contract_id) } - pub fn get_service_contract_bill(&self) -> ServiceContractBill { - pallet::ServiceContractBillByID::::get(self.contract_id) - } - pub fn calculate_contract_cost_tft( &self, balance: BalanceOf, @@ -90,9 +86,9 @@ impl Contract { * U64F64::from_num(seconds_elapsed); total_cost_u64f64.to_num::() } - types::ContractData::ServiceContract(service_contract) => { - let service_bill = self.get_service_contract_bill(); - service_contract.calculate_bill_cost(service_bill) + types::ContractData::ServiceContract(_) => { + // cost is not calculated here for service contracts + 0 } }; diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index dfe2e2e06..b7533cd4d 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -140,11 +140,6 @@ pub mod pallet { pub type ContractBillingInformationByID = StorageMap<_, Blake2_128Concat, u64, ContractBillingInformation, ValueQuery>; - #[pallet::storage] - #[pallet::getter(fn service_contract_bill_by_id)] - pub type ServiceContractBillByID = - StorageMap<_, Blake2_128Concat, u64, ServiceContractBill, ValueQuery>; - #[pallet::storage] #[pallet::getter(fn node_contract_resources)] pub type NodeContractResources = @@ -715,14 +710,14 @@ pub mod pallet { } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn service_contract_send_bill( + pub fn service_contract_bill( origin: OriginFor, contract_id: u64, variable_amount: u64, metadata: Vec, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_service_contract_send_bill(account_id, contract_id, variable_amount, metadata) + Self::_service_contract_bill(account_id, contract_id, variable_amount, metadata) } } @@ -1415,7 +1410,7 @@ impl Pallet { Ok(().into()) } - pub fn _service_contract_send_bill( + pub fn _service_contract_bill( account_id: T::AccountId, contract_id: u64, variable_amount: u64, @@ -1466,13 +1461,15 @@ impl Pallet { let bill_metadata = BoundedVec::try_from(metadata) .map_err(|_| Error::::ServiceContractBillMetadataTooLong)?; - // Create service contract bill and insert to list + // Create service contract bill let service_contract_bill = types::ServiceContractBill { variable_amount, window, metadata: bill_metadata, }; - ServiceContractBillByID::::insert(contract.contract_id, service_contract_bill); + + // Make consumer pay for service contract bill + Self::_service_contract_pay_bill(contract_id, service_contract_bill)?; // Update contract in list after modification service_contract.last_bill = now; @@ -1482,6 +1479,43 @@ impl Pallet { Ok(().into()) } + // Bill for a service contract + // Calculates how much TFT is due by the consumer and pay the amount to the service + fn _service_contract_pay_bill( + contract_id: u64, + bill: types::ServiceContractBill, + ) -> DispatchResultWithPostInfo { + let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + + let service_contract = Self::get_service_contract(&contract)?; + + let amount_due = service_contract.calculate_bill_cost_tft(bill)?; + + let service_twin = pallet_tfgrid::Twins::::get(service_contract.service_twin_id) + .ok_or(Error::::TwinNotExists)?; + + let consumer_twin = pallet_tfgrid::Twins::::get(service_contract.consumer_twin_id) + .ok_or(Error::::TwinNotExists)?; + + let usable_balance = Self::get_usable_balance(&consumer_twin.account_id); + + // TODO: Handle consumer out of founds + + ::Currency::transfer( + &consumer_twin.account_id, + &service_twin.account_id, + amount_due.min(usable_balance), + ExistenceRequirement::KeepAlive, + )?; + + log::info!( + "bill successfully payed by consumer for service contract with id {:?}", + contract_id, + ); + + Ok(().into()) + } + fn _change_power_target_node( node_id: u32, power_target: PowerTarget, @@ -1984,19 +2018,6 @@ impl Pallet { return Err(>::OffchainSignedTxError); } - fn _handle_bill_contract_by_type(contract_id: u64) -> DispatchResultWithPostInfo { - let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - - match contract.contract_type { - types::ContractData::ServiceContract(_) => { - return Self::_service_contract_pay_bill(contract_id); - } - _ => (), - }; - - Ok(().into()) - } - // Remove contract from billing loop if it does not exist anymore fn is_contract_billing_active(contract_id: u64) -> bool { if !Contracts::::contains_key(contract_id) { @@ -2014,50 +2035,6 @@ impl Pallet { true } - // Pay a bill for a service contract - // Calculates how much TFT is due by the consumer and pay the amount to the service - fn _service_contract_pay_bill(contract_id: u64) -> DispatchResultWithPostInfo { - if !Self::is_contract_billing_active(contract_id) { - return Ok(().into()); - } - - let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - - let service_contract = Self::get_service_contract(&contract)?; - - let service_contract_bill = contract.get_service_contract_bill(); // TODO: handle bill not exists - - let amount_due = service_contract.calculate_bill_cost_tft(service_contract_bill)?; - - // If there is nothing to be paid and the contract is not in state delete, return - // Can be that the users cancels the contract in the same block that it's getting billed - // where elapsed seconds would be 0, but we still have to distribute rewards - if amount_due == BalanceOf::::saturated_from(0 as u128) && !contract.is_state_delete() { - log::debug!("amount to be billed is 0, nothing to do"); - return Ok(().into()); - }; - - let _service_twin = pallet_tfgrid::Twins::::get(service_contract.service_twin_id) - .ok_or(Error::::TwinNotExists)?; - - let consumer_twin = pallet_tfgrid::Twins::::get(service_contract.consumer_twin_id) - .ok_or(Error::::TwinNotExists)?; - - let _usable_balance = Self::get_usable_balance(&consumer_twin.account_id); - - // If the contract is in delete state, remove all associated storage - if matches!(contract.state, types::ContractState::Deleted(_)) { - Self::remove_contract(contract.contract_id); - } - - log::info!( - "bill successfully payed by consumer for service contract with id {:?}", - contract_id, - ); - - Ok(().into()) - } - // Bills a contract (DeploymentContract or NameContract) // Calculates how much TFT is due by the user and distributes the rewards fn bill_contract(contract_id: u64) -> DispatchResultWithPostInfo { From 48e870ef124c240bf616a0008b4bf46260325e38 Mon Sep 17 00:00:00 2001 From: renauter Date: Wed, 16 Nov 2022 19:30:03 -0300 Subject: [PATCH 79/87] feat: simplifying service contract billing (2) --- .../pallets/pallet-smart-contract/src/cost.rs | 4 - .../pallets/pallet-smart-contract/src/lib.rs | 181 ++++++++++-------- .../pallet-smart-contract/src/tests.rs | 47 ++--- .../pallet-smart-contract/src/types.rs | 2 - 4 files changed, 121 insertions(+), 113 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index cef8ce22a..26c657933 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -86,10 +86,6 @@ impl Contract { * U64F64::from_num(seconds_elapsed); total_cost_u64f64.to_num::() } - types::ContractData::ServiceContract(_) => { - // cost is not calculated here for service contracts - 0 - } }; Ok(total_cost) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index b7533cd4d..3a4b08dce 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -135,6 +135,11 @@ pub mod pallet { #[pallet::getter(fn contracts)] pub type Contracts = StorageMap<_, Blake2_128Concat, u64, Contract, OptionQuery>; + #[pallet::storage] + #[pallet::getter(fn service_contracts)] + pub type ServiceContracts = + StorageMap<_, Blake2_128Concat, u64, ServiceContract, OptionQuery>; + #[pallet::storage] #[pallet::getter(fn contract_billing_information_by_id)] pub type ContractBillingInformationByID = @@ -213,6 +218,10 @@ pub mod pallet { #[pallet::getter(fn contract_id)] pub type ContractID = StorageValue<_, u64, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn service_contract_id)] + pub type ServiceContractID = StorageValue<_, u64, ValueQuery>; + #[pallet::storage] #[pallet::getter(fn group_id)] pub type GroupID = StorageValue<_, u32, ValueQuery>; @@ -309,13 +318,17 @@ pub mod pallet { NameContractCanceled { contract_id: u64, }, + /// A Service contract is created + ServiceContractCreated { + service_contract_id: u64, + }, /// A Service contract is approved ServiceContractApproved { - contract_id: u64, + service_contract_id: u64, }, /// A Service contract is canceled ServiceContractCanceled { - contract_id: u64, + service_contract_id: u64, }, /// IP got reserved by a Node contract IPsReserved { @@ -438,11 +451,13 @@ pub mod pallet { TwinNotAuthorizedToApproveServiceContract, TwinNotAuthorizedToRejectServiceContract, TwinNotAuthorizedToBillServiceContract, + ServiceContractNotExists, ServiceContractModificationNotAllowed, ServiceContractApprovalNotAllowed, ServiceContractBillingNotAllowed, ServiceContractBillMetadataTooLong, ServiceContractMetadataTooLong, + ServiceContractNotEnoughFundToPayBill, } #[pallet::genesis_config] @@ -673,51 +688,65 @@ pub mod pallet { #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn service_contract_set_metadata( origin: OriginFor, - contract_id: u64, + service_contract_id: u64, metadata: Vec, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_service_contract_set_metadata(account_id, contract_id, metadata) + Self::_service_contract_set_metadata(account_id, service_contract_id, metadata) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn service_contract_set_fees( origin: OriginFor, - contract_id: u64, + service_contract_id: u64, base_fee: u64, variable_fee: u64, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_service_contract_set_fees(account_id, contract_id, base_fee, variable_fee) + Self::_service_contract_set_fees( + account_id, + service_contract_id, + base_fee, + variable_fee, + ) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn service_contract_approve( origin: OriginFor, - contract_id: u64, + service_contract_id: u64, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_service_contract_approve(account_id, contract_id) + Self::_service_contract_approve(account_id, service_contract_id) } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn service_contract_reject( origin: OriginFor, - contract_id: u64, + service_contract_id: u64, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_service_contract_reject(account_id, contract_id) + Self::_service_contract_reject(account_id, service_contract_id) } + // #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + // pub fn service_contract_cancel( + // origin: OriginFor, + // service_contract_id: u64, + // ) -> DispatchResultWithPostInfo { + // let account_id = ensure_signed(origin)?; + // Self::_service_contract_cancel(account_id, service_contract_id) + // } + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn service_contract_bill( origin: OriginFor, - contract_id: u64, + service_contract_id: u64, variable_amount: u64, metadata: Vec, ) -> DispatchResultWithPostInfo { let account_id = ensure_signed(origin)?; - Self::_service_contract_bill(account_id, contract_id, variable_amount, metadata) + Self::_service_contract_bill(account_id, service_contract_id, variable_amount, metadata) } } @@ -1213,6 +1242,7 @@ impl Pallet { Error::::TwinNotAuthorizedToCreateServiceContract, ); + // Create service contract let service_contract = types::ServiceContract { service_twin_id, consumer_twin_id, @@ -1226,28 +1256,34 @@ impl Pallet { phantom: PhantomData, }; - let contract = Self::_create_contract( - caller_twin_id, - types::ContractData::ServiceContract(service_contract), - None, - )?; + // Get the service contract ID map and increment + let mut id = ServiceContractID::::get(); + id = id + 1; - Self::deposit_event(Event::ContractCreated(contract)); + // Insert into service contract map + ServiceContracts::::insert(id, &service_contract); + + // Update Contract ID + ServiceContractID::::put(id); + + // Trigger event for service contract creation + Self::deposit_event(Event::ServiceContractCreated { + service_contract_id: id, + }); Ok(().into()) } pub fn _service_contract_set_metadata( account_id: T::AccountId, - contract_id: u64, + service_contract_id: u64, metadata: Vec, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - - let mut service_contract = Self::get_service_contract(&contract)?; + let mut service_contract = ServiceContracts::::get(service_contract_id) + .ok_or(Error::::ServiceContractNotExists)?; // Only service or consumer can set metadata ensure!( @@ -1274,25 +1310,23 @@ impl Pallet { service_contract.state = types::ServiceContractState::AgreementReady; } - // Update contract in list after modification - contract.contract_type = types::ContractData::ServiceContract(service_contract); - Contracts::::insert(contract_id, contract); + // Update service contract in map after modification + ServiceContracts::::insert(service_contract_id, service_contract); Ok(().into()) } pub fn _service_contract_set_fees( account_id: T::AccountId, - contract_id: u64, + service_contract_id: u64, base_fee: u64, variable_fee: u64, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - - let mut service_contract = Self::get_service_contract(&contract)?; + let mut service_contract = ServiceContracts::::get(service_contract_id) + .ok_or(Error::::ServiceContractNotExists)?; // Only service can set fees ensure!( @@ -1317,23 +1351,21 @@ impl Pallet { service_contract.state = types::ServiceContractState::AgreementReady; } - // Update contract in list after modification - contract.contract_type = types::ContractData::ServiceContract(service_contract); - Contracts::::insert(contract_id, contract); + // Update service contract in map after modification + ServiceContracts::::insert(service_contract_id, service_contract); Ok(().into()) } pub fn _service_contract_approve( account_id: T::AccountId, - contract_id: u64, + service_contract_id: u64, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - - let mut service_contract = Self::get_service_contract(&contract)?; + let mut service_contract = ServiceContracts::::get(service_contract_id) + .ok_or(Error::::ServiceContractNotExists)?; // Allow to approve contract only if agreement is ready ensure!( @@ -1359,31 +1391,29 @@ impl Pallet { if service_contract.accepted_by_service && service_contract.accepted_by_consumer { // Change contract state to approved and emit event service_contract.state = types::ServiceContractState::ApprovedByBoth; - Self::deposit_event(Event::ServiceContractApproved { contract_id }); + Self::deposit_event(Event::ServiceContractApproved { + service_contract_id, + }); // Initialize billing time let now = >::get().saturated_into::() / 1000; service_contract.last_bill = now; - // Start billing inserting contract in billing loop - Self::insert_contract_to_bill(contract_id); } - // Update contract in list after modification - contract.contract_type = types::ContractData::ServiceContract(service_contract); - Contracts::::insert(contract_id, contract); + // Update service contract in map after modification + ServiceContracts::::insert(service_contract_id, service_contract); Ok(().into()) } pub fn _service_contract_reject( account_id: T::AccountId, - contract_id: u64, + service_contract_id: u64, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - - let service_contract = Self::get_service_contract(&contract)?; + let service_contract = ServiceContracts::::get(service_contract_id) + .ok_or(Error::::ServiceContractNotExists)?; // Allow to reject contract only if agreement is ready ensure!( @@ -1404,24 +1434,29 @@ impl Pallet { } // If one party (service or consumer) rejects then contract - // is canceled and removed from contract list - Self::remove_contract(contract_id); + // is canceled and removed from service contract map + // Self::_service_contract_cancel(service_contract_id); + // TODO + // info!("removing service contract"); + ServiceContracts::::remove(service_contract_id); + Self::deposit_event(Event::ServiceContractCanceled { + service_contract_id, + }); Ok(().into()) } pub fn _service_contract_bill( account_id: T::AccountId, - contract_id: u64, + service_contract_id: u64, variable_amount: u64, metadata: Vec, ) -> DispatchResultWithPostInfo { let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) .ok_or(Error::::TwinNotExists)?; - let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - - let mut service_contract = Self::get_service_contract(&contract)?; + let mut service_contract = ServiceContracts::::get(service_contract_id) + .ok_or(Error::::ServiceContractNotExists)?; // Only service can bill consumer for service contract ensure!( @@ -1469,26 +1504,23 @@ impl Pallet { }; // Make consumer pay for service contract bill - Self::_service_contract_pay_bill(contract_id, service_contract_bill)?; + Self::_service_contract_pay_bill(service_contract_id, service_contract_bill)?; // Update contract in list after modification service_contract.last_bill = now; - contract.contract_type = types::ContractData::ServiceContract(service_contract); - Contracts::::insert(contract_id, contract); + ServiceContracts::::insert(service_contract_id, service_contract); Ok(().into()) } - // Bill for a service contract + // Pay a service contract bill // Calculates how much TFT is due by the consumer and pay the amount to the service fn _service_contract_pay_bill( - contract_id: u64, + service_contract_id: u64, bill: types::ServiceContractBill, ) -> DispatchResultWithPostInfo { - let contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; - - let service_contract = Self::get_service_contract(&contract)?; - + let service_contract = ServiceContracts::::get(service_contract_id) + .ok_or(Error::::ServiceContractNotExists)?; let amount_due = service_contract.calculate_bill_cost_tft(bill)?; let service_twin = pallet_tfgrid::Twins::::get(service_contract.service_twin_id) @@ -1499,18 +1531,23 @@ impl Pallet { let usable_balance = Self::get_usable_balance(&consumer_twin.account_id); - // TODO: Handle consumer out of founds + // Handle consumer out of funds + ensure!( + usable_balance >= amount_due, + Error::::ServiceContractNotEnoughFundToPayBill, + ); + // Transfer amount due from consumer account to service account ::Currency::transfer( &consumer_twin.account_id, &service_twin.account_id, - amount_due.min(usable_balance), + amount_due, ExistenceRequirement::KeepAlive, )?; log::info!( "bill successfully payed by consumer for service contract with id {:?}", - contract_id, + service_contract_id, ); Ok(().into()) @@ -2421,9 +2458,6 @@ impl Pallet { twin_id: contract.twin_id, }); } - types::ContractData::ServiceContract(_service_contract) => { - Self::deposit_event(Event::ServiceContractCanceled { contract_id }); - } }; info!("removing contract"); Contracts::::remove(contract_id); @@ -2758,19 +2792,6 @@ impl Pallet { } } - pub fn get_service_contract( - contract: &types::Contract, - ) -> Result, DispatchErrorWithPostInfo> { - match contract.contract_type.clone() { - types::ContractData::ServiceContract(c) => Ok(c), - _ => { - return Err(DispatchErrorWithPostInfo::from( - Error::::InvalidContractType, - )) - } - } - } - fn get_usable_balance(account_id: &T::AccountId) -> BalanceOf { let balance = pallet_balances::pallet::Pallet::::usable_balance(account_id); let b = balance.saturated_into::(); diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 9b2937eeb..0b66d48ba 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1482,22 +1482,20 @@ fn test_service_contract_create_works() { run_to_block(1, None); create_service_consumer_contract(); - let contract = SmartContractModule::contracts(1).unwrap(); - let service_contract = get_service_contract(); assert_eq!( - contract.contract_type, - types::ContractData::ServiceContract(service_contract), + get_service_contract(), + SmartContractModule::service_contracts(1).unwrap(), ); let our_events = System::events(); assert_eq!(!our_events.is_empty(), true); assert_eq!( our_events.last().unwrap(), - &record(MockEvent::SmartContractModule(SmartContractEvent::< - TestRuntime, - >::ContractCreated( - contract - ))), + &record(MockEvent::SmartContractModule( + SmartContractEvent::ServiceContractCreated { + service_contract_id: 1, + } + )), ); }); } @@ -1513,12 +1511,11 @@ fn test_service_contract_set_metadata_works() { b"some_metadata".to_vec(), )); - let contract = SmartContractModule::contracts(1).unwrap(); let mut service_contract = get_service_contract(); service_contract.metadata = BoundedVec::try_from(b"some_metadata".to_vec()).unwrap(); assert_eq!( - contract.contract_type, - types::ContractData::ServiceContract(service_contract), + service_contract, + SmartContractModule::service_contracts(1).unwrap(), ); }); } @@ -1535,13 +1532,12 @@ fn test_service_contract_set_fees_works() { 10, )); - let contract = SmartContractModule::contracts(1).unwrap(); let mut service_contract = get_service_contract(); service_contract.base_fee = 100; service_contract.variable_fee = 10; assert_eq!( - contract.contract_type, - types::ContractData::ServiceContract(service_contract), + service_contract, + SmartContractModule::service_contracts(1).unwrap(), ); }); } @@ -1552,15 +1548,14 @@ fn test_service_contract_approve_works() { run_to_block(1, None); prepare_service_consumer_contract(); - let contract = SmartContractModule::contracts(1).unwrap(); let mut service_contract = get_service_contract(); service_contract.base_fee = 100; service_contract.variable_fee = 10; service_contract.metadata = BoundedVec::try_from(b"some_metadata".to_vec()).unwrap(); service_contract.state = types::ServiceContractState::AgreementReady; assert_eq!( - contract.contract_type, - types::ContractData::ServiceContract(service_contract.clone()), + service_contract, + SmartContractModule::service_contracts(1).unwrap(), ); // Service approves @@ -1569,11 +1564,10 @@ fn test_service_contract_approve_works() { 1, )); - let contract = SmartContractModule::contracts(1).unwrap(); service_contract.accepted_by_service = true; assert_eq!( - contract.contract_type, - types::ContractData::ServiceContract(service_contract.clone()), + service_contract, + SmartContractModule::service_contracts(1).unwrap(), ); // Consumer approves @@ -1582,13 +1576,12 @@ fn test_service_contract_approve_works() { 1, )); - let contract = SmartContractModule::contracts(1).unwrap(); service_contract.accepted_by_consumer = true; service_contract.last_bill = 1628082006; service_contract.state = types::ServiceContractState::ApprovedByBoth; assert_eq!( - contract.contract_type, - types::ContractData::ServiceContract(service_contract), + service_contract, + SmartContractModule::service_contracts(1).unwrap(), ); let our_events = System::events(); @@ -1598,7 +1591,7 @@ fn test_service_contract_approve_works() { &record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ServiceContractApproved { - contract_id: 1, + service_contract_id: 1, })), ); }); @@ -1615,7 +1608,7 @@ fn test_service_contract_reject_works() { 1, )); - assert_eq!(SmartContractModule::contracts(1).is_none(), true); + assert_eq!(SmartContractModule::service_contracts(1).is_none(), true); let our_events = System::events(); assert_eq!(!our_events.is_empty(), true); @@ -1624,7 +1617,7 @@ fn test_service_contract_reject_works() { &record(MockEvent::SmartContractModule(SmartContractEvent::< TestRuntime, >::ServiceContractCanceled { - contract_id: 1, + service_contract_id: 1, })), ); }); diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index 999e2b595..186d9770f 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -64,7 +64,6 @@ impl Contract { ContractData::CapacityReservationContract(c) => c.node_id, ContractData::DeploymentContract(_) => 0, ContractData::NameContract(_) => 0, - ContractData::ServiceContract(_) => 0, } } } @@ -160,7 +159,6 @@ pub enum ContractData { CapacityReservationContract(CapacityReservationContract), DeploymentContract(DeploymentContract), NameContract(NameContract), - ServiceContract(ServiceContract), } // impl Default for ContractData { From 123456368616385117576f10c6e985abbf1f890a Mon Sep 17 00:00:00 2001 From: renauter Date: Wed, 16 Nov 2022 20:48:01 -0300 Subject: [PATCH 80/87] feat: handle service contract cancelation --- .../pallets/pallet-smart-contract/src/lib.rs | 85 +++++++++++++++---- .../pallet-smart-contract/src/tests.rs | 1 + 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 3a4b08dce..7dec1aee2 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -329,6 +329,7 @@ pub mod pallet { /// A Service contract is canceled ServiceContractCanceled { service_contract_id: u64, + cause: types::Cause, }, /// IP got reserved by a Node contract IPsReserved { @@ -450,6 +451,7 @@ pub mod pallet { TwinNotAuthorizedToSetFees, TwinNotAuthorizedToApproveServiceContract, TwinNotAuthorizedToRejectServiceContract, + TwinNotAuthorizedToCancelServiceContract, TwinNotAuthorizedToBillServiceContract, ServiceContractNotExists, ServiceContractModificationNotAllowed, @@ -729,14 +731,18 @@ pub mod pallet { Self::_service_contract_reject(account_id, service_contract_id) } - // #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - // pub fn service_contract_cancel( - // origin: OriginFor, - // service_contract_id: u64, - // ) -> DispatchResultWithPostInfo { - // let account_id = ensure_signed(origin)?; - // Self::_service_contract_cancel(account_id, service_contract_id) - // } + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn service_contract_cancel( + origin: OriginFor, + service_contract_id: u64, + ) -> DispatchResultWithPostInfo { + let account_id = ensure_signed(origin)?; + Self::_service_contract_cancel( + account_id, + service_contract_id, + types::Cause::CanceledByUser, + ) + } #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn service_contract_bill( @@ -1433,16 +1439,48 @@ impl Pallet { )); } - // If one party (service or consumer) rejects then contract - // is canceled and removed from service contract map - // Self::_service_contract_cancel(service_contract_id); - // TODO - // info!("removing service contract"); + // If one party (service or consumer) rejects agreement + // then contract is canceled and removed from service contract map + Self::_service_contract_cancel( + account_id, + service_contract_id, + types::Cause::CanceledByUser, + )?; + + Ok(().into()) + } + + #[transactional] + pub fn _service_contract_cancel( + account_id: T::AccountId, + service_contract_id: u64, + cause: types::Cause, + ) -> DispatchResultWithPostInfo { + let twin_id = pallet_tfgrid::TwinIdByAccountID::::get(&account_id) + .ok_or(Error::::TwinNotExists)?; + + let service_contract = ServiceContracts::::get(service_contract_id) + .ok_or(Error::::ServiceContractNotExists)?; + + // Only service or consumer can cancel contract + ensure!( + twin_id == service_contract.service_twin_id + || twin_id == service_contract.consumer_twin_id, + Error::::TwinNotAuthorizedToCancelServiceContract, + ); + + // Remove contract from service contract map ServiceContracts::::remove(service_contract_id); Self::deposit_event(Event::ServiceContractCanceled { service_contract_id, + cause, }); + log::info!( + "successfully removed service contract with id {:?}", + service_contract_id, + ); + Ok(().into()) } @@ -1532,10 +1570,23 @@ impl Pallet { let usable_balance = Self::get_usable_balance(&consumer_twin.account_id); // Handle consumer out of funds - ensure!( - usable_balance >= amount_due, - Error::::ServiceContractNotEnoughFundToPayBill, - ); + // ensure!( + // usable_balance >= amount_due, + // Error::::ServiceContractNotEnoughFundToPayBill, + // ); + + // If consumer is out of funds then contract is canceled + // by service and removed from service contract map + if usable_balance < amount_due { + Self::_service_contract_cancel( + service_twin.account_id, + service_contract_id, + types::Cause::OutOfFunds, + )?; + return Err(DispatchErrorWithPostInfo::from( + Error::::ServiceContractNotEnoughFundToPayBill, + )); + } // Transfer amount due from consumer account to service account ::Currency::transfer( diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 0b66d48ba..2c4dbec18 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1618,6 +1618,7 @@ fn test_service_contract_reject_works() { TestRuntime, >::ServiceContractCanceled { service_contract_id: 1, + cause: types::Cause::CanceledByUser, })), ); }); From 36a44f42c0ce4f7e6647640b7b6b20a0ad3ae782 Mon Sep 17 00:00:00 2001 From: renauter Date: Thu, 17 Nov 2022 01:43:40 -0300 Subject: [PATCH 81/87] fix: resolve conflicts after merge --- .../pallets/pallet-smart-contract/src/mock.rs | 20 ------------------- .../pallet-smart-contract/src/tests.rs | 7 +++++-- .../pallets/pallet-tfgrid/src/lib.rs | 13 +++++------- .../pallet-tfgrid/src/migrations/v13.rs | 1 + 4 files changed, 11 insertions(+), 30 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index c77ac35c0..5fdd426db 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -54,10 +54,6 @@ use tfchain_support::{ // env_logger::init() at the beginning of the test use env_logger; -// set environment variable RUST_LOG=debug to see all logs when running the tests and call -// env_logger::init() at the beginning of the test -use env_logger; - pub type Signature = MultiSignature; pub type AccountId = <::Signer as IdentifyAccount>::AccountId; @@ -396,22 +392,6 @@ pub(crate) fn get_pub_config_gw6_input(gw6_input: &[u8]) -> GW6Input { BoundedVec::try_from(gw6_input.to_vec()).expect("Invalid gw6 input.") } -pub(crate) fn get_pub_config_ip4(ip4: &[u8]) -> TestIP4 { - IP4::try_from(ip4.to_vec()).expect("Invalid ip4 input") -} - -pub(crate) fn get_pub_config_gw4(gw4: &[u8]) -> TestGW4 { - GW4::try_from(gw4.to_vec()).expect("Invalid gw4 input") -} - -pub(crate) fn get_pub_config_ip6(ip6: &[u8]) -> TestIP6 { - IP6::try_from(ip6.to_vec()).expect("Invalid ip6 input") -} - -pub(crate) fn get_pub_config_gw6(gw6: &[u8]) -> TestGW6 { - GW6::try_from(gw6.to_vec()).expect("Invalid gw6 input") -} - /// Helper function to generate a crypto pair from seed fn get_from_seed(seed: &str) -> ::Public { TPublic::Pair::from_string(&format!("//{}", seed), None) diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 5a9a11b52..d88f52795 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -15,7 +15,10 @@ use pallet_tfgrid::{ }; use sp_core::H256; use sp_runtime::{assert_eq_error_rate, traits::SaturatedConversion, Perbill, Percent}; -use sp_std::convert::{TryFrom, TryInto}; +use sp_std::{ + convert::{TryFrom, TryInto}, + marker::PhantomData, +}; use substrate_fixed::types::U64F64; use tfchain_support::types::{ CapacityReservationPolicy, ConsumableResources, NodeFeatures, PowerState, PowerTarget, @@ -4032,7 +4035,7 @@ pub fn prepare_farm_with_three_nodes() { latitude: get_latitude_input(b"24.323112123"), longitude: get_longitude_input(b"64.233213231"), }; - + TfgridModule::create_node( Origin::signed(charlie()), 1, diff --git a/substrate-node/pallets/pallet-tfgrid/src/lib.rs b/substrate-node/pallets/pallet-tfgrid/src/lib.rs index 7a2b64911..f3b0656a9 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/lib.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/lib.rs @@ -9,8 +9,8 @@ use sp_std::prelude::*; use codec::Encode; use frame_support::dispatch::DispatchErrorWithPostInfo; use frame_support::{ - ensure, pallet_prelude::DispatchResultWithPostInfo, traits::EnsureOrigin, - weights::Pays, BoundedVec, + ensure, pallet_prelude::DispatchResultWithPostInfo, traits::EnsureOrigin, weights::Pays, + BoundedVec, }; use frame_system::{self as system, ensure_signed}; use hex::FromHex; @@ -1104,7 +1104,7 @@ pub mod pallet { }; let created = >::get().saturated_into::() / 1000; - let power_target = if NodesByFarmID::::get(farm_id).is_empty() { + let _power_target = if NodesByFarmID::::get(farm_id).is_empty() { PowerTarget::Up } else { PowerTarget::Down @@ -1898,7 +1898,7 @@ pub mod pallet { farm_twin_id == farm_twin.id, Error::::FarmerNotAuthorized ); - + // Call node deleted T::NodeChanged::node_deleted(&node); @@ -2195,10 +2195,7 @@ impl Pallet { Ok(().into()) } - fn _change_power_target_on_node( - node: &mut TfgridNode, - power_target: PowerTarget, - ) { + fn _change_power_target_on_node(node: &mut TfgridNode, power_target: PowerTarget) { Self::deposit_event(Event::PowerTargetChanged { farm_id: node.farm_id, node_id: node.id, diff --git a/substrate-node/pallets/pallet-tfgrid/src/migrations/v13.rs b/substrate-node/pallets/pallet-tfgrid/src/migrations/v13.rs index 09015d0d1..3a0616476 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/migrations/v13.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/migrations/v13.rs @@ -5,6 +5,7 @@ use crate::{ use frame_support::{pallet_prelude::Weight, traits::Get, traits::OnRuntimeUpgrade}; use log::info; use pallet_timestamp as timestamp; +use sp_runtime::SaturatedConversion; use sp_std::marker::PhantomData; use tfchain_support::resources::Resources; use tfchain_support::types::{ConsumableResources, Node, Power, PowerState, PowerTarget}; From 7771c1dcf33a72a74ebf88b05676b34870751fb3 Mon Sep 17 00:00:00 2001 From: renauter Date: Thu, 17 Nov 2022 11:26:58 -0300 Subject: [PATCH 82/87] fix: resolve conflicts after merge --- .../pallets/pallet-smart-contract/src/lib.rs | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 4e1a6c3b5..56f628424 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1540,39 +1540,6 @@ impl Pallet { Ok(().into()) } - fn _claim_resources_on_node(node_id: u32, resources: Resources) -> DispatchResultWithPostInfo { - let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - - ensure!( - node.resources.can_consume_resources(&resources), - Error::::NotEnoughResourcesOnNode - ); - //update the available resources - node.resources.consume(&resources); - - pallet_tfgrid::Nodes::::insert(node.id, &node); - - T::Tfgrid::node_resources_changed(node.id); - - Ok(().into()) - } - - fn _unclaim_resources_on_node( - node_id: u32, - resources: Resources, - ) -> DispatchResultWithPostInfo { - let mut node = pallet_tfgrid::Nodes::::get(node_id).ok_or(Error::::NodeNotExists)?; - - //update the available resources - node.resources.free(&resources); - - pallet_tfgrid::Nodes::::insert(node.id, &node); - - T::Tfgrid::node_resources_changed(node.id); - - Ok(().into()) - } - fn _change_used_resources_on_capacity_reservation( capacity_reservation_id: u64, to_free: Resources, From 089e5f57c4bbdd280404c6e1b122fb771f5b6ec9 Mon Sep 17 00:00:00 2001 From: renauter Date: Thu, 17 Nov 2022 23:07:50 -0300 Subject: [PATCH 83/87] test: service contract cancelation and billing --- .../pallets/pallet-smart-contract/src/cost.rs | 2 +- .../pallets/pallet-smart-contract/src/lib.rs | 8 +- .../pallet-smart-contract/src/tests.rs | 135 ++++++++++++++++-- 3 files changed, 125 insertions(+), 20 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/cost.rs b/substrate-node/pallets/pallet-smart-contract/src/cost.rs index c97da03fe..116b6edfb 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/cost.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/cost.rs @@ -116,7 +116,7 @@ impl ServiceContract { pub fn calculate_bill_cost(&self, service_bill: ServiceContractBill) -> u64 { // bill user for service usage for elpased usage (window) in seconds let contract_cost = U64F64::from_num(self.base_fee) - * U64F64::from_num(service_bill.window / 3600) + * U64F64::from_num(service_bill.window) / 3600 + U64F64::from_num(service_bill.variable_amount); contract_cost.round().to_num::() } diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 56f628424..1634f4f8c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1406,6 +1406,8 @@ impl Pallet { ); // Remove contract from service contract map + // Can be done at any state of contract + // so no need to handle state validation ServiceContracts::::remove(service_contract_id); Self::deposit_event(Event::ServiceContractCanceled { service_contract_id, @@ -1505,12 +1507,6 @@ impl Pallet { let usable_balance = Self::get_usable_balance(&consumer_twin.account_id); - // Handle consumer out of funds - // ensure!( - // usable_balance >= amount_due, - // Error::::ServiceContractNotEnoughFundToPayBill, - // ); - // If consumer is out of funds then contract is canceled // by service and removed from service contract map if usable_balance < amount_due { diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index f42d7911c..a9b8d9f4a 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -28,6 +28,10 @@ use tfchain_support::types::{FarmCertification, NodeCertification, PublicIP}; const GIGABYTE: u64 = 1024 * 1024 * 1024; +const BASE_FEE: u64 = 1000; +const VARIABLE_FEE: u64 = 1000; +const VARIABLE_AMOUNT: u64 = 100; + // GROUP TESTS // // -------------------- // @@ -1515,13 +1519,13 @@ fn test_service_contract_set_fees_works() { assert_ok!(SmartContractModule::service_contract_set_fees( Origin::signed(alice()), 1, - 100, - 10, + BASE_FEE, + VARIABLE_FEE, )); let mut service_contract = get_service_contract(); - service_contract.base_fee = 100; - service_contract.variable_fee = 10; + service_contract.base_fee = BASE_FEE; + service_contract.variable_fee = VARIABLE_FEE; assert_eq!( service_contract, SmartContractModule::service_contracts(1).unwrap(), @@ -1536,8 +1540,8 @@ fn test_service_contract_approve_works() { prepare_service_consumer_contract(); let mut service_contract = get_service_contract(); - service_contract.base_fee = 100; - service_contract.variable_fee = 10; + service_contract.base_fee = BASE_FEE; + service_contract.variable_fee = VARIABLE_FEE; service_contract.metadata = BoundedVec::try_from(b"some_metadata".to_vec()).unwrap(); service_contract.state = types::ServiceContractState::AgreementReady; assert_eq!( @@ -1564,7 +1568,7 @@ fn test_service_contract_approve_works() { )); service_contract.accepted_by_consumer = true; - service_contract.last_bill = 1628082006; + service_contract.last_bill = get_timestamp_in_seconds(1); service_contract.state = types::ServiceContractState::ApprovedByBoth; assert_eq!( service_contract, @@ -1612,9 +1616,97 @@ fn test_service_contract_reject_works() { } #[test] -fn test_service_contract_send_bill_works() { +fn test_service_contract_cancel_works() { new_test_ext().execute_with(|| { - // TODO + run_to_block(1, None); + create_service_consumer_contract(); + + assert_ok!(SmartContractModule::service_contract_cancel( + Origin::signed(alice()), + 1, + )); + + assert_eq!(SmartContractModule::service_contracts(1).is_none(), true); + + let our_events = System::events(); + assert_eq!(!our_events.is_empty(), true); + assert_eq!( + our_events.last().unwrap(), + &record(MockEvent::SmartContractModule(SmartContractEvent::< + TestRuntime, + >::ServiceContractCanceled { + service_contract_id: 1, + cause: types::Cause::CanceledByUser, + })), + ); + }); +} + +#[test] +fn test_service_contract_bill_works() { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { + run_to_block(1, None); + prepare_service_consumer_contract(); + + let service_contract = SmartContractModule::service_contracts(1).unwrap(); + assert_eq!(service_contract.last_bill, 0); + + approve_service_consumer_contract(); + + let service_contract = SmartContractModule::service_contracts(1).unwrap(); + assert_eq!(service_contract.last_bill, get_timestamp_in_seconds(1)); + + let consumer_twin = TfgridModule::twins(2).unwrap(); + let consumer_balance = Balances::free_balance(&consumer_twin.account_id); + assert_eq!(consumer_balance, 2500000000); + + // Bill 20 min after contract approval (= service start) + run_to_block(201, Some(&mut pool_state)); + assert_ok!(SmartContractModule::service_contract_bill( + Origin::signed(alice()), + 1, + VARIABLE_AMOUNT, + b"bill_metadata".to_vec(), + )); + + let service_contract = SmartContractModule::service_contracts(1).unwrap(); + assert_eq!(service_contract.last_bill, get_timestamp_in_seconds(201)); + + let consumer_balance = Balances::free_balance(&consumer_twin.account_id); + let window = get_timestamp_in_seconds(201) - get_timestamp_in_seconds(1); + let bill = types::ServiceContractBill { + variable_amount: VARIABLE_AMOUNT, + window, + metadata: bounded_vec![], + }; + let billed_amount_1 = service_contract.calculate_bill_cost_tft(bill).unwrap(); + + assert_eq!(2500000000 - consumer_balance, billed_amount_1); + + // Bill a second time, 1h10min after first billing + run_to_block(901, Some(&mut pool_state)); + assert_ok!(SmartContractModule::service_contract_bill( + Origin::signed(alice()), + 1, + VARIABLE_AMOUNT, + b"bill_metadata".to_vec(), + )); + + let service_contract = SmartContractModule::service_contracts(1).unwrap(); + assert_eq!(service_contract.last_bill, get_timestamp_in_seconds(901)); + + let consumer_balance = Balances::free_balance(&consumer_twin.account_id); + let bill = types::ServiceContractBill { + variable_amount: VARIABLE_AMOUNT, + window: 3600, // force a 1h bill here + metadata: bounded_vec![], + }; + let billed_amount_2 = service_contract.calculate_bill_cost_tft(bill).unwrap(); + + // Check that second billing was equivalent to a 1h + // billing even if window is greater than 1h + assert_eq!(2500000000 - consumer_balance - billed_amount_1, billed_amount_2); }); } @@ -3810,7 +3902,7 @@ fn push_nru_report_for_contract(contract_id: u64, block_number: u64) { consumption_reports.push(super::types::NruConsumption { contract_id, nru: 3 * gigabyte, - timestamp: 1628082000 + (6 * block_number), + timestamp: get_timestamp_in_seconds(block_number), window: 6 * block_number, }); @@ -3830,7 +3922,7 @@ fn check_report_cost( let contract_bill_event = types::ContractBill { contract_id, - timestamp: 1628082000 + (6 * block_number), + timestamp: get_timestamp_in_seconds(block_number), discount_level, amount_billed: amount_billed as u128, }; @@ -4511,8 +4603,21 @@ fn prepare_service_consumer_contract() { assert_ok!(SmartContractModule::service_contract_set_fees( Origin::signed(alice()), 1, - 100, - 10, + BASE_FEE, + VARIABLE_FEE, + )); +} + +fn approve_service_consumer_contract() { + // Service approves + assert_ok!(SmartContractModule::service_contract_approve( + Origin::signed(alice()), + 1, + )); + // Consumer approves + assert_ok!(SmartContractModule::service_contract_approve( + Origin::signed(bob()), + 1, )); } @@ -4530,3 +4635,7 @@ fn get_service_contract() -> types::ServiceContract { phantom: PhantomData, } } + +fn get_timestamp_in_seconds(block_number: u64) -> u64 { + 1628082000 + (6 * block_number) +} From 42167339abb8effe77ba0132fdc083894b4f454d Mon Sep 17 00:00:00 2001 From: renauter Date: Fri, 18 Nov 2022 00:05:44 -0300 Subject: [PATCH 84/87] test: refine service contract testing (1) --- .../pallets/pallet-smart-contract/src/lib.rs | 10 +- .../pallet-smart-contract/src/tests.rs | 119 ++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 1634f4f8c..59efdf7a5 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -452,8 +452,10 @@ pub mod pallet { TwinNotAuthorizedToCancelServiceContract, TwinNotAuthorizedToBillServiceContract, ServiceContractNotExists, + ServiceContractCreationNotAllowed, ServiceContractModificationNotAllowed, ServiceContractApprovalNotAllowed, + ServiceContractRejectionNotAllowed, ServiceContractBillingNotAllowed, ServiceContractBillMetadataTooLong, ServiceContractMetadataTooLong, @@ -1184,6 +1186,12 @@ impl Pallet { Error::::TwinNotAuthorizedToCreateServiceContract, ); + // Service twin and consumer twin can not be the same + ensure!( + service_twin_id != consumer_twin_id, + Error::::ServiceContractCreationNotAllowed, + ); + // Create service contract let service_contract = types::ServiceContract { service_twin_id, @@ -1363,7 +1371,7 @@ impl Pallet { service_contract.state, types::ServiceContractState::AgreementReady ), - Error::::ServiceContractApprovalNotAllowed, + Error::::ServiceContractRejectionNotAllowed, ); // Only service or consumer can reject agreement diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index a9b8d9f4a..665659d9b 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1491,6 +1491,40 @@ fn test_service_contract_create_works() { }); } +#[test] +fn test_service_contract_create_by_anyone_fails() { + new_test_ext().execute_with(|| { + create_twin(alice()); + create_twin(bob()); + create_twin(charlie()); + + assert_noop!( + SmartContractModule::service_contract_create( + Origin::signed(charlie()), + alice(), + bob(), + ), + Error::::TwinNotAuthorizedToCreateServiceContract + ); + }); +} + +#[test] +fn test_service_contract_create_same_account_fails() { + new_test_ext().execute_with(|| { + create_twin(alice()); + + assert_noop!( + SmartContractModule::service_contract_create( + Origin::signed(alice()), + alice(), + alice(), + ), + Error::::ServiceContractCreationNotAllowed + ); + }); +} + #[test] fn test_service_contract_set_metadata_works() { new_test_ext().execute_with(|| { @@ -1511,6 +1545,56 @@ fn test_service_contract_set_metadata_works() { }); } +#[test] +fn test_service_contract_set_metadata_by_unauthorized_fails() { + new_test_ext().execute_with(|| { + create_service_consumer_contract(); + create_twin(charlie()); + + assert_noop!( + SmartContractModule::service_contract_set_metadata( + Origin::signed(charlie()), + 1, + b"some_metadata".to_vec(), + ), + Error::::TwinNotAuthorizedToSetMetadata + ); + }); +} + +#[test] +fn test_service_contract_set_metadata_already_approved_fails() { + new_test_ext().execute_with(|| { + prepare_service_consumer_contract(); + approve_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_set_metadata( + Origin::signed(alice()), + 1, + b"some_metadata".to_vec(), + ), + Error::::ServiceContractModificationNotAllowed + ); + }); +} + +#[test] +fn test_service_contract_set_metadata_too_long_fails() { + new_test_ext().execute_with(|| { + create_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_set_metadata( + Origin::signed(alice()), + 1, + b"very_loooooooooooooooooooooooooooooooooooooooooooooooooong_metadata".to_vec(), + ), + Error::::ServiceContractMetadataTooLong + ); + }); +} + #[test] fn test_service_contract_set_fees_works() { new_test_ext().execute_with(|| { @@ -1533,6 +1617,41 @@ fn test_service_contract_set_fees_works() { }); } +#[test] +fn test_service_contract_set_fees_by_unauthorized_fails() { + new_test_ext().execute_with(|| { + create_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_set_fees( + Origin::signed(bob()), + 1, + BASE_FEE, + VARIABLE_FEE, + ), + Error::::TwinNotAuthorizedToSetFees + ); + }); +} + +#[test] +fn test_service_contract_set_fees_already_approved_fails() { + new_test_ext().execute_with(|| { + prepare_service_consumer_contract(); + approve_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_set_fees( + Origin::signed(alice()), + 1, + BASE_FEE, + VARIABLE_FEE, + ), + Error::::ServiceContractModificationNotAllowed + ); + }); +} + #[test] fn test_service_contract_approve_works() { new_test_ext().execute_with(|| { From c0a826374cbd2be003a7f6d4b9d3b82edd63c483 Mon Sep 17 00:00:00 2001 From: renauter Date: Fri, 18 Nov 2022 00:27:56 -0300 Subject: [PATCH 85/87] test: refine service contract testing (2) --- .../pallet-smart-contract/src/tests.rs | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 665659d9b..e0eed57d2 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1707,6 +1707,53 @@ fn test_service_contract_approve_works() { }); } +#[test] +fn test_service_contract_approve_agreement_not_ready_fails() { + new_test_ext().execute_with(|| { + create_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_approve( + Origin::signed(alice()), + 1, + ), + Error::::ServiceContractApprovalNotAllowed + ); + }); +} + +#[test] +fn test_service_contract_approve_already_approved_fails() { + new_test_ext().execute_with(|| { + prepare_service_consumer_contract(); + approve_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_approve( + Origin::signed(alice()), + 1, + ), + Error::::ServiceContractApprovalNotAllowed + ); + }); +} + +#[test] +fn test_service_contract_approve_by_unauthorized_fails() { + new_test_ext().execute_with(|| { + prepare_service_consumer_contract(); + create_twin(charlie()); + + assert_noop!( + SmartContractModule::service_contract_approve( + Origin::signed(charlie()), + 1, + ), + Error::::TwinNotAuthorizedToApproveServiceContract + ); + }); +} + #[test] fn test_service_contract_reject_works() { new_test_ext().execute_with(|| { @@ -1734,6 +1781,53 @@ fn test_service_contract_reject_works() { }); } +#[test] +fn test_service_contract_reject_agreement_not_ready_fails() { + new_test_ext().execute_with(|| { + create_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_reject( + Origin::signed(alice()), + 1, + ), + Error::::ServiceContractRejectionNotAllowed + ); + }); +} + +#[test] +fn test_service_contract_reject_already_approved_fails() { + new_test_ext().execute_with(|| { + prepare_service_consumer_contract(); + approve_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_reject( + Origin::signed(alice()), + 1, + ), + Error::::ServiceContractRejectionNotAllowed + ); + }); +} + +#[test] +fn test_service_contract_reject_by_unauthorized_fails() { + new_test_ext().execute_with(|| { + prepare_service_consumer_contract(); + create_twin(charlie()); + + assert_noop!( + SmartContractModule::service_contract_reject( + Origin::signed(charlie()), + 1, + ), + Error::::TwinNotAuthorizedToRejectServiceContract + ); + }); +} + #[test] fn test_service_contract_cancel_works() { new_test_ext().execute_with(|| { @@ -1761,6 +1855,22 @@ fn test_service_contract_cancel_works() { }); } +#[test] +fn test_service_contract_cancel_by_unauthorized_fails() { + new_test_ext().execute_with(|| { + create_service_consumer_contract(); + create_twin(charlie()); + + assert_noop!( + SmartContractModule::service_contract_cancel( + Origin::signed(charlie()), + 1, + ), + Error::::TwinNotAuthorizedToCancelServiceContract + ); + }); +} + #[test] fn test_service_contract_bill_works() { let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); From c769369b83faeef8a3413f5034d6be7800939ab6 Mon Sep 17 00:00:00 2001 From: renauter Date: Fri, 18 Nov 2022 01:22:03 -0300 Subject: [PATCH 86/87] test: refine service contract testing (3) --- .../pallets/pallet-smart-contract/src/lib.rs | 4 +- .../pallet-smart-contract/src/tests.rs | 113 ++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 59efdf7a5..46002681d 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -459,7 +459,7 @@ pub mod pallet { ServiceContractBillingNotAllowed, ServiceContractBillMetadataTooLong, ServiceContractMetadataTooLong, - ServiceContractNotEnoughFundToPayBill, + ServiceContractNotEnoughFundsToPayBill, } #[pallet::genesis_config] @@ -1524,7 +1524,7 @@ impl Pallet { types::Cause::OutOfFunds, )?; return Err(DispatchErrorWithPostInfo::from( - Error::::ServiceContractNotEnoughFundToPayBill, + Error::::ServiceContractNotEnoughFundsToPayBill, )); } diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index e0eed57d2..0aeddea1c 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1939,6 +1939,119 @@ fn test_service_contract_bill_works() { }); } +#[test] +fn test_service_contract_bill_by_unauthorized_fails() { + new_test_ext().execute_with(|| { + prepare_service_consumer_contract(); + approve_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_bill( + Origin::signed(bob()), + 1, + VARIABLE_AMOUNT, + b"bill_metadata".to_vec(), + ), + Error::::TwinNotAuthorizedToBillServiceContract + ); + }); +} + +#[test] +fn test_service_contract_bill_not_approved_fails() { + new_test_ext().execute_with(|| { + prepare_service_consumer_contract(); + + assert_noop!( + SmartContractModule::service_contract_bill( + Origin::signed(alice()), + 1, + VARIABLE_AMOUNT, + b"bill_metadata".to_vec(), + ), + Error::::ServiceContractBillingNotAllowed + ); + }); +} + +#[test] +fn test_service_contract_bill_variable_amount_too_high_fails() { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { + run_to_block(1, None); + prepare_service_consumer_contract(); + approve_service_consumer_contract(); + + // Bill 1h after contract approval (= service start) + run_to_block(601, Some(&mut pool_state)); + assert_noop!( + SmartContractModule::service_contract_bill( + Origin::signed(alice()), + 1, + VARIABLE_FEE + 1, // variable_amount a bit higher than variable fee + b"bill_metadata".to_vec(), + ), + Error::::ServiceContractBillingNotAllowed + ); + }); +} + +#[test] +fn test_service_contract_bill_metadata_too_long_fails() { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { + run_to_block(1, None); + prepare_service_consumer_contract(); + approve_service_consumer_contract(); + + // Bill 1h after contract approval (= service start) + run_to_block(601, Some(&mut pool_state)); + assert_noop!( + SmartContractModule::service_contract_bill( + Origin::signed(alice()), + 1, + VARIABLE_AMOUNT, + b"very_loooooooooooooooooooooooooooooooooooooooooooooooooong_metadata".to_vec(), + ), + Error::::ServiceContractBillMetadataTooLong + ); + }); +} + +#[test] +fn test_service_contract_bill_out_of_funds_fails() { + let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0); + ext.execute_with(|| { + run_to_block(1, None); + prepare_service_consumer_contract(); + approve_service_consumer_contract(); + + // Drain consumer account + let consumer_twin = TfgridModule::twins(2).unwrap(); + let consumer_balance = Balances::free_balance(&consumer_twin.account_id); + Balances::transfer( + Origin::signed(bob()), + alice(), + consumer_balance, + ) + .unwrap(); + let consumer_balance = Balances::free_balance(&consumer_twin.account_id); + assert_eq!(consumer_balance, 0); + + // Bill 1h after contract approval (= service start) + run_to_block(601, Some(&mut pool_state)); + assert_noop!( + SmartContractModule::service_contract_bill( + Origin::signed(alice()), + 1, + VARIABLE_AMOUNT, + b"bill_metadata".to_vec(), + ), + Error::::ServiceContractNotEnoughFundsToPayBill + ); + }); +} + // CAPACITY CONTRACT RESERVING ALL RESOURCES OF NODE TESTS // // -------------------------------------------- // From b7816c7a9e695feed97e9fef58cad558d303dcd9 Mon Sep 17 00:00:00 2001 From: renauter Date: Fri, 18 Nov 2022 11:38:27 -0300 Subject: [PATCH 87/87] fix out of funds test and do some cleanup --- .../pallets/pallet-smart-contract/src/lib.rs | 3 +- .../pallet-smart-contract/src/tests.rs | 30 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 46002681d..5a5a62e4e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -1165,7 +1165,6 @@ impl Pallet { Ok(().into()) } - #[transactional] pub fn _service_contract_create( caller: T::AccountId, service: T::AccountId, @@ -1394,7 +1393,6 @@ impl Pallet { Ok(().into()) } - #[transactional] pub fn _service_contract_cancel( account_id: T::AccountId, service_contract_id: u64, @@ -1430,6 +1428,7 @@ impl Pallet { Ok(().into()) } + #[transactional] pub fn _service_contract_bill( account_id: T::AccountId, service_contract_id: u64, diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index 0aeddea1c..aa07f4ed0 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -1687,7 +1687,7 @@ fn test_service_contract_approve_works() { )); service_contract.accepted_by_consumer = true; - service_contract.last_bill = get_timestamp_in_seconds(1); + service_contract.last_bill = get_timestamp_in_seconds_for_block(1); service_contract.state = types::ServiceContractState::ApprovedByBoth; assert_eq!( service_contract, @@ -1884,13 +1884,13 @@ fn test_service_contract_bill_works() { approve_service_consumer_contract(); let service_contract = SmartContractModule::service_contracts(1).unwrap(); - assert_eq!(service_contract.last_bill, get_timestamp_in_seconds(1)); + assert_eq!(service_contract.last_bill, get_timestamp_in_seconds_for_block(1)); let consumer_twin = TfgridModule::twins(2).unwrap(); let consumer_balance = Balances::free_balance(&consumer_twin.account_id); assert_eq!(consumer_balance, 2500000000); - // Bill 20 min after contract approval (= service start) + // Bill 20 min after contract approval run_to_block(201, Some(&mut pool_state)); assert_ok!(SmartContractModule::service_contract_bill( Origin::signed(alice()), @@ -1900,10 +1900,10 @@ fn test_service_contract_bill_works() { )); let service_contract = SmartContractModule::service_contracts(1).unwrap(); - assert_eq!(service_contract.last_bill, get_timestamp_in_seconds(201)); + assert_eq!(service_contract.last_bill, get_timestamp_in_seconds_for_block(201)); let consumer_balance = Balances::free_balance(&consumer_twin.account_id); - let window = get_timestamp_in_seconds(201) - get_timestamp_in_seconds(1); + let window = get_timestamp_in_seconds_for_block(201) - get_timestamp_in_seconds_for_block(1); let bill = types::ServiceContractBill { variable_amount: VARIABLE_AMOUNT, window, @@ -1923,7 +1923,7 @@ fn test_service_contract_bill_works() { )); let service_contract = SmartContractModule::service_contracts(1).unwrap(); - assert_eq!(service_contract.last_bill, get_timestamp_in_seconds(901)); + assert_eq!(service_contract.last_bill, get_timestamp_in_seconds_for_block(901)); let consumer_balance = Balances::free_balance(&consumer_twin.account_id); let bill = types::ServiceContractBill { @@ -1982,13 +1982,15 @@ fn test_service_contract_bill_variable_amount_too_high_fails() { prepare_service_consumer_contract(); approve_service_consumer_contract(); - // Bill 1h after contract approval (= service start) + // Bill 1h after contract approval run_to_block(601, Some(&mut pool_state)); + // set variable amount a bit higher than variable fee to trigger error + let variable_amount = VARIABLE_FEE + 1; assert_noop!( SmartContractModule::service_contract_bill( Origin::signed(alice()), 1, - VARIABLE_FEE + 1, // variable_amount a bit higher than variable fee + variable_amount, b"bill_metadata".to_vec(), ), Error::::ServiceContractBillingNotAllowed @@ -2004,7 +2006,7 @@ fn test_service_contract_bill_metadata_too_long_fails() { prepare_service_consumer_contract(); approve_service_consumer_contract(); - // Bill 1h after contract approval (= service start) + // Bill 1h after contract approval run_to_block(601, Some(&mut pool_state)); assert_noop!( SmartContractModule::service_contract_bill( @@ -2038,7 +2040,7 @@ fn test_service_contract_bill_out_of_funds_fails() { let consumer_balance = Balances::free_balance(&consumer_twin.account_id); assert_eq!(consumer_balance, 0); - // Bill 1h after contract approval (= service start) + // Bill 1h after contract approval run_to_block(601, Some(&mut pool_state)); assert_noop!( SmartContractModule::service_contract_bill( @@ -2047,7 +2049,7 @@ fn test_service_contract_bill_out_of_funds_fails() { VARIABLE_AMOUNT, b"bill_metadata".to_vec(), ), - Error::::ServiceContractNotEnoughFundsToPayBill + Error::::ServiceContractNotEnoughFundsToPayBill, ); }); } @@ -4244,7 +4246,7 @@ fn push_nru_report_for_contract(contract_id: u64, block_number: u64) { consumption_reports.push(super::types::NruConsumption { contract_id, nru: 3 * gigabyte, - timestamp: get_timestamp_in_seconds(block_number), + timestamp: get_timestamp_in_seconds_for_block(block_number), window: 6 * block_number, }); @@ -4264,7 +4266,7 @@ fn check_report_cost( let contract_bill_event = types::ContractBill { contract_id, - timestamp: get_timestamp_in_seconds(block_number), + timestamp: get_timestamp_in_seconds_for_block(block_number), discount_level, amount_billed: amount_billed as u128, }; @@ -4978,6 +4980,6 @@ fn get_service_contract() -> types::ServiceContract { } } -fn get_timestamp_in_seconds(block_number: u64) -> u64 { +fn get_timestamp_in_seconds_for_block(block_number: u64) -> u64 { 1628082000 + (6 * block_number) }