forked from ghostchain/ghost-node
123 lines
4.0 KiB
Rust
123 lines
4.0 KiB
Rust
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(())
|
|
}
|