forked from ghostchain/ghost-node
		
	rustfmt staking-miner and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
		
							parent
							
								
									e21ac88235
								
							
						
					
					
						commit
						ce26787a11
					
				@ -1,6 +1,6 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "ghost-miner"
 | 
					name = "ghost-miner"
 | 
				
			||||||
version = "1.5.0"
 | 
					version = "1.5.1"
 | 
				
			||||||
description = "A tool to submit NPoS election solutions for Ghost and Casper Network"
 | 
					description = "A tool to submit NPoS election solutions for Ghost and Casper Network"
 | 
				
			||||||
license.workspace = true
 | 
					license.workspace = true
 | 
				
			||||||
authors.workspace = true
 | 
					authors.workspace = true
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ use subxt::backend::rpc::RpcClient as RawRpcClient;
 | 
				
			|||||||
#[derive(Clone, Debug)]
 | 
					#[derive(Clone, Debug)]
 | 
				
			||||||
pub struct Client {
 | 
					pub struct Client {
 | 
				
			||||||
    /// Access to typed rpc calls from subxt.
 | 
					    /// Access to typed rpc calls from subxt.
 | 
				
			||||||
    rpc:: RpcClient,
 | 
					    rpc: RpcClient,
 | 
				
			||||||
    /// Access to chain APIs such as storage, events etc.
 | 
					    /// Access to chain APIs such as storage, events etc.
 | 
				
			||||||
    chain_api: ChainClient,
 | 
					    chain_api: ChainClient,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -30,13 +30,16 @@ impl Client {
 | 
				
			|||||||
                        "failed to connect to client due to {:?}, retrying soon...",
 | 
					                        "failed to connect to client due to {:?}, retrying soon...",
 | 
				
			||||||
                        e
 | 
					                        e
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                },
 | 
					                }
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            tokio::time::sleep(std::time::Duration::from_millis(2_500)).await;
 | 
					            tokio::time::sleep(std::time::Duration::from_millis(2_500)).await;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let chain_api = ChainClient::from_rpc_client(rpc.clone()).await?;
 | 
					        let chain_api = ChainClient::from_rpc_client(rpc.clone()).await?;
 | 
				
			||||||
        Ok(Self { rpc: RpcClient::new(rpc), chain_api })
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            rpc: RpcClient::new(rpc),
 | 
				
			||||||
 | 
					            chain_api,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Get a reference to the RPC interface exposed by subxt.
 | 
					    /// Get a reference to the RPC interface exposed by subxt.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										122
									
								
								utils/staking-miner/src/commands/dry_run.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								utils/staking-miner/src/commands/dry_run.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    client::Client,
 | 
				
			||||||
 | 
					    epm,
 | 
				
			||||||
 | 
					    error::Error,
 | 
				
			||||||
 | 
					    helpers::{signer_from_seed_or_path, storage_at},
 | 
				
			||||||
 | 
					    opt::Solver,
 | 
				
			||||||
 | 
					    prelude::*,
 | 
				
			||||||
 | 
					    static_types,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use clap::Parser;
 | 
				
			||||||
 | 
					use codec::Encode;
 | 
				
