mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-21 06:21:01 +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" }
|
sub-tokens = { git = "https://github.com/paritytech/substrate-debug-kit", branch = "master" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[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
|
### Building the injected image
|
||||||
|
|
||||||
First build the binary as documented [above](#building).
|
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
|
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.
|
//! 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 codec::Encode;
|
||||||
use frame_support::traits::Currency;
|
use frame_support::traits::Currency;
|
||||||
use sp_core::Bytes;
|
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
|
//! development. It is intended to run this bot with a `restart = true` way, so that it reports it
|
||||||
//! crash, but resumes work thereafter.
|
//! 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 dry_run;
|
||||||
mod emergency_solution;
|
mod emergency_solution;
|
||||||
mod monitor;
|
mod monitor;
|
||||||
|
mod opts;
|
||||||
mod prelude;
|
mod prelude;
|
||||||
mod rpc;
|
mod rpc;
|
||||||
|
mod runtime_versions;
|
||||||
mod signer;
|
mod signer;
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
pub(crate) use prelude::*;
|
pub(crate) use prelude::*;
|
||||||
pub(crate) use signer::get_account_info;
|
pub(crate) use signer::get_account_info;
|
||||||
|
|
||||||
|
use crate::opts::*;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use frame_election_provider_support::NposSolver;
|
use frame_election_provider_support::NposSolver;
|
||||||
use frame_support::traits::Get;
|
use frame_support::traits::Get;
|
||||||
use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
|
use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
|
||||||
use remote_externalities::{Builder, Mode, OnlineConfig};
|
use remote_externalities::{Builder, Mode, OnlineConfig};
|
||||||
use rpc::{RpcApiClient, SharedRpcClient};
|
use rpc::{RpcApiClient, SharedRpcClient};
|
||||||
|
use runtime_versions::RuntimeVersions;
|
||||||
use sp_npos_elections::BalancingConfig;
|
use sp_npos_elections::BalancingConfig;
|
||||||
use sp_runtime::{traits::Block as BlockT, DeserializeOwned, Perbill};
|
use sp_runtime::{traits::Block as BlockT, DeserializeOwned};
|
||||||
use tracing_subscriber::{fmt, EnvFilter};
|
|
||||||
|
|
||||||
use std::{ops::Deref, sync::Arc};
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
pub(crate) enum AnyRuntime {
|
pub(crate) enum AnyRuntime {
|
||||||
Polkadot,
|
Polkadot,
|
||||||
Kusama,
|
Kusama,
|
||||||
@@ -62,7 +66,6 @@ pub(crate) static mut RUNTIME: AnyRuntime = AnyRuntime::Polkadot;
|
|||||||
|
|
||||||
macro_rules! construct_runtime_prelude {
|
macro_rules! construct_runtime_prelude {
|
||||||
($runtime:ident) => { paste::paste! {
|
($runtime:ident) => { paste::paste! {
|
||||||
#[allow(unused_import)]
|
|
||||||
pub(crate) mod [<$runtime _runtime_exports>] {
|
pub(crate) mod [<$runtime _runtime_exports>] {
|
||||||
pub(crate) use crate::prelude::EPM;
|
pub(crate) use crate::prelude::EPM;
|
||||||
pub(crate) use [<$runtime _runtime>]::*;
|
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! {
|
frame_support::parameter_types! {
|
||||||
/// Number of balancing iterations for a solution algorithm. Set based on the [`Solvers`] CLI
|
/// Number of balancing iterations for a solution algorithm. Set based on the [`Solvers`] CLI
|
||||||
/// config.
|
/// config.
|
||||||
@@ -349,87 +288,6 @@ frame_support::parameter_types! {
|
|||||||
pub static Balancing: Option<BalancingConfig> = Some( BalancingConfig { iterations: BalanceIterations::get(), tolerance: 0 } );
|
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
|
/// Build the Ext at hash with all the data of `ElectionProviderMultiPhase` and any additional
|
||||||
/// pallets.
|
/// pallets.
|
||||||
async fn create_election_ext<T: EPM::Config, B: BlockT + DeserializeOwned>(
|
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() {
|
async fn main() {
|
||||||
fmt().with_env_filter(EnvFilter::from_default_env()).init();
|
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);
|
log::debug!(target: LOG_TARGET, "attempting to connect to {:?}", uri);
|
||||||
|
|
||||||
let rpc = loop {
|
let rpc = loop {
|
||||||
@@ -648,26 +506,51 @@ async fn main() {
|
|||||||
check_versions::<Runtime>(&rpc).await
|
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! {
|
let outcome = any_runtime! {
|
||||||
match command {
|
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| {
|
.map_err(|e| {
|
||||||
log::error!(target: LOG_TARGET, "Monitor error: {:?}", 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| {
|
.map_err(|e| {
|
||||||
log::error!(target: LOG_TARGET, "DryRun error: {:?}", 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| {
|
.map_err(|e| {
|
||||||
log::error!(target: LOG_TARGET, "EmergencySolution error: {:?}", 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);
|
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!(polkadot_version.spec_name, "polkadot".into());
|
||||||
assert_eq!(kusama_version.spec_name, "kusama".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 assert_cmd::{cargo::cargo_bin, Command};
|
||||||
|
use serde_json::{Result, Value};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_version_works() {
|
fn cli_version_works() {
|
||||||
@@ -10,3 +11,23 @@ fn cli_version_works() {
|
|||||||
|
|
||||||
assert_eq!(version, format!("{} {}", crate_name, env!("CARGO_PKG_VERSION")));
|
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