mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 19:01:08 +00:00
Fix 5560: add support for a new staking-miner info command (#5577)
* Refactoring opts out * Implement info command fix #5560 * remove useless change * Remove unnecessary brackets * Fix and add tests * Promote the uri flag to global * Ignore lint identity ops * Reverse adding #[allow(identity_op)] * Add cli test for the info command * Add licende headers and fix some grumbles * Add retrieval of the linked version and make the json output optional * Fix tests * Keep it generic and renamed builtin into linked * Rebase master * Add runtimes compatibility information * Silence erroneous warning about unsafe * Fix spellcheck * Update utils/staking-miner/src/runtime_versions.rs Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
This commit is contained in:
@@ -43,4 +43,4 @@ westend-runtime = { path = "../../runtime/westend" }
|
||||
sub-tokens = { git = "https://github.com/paritytech/substrate-debug-kit", branch = "master" }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.2"
|
||||
assert_cmd = "2.0.4"
|
||||
|
||||
@@ -28,7 +28,7 @@ There are 2 options to build a staking-miner Docker image:
|
||||
### Building the injected image
|
||||
|
||||
First build the binary as documented [above](#building).
|
||||
You may then inject the binary into a Docker base image usingfrom the root of the Polkadot repository:
|
||||
You may then inject the binary into a Docker base image from the root of the Polkadot repository:
|
||||
```
|
||||
docker build -t staking-miner -f scripts/ci/dockerfiles/staking-miner/staking-miner_injected.Dockerfile target/release
|
||||
```
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
//! The dry-run command.
|
||||
|
||||
use crate::{prelude::*, rpc::*, signer::Signer, DryRunConfig, Error, SharedRpcClient};
|
||||
use crate::{opts::DryRunConfig, prelude::*, rpc::*, signer::Signer, Error, SharedRpcClient};
|
||||
use codec::Encode;
|
||||
use frame_support::traits::Currency;
|
||||
use sp_core::Bytes;
|
||||
|
||||
@@ -28,30 +28,34 @@
|
||||
//! development. It is intended to run this bot with a `restart = true` way, so that it reports it
|
||||
//! crash, but resumes work thereafter.
|
||||
|
||||
// Silence erroneous warning about unsafe not being required whereas it is
|
||||
// see https://github.com/rust-lang/rust/issues/49112
|
||||
#![allow(unused_unsafe)]
|
||||
|
||||
mod dry_run;
|
||||
mod emergency_solution;
|
||||
mod monitor;
|
||||
mod opts;
|
||||
mod prelude;
|
||||
mod rpc;
|
||||
mod runtime_versions;
|
||||
mod signer;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
pub(crate) use prelude::*;
|
||||
pub(crate) use signer::get_account_info;
|
||||
|
||||
use crate::opts::*;
|
||||
use clap::Parser;
|
||||
use frame_election_provider_support::NposSolver;
|
||||
use frame_support::traits::Get;
|
||||
use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
|
||||
use remote_externalities::{Builder, Mode, OnlineConfig};
|
||||
use rpc::{RpcApiClient, SharedRpcClient};
|
||||
use runtime_versions::RuntimeVersions;
|
||||
use sp_npos_elections::BalancingConfig;
|
||||
use sp_runtime::{traits::Block as BlockT, DeserializeOwned, Perbill};
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
use sp_runtime::{traits::Block as BlockT, DeserializeOwned};
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
pub(crate) enum AnyRuntime {
|
||||
Polkadot,
|
||||
Kusama,
|
||||
@@ -62,7 +66,6 @@ pub(crate) static mut RUNTIME: AnyRuntime = AnyRuntime::Polkadot;
|
||||
|
||||
macro_rules! construct_runtime_prelude {
|
||||
($runtime:ident) => { paste::paste! {
|
||||
#[allow(unused_import)]
|
||||
pub(crate) mod [<$runtime _runtime_exports>] {
|
||||
pub(crate) use crate::prelude::EPM;
|
||||
pub(crate) use [<$runtime _runtime>]::*;
|
||||
@@ -278,70 +281,6 @@ impl<T: EPM::Config> std::fmt::Display for Error<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
enum Command {
|
||||
/// Monitor for the phase being signed, then compute.
|
||||
Monitor(MonitorConfig),
|
||||
/// Just compute a solution now, and don't submit it.
|
||||
DryRun(DryRunConfig),
|
||||
/// Provide a solution that can be submitted to the chain as an emergency response.
|
||||
EmergencySolution(EmergencySolutionConfig),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
enum Solver {
|
||||
SeqPhragmen {
|
||||
#[clap(long, default_value = "10")]
|
||||
iterations: usize,
|
||||
},
|
||||
PhragMMS {
|
||||
#[clap(long, default_value = "10")]
|
||||
iterations: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Submission strategy to use.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
enum SubmissionStrategy {
|
||||
// Only submit if at the time, we are the best.
|
||||
IfLeading,
|
||||
// Always submit.
|
||||
Always,
|
||||
// Submit if we are leading, or if the solution that's leading is more that the given `Perbill`
|
||||
// better than us. This helps detect obviously fake solutions and still combat them.
|
||||
ClaimBetterThan(Perbill),
|
||||
}
|
||||
|
||||
/// Custom `impl` to parse `SubmissionStrategy` from CLI.
|
||||
///
|
||||
/// Possible options:
|
||||
/// * --submission-strategy if-leading: only submit if leading
|
||||
/// * --submission-strategy always: always submit
|
||||
/// * --submission-strategy "percent-better <percent>": submit if submission is `n` percent better.
|
||||
///
|
||||
impl FromStr for SubmissionStrategy {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.trim();
|
||||
|
||||
let res = if s == "if-leading" {
|
||||
Self::IfLeading
|
||||
} else if s == "always" {
|
||||
Self::Always
|
||||
} else if s.starts_with("percent-better ") {
|
||||
let percent: u32 = s[15..].parse().map_err(|e| format!("{:?}", e))?;
|
||||
Self::ClaimBetterThan(Perbill::from_percent(percent))
|
||||
} else {
|
||||
return Err(s.into())
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
frame_support::parameter_types! {
|
||||
/// Number of balancing iterations for a solution algorithm. Set based on the [`Solvers`] CLI
|
||||
/// config.
|
||||
@@ -349,87 +288,6 @@ frame_support::parameter_types! {
|
||||
pub static Balancing: Option<BalancingConfig> = Some( BalancingConfig { iterations: BalanceIterations::get(), tolerance: 0 } );
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
struct MonitorConfig {
|
||||
/// They type of event to listen to.
|
||||
///
|
||||
/// Typically, finalized is safer and there is no chance of anything going wrong, but it can be
|
||||
/// slower. It is recommended to use finalized, if the duration of the signed phase is longer
|
||||
/// than the the finality delay.
|
||||
#[clap(long, default_value = "head", possible_values = &["head", "finalized"])]
|
||||
listen: String,
|
||||
|
||||
/// The solver algorithm to use.
|
||||
#[clap(subcommand)]
|
||||
solver: Solver,
|
||||
|
||||
/// Submission strategy to use.
|
||||
///
|
||||
/// Possible options:
|
||||
///
|
||||
/// `--submission-strategy if-leading`: only submit if leading.
|
||||
///
|
||||
/// `--submission-strategy always`: always submit.
|
||||
///
|
||||
/// `--submission-strategy "percent-better <percent>"`: submit if the submission is `n` percent better.
|
||||
#[clap(long, parse(try_from_str), default_value = "if-leading")]
|
||||
submission_strategy: SubmissionStrategy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
struct EmergencySolutionConfig {
|
||||
/// The block hash at which scraping happens. If none is provided, the latest head is used.
|
||||
#[clap(long)]
|
||||
at: Option<Hash>,
|
||||
|
||||
/// The solver algorithm to use.
|
||||
#[clap(subcommand)]
|
||||
solver: Solver,
|
||||
|
||||
/// The number of top backed winners to take. All are taken, if not provided.
|
||||
take: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
struct DryRunConfig {
|
||||
/// The block hash at which scraping happens. If none is provided, the latest head is used.
|
||||
#[clap(long)]
|
||||
at: Option<Hash>,
|
||||
|
||||
/// The solver algorithm to use.
|
||||
#[clap(subcommand)]
|
||||
solver: Solver,
|
||||
|
||||
/// Force create a new snapshot, else expect one to exist onchain.
|
||||
#[clap(long)]
|
||||
force_snapshot: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[clap(author, version, about)]
|
||||
struct Opt {
|
||||
/// The `ws` node to connect to.
|
||||
#[clap(long, short, default_value = DEFAULT_URI, env = "URI")]
|
||||
uri: String,
|
||||
|
||||
/// The path to a file containing the seed of the account. If the file is not found, the seed is
|
||||
/// used as-is.
|
||||
///
|
||||
/// Can also be provided via the `SEED` environment variable.
|
||||
///
|
||||
/// WARNING: Don't use an account with a large stash for this. Based on how the bot is
|
||||
/// configured, it might re-try and lose funds through transaction fees/deposits.
|
||||
#[clap(long, short, env = "SEED")]
|
||||
seed_or_path: String,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
/// Build the Ext at hash with all the data of `ElectionProviderMultiPhase` and any additional
|
||||
/// pallets.
|
||||
async fn create_election_ext<T: EPM::Config, B: BlockT + DeserializeOwned>(
|
||||
@@ -582,7 +440,7 @@ pub(crate) async fn check_versions<T: frame_system::Config + EPM::Config>(
|
||||
async fn main() {
|
||||
fmt().with_env_filter(EnvFilter::from_default_env()).init();
|
||||
|
||||
let Opt { uri, seed_or_path, command } = Opt::parse();
|
||||
let Opt { uri, command } = Opt::parse();
|
||||
log::debug!(target: LOG_TARGET, "attempting to connect to {:?}", uri);
|
||||
|
||||
let rpc = loop {
|
||||
@@ -648,26 +506,51 @@ async fn main() {
|
||||
check_versions::<Runtime>(&rpc).await
|
||||
};
|
||||
|
||||
let signer_account = any_runtime! {
|
||||
signer::signer_uri_from_string::<Runtime>(&seed_or_path, &rpc)
|
||||
.await
|
||||
.expect("Provided account is invalid, terminating.")
|
||||
};
|
||||
|
||||
let outcome = any_runtime! {
|
||||
match command {
|
||||
Command::Monitor(cmd) => monitor_cmd(rpc, cmd, signer_account).await
|
||||
Command::Monitor(monitor_config) =>
|
||||
{
|
||||
let signer_account = any_runtime! {
|
||||
signer::signer_uri_from_string::<Runtime>(&monitor_config.seed_or_path , &rpc)
|
||||
.await
|
||||
.expect("Provided account is invalid, terminating.")
|
||||
};
|
||||
monitor_cmd(rpc, monitor_config, signer_account).await
|
||||
.map_err(|e| {
|
||||
log::error!(target: LOG_TARGET, "Monitor error: {:?}", e);
|
||||
}),
|
||||
Command::DryRun(cmd) => dry_run_cmd(rpc, cmd, signer_account).await
|
||||
})},
|
||||
Command::DryRun(dryrun_config) => {
|
||||
let signer_account = any_runtime! {
|
||||
signer::signer_uri_from_string::<Runtime>(&dryrun_config.seed_or_path , &rpc)
|
||||
.await
|
||||
.expect("Provided account is invalid, terminating.")
|
||||
};
|
||||
dry_run_cmd(rpc, dryrun_config, signer_account).await
|
||||
.map_err(|e| {
|
||||
log::error!(target: LOG_TARGET, "DryRun error: {:?}", e);
|
||||
}),
|
||||
Command::EmergencySolution(cmd) => emergency_solution_cmd(rpc, cmd).await
|
||||
})},
|
||||
Command::EmergencySolution(emergency_solution_config) =>
|
||||
emergency_solution_cmd(rpc, emergency_solution_config).await
|
||||
.map_err(|e| {
|
||||
log::error!(target: LOG_TARGET, "EmergencySolution error: {:?}", e);
|
||||
}),
|
||||
Command::Info(info_opts) => {
|
||||
let remote_runtime_version = rpc.runtime_version(None).await.expect("runtime_version infallible; qed.");
|
||||
|
||||
let builtin_version = any_runtime! {
|
||||
Version::get()
|
||||
};
|
||||
|
||||
let versions = RuntimeVersions::new(&remote_runtime_version, &builtin_version);
|
||||
|
||||
if !info_opts.json {
|
||||
println!("{}", versions);
|
||||
} else {
|
||||
let versions = serde_json::to_string_pretty(&versions).expect("Failed serializing version info");
|
||||
println!("{}", versions);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
log::info!(target: LOG_TARGET, "round of execution finished. outcome = {:?}", outcome);
|
||||
@@ -696,102 +579,4 @@ mod tests {
|
||||
assert_eq!(polkadot_version.spec_name, "polkadot".into());
|
||||
assert_eq!(kusama_version.spec_name, "kusama".into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_monitor_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"--seed-or-path",
|
||||
"//Alice",
|
||||
"monitor",
|
||||
"--listen",
|
||||
"head",
|
||||
"seq-phragmen",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
seed_or_path: "//Alice".to_string(),
|
||||
command: Command::Monitor(MonitorConfig {
|
||||
listen: "head".to_string(),
|
||||
solver: Solver::SeqPhragmen { iterations: 10 },
|
||||
submission_strategy: SubmissionStrategy::IfLeading,
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_dry_run_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"--seed-or-path",
|
||||
"//Alice",
|
||||
"dry-run",
|
||||
"phrag-mms",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
seed_or_path: "//Alice".to_string(),
|
||||
command: Command::DryRun(DryRunConfig {
|
||||
at: None,
|
||||
solver: Solver::PhragMMS { iterations: 10 },
|
||||
force_snapshot: false,
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_emergency_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"--seed-or-path",
|
||||
"//Alice",
|
||||
"emergency-solution",
|
||||
"99",
|
||||
"phrag-mms",
|
||||
"--iterations",
|
||||
"1337",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
seed_or_path: "//Alice".to_string(),
|
||||
command: Command::EmergencySolution(EmergencySolutionConfig {
|
||||
take: Some(99),
|
||||
at: None,
|
||||
solver: Solver::PhragMMS { iterations: 1337 }
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submission_strategy_from_str_works() {
|
||||
use std::str::FromStr;
|
||||
|
||||
assert_eq!(SubmissionStrategy::from_str("if-leading"), Ok(SubmissionStrategy::IfLeading));
|
||||
assert_eq!(SubmissionStrategy::from_str("always"), Ok(SubmissionStrategy::Always));
|
||||
assert_eq!(
|
||||
SubmissionStrategy::from_str(" percent-better 99 "),
|
||||
Ok(SubmissionStrategy::ClaimBetterThan(Perbill::from_percent(99)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::prelude::*;
|
||||
use clap::Parser;
|
||||
use sp_runtime::Perbill;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[clap(author, version, about)]
|
||||
pub(crate) struct Opt {
|
||||
/// The `ws` node to connect to.
|
||||
#[clap(long, short, default_value = DEFAULT_URI, env = "URI", global = true)]
|
||||
pub uri: String,
|
||||
|
||||
#[clap(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) enum Command {
|
||||
/// Monitor for the phase being signed, then compute.
|
||||
Monitor(MonitorConfig),
|
||||
|
||||
/// Just compute a solution now, and don't submit it.
|
||||
DryRun(DryRunConfig),
|
||||
|
||||
/// Provide a solution that can be submitted to the chain as an emergency response.
|
||||
EmergencySolution(EmergencySolutionConfig),
|
||||
|
||||
/// Return information about the current version
|
||||
Info(InfoOpts),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) struct MonitorConfig {
|
||||
/// The path to a file containing the seed of the account. If the file is not found, the seed is
|
||||
/// used as-is.
|
||||
///
|
||||
/// Can also be provided via the `SEED` environment variable.
|
||||
///
|
||||
/// WARNING: Don't use an account with a large stash for this. Based on how the bot is
|
||||
/// configured, it might re-try and lose funds through transaction fees/deposits.
|
||||
#[clap(long, short, env = "SEED")]
|
||||
pub seed_or_path: String,
|
||||
|
||||
/// They type of event to listen to.
|
||||
///
|
||||
/// Typically, finalized is safer and there is no chance of anything going wrong, but it can be
|
||||
/// slower. It is recommended to use finalized, if the duration of the signed phase is longer
|
||||
/// than the the finality delay.
|
||||
#[clap(long, default_value = "head", possible_values = &["head", "finalized"])]
|
||||
pub listen: String,
|
||||
|
||||
/// The solver algorithm to use.
|
||||
#[clap(subcommand)]
|
||||
pub solver: Solver,
|
||||
|
||||
/// Submission strategy to use.
|
||||
///
|
||||
/// Possible options:
|
||||
///
|
||||
/// `--submission-strategy if-leading`: only submit if leading.
|
||||
///
|
||||
/// `--submission-strategy always`: always submit.
|
||||
///
|
||||
/// `--submission-strategy "percent-better <percent>"`: submit if the submission is `n` percent better.
|
||||
#[clap(long, parse(try_from_str), default_value = "if-leading")]
|
||||
pub submission_strategy: SubmissionStrategy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) struct DryRunConfig {
|
||||
/// The path to a file containing the seed of the account. If the file is not found, the seed is
|
||||
/// used as-is.
|
||||
///
|
||||
/// Can also be provided via the `SEED` environment variable.
|
||||
///
|
||||
/// WARNING: Don't use an account with a large stash for this. Based on how the bot is
|
||||
/// configured, it might re-try and lose funds through transaction fees/deposits.
|
||||
#[clap(long, short, env = "SEED")]
|
||||
pub seed_or_path: String,
|
||||
|
||||
/// The block hash at which scraping happens. If none is provided, the latest head is used.
|
||||
#[clap(long)]
|
||||
pub at: Option<Hash>,
|
||||
|
||||
/// The solver algorithm to use.
|
||||
#[clap(subcommand)]
|
||||
pub solver: Solver,
|
||||
|
||||
/// Force create a new snapshot, else expect one to exist onchain.
|
||||
#[clap(long)]
|
||||
pub force_snapshot: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) struct EmergencySolutionConfig {
|
||||
/// The block hash at which scraping happens. If none is provided, the latest head is used.
|
||||
#[clap(long)]
|
||||
pub at: Option<Hash>,
|
||||
|
||||
/// The solver algorithm to use.
|
||||
#[clap(subcommand)]
|
||||
pub solver: Solver,
|
||||
|
||||
/// The number of top backed winners to take. All are taken, if not provided.
|
||||
pub take: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) struct InfoOpts {
|
||||
/// Serialize the output as json
|
||||
#[clap(long, short)]
|
||||
pub json: bool,
|
||||
}
|
||||
|
||||
/// Submission strategy to use.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum SubmissionStrategy {
|
||||
// Only submit if at the time, we are the best.
|
||||
IfLeading,
|
||||
// Always submit.
|
||||
Always,
|
||||
// Submit if we are leading, or if the solution that's leading is more that the given `Perbill`
|
||||
// better than us. This helps detect obviously fake solutions and still combat them.
|
||||
ClaimBetterThan(Perbill),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) enum Solver {
|
||||
SeqPhragmen {
|
||||
#[clap(long, default_value = "10")]
|
||||
iterations: usize,
|
||||
},
|
||||
PhragMMS {
|
||||
#[clap(long, default_value = "10")]
|
||||
iterations: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Custom `impl` to parse `SubmissionStrategy` from CLI.
|
||||
///
|
||||
/// Possible options:
|
||||
/// * --submission-strategy if-leading: only submit if leading
|
||||
/// * --submission-strategy always: always submit
|
||||
/// * --submission-strategy "percent-better <percent>": submit if submission is `n` percent better.
|
||||
///
|
||||
impl FromStr for SubmissionStrategy {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.trim();
|
||||
|
||||
let res = if s == "if-leading" {
|
||||
Self::IfLeading
|
||||
} else if s == "always" {
|
||||
Self::Always
|
||||
} else if s.starts_with("percent-better ") {
|
||||
let percent: u32 = s[15..].parse().map_err(|e| format!("{:?}", e))?;
|
||||
Self::ClaimBetterThan(Perbill::from_percent(percent))
|
||||
} else {
|
||||
return Err(s.into())
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_super {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cli_monitor_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"monitor",
|
||||
"--seed-or-path",
|
||||
"//Alice",
|
||||
"--listen",
|
||||
"head",
|
||||
"seq-phragmen",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
command: Command::Monitor(MonitorConfig {
|
||||
seed_or_path: "//Alice".to_string(),
|
||||
listen: "head".to_string(),
|
||||
solver: Solver::SeqPhragmen { iterations: 10 },
|
||||
submission_strategy: SubmissionStrategy::IfLeading,
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_dry_run_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"dry-run",
|
||||
"--seed-or-path",
|
||||
"//Alice",
|
||||
"phrag-mms",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
command: Command::DryRun(DryRunConfig {
|
||||
seed_or_path: "//Alice".to_string(),
|
||||
at: None,
|
||||
solver: Solver::PhragMMS { iterations: 10 },
|
||||
force_snapshot: false,
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_emergency_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"emergency-solution",
|
||||
"99",
|
||||
"phrag-mms",
|
||||
"--iterations",
|
||||
"1337",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
command: Command::EmergencySolution(EmergencySolutionConfig {
|
||||
take: Some(99),
|
||||
at: None,
|
||||
solver: Solver::PhragMMS { iterations: 1337 }
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_info_works() {
|
||||
let opt = Opt::try_parse_from([env!("CARGO_PKG_NAME"), "--uri", "hi", "info"]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt { uri: "hi".to_string(), command: Command::Info(InfoOpts { json: false }) }
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submission_strategy_from_str_works() {
|
||||
use std::str::FromStr;
|
||||
|
||||
assert_eq!(SubmissionStrategy::from_str("if-leading"), Ok(SubmissionStrategy::IfLeading));
|
||||
assert_eq!(SubmissionStrategy::from_str("always"), Ok(SubmissionStrategy::Always));
|
||||
assert_eq!(
|
||||
SubmissionStrategy::from_str(" percent-better 99 "),
|
||||
Ok(SubmissionStrategy::ClaimBetterThan(Perbill::from_percent(99)))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_version::RuntimeVersion;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub(crate) struct RuntimeWrapper<'a>(pub &'a RuntimeVersion);
|
||||
|
||||
impl<'a> fmt::Display for RuntimeWrapper<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let width = 16;
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
r#" impl_name : {impl_name:>width$}
|
||||
spec_name : {spec_name:>width$}
|
||||
spec_version : {spec_version:>width$}
|
||||
transaction_version : {transaction_version:>width$}
|
||||
impl_version : {impl_version:>width$}
|
||||
authoringVersion : {authoring_version:>width$}
|
||||
state_version : {state_version:>width$}"#,
|
||||
spec_name = self.0.spec_name.to_string(),
|
||||
impl_name = self.0.impl_name.to_string(),
|
||||
spec_version = self.0.spec_version,
|
||||
impl_version = self.0.impl_version,
|
||||
authoring_version = self.0.authoring_version,
|
||||
transaction_version = self.0.transaction_version,
|
||||
state_version = self.0.state_version,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a RuntimeVersion> for RuntimeWrapper<'a> {
|
||||
fn from(r: &'a RuntimeVersion) -> Self {
|
||||
RuntimeWrapper(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub(crate) struct RuntimeVersions<'a> {
|
||||
/// The `RuntimeVersion` linked in the staking-miner
|
||||
pub linked: RuntimeWrapper<'a>,
|
||||
|
||||
/// The `RuntimeVersion` reported by the node we connect to via RPC
|
||||
pub remote: RuntimeWrapper<'a>,
|
||||
|
||||
/// This `bool` reports whether both remote and linked `RuntimeVersion` are compatible
|
||||
/// and if the staking-miner is expected to work properly against the remote runtime
|
||||
compatible: bool,
|
||||
}
|
||||
|
||||
impl<'a> RuntimeVersions<'a> {
|
||||
pub fn new(
|
||||
remote_runtime_version: &'a RuntimeVersion,
|
||||
linked_runtime_version: &'a RuntimeVersion,
|
||||
) -> Self {
|
||||
Self {
|
||||
remote: remote_runtime_version.into(),
|
||||
linked: linked_runtime_version.into(),
|
||||
compatible: are_runtimes_compatible(remote_runtime_version, linked_runtime_version),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether runtimes are compatible. Currently we only support equality.
|
||||
fn are_runtimes_compatible(r1: &RuntimeVersion, r2: &RuntimeVersion) -> bool {
|
||||
r1 == r2
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for RuntimeVersions<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let _ = write!(f, "- linked:\n{}", self.linked);
|
||||
let _ = write!(f, "- remote :\n{}", self.remote);
|
||||
write!(f, "Compatible: {}", if self.compatible { "YES" } else { "NO" })
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use assert_cmd::{cargo::cargo_bin, Command};
|
||||
use serde_json::{Result, Value};
|
||||
|
||||
#[test]
|
||||
fn cli_version_works() {
|
||||
@@ -10,3 +11,23 @@ fn cli_version_works() {
|
||||
|
||||
assert_eq!(version, format!("{} {}", crate_name, env!("CARGO_PKG_VERSION")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_info_works() {
|
||||
let crate_name = env!("CARGO_PKG_NAME");
|
||||
let output = Command::new(cargo_bin(crate_name))
|
||||
.arg("info")
|
||||
.arg("--json")
|
||||
.env("RUST_LOG", "none")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success(), "command returned with non-success exit code");
|
||||
let info = String::from_utf8_lossy(&output.stdout).trim().to_owned();
|
||||
let v: Result<Value> = serde_json::from_str(&info);
|
||||
let v = v.unwrap();
|
||||
assert!(!v["builtin"].to_string().is_empty());
|
||||
assert!(!v["builtin"]["spec_name"].to_string().is_empty());
|
||||
assert!(!v["builtin"]["spec_version"].to_string().is_empty());
|
||||
assert!(!v["remote"].to_string().is_empty());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user