			||||||
 | 
					use pallet_election_provider_multi_phase::RawSolution;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Parser)]
 | 
				
			||||||
 | 
					#[cfg_attr(test, derive(PartialEq))]
 | 
				
			||||||
 | 
					pub struct DryRunConfig {
 | 
				
			||||||
 | 
					    /// 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,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The number of winners to take, instead of the `desired_targets` in snapshot.
 | 
				
			||||||
 | 
					    // Doing this would cause the dry-run to typically fail, but that's fine, the program should
 | 
				
			||||||
 | 
					    // still print out some score, and that should be it.
 | 
				
			||||||
 | 
					    #[clap(long)]
 | 
				
			||||||
 | 
					    pub force_winner_count: Option<u32>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The path to a file containing the seed of the account. If the file is not found, the seed is
 | 
				
			||||||
 | 
					    /// used as-is. If this is not provided, we won't attempt to submit anything.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// 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: Option<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn dry_run_cmd<T>(client: Client, config: DryRunConfig) -> Result<(), Error>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    T: MinerConfig<AccountId = AccountId, MaxVotesPerVoter = static_types::MaxVotesPerVoter>
 | 
				
			||||||
 | 
					        + Send
 | 
				
			||||||
 | 
					        + Sync
 | 
				
			||||||
 | 
					        + 'static,
 | 
				
			||||||
 | 
					    T::Solution: Send,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let storage = storage_at(config.at, client.chain_api()).await?;
 | 
				
			||||||
 | 
					    let round = storage
 | 
				
			||||||
 | 
					        .fetch_or_default(&runtime::storage().election_provider_multi_phase().round())
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let miner_solution = epm::fetch_snapshot_and_mine_solution::<T>(
 | 
				
			||||||
 | 
					        client.chain_api(),
 | 
				
			||||||
 | 
					        config.at,
 | 
				
			||||||
 | 
					        config.solver,
 | 
				
			||||||
 | 
					        round,
 | 
				
			||||||
 | 
					        config.force_winner_count,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let solution = miner_solution.solution();
 | 
				
			||||||
 | 
					    let score = miner_solution.score();
 | 
				
			||||||
 | 
					    let raw_solution = RawSolution {
 | 
				
			||||||
 | 
					        solution,
 | 
				
			||||||
 | 
					        score,
 | 
				
			||||||
 | 
					        round,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log::info!(
 | 
				
			||||||
 | 
					        target: LOG_TARGET,
 | 
				
			||||||
 | 
					        "solution score {:?} / length {:?}",
 | 
				
			||||||
 | 
					        score,
 | 
				
			||||||
 | 
					        raw_solution.encode().len(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Now we've logged the score, check whether the solution makes sense. No point doing this
 | 
				
			||||||
 | 
					    // if force_winner_count is selected since it'll definitely fail in that case.
 | 
				
			||||||
 | 
					    if config.force_winner_count.is_none() {
 | 
				
			||||||
 | 
					        miner_solution.feasibility_check()?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If an account seed or path is provided, then do a dry run to the node. Otherwise,
 | 
				
			||||||
 | 
					    // we've logged the solution above and we do nothing else.
 | 
				
			||||||
 | 
					    if let Some(seed_or_path) = &config.seed_or_path {
 | 
				
			||||||
 | 
					        let signer = signer_from_seed_or_path(seed_or_path)?;
 | 
				
			||||||
 | 
					        let account_info = storage
 | 
				
			||||||
 | 
					            .fetch(
 | 
				
			||||||
 | 
					                &runtime::storage()
 | 
				
			||||||
 | 
					                    .system()
 | 
				
			||||||
 | 
					                    .account(signer.public_key().to_account_id()),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?
 | 
				
			||||||
 | 
					            .ok_or(Error::AccountDoesNotExists)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer.public_key().to_account_id(), account_info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let nonce = client
 | 
				
			||||||
 | 
					            .rpc()
 | 
				
			||||||
 | 
					            .system_account_next_index(&signer.public_key().to_account_id())
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        let tx = epm::signed_solution(raw_solution)?;
 | 
				
			||||||
 | 
					        let xt = client.chain_api().tx().create_signed_with_nonce(
 | 
				
			||||||
 | 
					            &tx,
 | 
				
			||||||
 | 
					            &signer,
 | 
				
			||||||
 | 
					            nonce,
 | 
				
			||||||
 | 
					            Default::default(),
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        let dry_run_bytes = client.rpc().dry_run(xt.encoded(), config.at).await?;
 | 
				
			||||||
 | 
					        let dry_run_result = dry_run_bytes.into_dry_run_result(&client.chain_api().metadata())?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log::info!(target: LOG_TARGET, "dry-run outcome is {:?}", dry_run_result);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								utils/staking-miner/src/commands/emergency_solution.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								utils/staking-miner/src/commands/emergency_solution.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    client::Client, epm, error::Error, helpers::storage_at, opt::Solver, prelude::*, static_types,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use clap::Parser;
 | 
				
			||||||
 | 
					use codec::Encode;
 | 
				
			||||||
 | 
					use sp_core::hexdisplay::HexDisplay;
 | 
				
			||||||
 | 
					use std::io::Write;
 | 
				
			||||||
 | 
					use subxt::tx::TxPayload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Parser)]
 | 
				
			||||||
 | 
					#[cfg_attr(test, derive(PartialEq))]
 | 
				
			||||||
 | 
					pub 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 instead. All are taken, if not provided.
 | 
				
			||||||
 | 
					    pub force_winner_count: Option<u32>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn emergency_solution_cmd<T>(
 | 
				
			||||||
 | 
					    client: Client,
 | 
				
			||||||
 | 
					    config: EmergencySolutionConfig,
 | 
				
			||||||
 | 
					) -> Result<(), Error>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    T: MinerConfig<AccountId = AccountId, MaxVotesPerVoter = static_types::MaxVotesPerVoter>
 | 
				
			||||||
 | 
					        + Send
 | 
				
			||||||
 | 
					        + Sync
 | 
				
			||||||
 | 
					        + 'static,
 | 
				
			||||||
 | 
					    T::Solution: Send,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if let Some(max_winners) = config.force_winner_count {
 | 
				
			||||||
 | 
					        static_types::MaxWinners::set(max_winners);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let storage = storage_at(config.at, client.chain_api()).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let round = storage
 | 
				
			||||||
 | 
					        .fetch_or_default(&runtime::storage().election_provider_multi_phase().round())
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let miner_solution = epm::fetch_snapshot_and_mine_solution::<T>(
 | 
				
			||||||
 | 
					        client.chain_api(),
 | 
				
			||||||
 | 
					        config.at,
 | 
				
			||||||
 | 
					        config.solver,
 | 
				
			||||||
 | 
					        round,
 | 
				
			||||||
 | 
					        config.force_winner_count,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let ready_solution = miner_solution.feasibility_check()?;
 | 
				
			||||||
 | 
					    let encoded_size = ready_solution.encoded_size();
 | 
				
			||||||
 | 
					    let score = ready_solution.score;
 | 
				
			||||||
 | 
					    let mut supports: Vec<_> = ready_solution.supports.into_inner();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // maybe truncate.
 | 
				
			||||||
 | 
					    if let Some(force_winner_count) = config.force_winner_count {
 | 
				
			||||||
 | 
					        log::info!(
 | 
				
			||||||
 | 
					            target: LOG_TARGET,
 | 
				
			||||||
 | 
					            "truncating {} winners to {}",
 | 
				
			||||||
 | 
					            supports.len(),
 | 
				
			||||||
 | 
					            force_winner_count
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        supports.sort_unstable_by_key(|(_, s)| s.total);
 | 
				
			||||||
 | 
					        supports.truncate(force_winner_count as usize);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let call = epm::set_emergency_result(supports.clone())?;
 | 
				
			||||||
 | 
					    let encoded_call = call.encode_call_data(&client.chain_api().metadata())?;
 | 
				
			||||||
 | 
					    let encoded_supports = supports.encode();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // write results to files.
 | 
				
			||||||
 | 
					    let mut supports_file = std::fs::File::create("solution.supports.bin")?;
 | 
				
			||||||
 | 
					    let mut encoded_call_file = std::fs::File::create("encoded.call")?;
 | 
				
			||||||
 | 
					    supports_file.write_all(&encoded_supports)?;
 | 
				
			||||||
 | 
					    encoded_call_file.write_all(&encoded_call)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let hex = HexDisplay::from(&encoded_call);
 | 
				
			||||||
 | 
					    log::info!(target: LOG_TARGET, "Hex call:\n {:?}", hex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log::info!(target: LOG_TARGET, "Use the hex encoded call above to construct the governance proposal or the extrinsic to submit.");
 | 
				
			||||||
 | 
					    log::info!(target: LOG_TARGET, "ReadySolution: size {:?} / score = {:?}", encoded_size, score);
 | 
				
			||||||
 | 
					    log::info!(target: LOG_TARGET, "`set_emergency_result` encoded call written to ./encoded.call");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								utils/staking-miner/src/commands/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								utils/staking-miner/src/commands/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					pub mod dry_run;
 | 
				
			||||||
 | 
					pub mod emergency_solution;
 | 
				
			||||||
 | 
					pub mod monitor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use dry_run::{dry_run_cmd, DryRunConfig};
 | 
				
			||||||
 | 
					pub use emergency_solution::{emergency_solution_cmd, EmergencySolutionConfig};
 | 
				
			||||||
 | 
					pub use monitor::{monitor_cmd, MonitorConfig};
 | 
				
			||||||
							
								
								
									
										858
									
								
								utils/staking-miner/src/commands/monitor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										858
									
								
								utils/staking-miner/src/commands/monitor.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,858 @@
 | 
				
			|||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    client::Client,
 | 
				
			||||||
 | 
					    epm,
 | 
				
			||||||
 | 
					    error::Error,
 | 
				
			||||||
 | 
					    helpers::{kill_main_task_if_critical_err, signer_from_seed_or_path, TimedFuture},
 | 
				
			||||||
 | 
					    opt::Solver,
 | 
				
			||||||
 | 
					    prelude::*,
 | 
				
			||||||
 | 
					    prometheus, static_types,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use clap::Parser;
 | 
				
			||||||
 | 
					use codec::{Decode, Encode};
 | 
				
			||||||
 | 
					use frame_election_provider_support::NposSolution;
 | 
				
			||||||
 | 
					use futures::future::TryFutureExt;
 | 
				
			||||||
 | 
					use jsonrpsee::core::Error as JsonRpseeError;
 | 
				
			||||||
 | 
					use pallet_election_provider_multi_phase::{RawSolution, SolutionOf};
 | 
				
			||||||
 | 
					use sp_runtime::Perbill;
 | 
				
			||||||
 | 
					use std::{str::FromStr, sync::Arc};
 | 
				
			||||||
 | 
					use subxt::{
 | 
				
			||||||
 | 
					    backend::{legacy::rpc_methods::DryRunResult, rpc::RpcSubscription},
 | 
				
			||||||
 | 
					    config::{DefaultExtrinsicParamsBuilder, Header as _},
 | 
				
			||||||
 | 
					    error::RpcError,
 | 
				
			||||||
 | 
					    tx::{TxInBlock, TxProgress},
 | 
				
			||||||
 | 
					    Error as SubxtError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use tokio::sync::Mutex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Parser)]
 | 
				
			||||||
 | 
					#[cfg_attr(test, derive(PartialEq))]
 | 
				
			||||||
 | 
					pub 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, value_enum, default_value_t = Listen::Finalized)]
 | 
				
			||||||
 | 
					    pub listen: Listen,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// 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.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// `--submission-strategy "no-worse-than  <percent>"`: submit if submission is no more than `n` percent worse.
 | 
				
			||||||
 | 
					    #[clap(long, value_parser, default_value = "if-leading")]
 | 
				
			||||||
 | 
					    pub submission_strategy: SubmissionStrategy,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// 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,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Delay in number seconds to wait until starting mining a solution.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// At every block when a solution is attempted
 | 
				
			||||||
 | 
					    /// a delay can be enforced to avoid submitting at
 | 
				
			||||||
 | 
					    /// "same time" and risk potential races with other miners.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// When this is enabled and there are competing solutions, your solution might not be submitted
 | 
				
			||||||
 | 
					    /// if the scores are equal.
 | 
				
			||||||
 | 
					    #[clap(long, default_value_t = 0)]
 | 
				
			||||||
 | 
					    pub delay: usize,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Verify the submission by `dry-run` the extrinsic to check the validity.
 | 
				
			||||||
 | 
					    /// If the extrinsic is invalid then the submission is ignored and the next block will attempted again.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// This requires a RPC endpoint that exposes unsafe RPC methods, if the RPC endpoint doesn't expose unsafe RPC methods
 | 
				
			||||||
 | 
					    /// then the miner will be terminated.
 | 
				
			||||||
 | 
					    #[clap(long)]
 | 
				
			||||||
 | 
					    pub dry_run: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The 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.
 | 
				
			||||||
 | 
					#[cfg_attr(test, derive(PartialEq))]
 | 
				
			||||||
 | 
					#[derive(clap::ValueEnum, Debug, Copy, Clone)]
 | 
				
			||||||
 | 
					pub enum Listen {
 | 
				
			||||||
 | 
					    /// Latest finalized head of the canonical chain.
 | 
				
			||||||
 | 
					    Finalized,
 | 
				
			||||||
 | 
					    /// Latest head of the canonical chain.
 | 
				
			||||||
 | 
					    Head,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Submission strategy to use.
 | 
				
			||||||
 | 
					#[derive(Debug, Copy, Clone)]
 | 
				
			||||||
 | 
					#[cfg_attr(test, derive(PartialEq))]
 | 
				
			||||||
 | 
					pub enum SubmissionStrategy {
 | 
				
			||||||
 | 
					    /// 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.
 | 
				
			||||||
 | 
					    /// Only submit if at the time, we are the best (or equal to it).
 | 
				
			||||||
 | 
					    IfLeading,
 | 
				
			||||||
 | 
					    /// Submit if we are no worse than `Perbill` worse than the best.
 | 
				
			||||||
 | 
					    ClaimNoWorseThan(Perbill),
 | 
				
			||||||
 | 
					    /// 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 let Some(percent) = s.strip_prefix("no-worse-than ") {
 | 
				
			||||||
 | 
					            let percent: u32 = percent.parse().map_err(|e| format!("{:?}", e))?;
 | 
				
			||||||
 | 
					            Self::ClaimNoWorseThan(Perbill::from_percent(percent))
 | 
				
			||||||
 | 
					        } else if let Some(percent) = s.strip_prefix("percent-better ") {
 | 
				
			||||||
 | 
					            let percent: u32 = percent.parse().map_err(|e| format!("{:?}", e))?;
 | 
				
			||||||
 | 
					            Self::ClaimBetterThan(Perbill::from_percent(percent))
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return Err(s.into());
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(res)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn monitor_cmd<T>(client: Client, config: MonitorConfig) -> Result<(), Error>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    T: MinerConfig<AccountId = AccountId, MaxVotesPerVoter = static_types::MaxVotesPerVoter>
 | 
				
			||||||
 | 
					        + Send
 | 
				
			||||||
 | 
					        + Sync
 | 
				
			||||||
 | 
					        + 'static,
 | 
				
			||||||
 | 
					    T::Solution: Send,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let signer = signer_from_seed_or_path(&config.seed_or_path)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let account_info = {
 | 
				
			||||||
 | 
					        let addr = runtime::storage()
 | 
				
			||||||
 | 
					            .system()
 | 
				
			||||||
 | 
					            .account(signer.public_key().to_account_id());
 | 
				
			||||||
 | 
					        client
 | 
				
			||||||
 | 
					            .chain_api()
 | 
				
			||||||
 | 
					            .storage()
 | 
				
			||||||
 | 
					            .at_latest()
 | 
				
			||||||
 | 
					            .await?
 | 
				
			||||||
 | 
					            .fetch(&addr)
 | 
				
			||||||
 | 
					            .await?
 | 
				
			||||||
 | 
					            .ok_or(Error::AccountDoesNotExists)?
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer.public_key().to_account_id(), account_info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if config.dry_run {
 | 
				
			||||||
 | 
					        // if we want to try-run, ensure the node supports it.
 | 
				
			||||||
 | 
					        dry_run_works(client.rpc()).await?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut subscription = heads_subscription(client.rpc(), config.listen).await?;
 | 
				
			||||||
 | 
					    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<Error>();
 | 
				
			||||||
 | 
					    let submit_lock = Arc::new(Mutex::new(()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    loop {
 | 
				
			||||||
 | 
					        let at = tokio::select! {
 | 
				
			||||||
 | 
					            maybe_rp = subscription.next() => {
 | 
				
			||||||
 | 
					                match maybe_rp {
 | 
				
			||||||
 | 
					                    Some(Ok(r)) => r,
 | 
				
			||||||
 | 
					                    Some(Err(e)) => {
 | 
				
			||||||
 | 
					                        log::error!(target: LOG_TARGET, "subscription failed to decode Header {:?}, this is bug please file an issue", e);
 | 
				
			||||||
 | 
					                        return Err(e.into());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    // The subscription was dropped, should only happen if:
 | 
				
			||||||
 | 
					                    //	- the connection was closed.
 | 
				
			||||||
 | 
					                    //	- the subscription could not keep up with the server.
 | 
				
			||||||
 | 
					                    None => {
 | 
				
			||||||
 | 
					                        log::warn!(target: LOG_TARGET, "subscription to `{:?}` terminated. Retrying..", config.listen);
 | 
				
			||||||
 | 
					                        subscription = heads_subscription(client.rpc(), config.listen).await?;
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            maybe_err = rx.recv() => {
 | 
				
			||||||
 | 
					                match maybe_err {
 | 
				
			||||||
 | 
					                    Some(err) => return Err(err),
 | 
				
			||||||
 | 
					                    None => unreachable!("at least one sender kept in the main loop should always return Some; qed"),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Spawn task and non-recoverable errors are sent back to the main task
 | 
				
			||||||
 | 
					        // such as if the connection has been closed.
 | 
				
			||||||
 | 
					        let tx2 = tx.clone();
 | 
				
			||||||
 | 
					        let client2 = client.clone();
 | 
				
			||||||
 | 
					        let signer2 = signer.clone();
 | 
				
			||||||
 | 
					        let config2 = config.clone();
 | 
				
			||||||
 | 
					        let submit_lock2 = submit_lock.clone();
 | 
				
			||||||
 | 
					        tokio::spawn(async move {
 | 
				
			||||||
 | 
					            if let Err(err) =
 | 
				
			||||||
 | 
					                mine_and_submit_solution::<T>(at, client2, signer2, config2, submit_lock2).await
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                kill_main_task_if_critical_err(&tx2, err)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let account_info = client
 | 
				
			||||||
 | 
					            .chain_api()
 | 
				
			||||||
 | 
					            .storage()
 | 
				
			||||||
 | 
					            .at_latest()
 | 
				
			||||||
 | 
					            .await?
 | 
				
			||||||
 | 
					            .fetch(
 | 
				
			||||||
 | 
					                &runtime::storage()
 | 
				
			||||||
 | 
					                    .system()
 | 
				
			||||||
 | 
					                    .account(signer.public_key().to_account_id()),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?
 | 
				
			||||||
 | 
					            .ok_or(Error::AccountDoesNotExists)?;
 | 
				
			||||||
 | 
					        // this is lossy but fine for now.
 | 
				
			||||||
 | 
					        prometheus::set_balance(account_info.data.free as f64);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Construct extrinsic at given block and watch it.
 | 
				
			||||||
 | 
					async fn mine_and_submit_solution<T>(
 | 
				
			||||||
 | 
					    at: Header,
 | 
				
			||||||
 | 
					    client: Client,
 | 
				
			||||||
 | 
					    signer: Signer,
 | 
				
			||||||
 | 
					    config: MonitorConfig,
 | 
				
			||||||
 | 
					    submit_lock: Arc<Mutex<()>>,
 | 
				
			||||||
 | 
					) -> Result<(), Error>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    T: MinerConfig<AccountId = AccountId, MaxVotesPerVoter = static_types::MaxVotesPerVoter>
 | 
				
			||||||
 | 
					        + Send
 | 
				
			||||||
 | 
					        + Sync
 | 
				
			||||||
 | 
					        + 'static,
 | 
				
			||||||
 | 
					    T::Solution: Send,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let block_hash = at.hash();
 | 
				
			||||||
 | 
					    log::trace!(target: LOG_TARGET, "new event at #{:?} ({:?})", at.number, block_hash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // NOTE: as we try to send at each block then the nonce is used guard against
 | 
				
			||||||
 | 
					    // submitting twice. Because once a solution has been accepted on chain
 | 
				
			||||||
 | 
					    // the "next transaction" at a later block but with the same nonce will be rejected
 | 
				
			||||||
 | 
					    let nonce = client
 | 
				
			||||||
 | 
					        .rpc()
 | 
				
			||||||
 | 
					        .system_account_next_index(&signer.public_key().to_account_id())
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ensure_signed_phase(client.chain_api(), block_hash)
 | 
				
			||||||
 | 
					        .inspect_err(|e| {
 | 
				
			||||||
 | 
					            log::debug!(
 | 
				
			||||||
 | 
					                target: LOG_TARGET,
 | 
				
			||||||
 | 
					                "ensure_signed_phase failed: {:?}; skipping block: {}",
 | 
				
			||||||
 | 
					                e,
 | 
				
			||||||
 | 
					                at.number
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let round_fut = async {
 | 
				
			||||||
 | 
					        client
 | 
				
			||||||
 | 
					            .chain_api()
 | 
				
			||||||
 | 
					            .storage()
 | 
				
			||||||
 | 
					            .at(block_hash)
 | 
				
			||||||
 | 
					            .fetch_or_default(&runtime::storage().election_provider_multi_phase().round())
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let round = round_fut
 | 
				
			||||||
 | 
					        .inspect_err(|e| log::error!(target: LOG_TARGET, "Mining solution failed: {:?}", e))
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ensure_no_previous_solution::<T::Solution>(
 | 
				
			||||||
 | 
					        client.chain_api(),
 | 
				
			||||||
 | 
					        block_hash,
 | 
				
			||||||
 | 
					        &signer.public_key().0.into(),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .inspect_err(|e| {
 | 
				
			||||||
 | 
					        log::debug!(
 | 
				
			||||||
 | 
					            target: LOG_TARGET,
 | 
				
			||||||
 | 
					            "ensure_no_previous_solution failed: {:?}; skipping block: {}",
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					            at.number
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tokio::time::sleep(std::time::Duration::from_secs(config.delay as u64)).await;
 | 
				
			||||||
 | 
					    let _lock = submit_lock.lock().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let (solution, score) = match epm::fetch_snapshot_and_mine_solution::<T>(
 | 
				
			||||||
 | 
					        &client.chain_api(),
 | 
				
			||||||
 | 
					        Some(block_hash),
 | 
				
			||||||
 | 
					        config.solver,
 | 
				
			||||||
 | 
					        round,
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .timed()
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        (Ok(mined_solution), elapsed) => {
 | 
				
			||||||
 | 
					            // check that the solution looks valid:
 | 
				
			||||||
 | 
					            mined_solution.feasibility_check()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // and then get the values we need from it:
 | 
				
			||||||
 | 
					            let solution = mined_solution.solution();
 | 
				
			||||||
 | 
					            let score = mined_solution.score();
 | 
				
			||||||
 | 
					            let size = mined_solution.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let elapsed_ms = elapsed.as_millis();
 | 
				
			||||||
 | 
					            let encoded_len = solution.encoded_size();
 | 
				
			||||||
 | 
					            let active_voters = solution.voter_count() as u32;
 | 
				
			||||||
 | 
					            let desired_targets = solution.unique_targets().len() as u32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let final_weight = tokio::task::spawn_blocking(move || {
 | 
				
			||||||
 | 
					                T::solution_weight(size.voters, size.targets, active_voters, desired_targets)
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            log::info!(
 | 
				
			||||||
 | 
					                target: LOG_TARGET,
 | 
				
			||||||
 | 
					                "Mined solution with {:?} size: {:?} round: {:?} at: {}, took: {} ms, len: {:?}, weight = {:?}",
 | 
				
			||||||
 | 
					                score,
 | 
				
			||||||
 | 
					                size,
 | 
				
			||||||
 | 
					                round,
 | 
				
			||||||
 | 
					                at.number(),
 | 
				
			||||||
 | 
					                elapsed_ms,
 | 
				
			||||||
 | 
					                encoded_len,
 | 
				
			||||||
 | 
					                final_weight,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            prometheus::set_length(encoded_len);
 | 
				
			||||||
 | 
					            prometheus::set_weight(final_weight);
 | 
				
			||||||
 | 
					            prometheus::observe_mined_solution_duration(elapsed_ms as f64);
 | 
				
			||||||
 | 
					            prometheus::set_score(score);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            (solution, score)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        (Err(e), _) => return Err(Error::Other(e.to_string())),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let best_head = get_latest_head(client.rpc(), config.listen).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ensure_signed_phase(client.chain_api(), best_head)
 | 
				
			||||||
 | 
					        .inspect_err(|e| {
 | 
				
			||||||
 | 
					            log::debug!(
 | 
				
			||||||
 | 
					                target: LOG_TARGET,
 | 
				
			||||||
 | 
					                "ensure_signed_phase failed: {:?}; skipping block: {}",
 | 
				
			||||||
 | 
					                e,
 | 
				
			||||||
 | 
					                at.number
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ensure_no_previous_solution::<T::Solution>(
 | 
				
			||||||
 | 
					        client.chain_api(),
 | 
				
			||||||
 | 
					        best_head,
 | 
				
			||||||
 | 
					        &signer.public_key().0.into(),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .inspect_err(|e| {
 | 
				
			||||||
 | 
					        log::debug!(
 | 
				
			||||||
 | 
					            target: LOG_TARGET,
 | 
				
			||||||
 | 
					            "ensure_no_previous_solution failed: {:?}; skipping block: {:?}",
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					            best_head,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match ensure_solution_passes_strategy(
 | 
				
			||||||
 | 
					        client.chain_api(),
 | 
				
			||||||
 | 
					        best_head,
 | 
				
			||||||
 | 
					        score,
 | 
				
			||||||
 | 
					        config.submission_strategy,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .timed()
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        (Ok(_), now) => {
 | 
				
			||||||
 | 
					            log::trace!(
 | 
				
			||||||
 | 
					                target: LOG_TARGET,
 | 
				
			||||||
 | 
					                "Solution validity verification took: {} ms",
 | 
				
			||||||
 | 
					                now.as_millis()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        (Err(e), _) => {
 | 
				
			||||||
 | 
					            log::debug!(
 | 
				
			||||||
 | 
					                target: LOG_TARGET,
 | 
				
			||||||
 | 
					                "ensure_no_better_solution failed: {:?}; skipping block: {}",
 | 
				
			||||||
 | 
					                e,
 | 
				
			||||||
 | 
					                at.number
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            return Err(e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    prometheus::on_submission_attempt();
 | 
				
			||||||
 | 
					    match submit_and_watch_solution::<T>(
 | 
				
			||||||
 | 
					        &client,
 | 
				
			||||||
 | 
					        signer,
 | 
				
			||||||
 | 
					        (solution, score, round),
 | 
				
			||||||
 | 
					        nonce,
 | 
				
			||||||
 | 
					        config.listen,
 | 
				
			||||||
 | 
					        config.dry_run,
 | 
				
			||||||
 | 
					        &at,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .timed()
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        (Ok(_), now) => {
 | 
				
			||||||
 | 
					            prometheus::on_submission_success();
 | 
				
			||||||
 | 
					            prometheus::observe_submit_and_watch_duration(now.as_millis() as f64);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        (Err(e), _) => {
 | 
				
			||||||
 | 
					            log::warn!(
 | 
				
			||||||
 | 
					                target: LOG_TARGET,
 | 
				
			||||||
 | 
					                "submit_and_watch_solution failed: {e}; skipping block: {}",
 | 
				
			||||||
 | 
					                at.number
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Ensure that now is the signed phase.
 | 
				
			||||||
 | 
					async fn ensure_signed_phase(api: &ChainClient, block_hash: Hash) -> Result<(), Error> {
 | 
				
			||||||
 | 
					    use pallet_election_provider_multi_phase::Phase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let addr = runtime::storage()
 | 
				
			||||||
 | 
					        .election_provider_multi_phase()
 | 
				
			||||||
 | 
					        .current_phase();
 | 
				
			||||||
 | 
					    let phase = api.storage().at(block_hash).fetch(&addr).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(Phase::Signed) = phase.map(|p| p.0) {
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        Err(Error::IncorrectPhase)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Ensure that our current `us` have not submitted anything previously.
 | 
				
			||||||
 | 
					async fn ensure_no_previous_solution<T>(
 | 
				
			||||||
 | 
					    api: &ChainClient,
 | 
				
			||||||
 | 
					    block_hash: Hash,
 | 
				
			||||||
 | 
					    us: &AccountId,
 | 
				
			||||||
 | 
					) -> Result<(), Error>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    T: NposSolution + scale_info::TypeInfo + Decode + 'static,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let addr = runtime::storage()
 | 
				
			||||||
 | 
					        .election_provider_multi_phase()
 | 
				
			||||||
 | 
					        .signed_submission_indices();
 | 
				
			||||||
 | 
					    let indices = api.storage().at(block_hash).fetch_or_default(&addr).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (_score, _, idx) in indices.0 {
 | 
				
			||||||
 | 
					        let submission = epm::signed_submission_at::<T>(idx, Some(block_hash), api).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Some(submission) = submission {
 | 
				
			||||||
 | 
					            if &submission.who == us {
 | 
				
			||||||
 | 
					                return Err(Error::AlreadySubmitted);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn ensure_solution_passes_strategy(
 | 
				
			||||||
 | 
					    api: &ChainClient,
 | 
				
			||||||
 | 
					    block_hash: Hash,
 | 
				
			||||||
 | 
					    score: sp_npos_elections::ElectionScore,
 | 
				
			||||||
 | 
					    strategy: SubmissionStrategy,
 | 
				
			||||||
 | 
					) -> Result<(), Error> {
 | 
				
			||||||
 | 
					    // don't care about current scores.
 | 
				
			||||||
 | 
					    if matches!(strategy, SubmissionStrategy::Always) {
 | 
				
			||||||
 | 
					        return Ok(());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let addr = runtime::storage()
 | 
				
			||||||
 | 
					        .election_provider_multi_phase()
 | 
				
			||||||
 | 
					        .signed_submission_indices();
 | 
				
			||||||
 | 
					    let indices = api.storage().at(block_hash).fetch_or_default(&addr).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log::debug!(target: LOG_TARGET, "submitted solutions: {:?}", indices.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if indices.0.last().map_or(true, |(best_score, _, _)| {
 | 
				
			||||||
 | 
					        score_passes_strategy(score, best_score.0, strategy)
 | 
				
			||||||
 | 
					    }) {
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        Err(Error::BetterScoreExist)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn submit_and_watch_solution<T: MinerConfig + Send + Sync + 'static>(
 | 
				
			||||||
 | 
					    client: &Client,
 | 
				
			||||||
 | 
					    signer: Signer,
 | 
				
			||||||
 | 
					    (solution, score, round): (SolutionOf<T>, sp_npos_elections::ElectionScore, u32),
 | 
				
			||||||
 | 
					    nonce: u64,
 | 
				
			||||||
 | 
					    listen: Listen,
 | 
				
			||||||
 | 
					    dry_run: bool,
 | 
				
			||||||
 | 
					    at: &Header,
 | 
				
			||||||
 | 
					) -> Result<(), Error> {
 | 
				
			||||||
 | 
					    let tx = epm::signed_solution(RawSolution {
 | 
				
			||||||
 | 
					        solution,
 | 
				
			||||||
 | 
					        score,
 | 
				
			||||||
 | 
					        round,
 | 
				
			||||||
 | 
					    })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: https://github.com/paritytech/polkadot-staking-miner/issues/730
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    // The extrinsic mortality length is static and doesn't know when the
 | 
				
			||||||
 | 
					    // signed phase ends.
 | 
				
			||||||
 | 
					    let signed_phase_len = client.chain_api().constants().at(&runtime::constants()
 | 
				
			||||||
 | 
					        .election_provider_multi_phase()
 | 
				
			||||||
 | 
					        .signed_phase())?;
 | 
				
			||||||
 | 
					    let xt_cfg = DefaultExtrinsicParamsBuilder::default()
 | 
				
			||||||
 | 
					        .mortal(at, signed_phase_len as u64)
 | 
				
			||||||
 | 
					        .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let xt =
 | 
				
			||||||
 | 
					        client
 | 
				
			||||||
 | 
					            .chain_api()
 | 
				
			||||||
 | 
					            .tx()
 | 
				
			||||||
 | 
					            .create_signed_with_nonce(&tx, &signer, nonce as u64, xt_cfg)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if dry_run {
 | 
				
			||||||
 | 
					        let dry_run_bytes = client.rpc().dry_run(xt.encoded(), None).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match dry_run_bytes.into_dry_run_result(&client.chain_api().metadata())? {
 | 
				
			||||||
 | 
					            DryRunResult::Success => (),
 | 
				
			||||||
 | 
					            DryRunResult::DispatchError(e) => {
 | 
				
			||||||
 | 
					                return Err(Error::TransactionRejected(e.to_string()))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            DryRunResult::TransactionValidityError => {
 | 
				
			||||||
 | 
					                return Err(Error::TransactionRejected(
 | 
				
			||||||
 | 
					                    "TransactionValidityError".into(),
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let tx_progress = xt.submit_and_watch().await.map_err(|e| {
 | 
				
			||||||
 | 
					        log::warn!(target: LOG_TARGET, "submit solution failed: {:?}", e);
 | 
				
			||||||
 | 
					        e
 | 
				
			||||||
 | 
					    })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match listen {
 | 
				
			||||||
 | 
					        Listen::Head => {
 | 
				
			||||||
 | 
					            let in_block = wait_for_in_block(tx_progress).await?;
 | 
				
			||||||
 | 
					            let events = in_block.fetch_events().await.expect("events should exist");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let solution_stored = events
 | 
				
			||||||
 | 
					                .find_first::<runtime::election_provider_multi_phase::events::SolutionStored>(
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if let Ok(Some(_)) = solution_stored {
 | 
				
			||||||
 | 
					                log::info!("Included at {:?}", in_block.block_hash());
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return Err(Error::Other(format!(
 | 
				
			||||||
 | 
					                    "No SolutionStored event found at {:?}",
 | 
				
			||||||
 | 
					                    in_block.block_hash()
 | 
				
			||||||
 | 
					                )));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Listen::Finalized => {
 | 
				
			||||||
 | 
					            let finalized = tx_progress.wait_for_finalized_success().await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let solution_stored = finalized
 | 
				
			||||||
 | 
					                .find_first::<runtime::election_provider_multi_phase::events::SolutionStored>(
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if let Ok(Some(_)) = solution_stored {
 | 
				
			||||||
 | 
					                log::info!("Finalized at {:?}", finalized.block_hash());
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return Err(Error::Other(format!(
 | 
				
			||||||
 | 
					                    "No SolutionStored event found at {:?}",
 | 
				
			||||||
 | 
					                    finalized.block_hash()
 | 
				
			||||||
 | 
					                )));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn heads_subscription(
 | 
				
			||||||
 | 
					    rpc: &RpcClient,
 | 
				
			||||||
 | 
					    listen: Listen,
 | 
				
			||||||
 | 
					) -> Result<RpcSubscription<Header>, Error> {
 | 
				
			||||||
 | 
					    match listen {
 | 
				
			||||||
 | 
					        Listen::Head => rpc.chain_subscribe_new_heads().await,
 | 
				
			||||||
 | 
					        Listen::Finalized => rpc.chain_subscribe_finalized_heads().await,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .map_err(Into::into)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn get_latest_head(rpc: &RpcClient, listen: Listen) -> Result<Hash, Error> {
 | 
				
			||||||
 | 
					    match listen {
 | 
				
			||||||
 | 
					        Listen::Head => match rpc.chain_get_block_hash(None).await {
 | 
				
			||||||
 | 
					            Ok(Some(hash)) => Ok(hash),
 | 
				
			||||||
 | 
					            Ok(None) => Err(Error::Other("Latest block not found".into())),
 | 
				
			||||||
 | 
					            Err(e) => Err(e.into()),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Listen::Finalized => rpc.chain_get_finalized_head().await.map_err(Into::into),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Returns `true` if `our_score` better the onchain `best_score` according the given strategy.
 | 
				
			||||||
 | 
					pub(crate) fn score_passes_strategy(
 | 
				
			||||||
 | 
					    our_score: sp_npos_elections::ElectionScore,
 | 
				
			||||||
 | 
					    best_score: sp_npos_elections::ElectionScore,
 | 
				
			||||||
 | 
					    strategy: SubmissionStrategy,
 | 
				
			||||||
 | 
					) -> bool {
 | 
				
			||||||
 | 
					    match strategy {
 | 
				
			||||||
 | 
					        SubmissionStrategy::Always => true,
 | 
				
			||||||
 | 
					        SubmissionStrategy::IfLeading => {
 | 
				
			||||||
 | 
					            our_score.strict_threshold_better(best_score, Perbill::zero())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        SubmissionStrategy::ClaimBetterThan(epsilon) => {
 | 
				
			||||||
 | 
					            our_score.strict_threshold_better(best_score, epsilon)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        SubmissionStrategy::ClaimNoWorseThan(epsilon) => {
 | 
				
			||||||
 | 
					            !best_score.strict_threshold_better(our_score, epsilon)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn dry_run_works(rpc: &RpcClient) -> Result<(), Error> {
 | 
				
			||||||
 | 
					    if let Err(SubxtError::Rpc(RpcError::ClientError(e))) = rpc.dry_run(&[], None).await {
 | 
				
			||||||
 | 
					        let rpc_err = match e.downcast::<JsonRpseeError>() {
 | 
				
			||||||
 | 
					            Ok(e) => *e,
 | 
				
			||||||
 | 
					            Err(_) => {
 | 
				
			||||||
 | 
					                return Err(Error::Other(
 | 
				
			||||||
 | 
					                    "Failed to downcast RPC error; this is a bug please file an issue".to_string(),
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let JsonRpseeError::Call(e) = rpc_err {
 | 
				
			||||||
 | 
					            if e.message() == "RPC call is unsafe to be called externally" {
 | 
				
			||||||
 | 
					                return Err(Error::Other(
 | 
				
			||||||
 | 
					                    "dry-run requires a RPC endpoint with `--rpc-methods unsafe`; \
 | 
				
			||||||
 | 
					                        either connect to another RPC endpoint or disable dry-run"
 | 
				
			||||||
 | 
					                        .to_string(),
 | 
				
			||||||
 | 
					                ));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Wait for the transaction to be in a block.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// **Note:** transaction statuses like `Invalid`/`Usurped`/`Dropped` indicate with some
 | 
				
			||||||
 | 
					/// probability that the transaction will not make it into a block but there is no guarantee
 | 
				
			||||||
 | 
					/// that this is true. In those cases the stream is closed however, so you currently have no way to find
 | 
				
			||||||
 | 
					/// out if they finally made it into a block or not.
 | 
				
			||||||
 | 
					async fn wait_for_in_block<T, C>(mut tx: TxProgress<T, C>) -> Result<TxInBlock<T, C>, subxt::Error>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    T: subxt::Config,
 | 
				
			||||||
 | 
					    C: subxt::client::OnlineClientT<T>,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use subxt::{error::TransactionError, tx::TxStatus};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while let Some(status) = tx.next().await {
 | 
				
			||||||
 | 
					        match status? {
 | 
				
			||||||
 | 
					            // Finalized or otherwise in a block! Return.
 | 
				
			||||||
 | 
					            TxStatus::InBestBlock(s) | TxStatus::InFinalizedBlock(s) => return Ok(s),
 | 
				
			||||||
 | 
					            // Error scenarios; return the error.
 | 
				
			||||||
 | 
					            TxStatus::Error { message } => return Err(TransactionError::Error(message).into()),
 | 
				
			||||||
 | 
					            TxStatus::Invalid { message } => return Err(TransactionError::Invalid(message).into()),
 | 
				
			||||||
 | 
					            TxStatus::Dropped { message } => return Err(TransactionError::Dropped(message).into()),
 | 
				
			||||||
 | 
					            // Ignore anything else and wait for next status event:
 | 
				
			||||||
 | 
					            _ => continue,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Err(RpcError::SubscriptionDropped.into())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn score_passes_strategy_works() {
 | 
				
			||||||
 | 
					        let s = |x| sp_npos_elections::ElectionScore {
 | 
				
			||||||
 | 
					            minimal_stake: x,
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let two = Perbill::from_percent(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // anything passes Always
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(0),
 | 
				
			||||||
 | 
					            s(0),
 | 
				
			||||||
 | 
					            SubmissionStrategy::Always
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(5),
 | 
				
			||||||
 | 
					            s(0),
 | 
				
			||||||
 | 
					            SubmissionStrategy::Always
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(5),
 | 
				
			||||||
 | 
					            s(10),
 | 
				
			||||||
 | 
					            SubmissionStrategy::Always
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // if leading
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(0),
 | 
				
			||||||
 | 
					            s(0),
 | 
				
			||||||
 | 
					            SubmissionStrategy::IfLeading
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(1),
 | 
				
			||||||
 | 
					            s(0),
 | 
				
			||||||
 | 
					            SubmissionStrategy::IfLeading
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(2),
 | 
				
			||||||
 | 
					            s(0),
 | 
				
			||||||
 | 
					            SubmissionStrategy::IfLeading
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(5),
 | 
				
			||||||
 | 
					            s(10),
 | 
				
			||||||
 | 
					            SubmissionStrategy::IfLeading
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(9),
 | 
				
			||||||
 | 
					            s(10),
 | 
				
			||||||
 | 
					            SubmissionStrategy::IfLeading
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(10),
 | 
				
			||||||
 | 
					            s(10),
 | 
				
			||||||
 | 
					            SubmissionStrategy::IfLeading
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // if better by 2%
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(50),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimBetterThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimBetterThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(101),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimBetterThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(102),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimBetterThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(103),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimBetterThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(150),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimBetterThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // if no less than 2% worse
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(50),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimNoWorseThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(!score_passes_strategy(
 | 
				
			||||||
 | 
					            s(97),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimNoWorseThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(98),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimNoWorseThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(99),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimNoWorseThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimNoWorseThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(101),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimNoWorseThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(102),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimNoWorseThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(103),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimNoWorseThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert!(score_passes_strategy(
 | 
				
			||||||
 | 
					            s(150),
 | 
				
			||||||
 | 
					            s(100),
 | 
				
			||||||
 | 
					            SubmissionStrategy::ClaimNoWorseThan(two)
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn submission_strategy_from_str_works() {
 | 
				
			||||||
 | 
					        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(Accuracy::from_percent(
 | 
				
			||||||
 | 
					                99
 | 
				
			||||||
 | 
					            )))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -13,13 +13,10 @@ use std::{
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use codec::{Decode, Encode};
 | 
					use codec::{Decode, Encode};
 | 
				
			||||||
use frame_election_provider_support::{
 | 
					use frame_election_provider_support::{Get, NposSolution, PhragMMS, SequentialPhragmen};
 | 
				
			||||||
    Get, NposSolution, PhragMMS, SequentialPhragmen,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use frame_support::{weights::Weight, BoundedVec};
 | 
					use frame_support::{weights::Weight, BoundedVec};
 | 
				
			||||||
use pallet_election_provider_multi_phase::{
 | 
					use pallet_election_provider_multi_phase::{
 | 
				
			||||||
    usigned::TrimmingStatus, RawSolution, ReadySolution, SolutionOf,
 | 
					    usigned::TrimmingStatus, RawSolution, ReadySolution, SolutionOf, SolutionOrSnapshotSize,
 | 
				
			||||||
    SolutionOrSnapshotSize,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use scale_info::{PortableRegistry, TypeInfo};
 | 
					use scale_info::{PortableRegistry, TypeInfo};
 | 
				
			||||||
use scale_value::scale::decode_as_type;
 | 
					use scale_value::scale::decode_as_type;
 | 
				
			||||||
@ -29,12 +26,16 @@ use subxt::{dynamic::Value, tx::DynamicPayload};
 | 
				
			|||||||
const EPM_PALLET_NAME: &str = "ElectionProviderMultiPhase";
 | 
					const EPM_PALLET_NAME: &str = "ElectionProviderMultiPhase";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TypeId = u32;
 | 
					type TypeId = u32;
 | 
				
			||||||
type MinerVoterOf = frame_election_provider_support::Voter<AccountId, crate::static_types::MaxVotesPerVoter>;
 | 
					type MinerVoterOf =
 | 
				
			||||||
 | 
					    frame_election_provider_support::Voter<AccountId, crate::static_types::MaxVotesPerVoter>;
 | 
				
			||||||
type RoundSnapshot = pallet_election_provider_multi_phase::RoundSnapshot<AccountId, MinerVoterOf>;
 | 
					type RoundSnapshot = pallet_election_provider_multi_phase::RoundSnapshot<AccountId, MinerVoterOf>;
 | 
				
			||||||
type Voters = Vec<(AccountId, VoteWeight, BoundedVec<AccountId, crate::static_types::MaxVotesPerVoter>)>;
 | 
					type Voters = Vec<(
 | 
				
			||||||
 | 
					    AccountId,
 | 
				
			||||||
 | 
					    VoteWeight,
 | 
				
			||||||
 | 
					    BoundedVec<AccountId, crate::static_types::MaxVotesPerVoter>,
 | 
				
			||||||
 | 
					)>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Copy, Clone, Debug)]
 | 
					#[derive(Copy, Clone, Debug, Debug)]
 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
struct EpmConstant {
 | 
					struct EpmConstant {
 | 
				
			||||||
    epm: &'static str,
 | 
					    epm: &'static str,
 | 
				
			||||||
    constant: &'static str,
 | 
					    constant: &'static str,
 | 
				
			||||||
@ -42,7 +43,10 @@ struct EpmConstant {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl EpmConstant {
 | 
					impl EpmConstant {
 | 
				
			||||||
    const fn new(constant: &'static str) -> Self {
 | 
					    const fn new(constant: &'static str) -> Self {
 | 
				
			||||||
        Self { epm: EPM_PALLET_NAME, constant }
 | 
					        Self {
 | 
				
			||||||
 | 
					            epm: EPM_PALLET_NAME,
 | 
				
			||||||
 | 
					            constant,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const fn to_parts(self) -> (&'static str, &'static str) {
 | 
					    const fn to_parts(self) -> (&'static str, &'static str) {
 | 
				
			||||||
@ -101,18 +105,24 @@ where
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            let est_weight: Weight = tokio::task::spawn_blocking(move || {
 | 
					            let est_weight: Weight = tokio::task::spawn_blocking(move || {
 | 
				
			||||||
                T::solution_weight(active_voters, targets_len, active_voters, desired_targets)
 | 
					                T::solution_weight(active_voters, targets_len, active_voters, desired_targets)
 | 
				
			||||||
            }).await?;
 | 
					            })
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let max_weight: Weight = T::MaxWeight::get();
 | 
					            let max_weight: Weight = T::MaxWeight::get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if est_weight.all_lt(max_weight) {
 | 
					            if est_weight.all_lt(max_weight) {
 | 
				
			||||||
                return Ok(Self {
 | 
					                return Ok(Self {
 | 
				
			||||||
                    state: State { voters, voters_by_stake }, 
 | 
					                    state: State {
 | 
				
			||||||
                    _marker: PhantomData 
 | 
					                        voters,
 | 
				
			||||||
 | 
					                        voters_by_stake,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    _marker: PhantomData,
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let Some((_, idx)) = voters_by_stake.pop_first() else { break };
 | 
					            let Some((_, idx)) = voters_by_stake.pop_first() else {
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
            let rm = voters[idx].0.clone();
 | 
					            let rm = voters[idx].0.clone();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (_voter, _stake, supports) in &mut voters {
 | 
					            for (_voter, _stake, supports) in &mut voters {
 | 
				
			||||||
@ -122,7 +132,9 @@ where
 | 
				
			|||||||
            targets.remove(&rm);
 | 
					            targets.remove(&rm);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Err(Error::Feasibility("Failed to pre-trim weight < T::MaxLength".to_string()));
 | 
					        return Err(Error::Feasibility(
 | 
				
			||||||
 | 
					            "Failed to pre-trim weight < T::MaxLength".to_string(),
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn trim(&mut self, n: usize) -> Result<State, Error> {
 | 
					    pub fn trim(&mut self, n: usize) -> Result<State, Error> {
 | 
				
			||||||
@ -139,7 +151,10 @@ where
 | 
				
			|||||||
                supports.retain(|a| a != &rm);
 | 
					                supports.retain(|a| a != &rm);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Ok(State { voters, voters_by_stake })
 | 
					        Ok(State {
 | 
				
			||||||
 | 
					            voters,
 | 
				
			||||||
 | 
					            voters_by_stake,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn to_voters(&self) -> Voters {
 | 
					    pub fn to_voters(&self) -> Voters {
 | 
				
			||||||
@ -197,28 +212,37 @@ fn read_constant<'a, T: serde::Deserialize<'a>>(
 | 
				
			|||||||
        .map_err(|e| Error::Subxt(e.into()))?;
 | 
					        .map_err(|e| Error::Subxt(e.into()))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    scale_value::serde::from_value::<_, T>(val).map_err(|e| {
 | 
					    scale_value::serde::from_value::<_, T>(val).map_err(|e| {
 | 
				
			||||||
        Error::InvalidMetadata(
 | 
					        Error::InvalidMetadata(format!(
 | 
				
			||||||
            format!("Decoding `{}` failed {}", std::any::type_name::<T>(), e)
 | 
					            "Decoding `{}` failed {}",
 | 
				
			||||||
        )
 | 
					            std::any::type_name::<T>(),
 | 
				
			||||||
 | 
					            e
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) fn set_emergency_result<A: Encode + TypeInfo + 'static>(
 | 
					pub(crate) fn set_emergency_result<A: Encode + TypeInfo + 'static>(
 | 
				
			||||||
    supports: frame_election_provider_support::Supports<A>,
 | 
					    supports: frame_election_provider_support::Supports<A>,
 | 
				
			||||||
) -> Result<DynamicPayload, Error> {
 | 
					) -> Result<DynamicPayload, Error> {
 | 
				
			||||||
    let scale_result = to_scale_value(supports).map_err(|e| {
 | 
					    let scale_result = to_scale_value(supports)
 | 
				
			||||||
        Error::DynamicTransaction(format!("Failed to encode `Supports`: {:?}", e))
 | 
					        .map_err(|e| Error::DynamicTransaction(format!("Failed to encode `Supports`: {:?}", e)))?;
 | 
				
			||||||
    })?;
 | 
					    Ok(subxt::dynamic::tx(
 | 
				
			||||||
    Ok(subxt::dynamic::tx(EPM_PALLET_NAME, "set_emergency_election_result", vec![scale_result]))
 | 
					        EPM_PALLET_NAME,
 | 
				
			||||||
 | 
					        "set_emergency_election_result",
 | 
				
			||||||
 | 
					        vec![scale_result],
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn signed_solution<S: NposSolution + Encode + TypeInfo + 'static>(
 | 
					pub fn signed_solution<S: NposSolution + Encode + TypeInfo + 'static>(
 | 
				
			||||||
    solution: RawSolution<S>,
 | 
					    solution: RawSolution<S>,
 | 
				
			||||||
) -> Result<DynamicPayload, Error> {
 | 
					) -> Result<DynamicPayload, Error> {
 | 
				
			||||||
    let scale_solution  = to_scale_value(solution).map_err(|e| {
 | 
					    let scale_solution = to_scale_value(solution).map_err(|e| {
 | 
				
			||||||
        Error::DynamicTransaction(format!("Failed to encode `RawSolution`: {:?}", e))
 | 
					        Error::DynamicTransaction(format!("Failed to encode `RawSolution`: {:?}", e))
 | 
				
			||||||
    })?;
 | 
					    })?;
 | 
				
			||||||
    Ok(subxt::dynamic::tx(EPM_PALLET_NAME, "submit", vec![scale_solution]))
 | 
					    Ok(subxt::dynamic::tx(
 | 
				
			||||||
 | 
					        EPM_PALLET_NAME,
 | 
				
			||||||
 | 
					        "submit",
 | 
				
			||||||
 | 
					        vec![scale_solution],
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn unsigned_solution<S: NposSolution + Encode + TypeInfo + 'static>(
 | 
					pub fn unsigned_solution<S: NposSolution + Encode + TypeInfo + 'static>(
 | 
				
			||||||
@ -227,7 +251,11 @@ pub fn unsigned_solution<S: NposSolution + Encode + TypeInfo + 'static>(
 | 
				
			|||||||
) -> Result<DynamicPayload, Error> {
 | 
					) -> Result<DynamicPayload, Error> {
 | 
				
			||||||
    let scale_solution = to_scale_value(solution)?;
 | 
					    let scale_solution = to_scale_value(solution)?;
 | 
				
			||||||
    let scale_witness = to_scale_value(witness)?;
 | 
					    let scale_witness = to_scale_value(witness)?;
 | 
				
			||||||
    Ok(subxt::dynamic::tx(EPM_PALLET_NAME, "submit_unsigned", vec![scale_solution, scale_witness]))
 | 
					    Ok(subxt::dynamic::tx(
 | 
				
			||||||
 | 
					        EPM_PALLET_NAME,
 | 
				
			||||||
 | 
					        "submit_unsigned",
 | 
				
			||||||
 | 
					        vec![scale_solution, scale_witness],
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn signed_submission<S: NposSolution + Decode + TypeInfo + 'static>(
 | 
					pub async fn signed_submission<S: NposSolution + Decode + TypeInfo + 'static>(
 | 
				
			||||||
@ -244,7 +272,7 @@ pub async fn signed_submission<S: NposSolution + Decode + TypeInfo + 'static>(
 | 
				
			|||||||
        Ok(Some(val)) => {
 | 
					        Ok(Some(val)) => {
 | 
				
			||||||
            let submissions = Decode::decode(&mut val.encode())?;
 | 
					            let submissions = Decode::decode(&mut val.encode())?;
 | 
				
			||||||
            Ok(Some(submissions))
 | 
					            Ok(Some(submissions))
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
        Ok(None) => Ok(None),
 | 
					        Ok(None) => Ok(None),
 | 
				
			||||||
        Err(err) => Err(err.into()),
 | 
					        Err(err) => Err(err.into()),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -263,7 +291,7 @@ pub async fn snapshot_at(
 | 
				
			|||||||
        Ok(Some(val)) => {
 | 
					        Ok(Some(val)) => {
 | 
				
			||||||
            let snapshot = Decode::decode(&mut val.encode())?;
 | 
					            let snapshot = Decode::decode(&mut val.encode())?;
 | 
				
			||||||
            Ok(Some(snapshot))
 | 
					            Ok(Some(snapshot))
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
        Ok(None) => Err(Error::EmptySnapshot),
 | 
					        Ok(None) => Err(Error::EmptySnapshot),
 | 
				
			||||||
        Err(err) => Err(err.into()),
 | 
					        Err(err) => Err(err.into()),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -274,7 +302,15 @@ pub async fn mine_solution<T>(
 | 
				
			|||||||
    targets: Vec<AccountId>,
 | 
					    targets: Vec<AccountId>,
 | 
				
			||||||
    voters: Voters,
 | 
					    voters: Voters,
 | 
				
			||||||
    desired_targets: u32,
 | 
					    desired_targets: u32,
 | 
				
			||||||
) -> Result<(SolutionOf<T>, ElectionScore, SolutionOrSnapshotSize, TrimmingStatus), Error>
 | 
					) -> Result<
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        SolutionOf<T>,
 | 
				
			||||||
 | 
					        ElectionScore,
 | 
				
			||||||
 | 
					        SolutionOrSnapshotSize,
 | 
				
			||||||
 | 
					        TrimmingStatus,
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    Error,
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    T: MinerConfig<AccountId = AccountId, MaxVotesPerVoter = static_types::MaxVotesPerVoter>
 | 
					    T: MinerConfig<AccountId = AccountId, MaxVotesPerVoter = static_types::MaxVotesPerVoter>
 | 
				
			||||||
        + Send
 | 
					        + Send
 | 
				
			||||||
@ -288,14 +324,18 @@ where
 | 
				
			|||||||
            Miner::<T>::mine_solution_with_snapshot::<
 | 
					            Miner::<T>::mine_solution_with_snapshot::<
 | 
				
			||||||
                SequentialPhragmen<AccountId, Accuracy, Balancing>,
 | 
					                SequentialPhragmen<AccountId, Accuracy, Balancing>,
 | 
				
			||||||
            >(voters, targets, desired_targets)
 | 
					            >(voters, targets, desired_targets)
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
        Solver::PhragMMS { iterations } => {
 | 
					        Solver::PhragMMS { iterations } => {
 | 
				
			||||||
            BalanceIterations::set(iterations);
 | 
					            BalanceIterations::set(iterations);
 | 
				
			||||||
            Miner::<T>::mine_solution_with_snapshot::<
 | 
					            Miner::<T>::mine_solution_with_snapshot::<PhragMMS<AccountId, Accuracy, Balancing>>(
 | 
				
			||||||
                PhragMMS<AccountId, Accuracy, Balancing>,
 | 
					                voters,
 | 
				
			||||||
            >(voters, targets, desired_targets)
 | 
					                targets,
 | 
				
			||||||
        },
 | 
					                desired_targets,
 | 
				
			||||||
    }).await {
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        Ok(Ok(s)) => Ok(s),
 | 
					        Ok(Ok(s)) => Ok(s),
 | 
				
			||||||
        Err(e) => Err(e.into()),
 | 
					        Err(e) => Err(e.into()),
 | 
				
			||||||
        Ok(Err(e)) => Err(Error::Other(format!("{:?}", e))),
 | 
					        Ok(Err(e)) => Err(Error::Other(format!("{:?}", e))),
 | 
				
			||||||
@ -322,13 +362,21 @@ where
 | 
				
			|||||||
    let desired_targets = match forced_desired_targets {
 | 
					    let desired_targets = match forced_desired_targets {
 | 
				
			||||||
        Some(x) => x,
 | 
					        Some(x) => x,
 | 
				
			||||||
        None => storage
 | 
					        None => storage
 | 
				
			||||||
            .fetch(&runtime::storage()::election_provider_multi_phase().desired_targets())
 | 
					            .fetch(
 | 
				
			||||||
 | 
					                &runtime::storage()
 | 
				
			||||||
 | 
					                    .election_provider_multi_phase()
 | 
				
			||||||
 | 
					                    .desired_targets(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .await?
 | 
					            .await?
 | 
				
			||||||
            .expect("Snapshot is non-empty; `desired_target` should exist; qed"),
 | 
					            .expect("Snapshot is non-empty; `desired_target` should exist; qed"),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let minimum_untrusted_score = storage
 | 
					    let minimum_untrusted_score = storage
 | 
				
			||||||
        .fetch(&runtime::storage().election_provider_multi_phase().minimum_untrusted_score())
 | 
					        .fetch(
 | 
				
			||||||
 | 
					            &runtime::storage()
 | 
				
			||||||
 | 
					                .election_provider_multi_phase()
 | 
				
			||||||
 | 
					                .minimum_untrusted_score(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        .await?
 | 
					        .await?
 | 
				
			||||||
        .map(|score| score.0);
 | 
					        .map(|score| score.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -339,7 +387,8 @@ where
 | 
				
			|||||||
        snapshot.targets.clone(),
 | 
					        snapshot.targets.clone(),
 | 
				
			||||||
        voters.to_voters(),
 | 
					        voters.to_voters(),
 | 
				
			||||||
        desired_targets,
 | 
					        desired_targets,
 | 
				
			||||||
    ).await?;
 | 
					    )
 | 
				
			||||||
 | 
					    .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if !trim_status.is_trimmed() {
 | 
					    if !trim_status.is_trimmed() {
 | 
				
			||||||
        return Ok(MinedSolution {
 | 
					        return Ok(MinedSolution {
 | 
				
			||||||
@ -368,14 +417,15 @@ where
 | 
				
			|||||||
            snapshot.targets.clone(),
 | 
					            snapshot.targets.clone(),
 | 
				
			||||||
            next_state.to_voters(),
 | 
					            next_state.to_voters(),
 | 
				
			||||||
            desired_targets,
 | 
					            desired_targets,
 | 
				
			||||||
        ).await?;
 | 
					        )
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if !trim_status.is_trimmed() {
 | 
					        if !trim_status.is_trimmed() {
 | 
				
			||||||
            best_solution = Some((solution, score, solution_or_snapshot_size));
 | 
					            best_solution = Some((solution, score, solution_or_snapshot_size));
 | 
				
			||||||
            h = mid - 1;
 | 
					            h = mid - 1;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            l = mid + 1;
 | 
					            l = mid + 1;
 | 
				
			||||||
        } ,
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Some((solution, score, solution_or_snapshot_size)) = best_solution {
 | 
					    if let Some((solution, score, solution_or_snapshot_size)) = best_solution {
 | 
				
			||||||
@ -425,9 +475,13 @@ where
 | 
				
			|||||||
        self.solution_or_snapshot_size
 | 
					        self.solution_or_snapshot_size
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn feasibility_check(&self) => Result<ReadySolution<AccountId, T::MaxWinners>, Error> {
 | 
					    pub fn feasibility_check(&self) -> Result<ReadySolution<AccountId, T::MaxWinners>, Error> {
 | 
				
			||||||
        match Miner::<T>::feasibility_check(
 | 
					        match Miner::<T>::feasibility_check(
 | 
				
			||||||
            RawSolution { solution: self.solution.clone(), score: self.score, round: self.round },
 | 
					            RawSolution {
 | 
				
			||||||
 | 
					                solution: self.solution.clone(),
 | 
				
			||||||
 | 
					                score: self.score,
 | 
				
			||||||
 | 
					                round: self.round,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            pallet_election_provider_multi_phase::ElectionCompute::Signed,
 | 
					            pallet_election_provider_multi_phase::ElectionCompute::Signed,
 | 
				
			||||||
            self.desired_targets,
 | 
					            self.desired_targets,
 | 
				
			||||||
            self.snapshot.clone(),
 | 
					            self.snapshot.clone(),
 | 
				
			||||||
@ -438,7 +492,7 @@ where
 | 
				
			|||||||
            Err(e) => {
 | 
					            Err(e) => {
 | 
				
			||||||
                log::error!(target: LOG_TARGET, "Solution feasibility error {:?}", e);
 | 
					                log::error!(target: LOG_TARGET, "Solution feasibility error {:?}", e);
 | 
				
			||||||
                Err(Error::Feasibility(format!("{:?}", e)))
 | 
					                Err(Error::Feasibility(format!("{:?}", e)))
 | 
				
			||||||
            },
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -483,7 +537,9 @@ pub async fn runtime_api_solution_weight<S: Encode + NposSolution + TypeInfo + '
 | 
				
			|||||||
    witness: SolutionOrSnapshotSize,
 | 
					    witness: SolutionOrSnapshotSize,
 | 
				
			||||||
) -> Result<Weight, Error> {
 | 
					) -> Result<Weight, Error> {
 | 
				
			||||||
    let tx = unsigned_solution(raw_solution, witness)?;
 | 
					    let tx = unsigned_solution(raw_solution, witness)?;
 | 
				
			||||||
    let client = SHARED_CLIENT.get().expect("shared client is configured as start; qed");
 | 
					    let client = SHARED_CLIENT
 | 
				
			||||||
 | 
					        .get()
 | 
				
			||||||
 | 
					        .expect("shared client is configured as start; qed");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let call_data = {
 | 
					    let call_data = {
 | 
				
			||||||
        let mut buffer = Vec::new();
 | 
					        let mut buffer = Vec::new();
 | 
				
			||||||
@ -499,7 +555,11 @@ pub async fn runtime_api_solution_weight<S: Encode + NposSolution + TypeInfo + '
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let bytes = client
 | 
					    let bytes = client
 | 
				
			||||||
        .rpc()
 | 
					        .rpc()
 | 
				
			||||||
        .state_call("TransactionPaymentCallApi_query_call_info", Some(&call_data), None)
 | 
					        .state_call(
 | 
				
			||||||
 | 
					            "TransactionPaymentCallApi_query_call_info",
 | 
				
			||||||
 | 
					            Some(&call_data),
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        .await?;
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let info: RuntimeDispatchInfo = Decode::decode(&mut bytes.as_ref())?;
 | 
					    let info: RuntimeDispatchInfo = Decode::decode(&mut bytes.as_ref())?;
 | 
				
			||||||
 | 
				
			|||||||
@ -41,14 +41,17 @@ where
 | 
				
			|||||||
            Poll::Ready(v) => {
 | 
					            Poll::Ready(v) => {
 | 
				
			||||||
                let elapsed = start.elapsed();
 | 
					                let elapsed = start.elapsed();
 | 
				
			||||||
                Poll::Ready((v, elapsed))
 | 
					                Poll::Ready((v, elapsed))
 | 
				
			||||||
            },
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait TimedFuture: Sized + Future {
 | 
					pub trait TimedFuture: Sized + Future {
 | 
				
			||||||
    fn timed(self) -> Timed<Self> {
 | 
					    fn timed(self) -> Timed<Self> {
 | 
				
			||||||
        Timed { inner: self, start: None }
 | 
					        Timed {
 | 
				
			||||||
 | 
					            inner: self,
 | 
				
			||||||
 | 
					            start: None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -59,15 +62,15 @@ pub struct RuntimeDispatchInfo {
 | 
				
			|||||||
    pub weight: Weight,
 | 
					    pub weight: Weight,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender<Error>, err::Error) {
 | 
					pub fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender<Error>, err: Error) {
 | 
				
			||||||
    match err {
 | 
					    match err {
 | 
				
			||||||
        Error::AlreadySubmitted |
 | 
					        Error::AlreadySubmitted
 | 
				
			||||||
        Error::BetterScoreExist |
 | 
					        | Error::BetterScoreExist
 | 
				
			||||||
        Error::IncorrectPhase |
 | 
					        | Error::IncorrectPhase
 | 
				
			||||||
        Error::TransactionRejected |
 | 
					        | Error::TransactionRejected
 | 
				
			||||||
        Error::JoinError |
 | 
					        | Error::JoinError
 | 
				
			||||||
        Error::Feasibility |
 | 
					        | Error::Feasibility
 | 
				
			||||||
        Error::EmptySnapshot => {},
 | 
					        | Error::EmptySnapshot => {}
 | 
				
			||||||
        Error::Subxt(SubxtError::Rpc(rpc_err)) => {
 | 
					        Error::Subxt(SubxtError::Rpc(rpc_err)) => {
 | 
				
			||||||
            log::debug!(target: LOG_TARGET, "rpc error: {:?}", rpc_err);
 | 
					            log::debug!(target: LOG_TARGET, "rpc error: {:?}", rpc_err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,10 +80,11 @@ pub fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender<Er
 | 
				
			|||||||
                        Ok(e) => *e,
 | 
					                        Ok(e) => *e,
 | 
				
			||||||
                        Err(_) => {
 | 
					                        Err(_) => {
 | 
				
			||||||
                            let _ = tx.send(Error::Other(
 | 
					                            let _ = tx.send(Error::Other(
 | 
				
			||||||
                                "Failed to downcast RPC error; this is a bug please file an issue".to_string()
 | 
					                                "Failed to downcast RPC error; this is a bug please file an issue"
 | 
				
			||||||
 | 
					                                    .to_string(),
 | 
				
			||||||
                            ));
 | 
					                            ));
 | 
				
			||||||
                            return;
 | 
					                            return;
 | 
				
			||||||
                        },
 | 
					                        }
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    match jsonrpsee_err {
 | 
					                    match jsonrpsee_err {
 | 
				
			||||||
@ -89,30 +93,30 @@ pub fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender<Er
 | 
				
			|||||||
                            const VERIFICATION_ERROR: i32 = 1002;
 | 
					                            const VERIFICATION_ERROR: i32 = 1002;
 | 
				
			||||||
                            use jsonrpsee::types::error::ErrorCode;
 | 
					                            use jsonrpsee::types::error::ErrorCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if e.code() == BAD_EXTRINSIC_FORMAT ||
 | 
					                            if e.code() == BAD_EXTRINSIC_FORMAT
 | 
				
			||||||
                                e.code() == VERIFICATION_ERROR ||
 | 
					                                || e.code() == VERIFICATION_ERROR
 | 
				
			||||||
                                e.code() == ErrorCode::MethodNotFound.code()
 | 
					                                || e.code() == ErrorCode::MethodNotFound.code()
 | 
				
			||||||
                            {
 | 
					                            {
 | 
				
			||||||
                                let _ = tx.send(Error::Subxt(SubxtError::Rpc(
 | 
					                                let _ = tx.send(Error::Subxt(SubxtError::Rpc(
 | 
				
			||||||
                                    RpcError::ClientError(Box::new(JsonRpseeError::Call(e))),
 | 
					                                    RpcError::ClientError(Box::new(JsonRpseeError::Call(e))),
 | 
				
			||||||
                                )));
 | 
					                                )));
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        },
 | 
					                        }
 | 
				
			||||||
                        JsonRpseeError::RequestTimeout => {},
 | 
					                        JsonRpseeError::RequestTimeout => {}
 | 
				
			||||||
                        err => {
 | 
					                        err => {
 | 
				
			||||||
                            let _ = tx.send(Error::Subxt(SubxtError::Rpc(RpcError::ClientError(
 | 
					                            let _ = tx.send(Error::Subxt(SubxtError::Rpc(RpcError::ClientError(
 | 
				
			||||||
                                Box::new(err),
 | 
					                                Box::new(err),
 | 
				
			||||||
                            ))));
 | 
					                            ))));
 | 
				
			||||||
                        },
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                }
 | 
				
			||||||
                RpcError::SubscriptionDropped => (),
 | 
					                RpcError::SubscriptionDropped => (),
 | 
				
			||||||
                _ => (),
 | 
					                _ => (),
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
        err => {
 | 
					        err => {
 | 
				
			||||||
            let _ = tx.send(err);
 | 
					            let _ = tx.send(err);
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -34,10 +34,7 @@ use std::str::FromStr;
 | 
				
			|||||||
use tokio::sync::oneshot;
 | 
					use tokio::sync::oneshot;
 | 
				
			||||||
use tracing_subscriber::EnvFilter;
 | 
					use tracing_subscriber::EnvFilter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{client::Client, opt::RuntimeVersion};
 | 
				
			||||||
    client::Client,
 | 
					 | 
				
			||||||
    opt::RuntimeVersion,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debig, Clone, Parser)]
 | 
					#[derive(Debig, Clone, Parser)]
 | 
				
			||||||
#[cfg_attr(test, derive(PartialEq))]
 | 
					#[cfg_attr(test, derive(PartialEq))]
 | 
				
			||||||
@ -96,7 +93,12 @@ macro_rules! any_runtime {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[tokio::main]
 | 
					#[tokio::main]
 | 
				
			||||||
async fn main() -> Result<(), Error> {
 | 
					async fn main() -> Result<(), Error> {
 | 
				
			||||||
    let Opt { uri, command, prometheus_port, log } = Opt::parse();
 | 
					    let Opt {
 | 
				
			||||||
 | 
					        uri,
 | 
				
			||||||
 | 
					        command,
 | 
				
			||||||
 | 
					        prometheus_port,
 | 
				
			||||||
 | 
					        log,
 | 
				
			||||||
 | 
					    } = Opt::parse();
 | 
				
			||||||
    let filter = EnvFilter::from_default_env().add_directive(log.parse()?);
 | 
					    let filter = EnvFilter::from_default_env().add_directive(log.parse()?);
 | 
				
			||||||
    tracing_subscriber::fmt().with_env_filter(filter).init();
 | 
					    tracing_subscriber::fmt().with_env_filter(filter).init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -109,7 +111,9 @@ async fn main() -> Result<(), Error> {
 | 
				
			|||||||
    log::info!(target: LOG_TARGET, "Connected to chain: {}", chain);
 | 
					    log::info!(target: LOG_TARGET, "Connected to chain: {}", chain);
 | 
				
			||||||
    epm::update_metadata_constants(client.chain_api())?;
 | 
					    epm::update_metadata_constants(client.chain_api())?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SHARED_CLIENT.set(client.clone()).expect("shared client only set once; qed");
 | 
					    SHARED_CLIENT
 | 
				
			||||||
 | 
					        .set(client.clone())
 | 
				
			||||||
 | 
					        .expect("shared client only set once; qed");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Start a new tokio tasl to perform the runtime updates in the backgound.
 | 
					    // Start a new tokio tasl to perform the runtime updates in the backgound.
 | 
				
			||||||
    // If this fails then the miner will be stopped and has to be re-started.
 | 
					    // If this fails then the miner will be stopped and has to be re-started.
 | 
				
			||||||
@ -203,7 +207,7 @@ async fn runtime_upgrade_task(client: ChainClient, tx: oneshot::Sender<Error>) {
 | 
				
			|||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            let _ = tx.send(e.into());
 | 
					            let _ = tx.send(e.into());
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    loop {
 | 
					    loop {
 | 
				
			||||||
@ -218,10 +222,10 @@ async fn runtime_upgrade_task(client: ChainClient, tx: oneshot::Sender<Error>) {
 | 
				
			|||||||
                    Err(e) => {
 | 
					                    Err(e) => {
 | 
				
			||||||
                        let _ = tx.send(e.into());
 | 
					                        let _ = tx.send(e.into());
 | 
				
			||||||
                        return;
 | 
					                        return;
 | 
				
			||||||
                    },
 | 
					                    }
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            },
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let version = update.runtime_version().spec_version;
 | 
					        let version = update.runtime_version().spec_version;
 | 
				
			||||||
@ -233,10 +237,10 @@ async fn runtime_upgrade_task(client: ChainClient, tx: oneshot::Sender<Error>) {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                prometheus::on_runtime_upgrade();
 | 
					                prometheus::on_runtime_upgrade();
 | 
				
			||||||
                log::info!(target: LOG_TARGET, "upgrade to version: {} successful", version);
 | 
					                log::info!(target: LOG_TARGET, "upgrade to version: {} successful", version);
 | 
				
			||||||
            },
 | 
					            }
 | 
				
			||||||
            Err(e) => {
 | 
					            Err(e) => {
 | 
				
			||||||
                log::debug!(target: LOG_TARGET, "upgrade to version: {} failed: {:?}", version, e);
 | 
					                log::debug!(target: LOG_TARGET, "upgrade to version: {} failed: {:?}", version, e);
 | 
				
			||||||
            },
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -262,7 +266,8 @@ mod tests {
 | 
				
			|||||||
            "--delay",
 | 
					            "--delay",
 | 
				
			||||||
            "12",
 | 
					            "12",
 | 
				
			||||||
            "seq-phragmen",
 | 
					            "seq-phragmen",
 | 
				
			||||||
        ]).unwrap();
 | 
					        ])
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            opt,
 | 
					            opt,
 | 
				
			||||||
@ -293,7 +298,8 @@ mod tests {
 | 
				
			|||||||
            "--seed-or-path",
 | 
					            "--seed-or-path",
 | 
				
			||||||
            "//Alice",
 | 
					            "//Alice",
 | 
				
			||||||
            "prag-mms",
 | 
					            "prag-mms",
 | 
				
			||||||
        ]).unwrap();
 | 
					        ])
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            opt,
 | 
					            opt,
 | 
				
			||||||
@ -323,7 +329,8 @@ mod tests {
 | 
				
			|||||||
            "prag-mms",
 | 
					            "prag-mms",
 | 
				
			||||||
            "--iterations",
 | 
					            "--iterations",
 | 
				
			||||||
            "1337",
 | 
					            "1337",
 | 
				
			||||||
        ]).unwrap();
 | 
					        ])
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            opt,
 | 
					            opt,
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ use sp_npos_elections::BalancingConfig;
 | 
				
			|||||||
use sp_runtime::DeserializeOwned;
 | 
					use sp_runtime::DeserializeOwned;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{collections::HashMap, fmt, str::FromStr};
 | 
					use std::{collections::HashMap, fmt, str::FromStr};
 | 
				
			||||||
use subxt::backend::legacy::rpc_methods:: as subxt_rpc;
 | 
					use subxt::backend::legacy::rpc_methods as subxt_rpc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Parser)]
 | 
					#[derive(Debug, Clone, Parser)]
 | 
				
			||||||
#[cfg_attr(test, derive(PartialEq))]
 | 
					#[cfg_attr(test, derive(PartialEq))]
 | 
				
			||||||
@ -18,7 +18,7 @@ pub enum Solver {
 | 
				
			|||||||
    PhragMMS {
 | 
					    PhragMMS {
 | 
				
			||||||
        #[clap(long, default_value = "10")]
 | 
					        #[clap(long, default_value = "10")]
 | 
				
			||||||
        iterations: usize,
 | 
					        iterations: usize,
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
frame_support::parameter_types! {
 | 
					frame_support::parameter_types! {
 | 
				
			||||||
@ -30,7 +30,7 @@ frame_support::parameter_types! {
 | 
				
			|||||||
#[derive(Debug, Copy, Clone)]
 | 
					#[derive(Debug, Copy, Clone)]
 | 
				
			||||||
pub enum Chain {
 | 
					pub enum Chain {
 | 
				
			||||||
    Ghost,
 | 
					    Ghost,
 | 
				
			||||||
    Casper
 | 
					    Casper,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl fmt::Display for Chain {
 | 
					impl fmt::Display for Chain {
 | 
				
			||||||
@ -64,8 +64,8 @@ impl TryFrom<subxt_rpc::RuntimeVersion> for Chain {
 | 
				
			|||||||
            .get("specName")
 | 
					            .get("specName")
 | 
				
			||||||
            .expect("RuntimeVersion must have specName; qed")
 | 
					            .expect("RuntimeVersion must have specName; qed")
 | 
				
			||||||
            .clone();
 | 
					            .clone();
 | 
				
			||||||
        let mut chain = serde_json::from_value::<String>(json)
 | 
					        let mut chain =
 | 
				
			||||||
            .expect("specName must be String; qed");
 | 
					            serde_json::from_value::<String>(json).expect("specName must be String; qed");
 | 
				
			||||||
        chain.make_ascii_lowercase();
 | 
					        chain.make_ascii_lowercase();
 | 
				
			||||||
        Chain::from_str(&chain)
 | 
					        Chain::from_str(&chain)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -96,8 +96,7 @@ impl From<subxt_rpc::RuntimeVersion> for RuntimeVersion {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
 | 
					#[derive(Deserialize, Serialize, PartialEq, Debug, Clone, Debug)]
 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
pub struct RuntimeVersion {
 | 
					pub struct RuntimeVersion {
 | 
				
			||||||
    pub spec_name: String,
 | 
					    pub spec_name: String,
 | 
				
			||||||
    pub impl_name: String,
 | 
					    pub impl_name: String,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
pub use pallet_election_provider_multi_phase::{Miner, MinerConfig};
 | 
					pub use pallet_election_provider_multi_phase::{Miner, MinerConfig};
 | 
				
			||||||
pub use subxt::ext::sp_core;
 | 
					pub use subxt::ext::sp_core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub use primitives::{AccountId, Header, Hash, Balance};
 | 
					pub use primitives::{AccountId, Balance, Hash, Header};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// pub type AccountId = sp_runtime::AccountId32;
 | 
					// pub type AccountId = sp_runtime::AccountId32;
 | 
				
			||||||
// pub type Header = subxt::config::substrate::SubstrateHeader<u32, subxt::config::substrate::BlakeTwo256>;
 | 
					// pub type Header = subxt::config::substrate::SubstrateHeader<u32, subxt::config::substrate::BlakeTwo256>;
 | 
				
			||||||
@ -21,7 +21,8 @@ pub type RpcClient = subxt::backend::legacy::LegacyPrcMethods<subxt::SubstrateCo
 | 
				
			|||||||
pub type ChainClient = subxt::OnlineClient<subxt::SubstrateConfig>;
 | 
					pub type ChainClient = subxt::OnlineClient<subxt::SubstrateConfig>;
 | 
				
			||||||
pub type Config = subxt::SubstrateConfig;
 | 
					pub type Config = subxt::SubstrateConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type SignedSubmission<S> = pallet_election_provider_multi_phase::SignedSubmission<AccountId, Balance, S>;
 | 
					pub type SignedSubmission<S> =
 | 
				
			||||||
 | 
					    pallet_election_provider_multi_phase::SignedSubmission<AccountId, Balance, S>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[subxt::subxt(
 | 
					#[subxt::subxt(
 | 
				
			||||||
    runtime_metadata_path = "artifacts/metadata.scale",
 | 
					    runtime_metadata_path = "artifacts/metadata.scale",
 | 
				
			||||||
 | 
				
			|||||||
@ -21,9 +21,15 @@ async fn serve_req(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
 | 
				
			|||||||
                .header(CONTENT_TYPE, encoder.format_type())
 | 
					                .header(CONTENT_TYPE, encoder.format_type())
 | 
				
			||||||
                .body(Body::from(buffer))
 | 
					                .body(Body::from(buffer))
 | 
				
			||||||
                .unwrap()
 | 
					                .unwrap()
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
        (&Method::GET, "/") => Response::builder().status(200).body(Body::from("")).unwrap(),
 | 
					        (&Method::GET, "/") => Response::builder()
 | 
				
			||||||
        _ => Response::builder().status(404).body(Body::from("")).unwrap(),
 | 
					            .status(200)
 | 
				
			||||||
 | 
					            .body(Body::from(""))
 | 
				
			||||||
 | 
					            .unwrap(),
 | 
				
			||||||
 | 
					        _ => Response::builder()
 | 
				
			||||||
 | 
					            .status(404)
 | 
				
			||||||
 | 
					            .body(Body::from(""))
 | 
				
			||||||
 | 
					            .unwrap(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(response)
 | 
					    Ok(response)
 | 
				
			||||||
@ -75,35 +81,40 @@ mod hidden {
 | 
				
			|||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_trim_started",
 | 
					            "staking_miner_trim_started",
 | 
				
			||||||
            "Number of started trimmed solutions",
 | 
					            "Number of started trimmed solutions",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static TRIMMED_SOLUTION_SUCCESS: Lazy<Counter> = Lazy::new(|| {
 | 
					    static TRIMMED_SOLUTION_SUCCESS: Lazy<Counter> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_trim_success",
 | 
					            "staking_miner_trim_success",
 | 
				
			||||||
            "Number of successful trimmed solutions",
 | 
					            "Number of successful trimmed solutions",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static SUBMISSIONS_STARTED: Lazy<Counter> = Lazy::new(|| {
 | 
					    static SUBMISSIONS_STARTED: Lazy<Counter> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_submissions_started",
 | 
					            "staking_miner_submissions_started",
 | 
				
			||||||
            "Number of submissions started",
 | 
					            "Number of submissions started",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static SUBMISSIONS_SUCCESS: Lazy<Counter> = Lazy::new(|| {
 | 
					    static SUBMISSIONS_SUCCESS: Lazy<Counter> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_submissions_success",
 | 
					            "staking_miner_submissions_success",
 | 
				
			||||||
            "Number of submissions finished successfully",
 | 
					            "Number of submissions finished successfully",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static MINED_SOLUTION_DURATION: Lazy<Gauge> = Lazy::new(|| {
 | 
					    static MINED_SOLUTION_DURATION: Lazy<Gauge> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_mining_duration_ms",
 | 
					            "staking_miner_mining_duration_ms",
 | 
				
			||||||
            "The mined solution time in milliseconds.",
 | 
					            "The mined solution time in milliseconds.",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static SUBMIT_SOLUTION_AND_WATCH_DURATION: Lazy<Gauge> = Lazy::new(|| {
 | 
					    static SUBMIT_SOLUTION_AND_WATCH_DURATION: Lazy<Gauge> = Lazy::new(|| {
 | 
				
			||||||
@ -117,49 +128,56 @@ mod hidden {
 | 
				
			|||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_balance",
 | 
					            "staking_miner_balance",
 | 
				
			||||||
            "The balance of the staking miner account",
 | 
					            "The balance of the staking miner account",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static SCORE_MINIMAL_STAKE: Lazy<Gauge> = Lazy::new(|| {
 | 
					    static SCORE_MINIMAL_STAKE: Lazy<Gauge> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_score_minimal_stake",
 | 
					            "staking_miner_score_minimal_stake",
 | 
				
			||||||
            "The minimal winner, in terms of total backing stake",
 | 
					            "The minimal winner, in terms of total backing stake",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static SCORE_SUM_STAKE: Lazy<Gauge> = Lazy::new(|| {
 | 
					    static SCORE_SUM_STAKE: Lazy<Gauge> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_score_sum_stake",
 | 
					            "staking_miner_score_sum_stake",
 | 
				
			||||||
            "The sum of the total backing of all winners",
 | 
					            "The sum of the total backing of all winners",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static SCORE_SUM_STAKE_SQUARED: Lazy<Gauge> = Lazy::new(|| {
 | 
					    static SCORE_SUM_STAKE_SQUARED: Lazy<Gauge> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_score_sum_stake_squared",
 | 
					            "staking_miner_score_sum_stake_squared",
 | 
				
			||||||
            "The sum of the total backing of all winners, aka. the variance.",
 | 
					            "The sum of the total backing of all winners, aka. the variance.",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static RUNTIME_UPGRADES: Lazy<Counter> = Lazy::new(|| {
 | 
					    static RUNTIME_UPGRADES: Lazy<Counter> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_runtime",
 | 
					            "staking_miner_runtime",
 | 
				
			||||||
            "Number of runtime upgrades performed",
 | 
					            "Number of runtime upgrades performed",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static SUBMISSION_LENGTH: Lazy<Gauge> = Lazy::new(|| {
 | 
					    static SUBMISSION_LENGTH: Lazy<Gauge> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_solution_length_bytes",
 | 
					            "staking_miner_solution_length_bytes",
 | 
				
			||||||
            "Number of bytes in the solution submitted",
 | 
					            "Number of bytes in the solution submitted",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static SUBMISSION_WEIGHT: Lazy<Gauge> = Lazy::new(|| {
 | 
					    static SUBMISSION_WEIGHT: Lazy<Gauge> = Lazy::new(|| {
 | 
				
			||||||
        register_counter!(opts!(
 | 
					        register_counter!(opts!(
 | 
				
			||||||
            "staking_miner_solution_weight",
 | 
					            "staking_miner_solution_weight",
 | 
				
			||||||
            "Weight of the solution submitted",
 | 
					            "Weight of the solution submitted",
 | 
				
			||||||
        )).unwrap()
 | 
					        ))
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn on_runtime_upgrade() {
 | 
					    pub fn on_runtime_upgrade() {
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,10 @@ impl std::fmt::Display for Signer {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl Clone for Signer {
 | 
					impl Clone for Signer {
 | 
				
			||||||
    fn clone(&self) -> Self {
 | 
					    fn clone(&self) -> Self {
 | 
				
			||||||
        Self { pair: self.pair.clone(), signer: PairSigner::new(self.pair.clone()) }
 | 
					        Self {
 | 
				
			||||||
 | 
					            pair: self.pair.clone(),
 | 
				
			||||||
 | 
					            signer: PairSigner::new(self.pair.clone()),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -46,7 +46,10 @@ mod max_weight {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    impl MaxWeight {
 | 
					    impl MaxWeight {
 | 
				
			||||||
        pub fn get() -> Weight {
 | 
					        pub fn get() -> Weight {
 | 
				
			||||||
            Weight::from_parts(REF_TIME.load(Ordering::SeqCst), PROOF_SIZE.load(Ordering::SeqCst))
 | 
					            Weight::from_parts(
 | 
				
			||||||
 | 
					                REF_TIME.load(Ordering::SeqCst),
 | 
				
			||||||
 | 
					                PROOF_SIZE.load(Ordering::SeqCst),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -84,7 +87,7 @@ pub mod ghost {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    #[derive(Debug)]
 | 
					    #[derive(Debug)]
 | 
				
			||||||
    pub struct MinerConfig;
 | 
					    pub struct MinerConfig;
 | 
				
			||||||
    impla pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig {
 | 
					    impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig {
 | 
				
			||||||
        type AccountId = AccountId;
 | 
					        type AccountId = AccountId;
 | 
				
			||||||
        type MaxLength = MaxLength;
 | 
					        type MaxLength = MaxLength;
 | 
				
			||||||
        type MaxWeight = MaxWeight;
 | 
					        type MaxWeight = MaxWeight;
 | 
				
			||||||
@ -100,18 +103,23 @@ pub mod ghost {
 | 
				
			|||||||
        ) -> Weight {
 | 
					        ) -> Weight {
 | 
				
			||||||
            let Some(votes) = epm::mock_votes(
 | 
					            let Some(votes) = epm::mock_votes(
 | 
				
			||||||
                active_voters,
 | 
					                active_voters,
 | 
				
			||||||
                desired_targets.try_into().expect("Desired targets < u16::MAX"),
 | 
					                desired_targets
 | 
				
			||||||
 | 
					                    .try_into()
 | 
				
			||||||
 | 
					                    .expect("Desired targets < u16::MAX"),
 | 
				
			||||||
            ) else {
 | 
					            ) else {
 | 
				
			||||||
                return Weight::MAX;
 | 
					                return Weight::MAX;
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let raw = RawSolution {
 | 
					            let raw = RawSolution {
 | 
				
			||||||
                solution: NposSolution16 { votes1: votes, ..Default::default() },
 | 
					                solution: NposSolution16 {
 | 
				
			||||||
 | 
					                    votes1: votes,
 | 
				
			||||||
 | 
					                    ..Default::default()
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                ..Default::default()
 | 
					                ..Default::default()
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if raw.solution.voter_count() != active_voters as usize ||
 | 
					            if raw.solution.voter_count() != active_voters as usize
 | 
				
			||||||
                raw.solution.unique_targets().len() != desired_targets as usize
 | 
					                || raw.solution.unique_targets().len() != desired_targets as usize
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Weight::MAX;
 | 
					                return Weight::MAX;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -119,7 +127,8 @@ pub mod ghost {
 | 
				
			|||||||
            futures::executor::block_on(epm::runtime_api_solution_weight(
 | 
					            futures::executor::block_on(epm::runtime_api_solution_weight(
 | 
				
			||||||
                raw,
 | 
					                raw,
 | 
				
			||||||
                SolutionOrSnapshotSize { voters, targets },
 | 
					                SolutionOrSnapshotSize { voters, targets },
 | 
				
			||||||
            )).expect("solution_weight should work")
 | 
					            ))
 | 
				
			||||||
 | 
					            .expect("solution_weight should work")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -139,7 +148,7 @@ pub mod casper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    #[derive(Debug)]
 | 
					    #[derive(Debug)]
 | 
				
			||||||
    pub struct MinerConfig;
 | 
					    pub struct MinerConfig;
 | 
				
			||||||
    impla pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig {
 | 
					    impl pallet_election_provider_multi_phase::unsigned::MinerConfig for MinerConfig {
 | 
				
			||||||
        type AccountId = AccountId;
 | 
					        type AccountId = AccountId;
 | 
				
			||||||
        type MaxLength = MaxLength;
 | 
					        type MaxLength = MaxLength;
 | 
				
			||||||
        type MaxWeight = MaxWeight;
 | 
					        type MaxWeight = MaxWeight;
 | 
				
			||||||
@ -155,18 +164,23 @@ pub mod casper {
 | 
				
			|||||||
        ) -> Weight {
 | 
					        ) -> Weight {
 | 
				
			||||||
            let Some(votes) = epm::mock_votes(
 | 
					            let Some(votes) = epm::mock_votes(
 | 
				
			||||||
                active_voters,
 | 
					                active_voters,
 | 
				
			||||||
                desired_targets.try_into().expect("Desired targets < u16::MAX"),
 | 
					                desired_targets
 | 
				
			||||||
 | 
					                    .try_into()
 | 
				
			||||||
 | 
					                    .expect("Desired targets < u16::MAX"),
 | 
				
			||||||
            ) else {
 | 
					            ) else {
 | 
				
			||||||
                return Weight::MAX;
 | 
					                return Weight::MAX;
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let raw = RawSolution {
 | 
					            let raw = RawSolution {
 | 
				
			||||||
                solution: NposSolution16 { votes1: votes, ..Default::default() },
 | 
					                solution: NposSolution16 {
 | 
				
			||||||
 | 
					                    votes1: votes,
 | 
				
			||||||
 | 
					                    ..Default::default()
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                ..Default::default()
 | 
					                ..Default::default()
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if raw.solution.voter_count() != active_voters as usize ||
 | 
					            if raw.solution.voter_count() != active_voters as usize
 | 
				
			||||||
                raw.solution.unique_targets().len() != desired_targets as usize
 | 
					                || raw.solution.unique_targets().len() != desired_targets as usize
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Weight::MAX;
 | 
					                return Weight::MAX;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -174,7 +188,8 @@ pub mod casper {
 | 
				
			|||||||
            futures::executor::block_on(epm::runtime_api_solution_weight(
 | 
					            futures::executor::block_on(epm::runtime_api_solution_weight(
 | 
				
			||||||
                raw,
 | 
					                raw,
 | 
				
			||||||
                SolutionOrSnapshotSize { voters, targets },
 | 
					                SolutionOrSnapshotSize { voters, targets },
 | 
				
			||||||
            )).expect("solution_weight should work")
 | 
					            ))
 | 
				
			||||||
 | 
					            .expect("solution_weight should work")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user