diff --git a/Cargo.lock b/Cargo.lock index c10d927546b6..1b82303a7a10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10321,6 +10321,7 @@ dependencies = [ "kusama-runtime", "lazy_static", "log", + "pallet-balances", "pallet-election-provider-multi-phase", "pallet-staking", "pallet-transaction-payment", @@ -10339,6 +10340,7 @@ dependencies = [ "sp-runtime", "sp-version", "structopt", + "sub-tokens", "thiserror", "tokio 0.2.21", "westend-runtime", @@ -10448,6 +10450,14 @@ dependencies = [ "syn", ] +[[package]] +name = "sub-tokens" +version = "0.1.0" +source = "git+https://github.com/paritytech/substrate-debug-kit?branch=master#971b667963fdb0049dae349eaecbe22f4181e49f" +dependencies = [ + "separator", +] + [[package]] name = "substrate-bip39" version = "0.4.2" diff --git a/utils/staking-miner/Cargo.toml b/utils/staking-miner/Cargo.toml index 64f960af6a25..01910e547aa0 100644 --- a/utils/staking-miner/Cargo.toml +++ b/utils/staking-miner/Cargo.toml @@ -21,6 +21,7 @@ thiserror = "1.0.26" remote-externalities = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-npos-elections = { git = "https://github.com/paritytech/substrate", branch = "master" } @@ -32,6 +33,7 @@ frame-support = { git = "https://github.com/paritytech/substrate", branch = "mas frame-election-provider-support = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-election-provider-multi-phase = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-staking = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" } core-primitives = { package = "polkadot-core-primitives", path = "../../core-primitives" } @@ -41,5 +43,7 @@ polkadot-runtime = { path = "../../runtime/polkadot" } kusama-runtime = { path = "../../runtime/kusama" } westend-runtime = { path = "../../runtime/westend" } +sub-tokens = { git = "https://github.com/paritytech/substrate-debug-kit", branch = "master" } + [dev-dependencies] sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/utils/staking-miner/src/dry_run.rs b/utils/staking-miner/src/dry_run.rs index 254bbd4178bc..79190793f9e9 100644 --- a/utils/staking-miner/src/dry_run.rs +++ b/utils/staking-miner/src/dry_run.rs @@ -20,6 +20,7 @@ use crate::{ params, prelude::*, rpc_helpers::*, signer::Signer, DryRunConfig, Error, SharedConfig, WsClient, }; use codec::Encode; +use frame_support::traits::Currency; /// Forcefully create the snapshot. This can be used to compute the election at anytime. fn force_create_snapshot(ext: &mut Ext) -> Result<(), Error> { @@ -35,18 +36,53 @@ fn force_create_snapshot(ext: &mut Ext) -> Result<(), Error> { } /// Helper method to print the encoded size of the snapshot. -fn measure_snapshot_size(ext: &mut Ext) { +async fn print_info( + client: &WsClient, + ext: &mut Ext, + raw_solution: &EPM::RawSolution>, + extrinsic: sp_core::Bytes, +) where + ::Currency: Currency, +{ ext.execute_with(|| { - log::info!(target: LOG_TARGET, "Metadata: {:?}", >::snapshot_metadata()); log::info!( target: LOG_TARGET, - "Encoded Length: {:?}", + "Snapshot Metadata: {:?}", + >::snapshot_metadata() + ); + log::info!( + target: LOG_TARGET, + "Snapshot Encoded Length: {:?}", >::snapshot() .expect("snapshot must exist before calling `measure_snapshot_size`") .encode() .len() ); - }) + + let snapshot_size = + >::snapshot_metadata().expect("snapshot must exist by now; qed."); + let deposit = EPM::Pallet::::deposit_for(&raw_solution, snapshot_size); + log::info!( + target: LOG_TARGET, + "solution score {:?} / deposit {:?} / length {:?}", + &raw_solution.score.iter().map(|x| Token::from(*x)).collect::>(), + Token::from(deposit), + raw_solution.encode().len(), + ); + }); + + let info = rpc::>( + client, + "payment_queryInfo", + params! { extrinsic }, + ) + .await; + log::info!( + target: LOG_TARGET, + "payment_queryInfo: (fee = {}) {:?}", + info.as_ref().map(|d| Token::from(d.partial_fee)).unwrap_or(Token::from(0)), + info, + ); } /// Find the stake threshold in order to have at most `count` voters. @@ -76,24 +112,40 @@ macro_rules! dry_run_cmd_for { ($runtime:ident) => { paste::paste! { signer: Signer, ) -> Result<(), Error> { use $crate::[<$runtime _runtime_exports>]::*; - let mut ext = crate::create_election_ext::(shared.uri.clone(), config.at, true).await?; + let mut ext = crate::create_election_ext::( + shared.uri.clone(), + config.at, + vec!["Staking".to_string(), "System".to_string(), "Balances".to_string()] + ).await?; force_create_snapshot::(&mut ext)?; - measure_snapshot_size::(&mut ext); - let (raw_solution, witness) = crate::mine_unchecked::(&mut ext, config.iterations, false)?; - log::info!(target: LOG_TARGET, "mined solution with {:?}", &raw_solution.score); + let (raw_solution, witness) = crate::mine_unchecked::(&mut ext, config.iterations, false)?; let nonce = crate::get_account_info::(client, &signer.account, config.at) .await? .map(|i| i.nonce) .expect("signer account is checked to exist upon startup; it can only die if it \ - transfers funds out of it, or get slashed. If it does not exist at this point, \ - it is likely due to a bug, or the signer got slashed. Terminating." - ); + transfers funds out of it, or get slashed. If it does not exist at this point, \ + it is likely due to a bug, or the signer got slashed. Terminating." + ); let tip = 0 as Balance; let era = sp_runtime::generic::Era::Immortal; - let extrinsic = ext.execute_with(|| create_uxt(raw_solution, witness, signer.clone(), nonce, tip, era)); + let extrinsic = ext.execute_with(|| create_uxt(raw_solution.clone(), witness, signer.clone(), nonce, tip, era)); let bytes = sp_core::Bytes(extrinsic.encode().to_vec()); + print_info::(client, &mut ext, &raw_solution, bytes.clone()).await; + + let feasibility_result = ext.execute_with(|| { + EPM::Pallet::::feasibility_check(raw_solution.clone(), EPM::ElectionCompute::Signed) + }); + log::info!(target: LOG_TARGET, "feasibility result is {:?}", feasibility_result.map(|_| ())); + + let dispatch_result = ext.execute_with(|| { + // manually tweak the phase. + EPM::CurrentPhase::::put(EPM::Phase::Signed); + EPM::Pallet::::submit(frame_system::RawOrigin::Signed(signer.account).into(), Box::new(raw_solution), witness) + }); + log::info!(target: LOG_TARGET, "dispatch result is {:?}", dispatch_result); + let outcome = rpc_decode::(client, "system_dryRun", params!{ bytes }).await?; log::info!(target: LOG_TARGET, "dry-run outcome is {:?}", outcome); Ok(()) diff --git a/utils/staking-miner/src/emergency_solution.rs b/utils/staking-miner/src/emergency_solution.rs index 9cadef607503..ff7464c0c042 100644 --- a/utils/staking-miner/src/emergency_solution.rs +++ b/utils/staking-miner/src/emergency_solution.rs @@ -26,7 +26,7 @@ macro_rules! emergency_solution_cmd_for { ($runtime:ident) => { paste::paste! { shared: SharedConfig, ) -> Result<(), Error> { use $crate::[<$runtime _runtime_exports>]::*; - let mut ext = crate::create_election_ext::(shared.uri.clone(), None, false).await?; + let mut ext = crate::create_election_ext::(shared.uri.clone(), None, vec![]).await?; ext.execute_with(|| { assert!(EPM::Pallet::::current_phase().is_emergency()); // NOTE: this internally calls feasibility_check, but we just re-do it here as an easy way diff --git a/utils/staking-miner/src/main.rs b/utils/staking-miner/src/main.rs index 56718c9ea3ac..2aabe722150d 100644 --- a/utils/staking-miner/src/main.rs +++ b/utils/staking-miner/src/main.rs @@ -38,6 +38,7 @@ mod signer; pub(crate) use prelude::*; pub(crate) use signer::get_account_info; +use frame_support::traits::Get; use jsonrpsee_ws_client::{WsClient, WsClientBuilder}; use remote_externalities::{Builder, Mode, OnlineConfig}; use sp_runtime::traits::Block as BlockT; @@ -93,8 +94,9 @@ macro_rules! construct_runtime_prelude { let address = ::Lookup::unlookup(account.clone()); let extrinsic = UncheckedExtrinsic::new_signed(call, address, signature.into(), extra); log::debug!( - target: crate::LOG_TARGET, "constructed extrinsic {}", - sp_core::hexdisplay::HexDisplay::from(&extrinsic.encode()) + target: crate::LOG_TARGET, "constructed extrinsic {} with length {}", + sp_core::hexdisplay::HexDisplay::from(&extrinsic.encode()), + extrinsic.encode().len(), ); extrinsic } @@ -172,14 +174,17 @@ macro_rules! any_runtime { unsafe { match $crate::RUNTIME { $crate::AnyRuntime::Polkadot => { + #[allow(unused)] use $crate::polkadot_runtime_exports::*; $($code)* }, $crate::AnyRuntime::Kusama => { + #[allow(unused)] use $crate::kusama_runtime_exports::*; $($code)* }, $crate::AnyRuntime::Westend => { + #[allow(unused)] use $crate::westend_runtime_exports::*; $($code)* } @@ -201,6 +206,7 @@ enum Error { AccountDoesNotExists, IncorrectPhase, AlreadySubmitted, + VersionMismatch, } impl From for Error { @@ -270,14 +276,14 @@ struct DryRunConfig { #[derive(Debug, Clone, StructOpt)] struct SharedConfig { /// The `ws` node to connect to. - #[structopt(long, default_value = DEFAULT_URI)] + #[structopt(long, short, default_value = DEFAULT_URI)] uri: String, /// The file from which we read the account seed. /// /// WARNING: don't use an account with a large stash for this. Based on how the bot is /// configured, it might re-try lose funds through transaction fees/deposits. - #[structopt(long)] + #[structopt(long, short)] account_seed: std::path::PathBuf, } @@ -291,34 +297,25 @@ struct Opt { command: Command, } -/// Build the `Ext` at `hash` with all the data of `ElectionProviderMultiPhase` and `Staking` -/// stored. +/// Build the Ext at hash with all the data of `ElectionProviderMultiPhase` and any additional +/// pallets. async fn create_election_ext( uri: String, at: Option, - with_staking: bool, + additional: Vec, ) -> Result { use frame_support::{storage::generator::StorageMap, traits::PalletInfo}; use sp_core::hashing::twox_128; + let mut modules = vec![::PalletInfo::name::>() + .expect("Pallet always has name; qed.") + .to_string()]; + modules.extend(additional); Builder::::new() .mode(Mode::Online(OnlineConfig { transport: uri.into(), at, - modules: if with_staking { - vec![ - ::PalletInfo::name::>() - .expect("Pallet always has name; qed.") - .to_string(), - ::PalletInfo::name::>() - .expect("Pallet always has name; qed.") - .to_string(), - ] - } else { - vec![::PalletInfo::name::>() - .expect("Pallet always has name; qed.") - .to_string()] - }, + modules, ..Default::default() })) .inject_hashed_prefix(&>::prefix_hash()) @@ -386,6 +383,34 @@ fn mine_dpos(ext: &mut Ext) -> Result<(), Error> { }) } +pub(crate) async fn check_versions( + client: &WsClient, + print: bool, +) -> Result<(), Error> { + let linked_version = T::Version::get(); + let on_chain_version = rpc_helpers::rpc::( + client, + "state_getRuntimeVersion", + params! {}, + ) + .await + .expect("runtime version RPC should always work; qed"); + + if print { + log::info!(target: LOG_TARGET, "linked version {:?}", linked_version); + log::info!(target: LOG_TARGET, "on-chain version {:?}", on_chain_version); + } + if linked_version != on_chain_version { + log::error!( + target: LOG_TARGET, + "VERSION MISMATCH: any transaction will fail with bad-proof" + ); + Err(Error::VersionMismatch) + } else { + Ok(()) + } +} + #[tokio::main] async fn main() { env_logger::Builder::from_default_env() @@ -422,6 +447,8 @@ async fn main() { sp_core::crypto::set_default_ss58_version( sp_core::crypto::Ss58AddressFormat::PolkadotAccount, ); + sub_tokens::dynamic::set_name("DOT"); + sub_tokens::dynamic::set_decimal_points(10_000_000_000); // safety: this program will always be single threaded, thus accessing global static is // safe. unsafe { @@ -432,6 +459,8 @@ async fn main() { sp_core::crypto::set_default_ss58_version( sp_core::crypto::Ss58AddressFormat::KusamaAccount, ); + sub_tokens::dynamic::set_name("KSM"); + sub_tokens::dynamic::set_decimal_points(1_000_000_000_000); // safety: this program will always be single threaded, thus accessing global static is // safe. unsafe { @@ -442,6 +471,8 @@ async fn main() { sp_core::crypto::set_default_ss58_version( sp_core::crypto::Ss58AddressFormat::PolkadotAccount, ); + sub_tokens::dynamic::set_name("WND"); + sub_tokens::dynamic::set_decimal_points(1_000_000_000_000); // safety: this program will always be single threaded, thus accessing global static is // safe. unsafe { @@ -455,6 +486,10 @@ async fn main() { } log::info!(target: LOG_TARGET, "connected to chain {:?}", chain); + let _ = any_runtime! { + check_versions::(&client, true).await + }; + let signer_account = any_runtime! { signer::read_signer_uri::<_, Runtime>(&shared.account_seed, &client) .await @@ -464,7 +499,6 @@ async fn main() { let outcome = any_runtime! { match command.clone() { Command::Monitor(c) => monitor_cmd(&client, shared, c, signer_account).await, - // --------------------^^ comes from the macro prelude, needs no generic. Command::DryRun(c) => dry_run_cmd(&client, shared, c, signer_account).await, Command::EmergencySolution => emergency_solution_cmd(shared.clone()).await, } @@ -477,7 +511,6 @@ mod tests { use super::*; fn get_version() -> sp_version::RuntimeVersion { - use frame_support::traits::Get; T::Version::get() } diff --git a/utils/staking-miner/src/monitor.rs b/utils/staking-miner/src/monitor.rs index 64cbb126489a..f14054426f80 100644 --- a/utils/staking-miner/src/monitor.rs +++ b/utils/staking-miner/src/monitor.rs @@ -86,6 +86,9 @@ macro_rules! monitor_cmd_for { ($runtime:tt) => { paste::paste! { let hash = now.hash(); log::debug!(target: LOG_TARGET, "new event at #{:?} ({:?})", now.number, hash); + // if the runtime version has changed, terminate + crate::check_versions::(client, false).await?; + // we prefer doing this check before fetching anything into a remote-ext. if ensure_signed_phase::(client, hash).await.is_err() { log::debug!(target: LOG_TARGET, "phase closed, not interested in this block at all."); @@ -99,7 +102,7 @@ macro_rules! monitor_cmd_for { ($runtime:tt) => { paste::paste! { // this will not be a solution. // grab an externalities without staking, just the election snapshot. - let mut ext = crate::create_election_ext::(shared.uri.clone(), Some(hash), false).await?; + let mut ext = crate::create_election_ext::(shared.uri.clone(), Some(hash), vec![]).await?; if ensure_no_previous_solution::(&mut ext, &signer.account).await.is_err() { log::debug!(target: LOG_TARGET, "We already have a solution in this phase, skipping."); diff --git a/utils/staking-miner/src/prelude.rs b/utils/staking-miner/src/prelude.rs index 646cab444b18..e3c25887af2d 100644 --- a/utils/staking-miner/src/prelude.rs +++ b/utils/staking-miner/src/prelude.rs @@ -46,3 +46,6 @@ pub type Ext = sp_io::TestExternalities; /// The key pair type being used. We "strongly" assume sr25519 for simplicity. pub type Pair = sp_core::sr25519::Pair; + +/// A dynamic token type used to represent account balances. +pub type Token = sub_tokens::dynamic::DynamicToken; diff --git a/utils/staking-miner/src/signer.rs b/utils/staking-miner/src/signer.rs index d5a0d391b54f..9ced41a93e72 100644 --- a/utils/staking-miner/src/signer.rs +++ b/utils/staking-miner/src/signer.rs @@ -16,7 +16,7 @@ //! Wrappers around creating a signer account. -use crate::{rpc_helpers, AccountId, Error, Index, Pair, WsClient, LOG_TARGET}; +use crate::{prelude::*, rpc_helpers, AccountId, Error, Index, Pair, WsClient, LOG_TARGET}; use sp_core::crypto::Pair as _; use std::path::Path; @@ -54,7 +54,11 @@ pub(crate) async fn get_account_info( /// Read the signer account's URI from the given `path`. pub(crate) async fn read_signer_uri< P: AsRef, - T: frame_system::Config, + T: frame_system::Config< + AccountId = AccountId, + Index = Index, + AccountData = pallet_balances::AccountData, + >, >( path: P, client: &WsClient, @@ -69,6 +73,12 @@ pub(crate) async fn read_signer_uri< let _info = get_account_info::(&client, &account, None) .await? .ok_or(Error::AccountDoesNotExists)?; - log::info!(target: LOG_TARGET, "loaded account {:?}, info: {:?}", &account, _info); + log::info!( + target: LOG_TARGET, + "loaded account {:?}, free: {:?}, info: {:?}", + &account, + Token::from(_info.data.free), + _info + ); Ok(Signer { account, pair, uri: uri.to_string() }) }