-
Notifications
You must be signed in to change notification settings - Fork 1.5k
check runtime version in staking miner #3628
Changes from all commits
1d206dc
e8cc9ad
b5904a2
ba1053c
1b03dd8
a519f91
80e547b
3d539ba
e584a4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ use crate::{ | |
| params, prelude::*, rpc_helpers::*, signer::Signer, DryRunConfig, Error, SharedConfig, WsClient, | ||
| }; | ||
| use codec::Encode; | ||
| use frame_support::traits::Currency; | ||
|
|
||
| /// Forcefully create the snapshot. This can be used to compute the election at anytime. | ||
| fn force_create_snapshot<T: EPM::Config>(ext: &mut Ext) -> Result<(), Error> { | ||
|
|
@@ -35,18 +36,53 @@ fn force_create_snapshot<T: EPM::Config>(ext: &mut Ext) -> Result<(), Error> { | |
| } | ||
|
|
||
| /// Helper method to print the encoded size of the snapshot. | ||
| fn measure_snapshot_size<T: EPM::Config>(ext: &mut Ext) { | ||
| async fn print_info<T: EPM::Config>( | ||
| client: &WsClient, | ||
| ext: &mut Ext, | ||
| raw_solution: &EPM::RawSolution<EPM::SolutionOf<T>>, | ||
| extrinsic: sp_core::Bytes, | ||
| ) where | ||
| <T as EPM::Config>::Currency: Currency<T::AccountId, Balance = Balance>, | ||
| { | ||
| ext.execute_with(|| { | ||
| log::info!(target: LOG_TARGET, "Metadata: {:?}", <EPM::Pallet<T>>::snapshot_metadata()); | ||
| log::info!( | ||
| target: LOG_TARGET, | ||
| "Encoded Length: {:?}", | ||
| "Snapshot Metadata: {:?}", | ||
| <EPM::Pallet<T>>::snapshot_metadata() | ||
| ); | ||
| log::info!( | ||
| target: LOG_TARGET, | ||
| "Snapshot Encoded Length: {:?}", | ||
| <EPM::Pallet<T>>::snapshot() | ||
| .expect("snapshot must exist before calling `measure_snapshot_size`") | ||
| .encode() | ||
| .len() | ||
| ); | ||
| }) | ||
|
|
||
| let snapshot_size = | ||
| <EPM::Pallet<T>>::snapshot_metadata().expect("snapshot must exist by now; qed."); | ||
| let deposit = EPM::Pallet::<T>::deposit_for(&raw_solution, snapshot_size); | ||
| log::info!( | ||
| target: LOG_TARGET, | ||
| "solution score {:?} / deposit {:?} / length {:?}", | ||
| &raw_solution.score.iter().map(|x| Token::from(*x)).collect::<Vec<_>>(), | ||
| Token::from(deposit), | ||
| raw_solution.encode().len(), | ||
| ); | ||
| }); | ||
|
|
||
| let info = rpc::<pallet_transaction_payment::RuntimeDispatchInfo<Balance>>( | ||
| client, | ||
| "payment_queryInfo", | ||
| params! { extrinsic }, | ||
| ) | ||
| .await; | ||
| log::info!( | ||
| target: LOG_TARGET, | ||
| "payment_queryInfo: (fee = {}) {:?}", | ||
| info.as_ref().map(|d| Token::from(d.partial_fee)).unwrap_or(Token::from(0)), | ||
| info, | ||
| ); | ||
| } | ||
|
|
||
| /// Find the stake threshold in order to have at most `count` voters. | ||
|
|
@@ -76,24 +112,40 @@ macro_rules! dry_run_cmd_for { ($runtime:ident) => { paste::paste! { | |
| signer: Signer, | ||
| ) -> Result<(), Error> { | ||
| use $crate::[<$runtime _runtime_exports>]::*; | ||
| let mut ext = crate::create_election_ext::<Runtime, Block>(shared.uri.clone(), config.at, true).await?; | ||
| let mut ext = crate::create_election_ext::<Runtime, Block>( | ||
| shared.uri.clone(), | ||
| config.at, | ||
| vec!["Staking".to_string(), "System".to_string(), "Balances".to_string()] | ||
| ).await?; | ||
| force_create_snapshot::<Runtime>(&mut ext)?; | ||
| measure_snapshot_size::<Runtime>(&mut ext); | ||
| let (raw_solution, witness) = crate::mine_unchecked::<Runtime>(&mut ext, config.iterations, false)?; | ||
| log::info!(target: LOG_TARGET, "mined solution with {:?}", &raw_solution.score); | ||
|
|
||
| let (raw_solution, witness) = crate::mine_unchecked::<Runtime>(&mut ext, config.iterations, false)?; | ||
| let nonce = crate::get_account_info::<Runtime>(client, &signer.account, config.at) | ||
| .await? | ||
| .map(|i| i.nonce) | ||
| .expect("signer account is checked to exist upon startup; it can only die if it \ | ||
| transfers funds out of it, or get slashed. If it does not exist at this point, \ | ||
| it is likely due to a bug, or the signer got slashed. Terminating." | ||
| ); | ||
| transfers funds out of it, or get slashed. If it does not exist at this point, \ | ||
| it is likely due to a bug, or the signer got slashed. Terminating." | ||
| ); | ||
| let tip = 0 as Balance; | ||
| let era = sp_runtime::generic::Era::Immortal; | ||
| let extrinsic = ext.execute_with(|| create_uxt(raw_solution, witness, signer.clone(), nonce, tip, era)); | ||
| let extrinsic = ext.execute_with(|| create_uxt(raw_solution.clone(), witness, signer.clone(), nonce, tip, era)); | ||
|
|
||
| let bytes = sp_core::Bytes(extrinsic.encode().to_vec()); | ||
| print_info::<Runtime>(client, &mut ext, &raw_solution, bytes.clone()).await; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what would happen if we didn't block on this call? Would it still print correctly? If so it could be a performance improvement
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure what you mean, elaborate? |
||
|
|
||
| let feasibility_result = ext.execute_with(|| { | ||
| EPM::Pallet::<Runtime>::feasibility_check(raw_solution.clone(), EPM::ElectionCompute::Signed) | ||
| }); | ||
| log::info!(target: LOG_TARGET, "feasibility result is {:?}", feasibility_result.map(|_| ())); | ||
|
|
||
| let dispatch_result = ext.execute_with(|| { | ||
| // manually tweak the phase. | ||
| EPM::CurrentPhase::<Runtime>::put(EPM::Phase::Signed); | ||
| EPM::Pallet::<Runtime>::submit(frame_system::RawOrigin::Signed(signer.account).into(), Box::new(raw_solution), witness) | ||
| }); | ||
| log::info!(target: LOG_TARGET, "dispatch result is {:?}", dispatch_result); | ||
|
|
||
| let outcome = rpc_decode::<sp_runtime::ApplyExtrinsicResult>(client, "system_dryRun", params!{ bytes }).await?; | ||
| log::info!(target: LOG_TARGET, "dry-run outcome is {:?}", outcome); | ||
| Ok(()) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,7 @@ mod signer; | |
| pub(crate) use prelude::*; | ||
| pub(crate) use signer::get_account_info; | ||
|
|
||
| use frame_support::traits::Get; | ||
| use jsonrpsee_ws_client::{WsClient, WsClientBuilder}; | ||
| use remote_externalities::{Builder, Mode, OnlineConfig}; | ||
| use sp_runtime::traits::Block as BlockT; | ||
|
|
@@ -93,8 +94,9 @@ macro_rules! construct_runtime_prelude { | |
| let address = <Runtime as frame_system::Config>::Lookup::unlookup(account.clone()); | ||
| let extrinsic = UncheckedExtrinsic::new_signed(call, address, signature.into(), extra); | ||
| log::debug!( | ||
| target: crate::LOG_TARGET, "constructed extrinsic {}", | ||
| sp_core::hexdisplay::HexDisplay::from(&extrinsic.encode()) | ||
| target: crate::LOG_TARGET, "constructed extrinsic {} with length {}", | ||
| sp_core::hexdisplay::HexDisplay::from(&extrinsic.encode()), | ||
| extrinsic.encode().len(), | ||
| ); | ||
| extrinsic | ||
| } | ||
|
|
@@ -172,14 +174,17 @@ macro_rules! any_runtime { | |
| unsafe { | ||
| match $crate::RUNTIME { | ||
| $crate::AnyRuntime::Polkadot => { | ||
| #[allow(unused)] | ||
| use $crate::polkadot_runtime_exports::*; | ||
| $($code)* | ||
| }, | ||
| $crate::AnyRuntime::Kusama => { | ||
| #[allow(unused)] | ||
| use $crate::kusama_runtime_exports::*; | ||
| $($code)* | ||
| }, | ||
| $crate::AnyRuntime::Westend => { | ||
| #[allow(unused)] | ||
| use $crate::westend_runtime_exports::*; | ||
| $($code)* | ||
| } | ||
|
|
@@ -201,6 +206,7 @@ enum Error { | |
| AccountDoesNotExists, | ||
| IncorrectPhase, | ||
| AlreadySubmitted, | ||
| VersionMismatch, | ||
| } | ||
|
|
||
| impl From<sp_core::crypto::SecretStringError> for Error { | ||
|
|
@@ -270,14 +276,14 @@ struct DryRunConfig { | |
| #[derive(Debug, Clone, StructOpt)] | ||
| struct SharedConfig { | ||
| /// The `ws` node to connect to. | ||
| #[structopt(long, default_value = DEFAULT_URI)] | ||
| #[structopt(long, short, default_value = DEFAULT_URI)] | ||
| uri: String, | ||
|
|
||
| /// The file from which we read the account seed. | ||
| /// | ||
| /// WARNING: don't use an account with a large stash for this. Based on how the bot is | ||
| /// configured, it might re-try lose funds through transaction fees/deposits. | ||
| #[structopt(long)] | ||
| #[structopt(long, short)] | ||
| account_seed: std::path::PathBuf, | ||
| } | ||
|
|
||
|
|
@@ -291,34 +297,25 @@ struct Opt { | |
| command: Command, | ||
| } | ||
|
|
||
| /// Build the `Ext` at `hash` with all the data of `ElectionProviderMultiPhase` and `Staking` | ||
| /// stored. | ||
| /// Build the Ext at hash with all the data of `ElectionProviderMultiPhase` and any additional | ||
| /// pallets. | ||
| async fn create_election_ext<T: EPM::Config, B: BlockT>( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe update this doc comment to "Build the |
||
| uri: String, | ||
| at: Option<B::Hash>, | ||
| with_staking: bool, | ||
| additional: Vec<String>, | ||
| ) -> Result<Ext, Error> { | ||
| use frame_support::{storage::generator::StorageMap, traits::PalletInfo}; | ||
| use sp_core::hashing::twox_128; | ||
|
|
||
| let mut modules = vec![<T as frame_system::Config>::PalletInfo::name::<EPM::Pallet<T>>() | ||
| .expect("Pallet always has name; qed.") | ||
| .to_string()]; | ||
| modules.extend(additional); | ||
| Builder::<B>::new() | ||
| .mode(Mode::Online(OnlineConfig { | ||
| transport: uri.into(), | ||
| at, | ||
| modules: if with_staking { | ||
| vec![ | ||
| <T as frame_system::Config>::PalletInfo::name::<EPM::Pallet<T>>() | ||
| .expect("Pallet always has name; qed.") | ||
| .to_string(), | ||
| <T as frame_system::Config>::PalletInfo::name::<pallet_staking::Pallet<T>>() | ||
| .expect("Pallet always has name; qed.") | ||
| .to_string(), | ||
| ] | ||
| } else { | ||
| vec![<T as frame_system::Config>::PalletInfo::name::<EPM::Pallet<T>>() | ||
| .expect("Pallet always has name; qed.") | ||
| .to_string()] | ||
| }, | ||
| modules, | ||
| ..Default::default() | ||
| })) | ||
| .inject_hashed_prefix(&<frame_system::BlockHash<T>>::prefix_hash()) | ||
|
|
@@ -386,6 +383,34 @@ fn mine_dpos<T: EPM::Config>(ext: &mut Ext) -> Result<(), Error> { | |
| }) | ||
| } | ||
|
|
||
| pub(crate) async fn check_versions<T: frame_system::Config>( | ||
| client: &WsClient, | ||
| print: bool, | ||
| ) -> Result<(), Error> { | ||
| let linked_version = T::Version::get(); | ||
| let on_chain_version = rpc_helpers::rpc::<sp_version::RuntimeVersion>( | ||
| client, | ||
| "state_getRuntimeVersion", | ||
| params! {}, | ||
| ) | ||
| .await | ||
| .expect("runtime version RPC should always work; qed"); | ||
|
|
||
| if print { | ||
| log::info!(target: LOG_TARGET, "linked version {:?}", linked_version); | ||
| log::info!(target: LOG_TARGET, "on-chain version {:?}", on_chain_version); | ||
| } | ||
| if linked_version != on_chain_version { | ||
| log::error!( | ||
| target: LOG_TARGET, | ||
| "VERSION MISMATCH: any transaction will fail with bad-proof" | ||
| ); | ||
| Err(Error::VersionMismatch) | ||
| } else { | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[tokio::main] | ||
| async fn main() { | ||
| env_logger::Builder::from_default_env() | ||
|
|
@@ -422,6 +447,8 @@ async fn main() { | |
| sp_core::crypto::set_default_ss58_version( | ||
| sp_core::crypto::Ss58AddressFormat::PolkadotAccount, | ||
| ); | ||
| sub_tokens::dynamic::set_name("DOT"); | ||
| sub_tokens::dynamic::set_decimal_points(10_000_000_000); | ||
| // safety: this program will always be single threaded, thus accessing global static is | ||
| // safe. | ||
| unsafe { | ||
|
|
@@ -432,6 +459,8 @@ async fn main() { | |
| sp_core::crypto::set_default_ss58_version( | ||
| sp_core::crypto::Ss58AddressFormat::KusamaAccount, | ||
| ); | ||
| sub_tokens::dynamic::set_name("KSM"); | ||
| sub_tokens::dynamic::set_decimal_points(1_000_000_000_000); | ||
| // safety: this program will always be single threaded, thus accessing global static is | ||
| // safe. | ||
| unsafe { | ||
|
|
@@ -442,6 +471,8 @@ async fn main() { | |
| sp_core::crypto::set_default_ss58_version( | ||
| sp_core::crypto::Ss58AddressFormat::PolkadotAccount, | ||
| ); | ||
| sub_tokens::dynamic::set_name("WND"); | ||
| sub_tokens::dynamic::set_decimal_points(1_000_000_000_000); | ||
| // safety: this program will always be single threaded, thus accessing global static is | ||
| // safe. | ||
| unsafe { | ||
|
|
@@ -455,6 +486,10 @@ async fn main() { | |
| } | ||
| log::info!(target: LOG_TARGET, "connected to chain {:?}", chain); | ||
|
|
||
| let _ = any_runtime! { | ||
| check_versions::<Runtime>(&client, true).await | ||
| }; | ||
|
|
||
| let signer_account = any_runtime! { | ||
| signer::read_signer_uri::<_, Runtime>(&shared.account_seed, &client) | ||
| .await | ||
|
|
@@ -464,7 +499,6 @@ async fn main() { | |
| let outcome = any_runtime! { | ||
| match command.clone() { | ||
| Command::Monitor(c) => monitor_cmd(&client, shared, c, signer_account).await, | ||
| // --------------------^^ comes from the macro prelude, needs no generic. | ||
| Command::DryRun(c) => dry_run_cmd(&client, shared, c, signer_account).await, | ||
| Command::EmergencySolution => emergency_solution_cmd(shared.clone()).await, | ||
| } | ||
|
|
@@ -477,7 +511,6 @@ mod tests { | |
| use super::*; | ||
|
|
||
| fn get_version<T: frame_system::Config>() -> sp_version::RuntimeVersion { | ||
| use frame_support::traits::Get; | ||
| T::Version::get() | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -86,6 +86,9 @@ macro_rules! monitor_cmd_for { ($runtime:tt) => { paste::paste! { | |
| let hash = now.hash(); | ||
| log::debug!(target: LOG_TARGET, "new event at #{:?} ({:?})", now.number, hash); | ||
|
|
||
| // if the runtime version has changed, terminate | ||
| crate::check_versions::<Runtime>(client, false).await?; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not neccessary for this PR, but is there a way we could parrell-ize the calls to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah this is a good improvement. Also a critical improvement. is that currently we need to scrape the data of the whole pallet to check if we have a solution in this round or not (since we pass the entire we should be able to early-exit from this check, without the need to scrape the entire
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| // we prefer doing this check before fetching anything into a remote-ext. | ||
| if ensure_signed_phase::<Runtime, Block>(client, hash).await.is_err() { | ||
| log::debug!(target: LOG_TARGET, "phase closed, not interested in this block at all."); | ||
|
|
@@ -99,7 +102,7 @@ macro_rules! monitor_cmd_for { ($runtime:tt) => { paste::paste! { | |
| // this will not be a solution. | ||
|
|
||
| // grab an externalities without staking, just the election snapshot. | ||
| let mut ext = crate::create_election_ext::<Runtime, Block>(shared.uri.clone(), Some(hash), false).await?; | ||
| let mut ext = crate::create_election_ext::<Runtime, Block>(shared.uri.clone(), Some(hash), vec![]).await?; | ||
|
|
||
| if ensure_no_previous_solution::<Runtime, Block>(&mut ext, &signer.account).await.is_err() { | ||
| log::debug!(target: LOG_TARGET, "We already have a solution in this phase, skipping."); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems quite expensive just to do a log message - is there somewhere else this is already encoded and we can just pul the length from that? Or maybe we can use
size_hint(&self) -> usize?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah it could, but it would make the code look less ergonomic and since this is dry-run and not at all time-sensitive, I intentionally overlooked it.