diff --git a/.github/workflows/review-trigger.yml b/.github/workflows/review-trigger.yml index 8b23dd30bb29ad7879543c064c3eb711cc87895d..061cf4ab09ed919f6de0a83f74a22c2062c3a258 100644 --- a/.github/workflows/review-trigger.yml +++ b/.github/workflows/review-trigger.yml @@ -21,6 +21,38 @@ jobs: - name: Skip merge queue if: ${{ contains(github.ref, 'gh-readonly-queue') }} run: exit 0 + - name: Get comments + id: comments + run: echo "bodies=$(gh pr view ${{ github.event.number }} --repo ${{ github.repository }} --json comments --jq '[.comments[].body]')" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ github.token }} + - name: Fail when author pushes new code + # Require new reviews when the author is pushing and he is not a member + if: | + github.event_name == 'pull_request_target' && + github.event.action == 'synchronize' && + github.event.sender.login == github.event.pull_request.user.login && + github.event.pull_request.author_association != 'MEMBER' + run: | + # We get the list of reviewers who approved the PR + REVIEWERS=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.number }}/reviews \ + --jq '{reviewers: [.[] | select(.state == "APPROVED") | .user.login]}') + + # We request them to review again + echo $REVIEWERS | gh api --method POST repos/${{ github.repository }}/pulls/${{ github.event.number }}/requested_reviewers --input - + + echo "::error::Project needs to be reviewed again" + exit 1 + env: + GH_TOKEN: ${{ github.token }} + - name: Comment requirements + # If the previous step failed and github-actions hasn't commented yet we comment instructions + if: failure() && !contains(fromJson(steps.comments.outputs.bodies), 'Review required! Latest push from author must always be reviewed') + run: | + gh pr comment ${{ github.event.number }} --repo ${{ github.repository }} --body "Review required! Latest push from author must always be reviewed" + env: + GH_TOKEN: ${{ github.token }} + COMMENTS: ${{ steps.comments.outputs.users }} - name: Get PR number env: PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/Cargo.lock b/Cargo.lock index 5e7959cf918b76ec00c0bf43e5bb6b91813d9155..db87de219ef97fafac7cdc5155800ca344a25d57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9595,7 +9595,7 @@ dependencies = [ [[package]] name = "pallet-assets" -version = "29.0.0" +version = "29.1.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9961,6 +9961,7 @@ dependencies = [ "frame-support", "frame-system", "parity-scale-codec", + "pretty_assertions", "scale-info", "sp-api", "sp-arithmetic", diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index f97b23ecaaa99fdaed0a54d00b2b89c14847750a..64ae1d0b669f2ea8fdfba0df73752a9b0f6e8aec 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -23,7 +23,7 @@ use crate::messages_call_ext::{ CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType, }; use bp_messages::{LaneId, MessageNonce}; -use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; +use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::{Chain, Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider}; use codec::{Codec, Decode, Encode}; use frame_support::{ @@ -589,7 +589,10 @@ where ); }, RelayerAccountAction::Slash(relayer, slash_account) => - RelayersPallet::<T::Runtime>::slash_and_deregister(&relayer, slash_account), + RelayersPallet::<T::Runtime>::slash_and_deregister( + &relayer, + ExplicitOrAccountParams::Params(slash_account), + ), } Ok(()) diff --git a/bridges/modules/relayers/src/benchmarking.rs b/bridges/modules/relayers/src/benchmarking.rs index 00c3814a4c38d9bf0f18b70c0eedc75c239b8ad0..ca312d44edfddd286eae1715655d538b6b00f070 100644 --- a/bridges/modules/relayers/src/benchmarking.rs +++ b/bridges/modules/relayers/src/benchmarking.rs @@ -106,7 +106,7 @@ benchmarks! { let slash_destination = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); T::prepare_rewards_account(slash_destination, Zero::zero()); }: { - crate::Pallet::<T>::slash_and_deregister(&relayer, slash_destination) + crate::Pallet::<T>::slash_and_deregister(&relayer, slash_destination.into()) } verify { assert!(!crate::Pallet::<T>::is_registration_active(&relayer)); diff --git a/bridges/modules/relayers/src/lib.rs b/bridges/modules/relayers/src/lib.rs index ce66c9df48e01c829a55d03ffe4437c41f006a6b..7a3a0f9ea94cbe5768bf6ee8c850355193ea44f0 100644 --- a/bridges/modules/relayers/src/lib.rs +++ b/bridges/modules/relayers/src/lib.rs @@ -21,7 +21,8 @@ #![warn(missing_docs)] use bp_relayers::{ - PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, + ExplicitOrAccountParams, PaymentProcedure, Registration, RelayerRewardsKeyProvider, + RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; @@ -242,7 +243,7 @@ pub mod pallet { /// It may fail inside, but error is swallowed and we only log it. pub fn slash_and_deregister( relayer: &T::AccountId, - slash_destination: RewardsAccountParams, + slash_destination: ExplicitOrAccountParams<T::AccountId>, ) { let registration = match RegisteredRelayers::<T>::take(relayer) { Some(registration) => registration, @@ -259,7 +260,7 @@ pub mod pallet { match T::StakeAndSlash::repatriate_reserved( relayer, - slash_destination, + slash_destination.clone(), registration.stake, ) { Ok(failed_to_slash) if failed_to_slash.is_zero() => { diff --git a/bridges/modules/relayers/src/stake_adapter.rs b/bridges/modules/relayers/src/stake_adapter.rs index 88af9b1877bfe85614f081ee66dbb28586b1d34b..7ba90d91dfd94e49bf0ff6ee8fcc06f80e287c41 100644 --- a/bridges/modules/relayers/src/stake_adapter.rs +++ b/bridges/modules/relayers/src/stake_adapter.rs @@ -17,7 +17,7 @@ //! Code that allows `NamedReservableCurrency` to be used as a `StakeAndSlash` //! mechanism of the relayers pallet. -use bp_relayers::{PayRewardFromAccount, RewardsAccountParams, StakeAndSlash}; +use bp_relayers::{ExplicitOrAccountParams, PayRewardFromAccount, StakeAndSlash}; use codec::Codec; use frame_support::traits::{tokens::BalanceStatus, NamedReservableCurrency}; use sp_runtime::{traits::Get, DispatchError, DispatchResult}; @@ -55,11 +55,14 @@ where fn repatriate_reserved( relayer: &AccountId, - beneficiary: RewardsAccountParams, + beneficiary: ExplicitOrAccountParams<AccountId>, amount: Currency::Balance, ) -> Result<Currency::Balance, DispatchError> { - let beneficiary_account = - PayRewardFromAccount::<(), AccountId>::rewards_account(beneficiary); + let beneficiary_account = match beneficiary { + ExplicitOrAccountParams::Explicit(account) => account, + ExplicitOrAccountParams::Params(params) => + PayRewardFromAccount::<(), AccountId>::rewards_account(params), + }; Currency::repatriate_reserved_named( &ReserveId::get(), relayer, @@ -134,7 +137,11 @@ mod tests { Balances::mint_into(&beneficiary_account, expected_balance).unwrap(); assert_eq!( - TestStakeAndSlash::repatriate_reserved(&1, beneficiary, test_stake()), + TestStakeAndSlash::repatriate_reserved( + &1, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ), Ok(test_stake()) ); assert_eq!(Balances::free_balance(1), 0); @@ -146,7 +153,11 @@ mod tests { Balances::mint_into(&2, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap(); assert_eq!( - TestStakeAndSlash::repatriate_reserved(&2, beneficiary, test_stake()), + TestStakeAndSlash::repatriate_reserved( + &2, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ), Ok(test_stake() - test_stake() / 3) ); assert_eq!(Balances::free_balance(2), test_stake() * 2 - test_stake() / 3); @@ -158,7 +169,11 @@ mod tests { Balances::mint_into(&3, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&3, test_stake()).unwrap(); assert_eq!( - TestStakeAndSlash::repatriate_reserved(&3, beneficiary, test_stake()), + TestStakeAndSlash::repatriate_reserved( + &3, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ), Ok(0) ); assert_eq!(Balances::free_balance(3), test_stake()); @@ -176,7 +191,12 @@ mod tests { Balances::mint_into(&3, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&3, test_stake()).unwrap(); - assert!(TestStakeAndSlash::repatriate_reserved(&3, beneficiary, test_stake()).is_err()); + assert!(TestStakeAndSlash::repatriate_reserved( + &3, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ) + .is_err()); assert_eq!(Balances::free_balance(3), test_stake()); assert_eq!(Balances::reserved_balance(3), test_stake()); assert_eq!(Balances::free_balance(beneficiary_account), 0); diff --git a/bridges/primitives/relayers/src/lib.rs b/bridges/primitives/relayers/src/lib.rs index c808c437b54cbaaa5813067a6413fe7189336ee6..2a9ef6a8e1e9aba999ea90045447f7a87fb3813b 100644 --- a/bridges/primitives/relayers/src/lib.rs +++ b/bridges/primitives/relayers/src/lib.rs @@ -19,7 +19,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -pub use registration::{Registration, StakeAndSlash}; +pub use registration::{ExplicitOrAccountParams, Registration, StakeAndSlash}; use bp_messages::LaneId; use bp_runtime::{ChainId, StorageDoubleMapKeyProvider}; diff --git a/bridges/primitives/relayers/src/registration.rs b/bridges/primitives/relayers/src/registration.rs index 38fa7c2d9075b7e84986eb4ff173cdb24db5610f..9d9b7e4812201390c6831ff020c12c8cc995c17a 100644 --- a/bridges/primitives/relayers/src/registration.rs +++ b/bridges/primitives/relayers/src/registration.rs @@ -46,6 +46,21 @@ use sp_runtime::{ DispatchError, DispatchResult, }; +/// Either explicit account reference or `RewardsAccountParams`. +#[derive(Clone, Debug)] +pub enum ExplicitOrAccountParams<AccountId> { + /// Explicit account reference. + Explicit(AccountId), + /// Account, referenced using `RewardsAccountParams`. + Params(RewardsAccountParams), +} + +impl<AccountId> From<RewardsAccountParams> for ExplicitOrAccountParams<AccountId> { + fn from(params: RewardsAccountParams) -> Self { + ExplicitOrAccountParams::Params(params) + } +} + /// Relayer registration. #[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] pub struct Registration<BlockNumber, Balance> { @@ -90,7 +105,7 @@ pub trait StakeAndSlash<AccountId, BlockNumber, Balance> { /// Returns `Ok(_)` with non-zero balance if we have failed to repatriate some portion of stake. fn repatriate_reserved( relayer: &AccountId, - beneficiary: RewardsAccountParams, + beneficiary: ExplicitOrAccountParams<AccountId>, amount: Balance, ) -> Result<Balance, DispatchError>; } @@ -113,7 +128,7 @@ where fn repatriate_reserved( _relayer: &AccountId, - _beneficiary: RewardsAccountParams, + _beneficiary: ExplicitOrAccountParams<AccountId>, _amount: Balance, ) -> Result<Balance, DispatchError> { Ok(Zero::zero()) diff --git a/polkadot/tests/benchmark_block.rs b/polkadot/tests/benchmark_block.rs index 99f95ef611a48266105ba6b413641867fab21101..bc268025985035a74996ba444e02b98471bb1125 100644 --- a/polkadot/tests/benchmark_block.rs +++ b/polkadot/tests/benchmark_block.rs @@ -58,7 +58,13 @@ async fn build_chain(runtime: &str, base_path: &Path) { let mut cmd = Command::new(cargo_bin("polkadot")) .stdout(process::Stdio::piped()) .stderr(process::Stdio::piped()) - .args(["--chain", runtime, "--force-authoring", "--alice"]) + .args([ + "--chain", + runtime, + "--force-authoring", + "--alice", + "--unsafe-force-node-key-generation", + ]) .arg("-d") .arg(base_path) .arg("--no-hardware-benchmarks") diff --git a/prdoc/pr_3852.prdoc b/prdoc/pr_3852.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f13e1766d518a14a0a5309e75937007bf937a79d --- /dev/null +++ b/prdoc/pr_3852.prdoc @@ -0,0 +1,25 @@ +title: (Breaking change)Enforce network key presence on authorities. + +doc: + - audience: Node Operator + description: | + (Breaking change) For all authority nodes, the node binary now enforces the presence + of a network key, instead of auto-generating when it is absent. + + Before this change, all node binaries were auto-generating the node key when it was not present, + that is dangerous because other nodes in the network expects a stable identity for authorities. + + To prevent accidental generation of node key, we removed this behaviour and node binary will now throw + an error if the network key is not present and operators will receive instructions to either persist + their network key or explicitly generate a new one with the `polkadot key generate-node-key`. + + To prevent this error on restart/upgrades node operators need to make sure their network key are always + persisted, if nodes already correctly persist all directories in `--base-path` then no action is needed. + +crates: + - name: sc-cli + bump: major + - name: polkadot + bump: major + - name: subkey + bump: minor \ No newline at end of file diff --git a/prdoc/pr_4089.prdoc b/prdoc/pr_4089.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..29ac736ccd082ec493b89824fab0f9519bd66c6d --- /dev/null +++ b/prdoc/pr_4089.prdoc @@ -0,0 +1,11 @@ +title: "pallet_broker: Support renewing leases expired in a previous period" + +doc: + - audience: Runtime User + description: | + Allow renewals of leases that ended before `start_sales` or in the first period after calling `start_sales`. + This ensures that everyone has a smooth experience when migrating to coretime and the timing of + `start_sales` isn't that important. + +crates: + - name: pallet-broker diff --git a/prdoc/pr_4118.prdoc b/prdoc/pr_4118.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..20f36c1b0a37cc330c23115daa59f7a6c14b3c6a --- /dev/null +++ b/prdoc/pr_4118.prdoc @@ -0,0 +1,13 @@ +title: "pallet assets: minor improvement on errors returned for some calls" + +doc: + - audience: Runtime Dev + description: | + Some calls in pallet assets have better errors. No new error is introduced, only more sensible choice are made. + - audience: Runtime User + description: | + Some calls in pallet assets have better errors. No new error is introduced, only more sensible choice are made. + +crates: + - name: pallet-assets + bump: minor diff --git a/substrate/bin/utils/subkey/src/lib.rs b/substrate/bin/utils/subkey/src/lib.rs index 33f28ef46a5e7805b2c00b047e413f049169f6ef..0ca65cd08a6bcade0522b112a9bd327ed898badd 100644 --- a/substrate/bin/utils/subkey/src/lib.rs +++ b/substrate/bin/utils/subkey/src/lib.rs @@ -310,7 +310,7 @@ use clap::Parser; use sc_cli::{ - Error, GenerateCmd, GenerateNodeKeyCmd, InspectKeyCmd, InspectNodeKeyCmd, SignCmd, VanityCmd, + Error, GenerateCmd, GenerateKeyCmdCommon, InspectKeyCmd, InspectNodeKeyCmd, SignCmd, VanityCmd, VerifyCmd, }; @@ -324,7 +324,7 @@ use sc_cli::{ pub enum Subkey { /// Generate a random node key, write it to a file or stdout and write the /// corresponding peer-id to stderr - GenerateNodeKey(GenerateNodeKeyCmd), + GenerateNodeKey(GenerateKeyCmdCommon), /// Generate a random account Generate(GenerateCmd), diff --git a/substrate/client/cli/src/commands/generate_node_key.rs b/substrate/client/cli/src/commands/generate_node_key.rs index 43851dc1af5ccf555668e2014ba6fe57c31e858c..bdb94eec93b4a38797b54a7e7459d91dc88a3794 100644 --- a/substrate/client/cli/src/commands/generate_node_key.rs +++ b/substrate/client/cli/src/commands/generate_node_key.rs @@ -17,23 +17,19 @@ //! Implementation of the `generate-node-key` subcommand -use crate::Error; -use clap::Parser; +use crate::{build_network_key_dir_or_default, Error, NODE_KEY_ED25519_FILE}; +use clap::{Args, Parser}; use libp2p_identity::{ed25519, Keypair}; +use sc_service::BasePath; use std::{ fs, io::{self, Write}, path::PathBuf, }; -/// The `generate-node-key` command -#[derive(Debug, Parser)] -#[command( - name = "generate-node-key", - about = "Generate a random node key, write it to a file or stdout \ - and write the corresponding peer-id to stderr" -)] -pub struct GenerateNodeKeyCmd { +/// Common arguments accross all generate key commands, subkey and node. +#[derive(Debug, Args, Clone)] +pub struct GenerateKeyCmdCommon { /// Name of file to save secret key to. /// If not given, the secret key is printed to stdout. #[arg(long)] @@ -45,32 +41,111 @@ pub struct GenerateNodeKeyCmd { bin: bool, } -impl GenerateNodeKeyCmd { +/// The `generate-node-key` command +#[derive(Debug, Clone, Parser)] +#[command( + name = "generate-node-key", + about = "Generate a random node key, write it to a file or stdout \ + and write the corresponding peer-id to stderr" +)] +pub struct GenerateNodeKeyCmd { + #[clap(flatten)] + pub common: GenerateKeyCmdCommon, + /// Specify the chain specification. + /// + /// It can be any of the predefined chains like dev, local, staging, polkadot, kusama. + #[arg(long, value_name = "CHAIN_SPEC")] + pub chain: Option<String>, + /// A directory where the key should be saved. If a key already + /// exists in the directory, it won't be overwritten. + #[arg(long, conflicts_with_all = ["file", "default_base_path"])] + base_path: Option<PathBuf>, + + /// Save the key in the default directory. If a key already + /// exists in the directory, it won't be overwritten. + #[arg(long, conflicts_with_all = ["base_path", "file"])] + default_base_path: bool, +} + +impl GenerateKeyCmdCommon { /// Run the command pub fn run(&self) -> Result<(), Error> { - let keypair = ed25519::Keypair::generate(); + generate_key(&self.file, self.bin, None, &None, false, None) + } +} + +impl GenerateNodeKeyCmd { + /// Run the command + pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> { + generate_key( + &self.common.file, + self.common.bin, + Some(chain_spec_id), + &self.base_path, + self.default_base_path, + Some(executable_name), + ) + } +} + +// Utility function for generating a key based on the provided CLI arguments +// +// `file` - Name of file to save secret key to +// `bin` +fn generate_key( + file: &Option<PathBuf>, + bin: bool, + chain_spec_id: Option<&str>, + base_path: &Option<PathBuf>, + default_base_path: bool, + executable_name: Option<&String>, +) -> Result<(), Error> { + let keypair = ed25519::Keypair::generate(); - let secret = keypair.secret(); + let secret = keypair.secret(); - let file_data = if self.bin { - secret.as_ref().to_owned() - } else { - array_bytes::bytes2hex("", secret).into_bytes() - }; + let file_data = if bin { + secret.as_ref().to_owned() + } else { + array_bytes::bytes2hex("", secret).into_bytes() + }; - match &self.file { - Some(file) => fs::write(file, file_data)?, - None => io::stdout().lock().write_all(&file_data)?, - } + match (file, base_path, default_base_path) { + (Some(file), None, false) => fs::write(file, file_data)?, + (None, Some(_), false) | (None, None, true) => { + let network_path = build_network_key_dir_or_default( + base_path.clone().map(BasePath::new), + chain_spec_id.unwrap_or_default(), + executable_name.ok_or(Error::Input("Executable name not provided".into()))?, + ); - eprintln!("{}", Keypair::from(keypair).public().to_peer_id()); + fs::create_dir_all(network_path.as_path())?; - Ok(()) + let key_path = network_path.join(NODE_KEY_ED25519_FILE); + if key_path.exists() { + eprintln!("Skip generation, a key already exists in {:?}", key_path); + return Err(Error::KeyAlreadyExistsInPath(key_path)); + } else { + eprintln!("Generating key in {:?}", key_path); + fs::write(key_path, file_data)? + } + }, + (None, None, false) => io::stdout().lock().write_all(&file_data)?, + (_, _, _) => { + // This should not happen, arguments are marked as mutually exclusive. + return Err(Error::Input("Mutually exclusive arguments provided".into())); + }, } + + eprintln!("{}", Keypair::from(keypair).public().to_peer_id()); + + Ok(()) } #[cfg(test)] -mod tests { +pub mod tests { + use crate::DEFAULT_NETWORK_CONFIG_PATH; + use super::*; use std::io::Read; use tempfile::Builder; @@ -80,9 +155,32 @@ mod tests { let mut file = Builder::new().prefix("keyfile").tempfile().unwrap(); let file_path = file.path().display().to_string(); let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]); - assert!(generate.run().is_ok()); + assert!(generate.run("test", &String::from("test")).is_ok()); let mut buf = String::new(); assert!(file.read_to_string(&mut buf).is_ok()); assert!(array_bytes::hex2bytes(&buf).is_ok()); } + + #[test] + fn generate_node_key_base_path() { + let base_dir = Builder::new().prefix("keyfile").tempdir().unwrap(); + let key_path = base_dir + .path() + .join("chains/test_id/") + .join(DEFAULT_NETWORK_CONFIG_PATH) + .join(NODE_KEY_ED25519_FILE); + let base_path = base_dir.path().display().to_string(); + let generate = + GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--base-path", &base_path]); + assert!(generate.run("test_id", &String::from("test")).is_ok()); + let buf = fs::read_to_string(key_path.as_path()).unwrap(); + assert!(array_bytes::hex2bytes(&buf).is_ok()); + + assert!(generate.run("test_id", &String::from("test")).is_err()); + let new_buf = fs::read_to_string(key_path).unwrap(); + assert_eq!( + array_bytes::hex2bytes(&new_buf).unwrap(), + array_bytes::hex2bytes(&buf).unwrap() + ); + } } diff --git a/substrate/client/cli/src/commands/inspect_node_key.rs b/substrate/client/cli/src/commands/inspect_node_key.rs index 6cf025a2d115066ed18277e4036a028965e3c32f..25a0a685650e3d39709e7f8e9af903d7d72070d0 100644 --- a/substrate/client/cli/src/commands/inspect_node_key.rs +++ b/substrate/client/cli/src/commands/inspect_node_key.rs @@ -79,7 +79,9 @@ impl InspectNodeKeyCmd { #[cfg(test)] mod tests { - use super::{super::GenerateNodeKeyCmd, *}; + use crate::commands::generate_node_key::GenerateNodeKeyCmd; + + use super::*; #[test] fn inspect_node_key() { @@ -87,7 +89,7 @@ mod tests { let path = path.to_str().unwrap(); let cmd = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", path]); - assert!(cmd.run().is_ok()); + assert!(cmd.run("test", &String::from("test")).is_ok()); let cmd = InspectNodeKeyCmd::parse_from(&["inspect-node-key", "--file", path]); assert!(cmd.run().is_ok()); diff --git a/substrate/client/cli/src/commands/key.rs b/substrate/client/cli/src/commands/key.rs index d49b7e4072c8eb5453b0540117dacaf23e89f912..52747b404622691cc0ef572ab91f39e3d23a3eb5 100644 --- a/substrate/client/cli/src/commands/key.rs +++ b/substrate/client/cli/src/commands/key.rs @@ -47,7 +47,10 @@ impl KeySubcommand { /// run the key subcommands pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> { match self { - KeySubcommand::GenerateNodeKey(cmd) => cmd.run(), + KeySubcommand::GenerateNodeKey(cmd) => { + let chain_spec = cli.load_spec(cmd.chain.as_deref().unwrap_or(""))?; + cmd.run(chain_spec.id(), &C::executable_name()) + }, KeySubcommand::Generate(cmd) => cmd.run(), KeySubcommand::Inspect(cmd) => cmd.run(), KeySubcommand::Insert(cmd) => cmd.run(cli), diff --git a/substrate/client/cli/src/commands/mod.rs b/substrate/client/cli/src/commands/mod.rs index 9d48d2bdf644fdc2ec6417148f138aafaf04f063..2d7a0dc72ff5355d2541ea098c140878649accdb 100644 --- a/substrate/client/cli/src/commands/mod.rs +++ b/substrate/client/cli/src/commands/mod.rs @@ -42,7 +42,7 @@ mod verify; pub use self::{ build_spec_cmd::BuildSpecCmd, chain_info_cmd::ChainInfoCmd, check_block_cmd::CheckBlockCmd, export_blocks_cmd::ExportBlocksCmd, export_state_cmd::ExportStateCmd, generate::GenerateCmd, - generate_node_key::GenerateNodeKeyCmd, import_blocks_cmd::ImportBlocksCmd, + generate_node_key::GenerateKeyCmdCommon, import_blocks_cmd::ImportBlocksCmd, insert_key::InsertKeyCmd, inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd, key::KeySubcommand, purge_chain_cmd::PurgeChainCmd, revert_cmd::RevertCmd, run_cmd::RunCmd, sign::SignCmd, vanity::VanityCmd, verify::VerifyCmd, diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index 5def9ce9b72620eb7942ac6ee68b16493f6b8053..70a4885e5eef79780bdbec5b2cb7550b28ad866a 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -428,8 +428,10 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized { /// By default this is retrieved from `NodeKeyParams` if it is available. Otherwise its /// `NodeKeyConfig::default()`. fn node_key(&self, net_config_dir: &PathBuf) -> Result<NodeKeyConfig> { + let is_dev = self.is_dev()?; + let role = self.role(is_dev)?; self.node_key_params() - .map(|x| x.node_key(net_config_dir)) + .map(|x| x.node_key(net_config_dir, role, is_dev)) .unwrap_or_else(|| Ok(Default::default())) } @@ -463,11 +465,9 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized { let is_dev = self.is_dev()?; let chain_id = self.chain_id(is_dev)?; let chain_spec = cli.load_spec(&chain_id)?; - let base_path = self - .base_path()? - .unwrap_or_else(|| BasePath::from_project("", "", &C::executable_name())); - let config_dir = base_path.config_dir(chain_spec.id()); - let net_config_dir = config_dir.join(DEFAULT_NETWORK_CONFIG_PATH); + let base_path = base_path_or_default(self.base_path()?, &C::executable_name()); + let config_dir = build_config_dir(&base_path, chain_spec.id()); + let net_config_dir = build_net_config_dir(&config_dir); let client_id = C::client_id(); let database_cache_size = self.database_cache_size()?.unwrap_or(1024); let database = self.database()?.unwrap_or( @@ -665,3 +665,33 @@ pub fn generate_node_name() -> String { } } } + +/// Returns the value of `base_path` or the default_path if it is None +pub(crate) fn base_path_or_default( + base_path: Option<BasePath>, + executable_name: &String, +) -> BasePath { + base_path.unwrap_or_else(|| BasePath::from_project("", "", executable_name)) +} + +/// Returns the default path for configuration directory based on the chain_spec +pub(crate) fn build_config_dir(base_path: &BasePath, chain_spec_id: &str) -> PathBuf { + base_path.config_dir(chain_spec_id) +} + +/// Returns the default path for the network configuration inside the configuration dir +pub(crate) fn build_net_config_dir(config_dir: &PathBuf) -> PathBuf { + config_dir.join(DEFAULT_NETWORK_CONFIG_PATH) +} + +/// Returns the default path for the network directory starting from the provided base_path +/// or from the default base_path. +pub(crate) fn build_network_key_dir_or_default( + base_path: Option<BasePath>, + chain_spec_id: &str, + executable_name: &String, +) -> PathBuf { + let config_dir = + build_config_dir(&base_path_or_default(base_path, executable_name), chain_spec_id); + build_net_config_dir(&config_dir) +} diff --git a/substrate/client/cli/src/error.rs b/substrate/client/cli/src/error.rs index 6c0cfca4932efca53ff6f8d38e8ae4de004f8b91..90ad048009ade90b5ccd4cfc264cc85e3b311843 100644 --- a/substrate/client/cli/src/error.rs +++ b/substrate/client/cli/src/error.rs @@ -18,6 +18,8 @@ //! Initialization errors. +use std::path::PathBuf; + use sp_core::crypto; /// Result type alias for the CLI. @@ -78,6 +80,20 @@ pub enum Error { #[error(transparent)] GlobalLoggerError(#[from] sc_tracing::logging::Error), + + #[error( + "Starting an authorithy without network key in {0}. + \n This is not a safe operation because other authorities in the network may depend on your node having a stable identity. + \n Otherwise these other authorities may not being able to reach you. + \n If it is the first time running your node you could use one of the following methods: + \n 1. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --base-path <YOUR_BASE_PATH> + \n 2. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --file <YOUR_PATH_TO_NODE_KEY> + \n 3. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --default-base-path + \n 4. [Unsafe] Pass --unsafe-force-node-key-generation and make sure you remove it for subsequent node restarts" + )] + NetworkKeyNotFound(PathBuf), + #[error("A network key already exists in path {0}")] + KeyAlreadyExistsInPath(PathBuf), } impl From<&str> for Error { diff --git a/substrate/client/cli/src/params/node_key_params.rs b/substrate/client/cli/src/params/node_key_params.rs index 53f19f58e1fb0d657d387fd50a8a2346c238ead4..7058af19f1d4a5edc31a8e93d062f7f93c065f1e 100644 --- a/substrate/client/cli/src/params/node_key_params.rs +++ b/substrate/client/cli/src/params/node_key_params.rs @@ -18,15 +18,16 @@ use clap::Args; use sc_network::config::{identity::ed25519, NodeKeyConfig}; +use sc_service::Role; use sp_core::H256; use std::{path::PathBuf, str::FromStr}; -use crate::{arg_enums::NodeKeyType, error}; +use crate::{arg_enums::NodeKeyType, error, Error}; /// The file name of the node's Ed25519 secret key inside the chain-specific /// network config directory, if neither `--node-key` nor `--node-key-file` /// is specified in combination with `--node-key-type=ed25519`. -const NODE_KEY_ED25519_FILE: &str = "secret_ed25519"; +pub(crate) const NODE_KEY_ED25519_FILE: &str = "secret_ed25519"; /// Parameters used to create the `NodeKeyConfig`, which determines the keypair /// used for libp2p networking. @@ -79,22 +80,48 @@ pub struct NodeKeyParams { /// the chosen type. #[arg(long, value_name = "FILE")] pub node_key_file: Option<PathBuf>, + + /// Forces key generation if node-key-file file does not exist. + /// + /// This is an unsafe feature for production networks, because as an active authority + /// other authorities may depend on your node having a stable identity and they might + /// not being able to reach you if your identity changes after entering the active set. + /// + /// For minimal node downtime if no custom `node-key-file` argument is provided + /// the network-key is usually persisted accross nodes restarts, + /// in the `network` folder from directory provided in `--base-path` + /// + /// Warning!! If you ever run the node with this argument, make sure + /// you remove it for the subsequent restarts. + #[arg(long)] + pub unsafe_force_node_key_generation: bool, } impl NodeKeyParams { /// Create a `NodeKeyConfig` from the given `NodeKeyParams` in the context /// of an optional network config storage directory. - pub fn node_key(&self, net_config_dir: &PathBuf) -> error::Result<NodeKeyConfig> { + pub fn node_key( + &self, + net_config_dir: &PathBuf, + role: Role, + is_dev: bool, + ) -> error::Result<NodeKeyConfig> { Ok(match self.node_key_type { NodeKeyType::Ed25519 => { let secret = if let Some(node_key) = self.node_key.as_ref() { parse_ed25519_secret(node_key)? } else { - sc_network::config::Secret::File( - self.node_key_file - .clone() - .unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE)), - ) + let key_path = self + .node_key_file + .clone() + .unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE)); + if !self.unsafe_force_node_key_generation && + role.is_authority() && !is_dev && + !key_path.exists() + { + return Err(Error::NetworkKeyNotFound(key_path)) + } + sc_network::config::Secret::File(key_path) }; NodeKeyConfig::Ed25519(secret) @@ -122,7 +149,8 @@ mod tests { use super::*; use clap::ValueEnum; use libp2p_identity::ed25519; - use std::fs; + use std::fs::{self, File}; + use tempfile::TempDir; #[test] fn test_node_key_config_input() { @@ -136,8 +164,9 @@ mod tests { node_key_type, node_key: Some(format!("{:x}", H256::from_slice(sk.as_ref()))), node_key_file: None, + unsafe_force_node_key_generation: false, }; - params.node_key(net_config_dir).and_then(|c| match c { + params.node_key(net_config_dir, Role::Authority, false).and_then(|c| match c { NodeKeyConfig::Ed25519(sc_network::config::Secret::Input(ref ski)) if node_key_type == NodeKeyType::Ed25519 && &sk[..] == ski.as_ref() => Ok(()), @@ -156,10 +185,11 @@ mod tests { node_key_type: NodeKeyType::Ed25519, node_key: None, node_key_file: Some(file), + unsafe_force_node_key_generation: false, }; let node_key = params - .node_key(&PathBuf::from("not-used")) + .node_key(&PathBuf::from("not-used"), Role::Authority, false) .expect("Creates node key config") .into_keypair() .expect("Creates node key pair"); @@ -186,29 +216,58 @@ mod tests { #[test] fn test_node_key_config_default() { - fn with_def_params<F>(f: F) -> error::Result<()> + fn with_def_params<F>(f: F, unsafe_force_node_key_generation: bool) -> error::Result<()> where F: Fn(NodeKeyParams) -> error::Result<()>, { NodeKeyType::value_variants().iter().try_for_each(|t| { let node_key_type = *t; - f(NodeKeyParams { node_key_type, node_key: None, node_key_file: None }) + f(NodeKeyParams { + node_key_type, + node_key: None, + node_key_file: None, + unsafe_force_node_key_generation, + }) }) } - fn some_config_dir(net_config_dir: &PathBuf) -> error::Result<()> { - with_def_params(|params| { - let dir = PathBuf::from(net_config_dir.clone()); - let typ = params.node_key_type; - params.node_key(net_config_dir).and_then(move |c| match c { - NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f)) - if typ == NodeKeyType::Ed25519 && f == &dir.join(NODE_KEY_ED25519_FILE) => - Ok(()), - _ => Err(error::Error::Input("Unexpected node key config".into())), - }) - }) + fn some_config_dir( + net_config_dir: &PathBuf, + unsafe_force_node_key_generation: bool, + role: Role, + is_dev: bool, + ) -> error::Result<()> { + with_def_params( + |params| { + let dir = PathBuf::from(net_config_dir.clone()); + let typ = params.node_key_type; + let role = role.clone(); + params.node_key(net_config_dir, role, is_dev).and_then(move |c| match c { + NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f)) + if typ == NodeKeyType::Ed25519 && + f == &dir.join(NODE_KEY_ED25519_FILE) => + Ok(()), + _ => Err(error::Error::Input("Unexpected node key config".into())), + }) + }, + unsafe_force_node_key_generation, + ) } - assert!(some_config_dir(&PathBuf::from_str("x").unwrap()).is_ok()); + assert!(some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Full, false).is_ok()); + assert!( + some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, true).is_ok() + ); + assert!( + some_config_dir(&PathBuf::from_str("x").unwrap(), true, Role::Authority, false).is_ok() + ); + assert!(matches!( + some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, false), + Err(Error::NetworkKeyNotFound(_)) + )); + + let tempdir = TempDir::new().unwrap(); + let _file = File::create(tempdir.path().join(NODE_KEY_ED25519_FILE)).unwrap(); + assert!(some_config_dir(&tempdir.path().into(), false, Role::Authority, false).is_ok()); } } diff --git a/substrate/client/tracing/src/logging/event_format.rs b/substrate/client/tracing/src/logging/event_format.rs index 9589c1dfee28d11fe0dd7d409f9cec6e3563b812..8d875427841e94c933ed0a704050409c7e23552a 100644 --- a/substrate/client/tracing/src/logging/event_format.rs +++ b/substrate/client/tracing/src/logging/event_format.rs @@ -21,6 +21,7 @@ use ansi_term::Colour; use regex::Regex; use std::fmt::{self, Write}; use tracing::{Event, Level, Subscriber}; +use tracing_log::NormalizeEvent; use tracing_subscriber::{ fmt::{format, time::FormatTime, FmtContext, FormatEvent, FormatFields}, registry::LookupSpan, @@ -60,10 +61,12 @@ where N: for<'a> FormatFields<'a> + 'static, { let mut writer = &mut ControlCodeSanitizer::new(!self.enable_color, writer); + let normalized_meta = event.normalized_metadata(); + let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata()); time::write(&self.timer, &mut format::Writer::new(&mut writer), self.enable_color)?; if self.display_level { - let fmt_level = FmtLevel::new(event.metadata().level(), self.enable_color); + let fmt_level = FmtLevel::new(meta.level(), self.enable_color); write!(writer, "{} ", fmt_level)?; } @@ -81,7 +84,7 @@ where } if self.display_target { - write!(writer, "{}: ", event.metadata().target())?; + write!(writer, "{}: ", meta.target())?; } // Custom code to display node name diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index 3b95750c14c8c9c78ad86442085580810e447f5a..ed6df77e15232d7249fdcdb5da41a673ef420994 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-assets" -version = "29.0.0" +version = "29.1.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 8791aaa736b350292cfce58c4d4067f3e8af101b..4a5fb06ee2c82ecce0f936d546f317b157fb9722 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -491,7 +491,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?; ensure!( d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, - Error::<T, I>::AssetNotLive + Error::<T, I>::IncorrectStatus ); let actual = Self::decrease_balance(id.clone(), target, amount, f, |actual, details| { diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 6891f04dfb51aae356a708112a7bba284e894be2..c6b379e1d060600f8c531ef75c3663cbb667e230 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -987,7 +987,7 @@ pub mod pallet { let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?; ensure!( d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, - Error::<T, I>::AssetNotLive + Error::<T, I>::IncorrectStatus ); ensure!(origin == d.freezer, Error::<T, I>::NoPermission); let who = T::Lookup::lookup(who)?; @@ -1024,7 +1024,7 @@ pub mod pallet { let details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?; ensure!( details.status == AssetStatus::Live || details.status == AssetStatus::Frozen, - Error::<T, I>::AssetNotLive + Error::<T, I>::IncorrectStatus ); ensure!(origin == details.admin, Error::<T, I>::NoPermission); let who = T::Lookup::lookup(who)?; @@ -1113,7 +1113,7 @@ pub mod pallet { Asset::<T, I>::try_mutate(id.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?; - ensure!(details.status == AssetStatus::Live, Error::<T, I>::LiveAsset); + ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive); ensure!(origin == details.owner, Error::<T, I>::NoPermission); if details.owner == owner { return Ok(()) @@ -1669,7 +1669,7 @@ pub mod pallet { let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?; ensure!( d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, - Error::<T, I>::AssetNotLive + Error::<T, I>::IncorrectStatus ); ensure!(origin == d.freezer, Error::<T, I>::NoPermission); let who = T::Lookup::lookup(who)?; diff --git a/substrate/frame/broker/Cargo.toml b/substrate/frame/broker/Cargo.toml index 969f13e269de4208a220150d688a21ffcc0191f1..f36b94c3cec567ebe25a2194876598e5a3b92a56 100644 --- a/substrate/frame/broker/Cargo.toml +++ b/substrate/frame/broker/Cargo.toml @@ -29,6 +29,7 @@ frame-system = { path = "../system", default-features = false } [dev-dependencies] sp-io = { path = "../../primitives/io" } +pretty_assertions = "1.3.0" [features] default = ["std"] diff --git a/substrate/frame/broker/src/mock.rs b/substrate/frame/broker/src/mock.rs index c7205058c9720b15838de3684a33303a45cb8c68..6219b4eff1b457294c9f3656e4be145c3639e2e0 100644 --- a/substrate/frame/broker/src/mock.rs +++ b/substrate/frame/broker/src/mock.rs @@ -29,7 +29,7 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_arithmetic::Perbill; -use sp_core::{ConstU32, ConstU64}; +use sp_core::{ConstU32, ConstU64, Get}; use sp_runtime::{ traits::{BlockNumberProvider, Identity}, BuildStorage, Saturating, @@ -210,6 +210,15 @@ pub fn advance_to(b: u64) { } } +pub fn advance_sale_period() { + let sale = SaleInfo::<Test>::get().unwrap(); + + let target_block_number = + sale.region_begin as u64 * <<Test as crate::Config>::TimeslicePeriod as Get<u64>>::get(); + + advance_to(target_block_number) +} + pub fn pot() -> u64 { balance(Broker::account_id()) } diff --git a/substrate/frame/broker/src/tests.rs b/substrate/frame/broker/src/tests.rs index 0aacd7ef3696c600ed9546d66fc84fb851553ee6..d738d34450336e8aa8c20eb2c7d232d2c69db592 100644 --- a/substrate/frame/broker/src/tests.rs +++ b/substrate/frame/broker/src/tests.rs @@ -24,6 +24,7 @@ use frame_support::{ BoundedVec, }; use frame_system::RawOrigin::Root; +use pretty_assertions::assert_eq; use sp_runtime::{traits::Get, TokenError}; use CoreAssignment::*; use CoretimeTraceItem::*; @@ -891,6 +892,161 @@ fn short_leases_are_cleaned() { }); } +#[test] +fn leases_can_be_renewed() { + TestExt::new().endow(1, 1000).execute_with(|| { + // Timeslice period is 2. + // + // Sale 1 starts at block 7, Sale 2 starts at 13. + + // Set lease to expire in sale 1 and start sales. + assert_ok!(Broker::do_set_lease(2001, 9)); + assert_eq!(Leases::<Test>::get().len(), 1); + // Start the sales with only one core for this lease. + assert_ok!(Broker::do_start_sales(100, 1)); + + // Advance to sale period 1, we should get an AllowedRenewal for task 2001 for the next + // sale. + advance_sale_period(); + assert_eq!( + AllowedRenewals::<Test>::get(AllowedRenewalId { core: 0, when: 10 }), + Some(AllowedRenewalRecord { + price: 100, + completion: CompletionStatus::Complete( + vec![ScheduleItem { mask: CoreMask::complete(), assignment: Task(2001) }] + .try_into() + .unwrap() + ) + }) + ); + // And the lease has been removed from storage. + assert_eq!(Leases::<Test>::get().len(), 0); + + // Advance to sale period 2, where we can renew. + advance_sale_period(); + assert_ok!(Broker::do_renew(1, 0)); + // We renew for the base price of the previous sale period. + assert_eq!(balance(1), 900); + + // We just renewed for this period. + advance_sale_period(); + // Now we are off core and the core is pooled. + advance_sale_period(); + // Check the trace agrees. + assert_eq!( + CoretimeTrace::get(), + vec![ + // Period 0 gets no assign core, but leases are on-core. + // Period 1: + ( + 6, + AssignCore { + core: 0, + begin: 8, + assignment: vec![(CoreAssignment::Task(2001), 57600)], + end_hint: None, + }, + ), + // Period 2 - expiring at the end of this period, so we called renew. + ( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(CoreAssignment::Task(2001), 57600)], + end_hint: None, + }, + ), + // Period 3 - we get assigned a core because we called renew in period 2. + ( + 18, + AssignCore { + core: 0, + begin: 20, + assignment: vec![(CoreAssignment::Task(2001), 57600)], + end_hint: None, + }, + ), + // Period 4 - we don't get a core as we didn't call renew again. + // This core is recycled into the pool. + ( + 24, + AssignCore { + core: 0, + begin: 26, + assignment: vec![(CoreAssignment::Pool, 57600)], + end_hint: None, + }, + ), + ] + ); + }); +} + +// We understand that this does not work as intended for leases that expire within `region_length` +// timeslices after calling `start_sales`. +#[test] +fn short_leases_cannot_be_renewed() { + TestExt::new().endow(1, 1000).execute_with(|| { + // Timeslice period is 2. + // + // Sale 1 starts at block 7, Sale 2 starts at 13. + + // Set lease to expire in sale period 0 and start sales. + assert_ok!(Broker::do_set_lease(2001, 3)); + assert_eq!(Leases::<Test>::get().len(), 1); + // Start the sales with one core for this lease. + assert_ok!(Broker::do_start_sales(100, 1)); + + // The lease is removed. + assert_eq!(Leases::<Test>::get().len(), 0); + + // We should have got an entry in AllowedRenewals, but we don't because rotate_sale + // schedules leases a period in advance. This renewal should be in the period after next + // because while bootstrapping our way into the sale periods, we give everything a lease for + // period 1, so they can renew for period 2. So we have a core until the end of period 1, + // but we are not marked as able to renew because we expired before sale period 1 starts. + // + // This should be fixed. + assert_eq!(AllowedRenewals::<Test>::get(AllowedRenewalId { core: 0, when: 10 }), None); + // And the lease has been removed from storage. + assert_eq!(Leases::<Test>::get().len(), 0); + + // Advance to sale period 2, where we now cannot renew. + advance_to(13); + assert_noop!(Broker::do_renew(1, 0), Error::<Test>::NotAllowed); + + // Check the trace. + assert_eq!( + CoretimeTrace::get(), + vec![ + // Period 0 gets no assign core, but leases are on-core. + // Period 1 we get assigned a core due to the way the sales are bootstrapped. + ( + 6, + AssignCore { + core: 0, + begin: 8, + assignment: vec![(CoreAssignment::Task(2001), 57600)], + end_hint: None, + }, + ), + // Period 2 - we don't get a core as we couldn't renew. + // This core is recycled into the pool. + ( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(CoreAssignment::Pool, 57600)], + end_hint: None, + }, + ), + ] + ); + }); +} + #[test] fn leases_are_limited() { TestExt::new().execute_with(|| { @@ -1092,3 +1248,141 @@ fn config_works() { assert_noop!(Broker::configure(Root.into(), cfg), Error::<Test>::InvalidConfig); }); } + +/// Ensure that a lease that ended before `start_sales` was called can be renewed. +#[test] +fn renewal_works_leases_ended_before_start_sales() { + TestExt::new().endow(1, 1000).execute_with(|| { + let config = Configuration::<Test>::get().unwrap(); + + // This lease is ended before `start_stales` was called. + assert_ok!(Broker::do_set_lease(1, 1)); + + // Go to some block to ensure that the lease of task 1 already ended. + advance_to(5); + + // This lease will end three sale periods in. + assert_ok!(Broker::do_set_lease( + 2, + Broker::latest_timeslice_ready_to_commit(&config) + config.region_length * 3 + )); + + // This intializes the first sale and the period 0. + assert_ok!(Broker::do_start_sales(100, 2)); + assert_noop!(Broker::do_renew(1, 1), Error::<Test>::Unavailable); + assert_noop!(Broker::do_renew(1, 0), Error::<Test>::Unavailable); + + // Lease for task 1 should have been dropped. + assert!(Leases::<Test>::get().iter().any(|l| l.task == 2)); + + // This intializes the second and the period 1. + advance_sale_period(); + + // Now we can finally renew the core 0 of task 1. + let new_core = Broker::do_renew(1, 0).unwrap(); + // Renewing the active lease doesn't work. + assert_noop!(Broker::do_renew(1, 1), Error::<Test>::SoldOut); + assert_eq!(balance(1), 900); + + // This intializes the third sale and the period 2. + advance_sale_period(); + let new_core = Broker::do_renew(1, new_core).unwrap(); + + // Renewing the active lease doesn't work. + assert_noop!(Broker::do_renew(1, 0), Error::<Test>::SoldOut); + assert_eq!(balance(1), 800); + + // All leases should have ended + assert!(Leases::<Test>::get().is_empty()); + + // This intializes the fourth sale and the period 3. + advance_sale_period(); + + // Renew again + assert_eq!(0, Broker::do_renew(1, new_core).unwrap()); + // Renew the task 2. + assert_eq!(1, Broker::do_renew(1, 0).unwrap()); + assert_eq!(balance(1), 600); + + // This intializes the fifth sale and the period 4. + advance_sale_period(); + + assert_eq!( + CoretimeTrace::get(), + vec![ + ( + 10, + AssignCore { + core: 0, + begin: 12, + assignment: vec![(Task(1), 57600)], + end_hint: None + } + ), + ( + 10, + AssignCore { + core: 1, + begin: 12, + assignment: vec![(Task(2), 57600)], + end_hint: None + } + ), + ( + 16, + AssignCore { + core: 0, + begin: 18, + assignment: vec![(Task(2), 57600)], + end_hint: None + } + ), + ( + 16, + AssignCore { + core: 1, + begin: 18, + assignment: vec![(Task(1), 57600)], + end_hint: None + } + ), + ( + 22, + AssignCore { + core: 0, + begin: 24, + assignment: vec![(Task(2), 57600)], + end_hint: None, + }, + ), + ( + 22, + AssignCore { + core: 1, + begin: 24, + assignment: vec![(Task(1), 57600)], + end_hint: None, + }, + ), + ( + 28, + AssignCore { + core: 0, + begin: 30, + assignment: vec![(Task(1), 57600)], + end_hint: None, + }, + ), + ( + 28, + AssignCore { + core: 1, + begin: 30, + assignment: vec![(Task(2), 57600)], + end_hint: None, + }, + ), + ] + ); + }); +} diff --git a/substrate/frame/broker/src/tick_impls.rs b/substrate/frame/broker/src/tick_impls.rs index 388370bce4d4b8045122c02ad758b62b4476fd15..04e9a65bf8f67b11ef66edf7268eda74e336e65d 100644 --- a/substrate/frame/broker/src/tick_impls.rs +++ b/substrate/frame/broker/src/tick_impls.rs @@ -216,11 +216,10 @@ impl<T: Config> Pallet<T> { let assignment = CoreAssignment::Task(task); let schedule = BoundedVec::truncate_from(vec![ScheduleItem { mask, assignment }]); Workplan::<T>::insert((region_begin, first_core), &schedule); - // Separate these to avoid missed expired leases hanging around forever. - let expired = until < region_end; - let expiring = until >= region_begin && expired; - if expiring { - // last time for this one - make it renewable. + // Will the lease expire at the end of the period? + let expire = until < region_end; + if expire { + // last time for this one - make it renewable in the next sale. let renewal_id = AllowedRenewalId { core: first_core, when: region_end }; let record = AllowedRenewalRecord { price, completion: Complete(schedule) }; AllowedRenewals::<T>::insert(renewal_id, &record); @@ -232,8 +231,10 @@ impl<T: Config> Pallet<T> { }); Self::deposit_event(Event::LeaseEnding { when: region_end, task }); } + first_core.saturating_inc(); - !expired + + !expire }); Leases::<T>::put(&leases); diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index 544ba72141ebd22291c04a54f34adbd873b335aa..2f553819b1bc09b911ad3664aa555c661863a40b 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -68,4 +68,4 @@ std = [ disable-logging = ["log/max_level_off"] # Do not report the documentation in the metadata. no-metadata-docs = ["sp-api-proc-macro/no-metadata-docs"] -frame-metadata = ["sp-api-proc-macro/frame-metadata", "sp-metadata-ir"] +frame-metadata = ["sp-metadata-ir"] diff --git a/substrate/primitives/api/proc-macro/Cargo.toml b/substrate/primitives/api/proc-macro/Cargo.toml index f5406758687ae20e9ce13a2cf02959c5cccbd576..b1bc547f3e4ae82e4e4331643c0aef14e1df3451 100644 --- a/substrate/primitives/api/proc-macro/Cargo.toml +++ b/substrate/primitives/api/proc-macro/Cargo.toml @@ -35,4 +35,3 @@ assert_matches = "1.3.0" default = ["std"] std = ["blake2/std"] no-metadata-docs = [] -frame-metadata = [] diff --git a/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs index e34e4c0e7672164afb41702ab3bae87082df9062..cb213f2fd627b628f2b763ff7e3f132eb849bed4 100644 --- a/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -193,10 +193,7 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result<TokenStream> { get_api_version(&found_attributes).map(|v| generate_runtime_api_version(v as u32))?; let id = generate_runtime_api_id(&decl.ident.to_string()); - #[cfg(feature = "frame-metadata")] let metadata = crate::runtime_metadata::generate_decl_runtime_metadata(&decl); - #[cfg(not(feature = "frame-metadata"))] - let metadata = quote!(); let trait_api_version = get_api_version(&found_attributes)?; diff --git a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs index 87a381fd7bf92bc72e30babf018b5dcb0141e571..2c423f8c28dd435599e1cc5b2d5a49807fd73abb 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -821,10 +821,7 @@ fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result<TokenStream> { let wasm_interface = generate_wasm_interface(api_impls)?; let api_impls_for_runtime_api = generate_api_impl_for_runtime_api(api_impls)?; - #[cfg(feature = "frame-metadata")] let runtime_metadata = crate::runtime_metadata::generate_impl_runtime_metadata(api_impls)?; - #[cfg(not(feature = "frame-metadata"))] - let runtime_metadata = quote!(); let impl_ = quote!( #base_runtime_api diff --git a/substrate/primitives/api/proc-macro/src/lib.rs b/substrate/primitives/api/proc-macro/src/lib.rs index 06e148880e975f88e07836a4a5ca52fd5e5527b1..d34f4b7f9cf6a388438b203dbad9f1e0c68b3481 100644 --- a/substrate/primitives/api/proc-macro/src/lib.rs +++ b/substrate/primitives/api/proc-macro/src/lib.rs @@ -25,7 +25,6 @@ mod common; mod decl_runtime_apis; mod impl_runtime_apis; mod mock_impl_runtime_apis; -#[cfg(feature = "frame-metadata")] mod runtime_metadata; mod utils; diff --git a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs index 41849401291e633924e6f530ffc5bc3474ba6283..9944927d557302db454e9589be681ffb26c7558e 100644 --- a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs +++ b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs @@ -164,15 +164,17 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { let (impl_generics, _, where_clause) = generics.split_for_impl(); quote!( - #( #attrs )* - #[inline(always)] - pub fn runtime_metadata #impl_generics () -> #crate_::metadata_ir::RuntimeApiMetadataIR - #where_clause - { - #crate_::metadata_ir::RuntimeApiMetadataIR { - name: #trait_name, - methods: #crate_::vec![ #( #methods, )* ], - docs: #docs, + #crate_::frame_metadata_enabled! { + #( #attrs )* + #[inline(always)] + pub fn runtime_metadata #impl_generics () -> #crate_::metadata_ir::RuntimeApiMetadataIR + #where_clause + { + #crate_::metadata_ir::RuntimeApiMetadataIR { + name: #trait_name, + methods: #crate_::vec![ #( #methods, )* ], + docs: #docs, + } } } ) @@ -255,14 +257,16 @@ pub fn generate_impl_runtime_metadata(impls: &[ItemImpl]) -> Result<TokenStream2 // `construct_runtime!` is called. Ok(quote!( - #[doc(hidden)] - trait InternalImplRuntimeApis { - #[inline(always)] - fn runtime_metadata(&self) -> #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> { - #crate_::vec![ #( #metadata, )* ] + #crate_::frame_metadata_enabled! { + #[doc(hidden)] + trait InternalImplRuntimeApis { + #[inline(always)] + fn runtime_metadata(&self) -> #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> { + #crate_::vec![ #( #metadata, )* ] + } } + #[doc(hidden)] + impl InternalImplRuntimeApis for #runtime_name {} } - #[doc(hidden)] - impl InternalImplRuntimeApis for #runtime_name {} )) } diff --git a/substrate/primitives/api/proc-macro/src/utils.rs b/substrate/primitives/api/proc-macro/src/utils.rs index a6570a98f1f78361398bd5a5a2c101af0a133988..d90b56058648ba8653992c210586238028629689 100644 --- a/substrate/primitives/api/proc-macro/src/utils.rs +++ b/substrate/primitives/api/proc-macro/src/utils.rs @@ -261,7 +261,6 @@ pub fn versioned_trait_name(trait_ident: &Ident, version: u64) -> Ident { } /// Extract the documentation from the provided attributes. -#[cfg(feature = "frame-metadata")] pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec<syn::Lit> { use quote::ToTokens; attrs @@ -277,7 +276,6 @@ pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec<syn::Lit> { } /// Filters all attributes except the cfg ones. -#[cfg(feature = "frame-metadata")] pub fn filter_cfg_attributes(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> { attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect() } diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index a945b9f21f3cff60d6a9d5e24aa07704a1dc8d31..20f989c4882e35fe06d5496f851b4adec2c6f1c0 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -838,3 +838,4 @@ decl_runtime_apis! { sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $); sp_core::generate_feature_enabled_macro!(std_disabled, not(feature = "std"), $); +sp_core::generate_feature_enabled_macro!(frame_metadata_enabled, feature = "frame-metadata", $);