diff --git a/Cargo.lock b/Cargo.lock index 318eb7639..6b6e82622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1771,6 +1771,22 @@ dependencies = [ "strum", ] +[[package]] +name = "pallet-transaction-payment" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#28f0cb18c96fb82f810f1d9be69fb609fbb7217e" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 6.0.0 (git+https://github.com/paritytech/substrate?branch=master)", + "sp-io 6.0.0 (git+https://github.com/paritytech/substrate?branch=master)", + "sp-runtime 6.0.0 (git+https://github.com/paritytech/substrate?branch=master)", + "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=master)", +] + [[package]] name = "parity-scale-codec" version = "3.1.5" @@ -3373,6 +3389,7 @@ dependencies = [ "log", "once_cell", "pallet-election-provider-multi-phase", + "pallet-transaction-payment", "parity-scale-codec", "paste", "pin-project-lite", diff --git a/Cargo.toml b/Cargo.toml index 6466980e1..9ba2a6eef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } frame-election-provider-support = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-election-provider-multi-phase = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-npos-elections = { git = "https://github.com/paritytech/substrate", branch = "master" } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/src/chain.rs b/src/chain.rs index 2ff5dea17..64f227725 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -6,6 +6,14 @@ // polkadot, that only has `const` and `type`s that are used in the runtime, and we can import // that. +use codec::{Decode, Encode}; +use jsonrpsee::{core::client::ClientT, rpc_params}; +use once_cell::sync::OnceCell; +use pallet_transaction_payment::RuntimeDispatchInfo; +use sp_core::Bytes; + +pub static SHARED_CLIENT: OnceCell = OnceCell::new(); + macro_rules! impl_atomic_u32_parameter_types { ($mod:ident, $name:ident) => { mod $mod { @@ -130,16 +138,35 @@ pub mod westend { type Solution = NposSolution16; // SYNC - fn solution_weight(v: u32, _t: u32, a: u32, d: u32) -> Weight { - // feasibility weight. - (31_722_000 as Weight) - // Standard Error: 8_000 - .saturating_add((1_255_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 28_000 - .saturating_add((8_972_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 42_000 - .saturating_add((966_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(static_types::DbWeight::get().reads(4 as Weight)) + fn solution_weight( + voters: u32, + targets: u32, + active_voters: u32, + desired_targets: u32, + ) -> Weight { + use _feps::NposSolution; + use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; + + // Mock a RawSolution to get the correct weight without having to do the heavy work. + let raw = RawSolution { + solution: NposSolution16 { + votes1: mock_votes( + active_voters, + desired_targets.try_into().expect("Desired targets < u16::MAX"), + ), + ..Default::default() + }, + ..Default::default() + }; + + assert_eq!(raw.solution.voter_count(), active_voters as usize); + assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); + + let tx = runtime::tx() + .election_provider_multi_phase() + .submit_unsigned(raw, SolutionOrSnapshotSize { voters, targets }); + + get_weight(tx) } } @@ -166,6 +193,11 @@ pub mod westend { #[subxt(substitute_type = "pallet_election_provider_multi_phase::Phase")] use ::pallet_election_provider_multi_phase::Phase; + + #[subxt( + substitute_type = "pallet_election_provider_multi_phase::SolutionOrSnapshotSize" + )] + use ::pallet_election_provider_multi_phase::SolutionOrSnapshotSize; } pub use runtime::runtime_types; @@ -206,16 +238,35 @@ pub mod polkadot { type Solution = NposSolution16; // SYNC - fn solution_weight(v: u32, _t: u32, a: u32, d: u32) -> Weight { - // feasibility weight. - (31_722_000 as Weight) - // Standard Error: 8_000 - .saturating_add((1_255_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 28_000 - .saturating_add((8_972_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 42_000 - .saturating_add((966_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(static_types::DbWeight::get().reads(4 as Weight)) + fn solution_weight( + voters: u32, + targets: u32, + active_voters: u32, + desired_targets: u32, + ) -> Weight { + use _feps::NposSolution; + use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; + + // Mock a RawSolution to get the correct weight without having to do the heavy work. + let raw = RawSolution { + solution: NposSolution16 { + votes1: mock_votes( + active_voters, + desired_targets.try_into().expect("Desired targets < u16::MAX"), + ), + ..Default::default() + }, + ..Default::default() + }; + + assert_eq!(raw.solution.voter_count(), active_voters as usize); + assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); + + let tx = runtime::tx() + .election_provider_multi_phase() + .submit_unsigned(raw, SolutionOrSnapshotSize { voters, targets }); + + get_weight(tx) } } @@ -242,6 +293,11 @@ pub mod polkadot { #[subxt(substitute_type = "pallet_election_provider_multi_phase::Phase")] use ::pallet_election_provider_multi_phase::Phase; + + #[subxt( + substitute_type = "pallet_election_provider_multi_phase::SolutionOrSnapshotSize" + )] + use ::pallet_election_provider_multi_phase::SolutionOrSnapshotSize; } pub use runtime::runtime_types; @@ -282,16 +338,35 @@ pub mod kusama { type Solution = NposSolution24; // SYNC - fn solution_weight(v: u32, _t: u32, a: u32, d: u32) -> Weight { - // feasibility weight. - (31_722_000 as Weight) - // Standard Error: 8_000 - .saturating_add((1_255_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 28_000 - .saturating_add((8_972_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 42_000 - .saturating_add((966_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(static_types::DbWeight::get().reads(4 as Weight)) + fn solution_weight( + voters: u32, + targets: u32, + active_voters: u32, + desired_targets: u32, + ) -> Weight { + use _feps::NposSolution; + use pallet_election_provider_multi_phase::{RawSolution, SolutionOrSnapshotSize}; + + // Mock a RawSolution to get the correct weight without having to do the heavy work. + let raw = RawSolution { + solution: NposSolution24 { + votes1: mock_votes( + active_voters, + desired_targets.try_into().expect("Desired targets < u16::MAX"), + ), + ..Default::default() + }, + ..Default::default() + }; + + assert_eq!(raw.solution.voter_count(), active_voters as usize); + assert_eq!(raw.solution.unique_targets().len(), desired_targets as usize); + + let tx = runtime::tx() + .election_provider_multi_phase() + .submit_unsigned(raw, SolutionOrSnapshotSize { voters, targets }); + + get_weight(tx) } } @@ -318,6 +393,11 @@ pub mod kusama { #[subxt(substitute_type = "pallet_election_provider_multi_phase::Phase")] use ::pallet_election_provider_multi_phase::Phase; + + #[subxt( + substitute_type = "pallet_election_provider_multi_phase::SolutionOrSnapshotSize" + )] + use ::pallet_election_provider_multi_phase::SolutionOrSnapshotSize; } pub use runtime::runtime_types; @@ -333,3 +413,55 @@ pub mod kusama { }; } } + +/// Helper to fetch the weight from a remote node +/// +/// Panics: if the RPC call fails or if decoding the response as a `Weight` fails. +fn get_weight(tx: subxt::tx::StaticTxPayload) -> Weight { + futures::executor::block_on(async { + let client = SHARED_CLIENT.get().expect("shared client is configured as start; qed"); + + let call_data = { + let mut buffer = Vec::new(); + + let encoded_call = client.tx().call_data(&tx).unwrap(); + let encoded_len = encoded_call.len() as u32; + + buffer.extend(encoded_call); + encoded_len.encode_to(&mut buffer); + + Bytes(buffer) + }; + + let bytes: Bytes = client + .rpc() + .client + .request( + "state_call", + rpc_params!["TransactionPaymentCallApi_query_call_info", call_data], + ) + .await + .unwrap(); + + let info: RuntimeDispatchInfo = Decode::decode(&mut bytes.0.as_ref()).unwrap(); + + log::debug!( + target: LOG_TARGET, + "Received weight of `Solution Extrinsic` from remote node: {:?}", + info.weight + ); + + info.weight + }) +} + +fn mock_votes(voters: u32, desired_targets: u16) -> Vec<(u32, u16)> { + assert!(voters >= desired_targets as u32); + (0..voters).zip((0..desired_targets).cycle()).collect() +} + +#[cfg(test)] +fn mock_votes_works() { + assert_eq!(mock_votes(3, 2), vec![(0, 0), (1, 1), (2, 0)]); + assert_eq!(mock_votes(3, 3), vec![(0, 0), (1, 1), (2, 2)]); +} diff --git a/src/main.rs b/src/main.rs index 5608b99b5..6ecdf6b8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,15 +53,29 @@ async fn main() -> Result<(), Error> { let Opt { uri, command, prometheus_port } = Opt::parse(); log::debug!(target: LOG_TARGET, "attempting to connect to {:?}", uri); - let rpc = WsClientBuilder::default().max_request_body_size(u32::MAX).build(uri).await?; - let api = SubxtClient::from_rpc_client(rpc).await?; + let rpc = loop { + match WsClientBuilder::default().max_request_body_size(u32::MAX).build(&uri).await { + Ok(rpc) => break rpc, + Err(e) => { + log::warn!( + target: LOG_TARGET, + "failed to connect to client due to {:?}, retrying soon..", + e, + ); + }, + }; + tokio::time::sleep(std::time::Duration::from_millis(2_500)).await; + }; + let api = SubxtClient::from_rpc_client(rpc).await?; let runtime_version = api.rpc().runtime_version(None).await?; let chain = Chain::try_from(runtime_version)?; let _prometheus_handle = prometheus::run(prometheus_port.unwrap_or(DEFAULT_PROMETHEUS_PORT)) .map_err(|e| log::warn!("Failed to start prometheus endpoint: {}", e)); log::info!(target: LOG_TARGET, "Connected to chain: {}", chain); + chain::SHARED_CLIENT.set(api.clone()).expect("shared client only set once; qed"); + let outcome = any_runtime!(chain, { tls_update_runtime_constants(&api); diff --git a/src/monitor.rs b/src/monitor.rs index 4a0017723..21ebc5026 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -115,13 +115,13 @@ macro_rules! monitor_cmd_for { return; } - let addr = runtime::storage().election_provider_multi_phase().round(); - let round = match api.storage().fetch(&addr, Some(hash)).await { + let round = match api.storage().fetch(&runtime::storage().election_provider_multi_phase().round(), Some(hash)).await { Ok(Some(round)) => round, - Ok(None) => unreachable!("Round must always exist"), + // Default round is 1 + // https://github.com/paritytech/substrate/blob/49b06901eb65f2c61ff0934d66987fd955d5b8f5/frame/election-provider-multi-phase/src/lib.rs#L1188 + Ok(None) => 1, Err(e) => { log::error!(target: LOG_TARGET, "Mining solution failed: {:?}", e); - kill_main_task_if_critical_err(&tx, e.into()); return; }, @@ -219,7 +219,9 @@ macro_rules! monitor_cmd_for { match res { Ok(Some(Phase::Signed)) => Ok(()), Ok(Some(_)) => Err(Error::IncorrectPhase), - Ok(None) => unreachable!("Phase should always exist"), + // Default phase is None + // https://github.com/paritytech/substrate/blob/49b06901eb65f2c61ff0934d66987fd955d5b8f5/frame/election-provider-multi-phase/src/lib.rs#L1193 + Ok(None) => Err(Error::IncorrectPhase), Err(e) => Err(e.into()), } } diff --git a/src/prelude.rs b/src/prelude.rs index 10088cd77..b279f32a9 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -32,6 +32,8 @@ pub type Header = subxt::ext::sp_runtime::generic::Header; /// The header type. We re-export it here, but we can easily get it from block as well. pub type Hash = sp_core::H256; +/// Balance type +pub type Balance = u128; pub use subxt::ext::{ sp_core,