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,16 +387,17 @@ 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 {
|
||||||
round,
|
round,
|
||||||
desired_targets,
|
desired_targets,
|
||||||
snapshot,
|
snapshot,
|
||||||
minimum_untrusted_score,
|
minimum_untrusted_score,
|
||||||
solution,
|
solution,
|
||||||
score,
|
score,
|
||||||
solution_or_snapshot_size,
|
solution_or_snapshot_size,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -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,12 +93,17 @@ 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();
|
||||||
|
|
||||||
let client = Client::new(&uri).await?;
|
let client = Client::new(&uri).await?;
|
||||||
let runtime_version: RuntimeVersion =
|
let runtime_version: RuntimeVersion =
|
||||||
client.rpc().state_get_runtime_version(None).await?.into();
|
client.rpc().state_get_runtime_version(None).await?.into();
|
||||||
let chain = opt::Chain::from_str(&runtime_version.spec_name)?;
|
let chain = opt::Chain::from_str(&runtime_version.spec_name)?;
|
||||||
let _prometheus_handle = prometheus::run(prometheus_port)
|
let _prometheus_handle = prometheus::run(prometheus_port)
|
||||||
@ -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