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, /// 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, /// 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, } pub async fn dry_run_cmd(client: Client, config: DryRunConfig) -> Result<(), Error> where T: MinerConfig + 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::( 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(()) }