mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-20 18:41:01 +00:00
Staking Miner (#3141)
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com> Co-authored-by: Peter Goodspeed-Niklaus <peter.r.goodspeedniklaus@gmail.com>
This commit is contained in:
Generated
+44
@@ -3181,6 +3181,12 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonrpsee"
|
||||||
|
version = "0.2.0-alpha.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4e48ecdd757b22fae87e87aad2dbadf11c56499c6509763c8ef20db16ffb0e9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpsee-proc-macros"
|
name = "jsonrpsee-proc-macros"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -9984,6 +9990,44 @@ version = "1.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "staking-miner"
|
||||||
|
version = "0.9.0"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger 0.8.4",
|
||||||
|
"frame-election-provider-support",
|
||||||
|
"frame-support",
|
||||||
|
"frame-system",
|
||||||
|
"hex",
|
||||||
|
"jsonrpsee",
|
||||||
|
"jsonrpsee-types",
|
||||||
|
"jsonrpsee-ws-client",
|
||||||
|
"kusama-runtime",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"pallet-election-provider-multi-phase",
|
||||||
|
"pallet-staking",
|
||||||
|
"pallet-transaction-payment",
|
||||||
|
"parity-scale-codec",
|
||||||
|
"paste 1.0.5",
|
||||||
|
"polkadot-core-primitives",
|
||||||
|
"polkadot-runtime",
|
||||||
|
"polkadot-runtime-common",
|
||||||
|
"remote-externalities",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sp-core",
|
||||||
|
"sp-io",
|
||||||
|
"sp-npos-elections",
|
||||||
|
"sp-runtime",
|
||||||
|
"sp-transaction-pool",
|
||||||
|
"sp-version",
|
||||||
|
"structopt",
|
||||||
|
"thiserror",
|
||||||
|
"tokio 0.2.21",
|
||||||
|
"westend-runtime",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ members = [
|
|||||||
"parachain/test-parachains",
|
"parachain/test-parachains",
|
||||||
"parachain/test-parachains/adder",
|
"parachain/test-parachains/adder",
|
||||||
"parachain/test-parachains/adder/collator",
|
"parachain/test-parachains/adder/collator",
|
||||||
|
"utils/staking-miner",
|
||||||
]
|
]
|
||||||
|
|
||||||
# We want to be able to build the bridge relayer without pulling it (and all of its
|
# We want to be able to build the bridge relayer without pulling it (and all of its
|
||||||
|
|||||||
@@ -1213,7 +1213,7 @@ pub fn polkadot_testnet_genesis(
|
|||||||
},
|
},
|
||||||
staking: polkadot::StakingConfig {
|
staking: polkadot::StakingConfig {
|
||||||
minimum_validator_count: 1,
|
minimum_validator_count: 1,
|
||||||
validator_count: 2,
|
validator_count: initial_authorities.len() as u32,
|
||||||
stakers: initial_authorities
|
stakers: initial_authorities
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
@@ -1312,7 +1312,7 @@ pub fn kusama_testnet_genesis(
|
|||||||
},
|
},
|
||||||
staking: kusama::StakingConfig {
|
staking: kusama::StakingConfig {
|
||||||
minimum_validator_count: 1,
|
minimum_validator_count: 1,
|
||||||
validator_count: 2,
|
validator_count: initial_authorities.len() as u32,
|
||||||
stakers: initial_authorities
|
stakers: initial_authorities
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
@@ -1416,7 +1416,7 @@ pub fn westend_testnet_genesis(
|
|||||||
},
|
},
|
||||||
staking: westend::StakingConfig {
|
staking: westend::StakingConfig {
|
||||||
minimum_validator_count: 1,
|
minimum_validator_count: 1,
|
||||||
validator_count: 2,
|
validator_count: initial_authorities.len() as u32,
|
||||||
stakers: initial_authorities
|
stakers: initial_authorities
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
|
|||||||
@@ -18,10 +18,14 @@
|
|||||||
|
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
parameter_types,
|
parameter_types,
|
||||||
traits::Get,
|
weights::{DispatchClass, Weight},
|
||||||
weights::{DispatchClass, Weight, WeightToFeePolynomial},
|
|
||||||
};
|
};
|
||||||
use sp_runtime::Perbill;
|
use sp_runtime::{
|
||||||
|
traits::{Zero, Dispatchable},
|
||||||
|
FixedU128, FixedPointNumber, Perbill,
|
||||||
|
};
|
||||||
|
use pallet_transaction_payment::OnChargeTransaction;
|
||||||
|
use frame_support::weights::{DispatchInfo, Pays};
|
||||||
use super::{BlockExecutionWeight, BlockLength, BlockWeights};
|
use super::{BlockExecutionWeight, BlockLength, BlockWeights};
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
@@ -44,18 +48,21 @@ parameter_types! {
|
|||||||
.get(DispatchClass::Normal);
|
.get(DispatchClass::Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the expected fee for submitting an election solution.
|
pub fn fee_for_submit_call<T>(
|
||||||
///
|
multiplier: FixedU128,
|
||||||
/// This is `multiplier` multiplied by the fee for the expected submission weight according to the
|
weight: Weight,
|
||||||
/// weight info.
|
length: u32,
|
||||||
///
|
) -> primitives::v1::Balance
|
||||||
/// Assumes that the signed submission queue is full.
|
|
||||||
pub fn fee_for_submit_call<T, WeightToFee, WeightInfo>(multiplier: Perbill) -> WeightToFee::Balance
|
|
||||||
where
|
where
|
||||||
T: pallet_election_provider_multi_phase::Config,
|
T: pallet_transaction_payment::Config,
|
||||||
WeightToFee: WeightToFeePolynomial,
|
<T as pallet_transaction_payment::Config>::OnChargeTransaction:
|
||||||
WeightInfo: pallet_election_provider_multi_phase::WeightInfo,
|
OnChargeTransaction<T, Balance = primitives::v1::Balance>,
|
||||||
|
<T as frame_system::Config>::Call: Dispatchable<Info = DispatchInfo>,
|
||||||
{
|
{
|
||||||
let expected_weight = WeightInfo::submit(T::SignedMaxSubmissions::get());
|
let info = DispatchInfo { weight, class: DispatchClass::Normal, pays_fee: Pays::Yes };
|
||||||
multiplier * WeightToFee::calc(&expected_weight)
|
multiplier.saturating_mul_int(pallet_transaction_payment::Pallet::<T>::compute_fee(
|
||||||
|
length,
|
||||||
|
&info,
|
||||||
|
Zero::zero(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ use xcm_builder::{
|
|||||||
use xcm_executor::XcmExecutor;
|
use xcm_executor::XcmExecutor;
|
||||||
use sp_arithmetic::Perquintill;
|
use sp_arithmetic::Perquintill;
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
create_runtime_str, generic, impl_opaque_keys,
|
create_runtime_str, generic, impl_opaque_keys, ApplyExtrinsicResult, KeyTypeId, Percent,
|
||||||
ApplyExtrinsicResult, KeyTypeId, Percent, Permill, Perbill,
|
Permill, Perbill, FixedPointNumber,
|
||||||
transaction_validity::{TransactionValidity, TransactionSource, TransactionPriority},
|
transaction_validity::{TransactionValidity, TransactionSource, TransactionPriority},
|
||||||
traits::{
|
traits::{
|
||||||
BlakeTwo256, Block as BlockT, OpaqueKeys, ConvertInto, AccountIdLookup,
|
BlakeTwo256, Block as BlockT, OpaqueKeys, ConvertInto, AccountIdLookup,
|
||||||
@@ -101,6 +101,7 @@ pub use pallet_staking::StakerStatus;
|
|||||||
pub use sp_runtime::BuildStorage;
|
pub use sp_runtime::BuildStorage;
|
||||||
pub use pallet_timestamp::Call as TimestampCall;
|
pub use pallet_timestamp::Call as TimestampCall;
|
||||||
pub use pallet_balances::Call as BalancesCall;
|
pub use pallet_balances::Call as BalancesCall;
|
||||||
|
pub use pallet_election_provider_multi_phase::Call as EPMCall;
|
||||||
|
|
||||||
/// Constant values used within the runtime.
|
/// Constant values used within the runtime.
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
@@ -348,6 +349,7 @@ impl pallet_session::historical::Config for Runtime {
|
|||||||
type FullIdentificationOf = pallet_staking::ExposureOf<Runtime>;
|
type FullIdentificationOf = pallet_staking::ExposureOf<Runtime>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use pallet_election_provider_multi_phase::WeightInfo;
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
// phase durations. 1/4 of the last session for each.
|
// phase durations. 1/4 of the last session for each.
|
||||||
pub const SignedPhase: u32 = EPOCH_DURATION_IN_SLOTS / 4;
|
pub const SignedPhase: u32 = EPOCH_DURATION_IN_SLOTS / 4;
|
||||||
@@ -360,11 +362,14 @@ parameter_types! {
|
|||||||
// This formula is currently adjusted such that a typical solution will spend an amount equal
|
// This formula is currently adjusted such that a typical solution will spend an amount equal
|
||||||
// to the base deposit for every 50 kb.
|
// to the base deposit for every 50 kb.
|
||||||
pub const SignedDepositByte: Balance = deposit(1, 0) / (50 * 1024);
|
pub const SignedDepositByte: Balance = deposit(1, 0) / (50 * 1024);
|
||||||
pub SignedRewardBase: Balance = fee_for_submit_call::<
|
pub SignedRewardBase: Balance = fee_for_submit_call::<Runtime>(
|
||||||
Runtime,
|
// give 20% threshold.
|
||||||
crate::constants::fee::WeightToFee,
|
sp_runtime::FixedU128::saturating_from_rational(12, 10),
|
||||||
crate::weights::pallet_election_provider_multi_phase::WeightInfo<Runtime>,
|
// maximum weight possible.
|
||||||
>(Perbill::from_perthousand(1500));
|
weights::pallet_election_provider_multi_phase::WeightInfo::<Runtime>::submit(SignedMaxSubmissions::get()),
|
||||||
|
// assume a solution of 100kb length.
|
||||||
|
100 * 1024
|
||||||
|
);
|
||||||
|
|
||||||
// fallback: emergency phase.
|
// fallback: emergency phase.
|
||||||
pub const Fallback: pallet_election_provider_multi_phase::FallbackStrategy =
|
pub const Fallback: pallet_election_provider_multi_phase::FallbackStrategy =
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ use primitives::v1::{
|
|||||||
ValidatorIndex, InboundDownwardMessage, InboundHrmpMessage, SessionInfo,
|
ValidatorIndex, InboundDownwardMessage, InboundHrmpMessage, SessionInfo,
|
||||||
};
|
};
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
create_runtime_str, generic, impl_opaque_keys, ApplyExtrinsicResult,
|
create_runtime_str, generic, impl_opaque_keys, ApplyExtrinsicResult, FixedPointNumber,
|
||||||
KeyTypeId, Percent, Permill, Perbill, curve::PiecewiseLinear,
|
KeyTypeId, Percent, Permill, Perbill, curve::PiecewiseLinear,
|
||||||
transaction_validity::{TransactionValidity, TransactionSource, TransactionPriority},
|
transaction_validity::{TransactionValidity, TransactionSource, TransactionPriority},
|
||||||
traits::{
|
traits::{
|
||||||
@@ -77,6 +77,7 @@ pub use pallet_staking::StakerStatus;
|
|||||||
pub use sp_runtime::BuildStorage;
|
pub use sp_runtime::BuildStorage;
|
||||||
pub use pallet_timestamp::Call as TimestampCall;
|
pub use pallet_timestamp::Call as TimestampCall;
|
||||||
pub use pallet_balances::Call as BalancesCall;
|
pub use pallet_balances::Call as BalancesCall;
|
||||||
|
pub use pallet_election_provider_multi_phase::Call as EPMCall;
|
||||||
|
|
||||||
/// Constant values used within the runtime.
|
/// Constant values used within the runtime.
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
@@ -328,6 +329,7 @@ impl pallet_session::historical::Config for Runtime {
|
|||||||
type FullIdentificationOf = pallet_staking::ExposureOf<Runtime>;
|
type FullIdentificationOf = pallet_staking::ExposureOf<Runtime>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use pallet_election_provider_multi_phase::WeightInfo;
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
// phase durations. 1/4 of the last session for each.
|
// phase durations. 1/4 of the last session for each.
|
||||||
pub const SignedPhase: u32 = EPOCH_DURATION_IN_SLOTS / 4;
|
pub const SignedPhase: u32 = EPOCH_DURATION_IN_SLOTS / 4;
|
||||||
@@ -340,11 +342,14 @@ parameter_types! {
|
|||||||
// This formula is currently adjusted such that a typical solution will spend an amount equal
|
// This formula is currently adjusted such that a typical solution will spend an amount equal
|
||||||
// to the base deposit for every 50 kb.
|
// to the base deposit for every 50 kb.
|
||||||
pub const SignedDepositByte: Balance = deposit(1, 0) / (50 * 1024);
|
pub const SignedDepositByte: Balance = deposit(1, 0) / (50 * 1024);
|
||||||
pub SignedRewardBase: Balance = fee_for_submit_call::<
|
pub SignedRewardBase: Balance = fee_for_submit_call::<Runtime>(
|
||||||
Runtime,
|
// give 20% threshold.
|
||||||
crate::constants::fee::WeightToFee,
|
sp_runtime::FixedU128::saturating_from_rational(12, 10),
|
||||||
crate::weights::pallet_election_provider_multi_phase::WeightInfo<Runtime>,
|
// maximum weight possible.
|
||||||
>(Perbill::from_perthousand(1500));
|
weights::pallet_election_provider_multi_phase::WeightInfo::<Runtime>::submit(SignedMaxSubmissions::get()),
|
||||||
|
// assume a solution of 200kb length.
|
||||||
|
200 * 1024
|
||||||
|
);
|
||||||
|
|
||||||
// fallback: emergency phase.
|
// fallback: emergency phase.
|
||||||
pub const Fallback: pallet_election_provider_multi_phase::FallbackStrategy =
|
pub const Fallback: pallet_election_provider_multi_phase::FallbackStrategy =
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ use xcm_builder::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
create_runtime_str, generic, impl_opaque_keys,
|
create_runtime_str, generic, impl_opaque_keys, FixedPointNumber,
|
||||||
ApplyExtrinsicResult, KeyTypeId, Perbill, curve::PiecewiseLinear,
|
ApplyExtrinsicResult, KeyTypeId, Perbill, curve::PiecewiseLinear,
|
||||||
transaction_validity::{TransactionValidity, TransactionSource, TransactionPriority},
|
transaction_validity::{TransactionValidity, TransactionSource, TransactionPriority},
|
||||||
traits::{
|
traits::{
|
||||||
@@ -100,6 +100,7 @@ pub use pallet_staking::StakerStatus;
|
|||||||
pub use sp_runtime::BuildStorage;
|
pub use sp_runtime::BuildStorage;
|
||||||
pub use pallet_timestamp::Call as TimestampCall;
|
pub use pallet_timestamp::Call as TimestampCall;
|
||||||
pub use pallet_balances::Call as BalancesCall;
|
pub use pallet_balances::Call as BalancesCall;
|
||||||
|
pub use pallet_election_provider_multi_phase::Call as EPMCall;
|
||||||
|
|
||||||
/// Constant values used within the runtime.
|
/// Constant values used within the runtime.
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
@@ -333,6 +334,7 @@ impl pallet_session::historical::Config for Runtime {
|
|||||||
type FullIdentificationOf = pallet_staking::ExposureOf<Runtime>;
|
type FullIdentificationOf = pallet_staking::ExposureOf<Runtime>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use pallet_election_provider_multi_phase::WeightInfo;
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
// phase durations. 1/4 of the last session for each.
|
// phase durations. 1/4 of the last session for each.
|
||||||
pub const SignedPhase: u32 = EPOCH_DURATION_IN_SLOTS / 4;
|
pub const SignedPhase: u32 = EPOCH_DURATION_IN_SLOTS / 4;
|
||||||
@@ -345,11 +347,14 @@ parameter_types! {
|
|||||||
// This formula is currently adjusted such that a typical solution will spend an amount equal
|
// This formula is currently adjusted such that a typical solution will spend an amount equal
|
||||||
// to the base deposit for every 50 kb.
|
// to the base deposit for every 50 kb.
|
||||||
pub const SignedDepositByte: Balance = deposit(1, 0) / (50 * 1024);
|
pub const SignedDepositByte: Balance = deposit(1, 0) / (50 * 1024);
|
||||||
pub SignedRewardBase: Balance = fee_for_submit_call::<
|
pub SignedRewardBase: Balance = fee_for_submit_call::<Runtime>(
|
||||||
Runtime,
|
// give 20% threshold.
|
||||||
crate::constants::fee::WeightToFee,
|
sp_runtime::FixedU128::saturating_from_rational(12, 10),
|
||||||
crate::weights::pallet_election_provider_multi_phase::WeightInfo<Runtime>,
|
// maximum weight possible.
|
||||||
>(Perbill::from_perthousand(1500));
|
weights::pallet_election_provider_multi_phase::WeightInfo::<Runtime>::submit(SignedMaxSubmissions::get()),
|
||||||
|
// assume a solution of 100kb length.
|
||||||
|
100 * 1024
|
||||||
|
);
|
||||||
|
|
||||||
// fallback: emergency phase.
|
// fallback: emergency phase.
|
||||||
pub const Fallback: pallet_election_provider_multi_phase::FallbackStrategy =
|
pub const Fallback: pallet_election_provider_multi_phase::FallbackStrategy =
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*.key
|
||||||
|
*.bin
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
[package]
|
||||||
|
name = "staking-miner"
|
||||||
|
version = "0.9.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||||
|
tokio = { version = "0.2", features = ["macros"] }
|
||||||
|
log = "0.4.11"
|
||||||
|
env_logger = "0.8.3"
|
||||||
|
structopt = "0.3.0"
|
||||||
|
jsonrpsee-ws-client = { version = "0.2.0", default-features = false, features = ["tokio02"] }
|
||||||
|
jsonrpsee-types = { version = "0.2.0" }
|
||||||
|
jsonrpsee = "=0.2.0-alpha.6"
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = "1.0.0"
|
||||||
|
hex = "0.4.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
paste = "1.0.5"
|
||||||
|
thiserror = "1.0.0"
|
||||||
|
|
||||||
|
remote-externalities = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
|
||||||
|
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
sp-npos-elections = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
|
||||||
|
|
||||||
|
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
frame-support = { 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-staking = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
|
||||||
|
core-primitives = { package = "polkadot-core-primitives", path = "../../core-primitives" }
|
||||||
|
|
||||||
|
runtime-common = { package = "polkadot-runtime-common", path = "../../runtime/common" }
|
||||||
|
polkadot-runtime = { path = "../../runtime/polkadot" }
|
||||||
|
kusama-runtime = { path = "../../runtime/kusama" }
|
||||||
|
westend-runtime = { path = "../../runtime/westend" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! The dry-run command.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
params, prelude::*, rpc_helpers::*, signer::Signer, DryRunConfig, Error, SharedConfig, WsClient,
|
||||||
|
};
|
||||||
|
use codec::Encode;
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
ext.execute_with(|| {
|
||||||
|
if <EPM::Snapshot<T>>::exists() {
|
||||||
|
log::info!(target: LOG_TARGET, "snapshot already exists.");
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
log::info!(target: LOG_TARGET, "creating a fake snapshot now.");
|
||||||
|
<EPM::Pallet<T>>::create_snapshot().map(|_| ()).map_err(Into::into)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method to print the encoded size of the snapshot.
|
||||||
|
fn measure_snapshot_size<T: EPM::Config>(ext: &mut Ext) {
|
||||||
|
ext.execute_with(|| {
|
||||||
|
log::info!(target: LOG_TARGET, "Metadata: {:?}", <EPM::Pallet<T>>::snapshot_metadata());
|
||||||
|
log::info!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
"Encoded Length: {:?}",
|
||||||
|
<EPM::Pallet<T>>::snapshot()
|
||||||
|
.expect("snapshot must exist before calling `measure_snapshot_size`")
|
||||||
|
.encode()
|
||||||
|
.len()
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the stake threshold in order to have at most `count` voters.
|
||||||
|
#[allow(unused)]
|
||||||
|
fn find_threshold<T: EPM::Config>(ext: &mut Ext, count: usize) {
|
||||||
|
ext.execute_with(|| {
|
||||||
|
let mut voters = <EPM::Pallet<T>>::snapshot()
|
||||||
|
.expect("snapshot must exist before calling `measure_snapshot_size`")
|
||||||
|
.voters;
|
||||||
|
voters.sort_by_key(|(_voter, weight, _targets)| std::cmp::Reverse(*weight));
|
||||||
|
match voters.get(count) {
|
||||||
|
Some(threshold_voter) => println!("smallest allowed voter is {:?}", threshold_voter),
|
||||||
|
None => {
|
||||||
|
println!("requested truncation to {} voters but had only {}", count, voters.len());
|
||||||
|
println!("smallest current voter: {:?}", voters.last());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! dry_run_cmd_for { ($runtime:ident) => { paste::paste! {
|
||||||
|
/// Execute the dry-run command.
|
||||||
|
pub(crate) async fn [<dry_run_cmd_ $runtime>](
|
||||||
|
client: &WsClient,
|
||||||
|
shared: SharedConfig,
|
||||||
|
config: DryRunConfig,
|
||||||
|
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?;
|
||||||
|
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 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."
|
||||||
|
);
|
||||||
|
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 bytes = sp_core::Bytes(extrinsic.encode().to_vec());
|
||||||
|
let outcome = rpc_decode::<sp_runtime::ApplyExtrinsicResult>(client, "system_dryRun", params!{ bytes }).await?;
|
||||||
|
log::info!(target: LOG_TARGET, "dry-run outcome is {:?}", outcome);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}}}
|
||||||
|
|
||||||
|
dry_run_cmd_for!(polkadot);
|
||||||
|
dry_run_cmd_for!(kusama);
|
||||||
|
dry_run_cmd_for!(westend);
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! The emergency-solution command.
|
||||||
|
|
||||||
|
use crate::{prelude::*, SharedConfig, Error};
|
||||||
|
use std::io::Write;
|
||||||
|
use codec::Encode;
|
||||||
|
|
||||||
|
macro_rules! emergency_solution_cmd_for { ($runtime:ident) => { paste::paste! {
|
||||||
|
/// Execute the emergency-solution command.
|
||||||
|
pub(crate) async fn [<emergency_solution_cmd_ $runtime>](
|
||||||
|
shared: SharedConfig,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
use $crate::[<$runtime _runtime_exports>]::*;
|
||||||
|
let mut ext = crate::create_election_ext::<Runtime, Block>(shared.uri.clone(), None, false).await?;
|
||||||
|
ext.execute_with(|| {
|
||||||
|
assert!(EPM::Pallet::<Runtime>::current_phase().is_emergency());
|
||||||
|
// NOTE: this internally calls feasibility_check, but we just re-do it here as an easy way
|
||||||
|
// to get a `ReadySolution`.
|
||||||
|
let (raw_solution, _) = <EPM::Pallet<Runtime>>::mine_solution(50)?;
|
||||||
|
log::info!(target: LOG_TARGET, "mined solution with {:?}", &raw_solution.score);
|
||||||
|
let ready_solution = EPM::Pallet::<Runtime>::feasibility_check(raw_solution, EPM::ElectionCompute::Signed)?;
|
||||||
|
let encoded_ready = ready_solution.encode();
|
||||||
|
let encoded_support = ready_solution.supports.encode();
|
||||||
|
let mut solution_file = std::fs::File::create("solution.bin")?;
|
||||||
|
let mut supports_file = std::fs::File::create("solution.supports.bin")?;
|
||||||
|
solution_file.write_all(&encoded_ready)?;
|
||||||
|
supports_file.write_all(&encoded_support)?;
|
||||||
|
log::info!(target: LOG_TARGET, "ReadySolution: size {:?} / score = {:?}", encoded_ready.len(), ready_solution.score);
|
||||||
|
log::trace!(target: LOG_TARGET, "Supports: {}", sp_core::hexdisplay::HexDisplay::from(&encoded_support));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}}
|
||||||
|
|
||||||
|
emergency_solution_cmd_for!(polkadot);
|
||||||
|
emergency_solution_cmd_for!(kusama);
|
||||||
|
emergency_solution_cmd_for!(westend);
|
||||||
@@ -0,0 +1,493 @@
|
|||||||
|
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! # Polkadot Staking Miner.
|
||||||
|
//!
|
||||||
|
//! Simple bot capable of monitoring a polkadot (and cousins) chain and submitting solutions to the
|
||||||
|
//! 'pallet-election-provider-multi-phase'. See `--help` for more details.
|
||||||
|
//!
|
||||||
|
//! # Implementation Notes:
|
||||||
|
//!
|
||||||
|
//! - First draft: Be aware that this is the first draft and there might be bugs, or undefined
|
||||||
|
//! behaviors. Don't attach this bot to an account with lots of funds.
|
||||||
|
//! - Quick to crash: The bot is written so that it only continues to work if everything goes well.
|
||||||
|
//! In case of any failure (RPC, logic, IO), it will crash. This was a decision to simplify the
|
||||||
|
//! development. It is intended to run this bot with a `restart = true` way, so that it reports it
|
||||||
|
//! crash, but resumes work thereafter.
|
||||||
|
|
||||||
|
mod dry_run;
|
||||||
|
mod emergency_solution;
|
||||||
|
mod monitor;
|
||||||
|
mod prelude;
|
||||||
|
mod rpc_helpers;
|
||||||
|
mod signer;
|
||||||
|
|
||||||
|
pub(crate) use prelude::*;
|
||||||
|
pub(crate) use signer::get_account_info;
|
||||||
|
|
||||||
|
use jsonrpsee_ws_client::{WsClient, WsClientBuilder};
|
||||||
|
use remote_externalities::{Builder, Mode, OnlineConfig};
|
||||||
|
use sp_runtime::traits::Block as BlockT;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
pub(crate) enum AnyRuntime {
|
||||||
|
Polkadot,
|
||||||
|
Kusama,
|
||||||
|
Westend,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) static mut RUNTIME: AnyRuntime = AnyRuntime::Polkadot;
|
||||||
|
|
||||||
|
macro_rules! construct_runtime_prelude {
|
||||||
|
($runtime:ident) => { paste::paste! {
|
||||||
|
#[allow(unused_import)]
|
||||||
|
pub(crate) mod [<$runtime _runtime_exports>] {
|
||||||
|
pub(crate) use crate::prelude::EPM;
|
||||||
|
pub(crate) use [<$runtime _runtime>]::*;
|
||||||
|
pub(crate) use crate::monitor::[<monitor_cmd_ $runtime>] as monitor_cmd;
|
||||||
|
pub(crate) use crate::dry_run::[<dry_run_cmd_ $runtime>] as dry_run_cmd;
|
||||||
|
pub(crate) use crate::emergency_solution::[<emergency_solution_cmd_ $runtime>] as emergency_solution_cmd;
|
||||||
|
pub(crate) use private::{[<create_uxt_ $runtime>] as create_uxt};
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
use super::*;
|
||||||
|
pub(crate) fn [<create_uxt_ $runtime>](
|
||||||
|
raw_solution: EPM::RawSolution<EPM::CompactOf<Runtime>>,
|
||||||
|
witness: u32,
|
||||||
|
signer: crate::signer::Signer,
|
||||||
|
nonce: crate::prelude::Index,
|
||||||
|
tip: crate::prelude::Balance,
|
||||||
|
era: sp_runtime::generic::Era,
|
||||||
|
) -> UncheckedExtrinsic {
|
||||||
|
use codec::Encode as _;
|
||||||
|
use sp_core::Pair as _;
|
||||||
|
use sp_runtime::traits::StaticLookup as _;
|
||||||
|
|
||||||
|
let crate::signer::Signer { account, pair, .. } = signer;
|
||||||
|
|
||||||
|
let local_call = EPMCall::<Runtime>::submit(raw_solution, witness);
|
||||||
|
let call: Call = <EPMCall<Runtime> as std::convert::TryInto<Call>>::try_into(local_call)
|
||||||
|
.expect("election provider pallet must exist in the runtime, thus \
|
||||||
|
inner call can be converted, qed."
|
||||||
|
);
|
||||||
|
|
||||||
|
let extra: SignedExtra = crate::[<signed_ext_builder_ $runtime>](nonce, tip, era);
|
||||||
|
let raw_payload = SignedPayload::new(call, extra).expect("creating signed payload infallible; qed.");
|
||||||
|
let signature = raw_payload.using_encoded(|payload| {
|
||||||
|
pair.clone().sign(payload)
|
||||||
|
});
|
||||||
|
let (call, extra, _) = raw_payload.deconstruct();
|
||||||
|
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())
|
||||||
|
);
|
||||||
|
extrinsic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: we might be able to use some code from the bridges repo here.
|
||||||
|
fn signed_ext_builder_polkadot(
|
||||||
|
nonce: Index,
|
||||||
|
tip: Balance,
|
||||||
|
era: sp_runtime::generic::Era,
|
||||||
|
) -> polkadot_runtime_exports::SignedExtra {
|
||||||
|
use polkadot_runtime_exports::Runtime;
|
||||||
|
(
|
||||||
|
frame_system::CheckSpecVersion::<Runtime>::new(),
|
||||||
|
frame_system::CheckTxVersion::<Runtime>::new(),
|
||||||
|
frame_system::CheckGenesis::<Runtime>::new(),
|
||||||
|
frame_system::CheckMortality::<Runtime>::from(era),
|
||||||
|
frame_system::CheckNonce::<Runtime>::from(nonce),
|
||||||
|
frame_system::CheckWeight::<Runtime>::new(),
|
||||||
|
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
|
||||||
|
runtime_common::claims::PrevalidateAttests::<Runtime>::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signed_ext_builder_kusama(
|
||||||
|
nonce: Index,
|
||||||
|
tip: Balance,
|
||||||
|
era: sp_runtime::generic::Era,
|
||||||
|
) -> kusama_runtime_exports::SignedExtra {
|
||||||
|
use kusama_runtime_exports::Runtime;
|
||||||
|
(
|
||||||
|
frame_system::CheckSpecVersion::<Runtime>::new(),
|
||||||
|
frame_system::CheckTxVersion::<Runtime>::new(),
|
||||||
|
frame_system::CheckGenesis::<Runtime>::new(),
|
||||||
|
frame_system::CheckMortality::<Runtime>::from(era),
|
||||||
|
frame_system::CheckNonce::<Runtime>::from(nonce),
|
||||||
|
frame_system::CheckWeight::<Runtime>::new(),
|
||||||
|
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signed_ext_builder_westend(
|
||||||
|
nonce: Index,
|
||||||
|
tip: Balance,
|
||||||
|
era: sp_runtime::generic::Era,
|
||||||
|
) -> westend_runtime_exports::SignedExtra {
|
||||||
|
use westend_runtime_exports::Runtime;
|
||||||
|
(
|
||||||
|
frame_system::CheckSpecVersion::<Runtime>::new(),
|
||||||
|
frame_system::CheckTxVersion::<Runtime>::new(),
|
||||||
|
frame_system::CheckGenesis::<Runtime>::new(),
|
||||||
|
frame_system::CheckMortality::<Runtime>::from(era),
|
||||||
|
frame_system::CheckNonce::<Runtime>::from(nonce),
|
||||||
|
frame_system::CheckWeight::<Runtime>::new(),
|
||||||
|
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
construct_runtime_prelude!(polkadot);
|
||||||
|
construct_runtime_prelude!(kusama);
|
||||||
|
construct_runtime_prelude!(westend);
|
||||||
|
|
||||||
|
// NOTE: this is no longer used extensively, most of the per-runtime stuff us delegated to
|
||||||
|
// `construct_runtime_prelude` and macro's the import directly from it. A part of the code is also
|
||||||
|
// still generic over `T`. My hope is to still make everything generic over a `Runtime`, but sadly
|
||||||
|
// that is not currently possible as each runtime has its unique `Call`, and all Calls are not
|
||||||
|
// sharing any generic trait. In other words, to create the `UncheckedExtrinsic` of each chain, you
|
||||||
|
// need the concrete `Call` of that chain as well.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! any_runtime {
|
||||||
|
($($code:tt)*) => {
|
||||||
|
unsafe {
|
||||||
|
match $crate::RUNTIME {
|
||||||
|
$crate::AnyRuntime::Polkadot => {
|
||||||
|
use $crate::polkadot_runtime_exports::*;
|
||||||
|
$($code)*
|
||||||
|
},
|
||||||
|
$crate::AnyRuntime::Kusama => {
|
||||||
|
use $crate::kusama_runtime_exports::*;
|
||||||
|
$($code)*
|
||||||
|
},
|
||||||
|
$crate::AnyRuntime::Westend => {
|
||||||
|
use $crate::westend_runtime_exports::*;
|
||||||
|
$($code)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
enum Error {
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
Jsonrpsee(#[from] jsonrpsee_ws_client::Error),
|
||||||
|
Codec(#[from] codec::Error),
|
||||||
|
Crypto(sp_core::crypto::SecretStringError),
|
||||||
|
RemoteExternalities(&'static str),
|
||||||
|
PalletMiner(EPM::unsigned::MinerError),
|
||||||
|
PalletElection(EPM::ElectionError),
|
||||||
|
PalletFeasibility(EPM::FeasibilityError),
|
||||||
|
AccountDoesNotExists,
|
||||||
|
IncorrectPhase,
|
||||||
|
AlreadySubmitted,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sp_core::crypto::SecretStringError> for Error {
|
||||||
|
fn from(e: sp_core::crypto::SecretStringError) -> Error {
|
||||||
|
Error::Crypto(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EPM::unsigned::MinerError> for Error {
|
||||||
|
fn from(e: EPM::unsigned::MinerError) -> Error {
|
||||||
|
Error::PalletMiner(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EPM::ElectionError> for Error {
|
||||||
|
fn from(e: EPM::ElectionError) -> Error {
|
||||||
|
Error::PalletElection(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EPM::FeasibilityError> for Error {
|
||||||
|
fn from(e: EPM::FeasibilityError) -> Error {
|
||||||
|
Error::PalletFeasibility(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
<Error as std::fmt::Debug>::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, StructOpt)]
|
||||||
|
enum Command {
|
||||||
|
/// Monitor for the phase being signed, then compute.
|
||||||
|
Monitor(MonitorConfig),
|
||||||
|
/// Just compute a solution now, and don't submit it.
|
||||||
|
DryRun(DryRunConfig),
|
||||||
|
/// Provide a solution that can be submitted to the chian as an emergency response.
|
||||||
|
EmergencySolution,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, StructOpt)]
|
||||||
|
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.
|
||||||
|
#[structopt(long, default_value = "head", possible_values = &["head", "finalized"])]
|
||||||
|
listen: String,
|
||||||
|
|
||||||
|
#[structopt(long, short, default_value = "10")]
|
||||||
|
iterations: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, StructOpt)]
|
||||||
|
struct DryRunConfig {
|
||||||
|
/// The block hash at which scraping happens. If none is provided, the latest head is used.
|
||||||
|
#[structopt(long)]
|
||||||
|
at: Option<Hash>,
|
||||||
|
|
||||||
|
#[structopt(long, short, default_value = "10")]
|
||||||
|
iterations: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, StructOpt)]
|
||||||
|
struct SharedConfig {
|
||||||
|
/// The ws node to connect to.
|
||||||
|
#[structopt(long, 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)]
|
||||||
|
account_seed: std::path::PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, StructOpt)]
|
||||||
|
struct Opt {
|
||||||
|
/// The ws node to connect to.
|
||||||
|
#[structopt(flatten)]
|
||||||
|
shared: SharedConfig,
|
||||||
|
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the `Ext` at `hash` with all the data of `ElectionProviderMultiPhase` and `Staking`
|
||||||
|
/// stored.
|
||||||
|
async fn create_election_ext<T: EPM::Config, B: BlockT>(
|
||||||
|
uri: String,
|
||||||
|
at: Option<B::Hash>,
|
||||||
|
with_staking: bool,
|
||||||
|
) -> Result<Ext, Error> {
|
||||||
|
use frame_support::{storage::generator::StorageMap, traits::PalletInfo};
|
||||||
|
let system_block_hash_key = <frame_system::BlockHash<T>>::prefix_hash();
|
||||||
|
|
||||||
|
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()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
.inject_hashed_prefix(&system_block_hash_key)
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.map_err(|why| Error::RemoteExternalities(why))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the election at the given block number. It expects to NOT be `Phase::Off`. In other
|
||||||
|
/// words, the snapshot must exists on the given externalities.
|
||||||
|
fn mine_unchecked<T: EPM::Config>(
|
||||||
|
ext: &mut Ext,
|
||||||
|
iterations: usize,
|
||||||
|
do_feasibility: bool,
|
||||||
|
) -> Result<(EPM::RawSolution<EPM::CompactOf<T>>, u32), Error> {
|
||||||
|
ext.execute_with(|| {
|
||||||
|
let (solution, _) = <EPM::Pallet<T>>::mine_solution(iterations)?;
|
||||||
|
if do_feasibility {
|
||||||
|
let _ = <EPM::Pallet<T>>::feasibility_check(solution.clone(), EPM::ElectionCompute::Signed)?;
|
||||||
|
}
|
||||||
|
let witness = <EPM::SignedSubmissions<T>>::decode_len().unwrap_or_default();
|
||||||
|
Ok((solution, witness as u32))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn mine_dpos<T: EPM::Config>(ext: &mut Ext) -> Result<(), Error> {
|
||||||
|
ext.execute_with(|| {
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use EPM::RoundSnapshot;
|
||||||
|
let RoundSnapshot { voters, .. } = EPM::Snapshot::<T>::get().unwrap();
|
||||||
|
let desired_targets = EPM::DesiredTargets::<T>::get().unwrap();
|
||||||
|
let mut candidates_and_backing = BTreeMap::<T::AccountId, u128>::new();
|
||||||
|
voters.into_iter().for_each(|(who, stake, targets)| {
|
||||||
|
if targets.len() == 0 {
|
||||||
|
println!("target = {:?}", (who, stake, targets));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let share: u128 = (stake as u128) / (targets.len() as u128);
|
||||||
|
for target in targets {
|
||||||
|
*candidates_and_backing.entry(target.clone()).or_default() += share
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut candidates_and_backing =
|
||||||
|
candidates_and_backing.into_iter().collect::<Vec<(_, _)>>();
|
||||||
|
candidates_and_backing.sort_by_key(|(_, total_stake)| *total_stake);
|
||||||
|
let winners = candidates_and_backing
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.take(desired_targets as usize)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let score = {
|
||||||
|
let min_staker = *winners.last().map(|(_, stake)| stake).unwrap();
|
||||||
|
let sum_stake = winners.iter().fold(0u128, |acc, (_, stake)| acc + stake);
|
||||||
|
let sum_squared = winners.iter().fold(0u128, |acc, (_, stake)| acc + stake);
|
||||||
|
[min_staker, sum_stake, sum_squared]
|
||||||
|
};
|
||||||
|
println!("mined a dpos-like solution with score = {:?}", score);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
env_logger::Builder::from_default_env().format_module_path(true).format_level(true).init();
|
||||||
|
let Opt { shared, command } = Opt::from_args();
|
||||||
|
log::debug!(target: LOG_TARGET, "attempting to connect to {:?}", shared.uri);
|
||||||
|
|
||||||
|
let client = loop {
|
||||||
|
let maybe_client = WsClientBuilder::default()
|
||||||
|
.connection_timeout(std::time::Duration::new(20, 0))
|
||||||
|
.max_request_body_size(u32::MAX)
|
||||||
|
.build(&shared.uri)
|
||||||
|
.await;
|
||||||
|
match maybe_client {
|
||||||
|
Ok(client) => break client,
|
||||||
|
Err(why) => {
|
||||||
|
log::warn!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
"failed to connect to client due to {:?}, retrying soon..",
|
||||||
|
why
|
||||||
|
);
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(2500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let chain = rpc_helpers::rpc::<String>(&client, "system_chain", params! {})
|
||||||
|
.await
|
||||||
|
.expect("system_chain infallible; qed.");
|
||||||
|
match chain.to_lowercase().as_str() {
|
||||||
|
"polkadot" | "development" => {
|
||||||
|
sp_core::crypto::set_default_ss58_version(
|
||||||
|
sp_core::crypto::Ss58AddressFormat::PolkadotAccount,
|
||||||
|
);
|
||||||
|
// safety: this program will always be single threaded, thus accessing global static is
|
||||||
|
// safe.
|
||||||
|
unsafe {
|
||||||
|
RUNTIME = AnyRuntime::Polkadot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"kusama" | "kusama-dev" => {
|
||||||
|
sp_core::crypto::set_default_ss58_version(
|
||||||
|
sp_core::crypto::Ss58AddressFormat::KusamaAccount,
|
||||||
|
);
|
||||||
|
// safety: this program will always be single threaded, thus accessing global static is
|
||||||
|
// safe.
|
||||||
|
unsafe {
|
||||||
|
RUNTIME = AnyRuntime::Kusama;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"westend" => {
|
||||||
|
sp_core::crypto::set_default_ss58_version(
|
||||||
|
sp_core::crypto::Ss58AddressFormat::PolkadotAccount,
|
||||||
|
);
|
||||||
|
// safety: this program will always be single threaded, thus accessing global static is
|
||||||
|
// safe.
|
||||||
|
unsafe {
|
||||||
|
RUNTIME = AnyRuntime::Westend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
eprintln!("unexpected chain: {:?}", chain);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::info!(target: LOG_TARGET, "connected to chain {:?}", chain);
|
||||||
|
|
||||||
|
let signer_account = any_runtime! {
|
||||||
|
signer::read_signer_uri::<_, Runtime>(&shared.account_seed, &client)
|
||||||
|
.await
|
||||||
|
.expect("Provided account is invalid, terminating.")
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
log::info!(target: LOG_TARGET, "round of execution finished. outcome = {:?}", outcome);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn get_version<T: frame_system::Config>() -> sp_version::RuntimeVersion {
|
||||||
|
use frame_support::traits::Get;
|
||||||
|
T::Version::get()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn any_runtime_works() {
|
||||||
|
unsafe {
|
||||||
|
RUNTIME = AnyRuntime::Polkadot;
|
||||||
|
}
|
||||||
|
let polkadot_version = any_runtime! { get_version::<Runtime>() };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
RUNTIME = AnyRuntime::Kusama;
|
||||||
|
}
|
||||||
|
let kusama_version = any_runtime! { get_version::<Runtime>() };
|
||||||
|
|
||||||
|
assert_eq!(polkadot_version.spec_name, "polkadot".into());
|
||||||
|
assert_eq!(kusama_version.spec_name, "kusama".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! The monitor command.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
params, prelude::*, rpc_helpers::*, signer::Signer, Error, MonitorConfig, SharedConfig,
|
||||||
|
};
|
||||||
|
use codec::Encode;
|
||||||
|
use jsonrpsee_ws_client::{
|
||||||
|
traits::SubscriptionClient, v2::params::JsonRpcParams, Subscription, WsClient,
|
||||||
|
};
|
||||||
|
use sp_transaction_pool::TransactionStatus;
|
||||||
|
|
||||||
|
/// Ensure that now is the signed phase.
|
||||||
|
async fn ensure_signed_phase<T: EPM::Config, B: BlockT>(
|
||||||
|
client: &WsClient,
|
||||||
|
at: B::Hash,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let key = sp_core::storage::StorageKey(EPM::CurrentPhase::<T>::hashed_key().to_vec());
|
||||||
|
let phase = get_storage::<EPM::Phase<BlockNumber>>(client, params! {key, at})
|
||||||
|
.await?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if phase.is_signed() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::IncorrectPhase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that our current `us` have not submitted anything previously.
|
||||||
|
async fn ensure_no_previous_solution<
|
||||||
|
T: EPM::Config + frame_system::Config<AccountId = AccountId>,
|
||||||
|
B: BlockT,
|
||||||
|
>(
|
||||||
|
ext: &mut Ext,
|
||||||
|
us: &AccountId,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
use EPM::signed::SignedSubmissions;
|
||||||
|
ext.execute_with(|| {
|
||||||
|
if <SignedSubmissions<T>>::get().iter().any(|ss| &ss.who == us) {
|
||||||
|
Err(Error::AlreadySubmitted)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! monitor_cmd_for { ($runtime:tt) => { paste::paste! {
|
||||||
|
/// The monitor command.
|
||||||
|
pub(crate) async fn [<monitor_cmd_ $runtime>](
|
||||||
|
client: &WsClient,
|
||||||
|
shared: SharedConfig,
|
||||||
|
config: MonitorConfig,
|
||||||
|
signer: Signer,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
use $crate::[<$runtime _runtime_exports>]::*;
|
||||||
|
let (sub, unsub) = if config.listen == "head" {
|
||||||
|
("chain_subscribeNewHeads", "chain_unsubscribeNewHeads")
|
||||||
|
} else {
|
||||||
|
("chain_subscribeFinalizedHeads", "chain_unsubscribeFinalizedHeads")
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", sub, unsub);
|
||||||
|
let mut subscription: Subscription<Header> = client
|
||||||
|
.subscribe(&sub, JsonRpcParams::NoParams, &unsub)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
while let Some(now) = subscription.next().await? {
|
||||||
|
let hash = now.hash();
|
||||||
|
log::debug!(target: LOG_TARGET, "new event at #{:?} ({:?})", now.number, hash);
|
||||||
|
|
||||||
|
// 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.");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: we don't check the score of any of the submitted solutions. If we submit a weak
|
||||||
|
// one, as long as we are valid, we will end up getting our deposit back, so not a big
|
||||||
|
// deal for now. Note that to avoid an unfeasible solution, we should make sure that we
|
||||||
|
// only start the process on a finalized snapshot. If the signed phase is long enough,
|
||||||
|
// 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?;
|
||||||
|
|
||||||
|
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.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (raw_solution, witness) = crate::mine_unchecked::<Runtime>(&mut ext, config.iterations, true)?;
|
||||||
|
log::info!(target: LOG_TARGET, "mined solution with {:?}", &raw_solution.score);
|
||||||
|
|
||||||
|
let nonce = crate::get_account_info::<Runtime>(client, &signer.account, Some(hash))
|
||||||
|
.await?
|
||||||
|
.map(|i| i.nonce)
|
||||||
|
.expect(crate::signer::SIGNER_ACCOUNT_WILL_EXIST);
|
||||||
|
let tip = 0 as Balance;
|
||||||
|
let period = <Runtime as frame_system::Config>::BlockHashCount::get() / 2;
|
||||||
|
let current_block = now.number.saturating_sub(1);
|
||||||
|
let era = sp_runtime::generic::Era::mortal(period.into(), current_block.into());
|
||||||
|
log::trace!(target: LOG_TARGET, "transaction mortality: {:?} -> {:?}", era.birth(current_block.into()), era.death(current_block.into()));
|
||||||
|
let extrinsic = ext.execute_with(|| create_uxt(raw_solution, witness, signer.clone(), nonce, tip, era));
|
||||||
|
let bytes = sp_core::Bytes(extrinsic.encode());
|
||||||
|
|
||||||
|
let mut tx_subscription: Subscription<
|
||||||
|
TransactionStatus<<Block as BlockT>::Hash, <Block as BlockT>::Hash>
|
||||||
|
> = client
|
||||||
|
.subscribe(&"author_submitAndWatchExtrinsic", params! { bytes }, "author_unwatchExtrinsic")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let _success = while let Some(status_update) = tx_subscription.next().await? {
|
||||||
|
log::trace!(target: LOG_TARGET, "status update {:?}", status_update);
|
||||||
|
match status_update {
|
||||||
|
TransactionStatus::Ready | TransactionStatus::Broadcast(_) | TransactionStatus::Future => continue,
|
||||||
|
TransactionStatus::InBlock(hash) => {
|
||||||
|
log::info!(target: LOG_TARGET, "included at {:?}", hash);
|
||||||
|
let key = sp_core::storage::StorageKey(frame_system::Events::<Runtime>::hashed_key().to_vec());
|
||||||
|
let events =get_storage::<
|
||||||
|
Vec<frame_system::EventRecord<Event, <Block as BlockT>::Hash>>
|
||||||
|
>(client, params!{ key, hash }).await?.unwrap_or_default();
|
||||||
|
log::info!(target: LOG_TARGET, "events at inclusion {:?}", events);
|
||||||
|
}
|
||||||
|
TransactionStatus::Retracted(hash) => {
|
||||||
|
log::info!(target: LOG_TARGET, "Retracted at {:?}", hash);
|
||||||
|
}
|
||||||
|
TransactionStatus::Finalized(hash) => {
|
||||||
|
log::info!(target: LOG_TARGET, "Finalized at {:?}", hash);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::warn!(target: LOG_TARGET, "Stopping listen due to other status {:?}", status_update);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}}}
|
||||||
|
|
||||||
|
monitor_cmd_for!(polkadot);
|
||||||
|
monitor_cmd_for!(kusama);
|
||||||
|
monitor_cmd_for!(westend);
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Types that we don't fetch from a particular runtime and just assume that they are constant all
|
||||||
|
//! of the place.
|
||||||
|
//!
|
||||||
|
//! It is actually easy to convert the rest as well, but it'll be a lot of noise in our codebase,
|
||||||
|
//! needing to sprinkle `any_runtime` in a few extra places.
|
||||||
|
|
||||||
|
/// The account id type.
|
||||||
|
pub type AccountId = core_primitives::AccountId;
|
||||||
|
/// The block number type.
|
||||||
|
pub type BlockNumber = core_primitives::BlockNumber;
|
||||||
|
/// The balance type.
|
||||||
|
pub type Balance = core_primitives::Balance;
|
||||||
|
/// The index of an account.
|
||||||
|
pub type Index = core_primitives::AccountIndex;
|
||||||
|
/// The hash type. We re-export it here, but we can easily get it from block as well.
|
||||||
|
pub type Hash = core_primitives::Hash;
|
||||||
|
|
||||||
|
pub use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||||
|
|
||||||
|
/// Default URI to connect to.
|
||||||
|
pub const DEFAULT_URI: &'static str = "wss://rpc.polkadot.io";
|
||||||
|
/// The logging target.
|
||||||
|
pub const LOG_TARGET: &'static str = "staking-miner";
|
||||||
|
|
||||||
|
/// The election provider pallet.
|
||||||
|
pub use pallet_election_provider_multi_phase as EPM;
|
||||||
|
|
||||||
|
/// The externalities type.
|
||||||
|
pub type Ext = sp_io::TestExternalities;
|
||||||
|
|
||||||
|
/// The key pair type being used. We 'strongly' assume sr25519 for simplicity.
|
||||||
|
pub type Pair = sp_core::sr25519::Pair;
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Helper method for RPC.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use jsonrpsee_ws_client::traits::Client;
|
||||||
|
pub(crate) use jsonrpsee_ws_client::v2::params::JsonRpcParams;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! params {
|
||||||
|
($($param:expr),*) => {
|
||||||
|
{
|
||||||
|
let mut __params = vec![];
|
||||||
|
$(
|
||||||
|
__params.push(serde_json::to_value($param).expect("json serialization infallible; qed."));
|
||||||
|
)*
|
||||||
|
$crate::rpc_helpers::JsonRpcParams::Array(__params)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
() => {
|
||||||
|
$crate::rpc::JsonRpcParams::NoParams,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make the rpc request, returning `Ret`.
|
||||||
|
pub(crate) async fn rpc<'a, Ret: serde::de::DeserializeOwned>(
|
||||||
|
client: &WsClient,
|
||||||
|
method: &'a str,
|
||||||
|
params: JsonRpcParams<'a>,
|
||||||
|
) -> Result<Ret, Error> {
|
||||||
|
client.request::<Ret>(method, params).await.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make the rpc request, decode the outcome into `Dec`. Don't use for storage, it will fail for
|
||||||
|
/// non-existent storage items.
|
||||||
|
pub(crate) async fn rpc_decode<'a, Dec: codec::Decode>(
|
||||||
|
client: &WsClient,
|
||||||
|
method: &'a str,
|
||||||
|
params: JsonRpcParams<'a>,
|
||||||
|
) -> Result<Dec, Error> {
|
||||||
|
let bytes = rpc::<sp_core::Bytes>(client, method, params).await?;
|
||||||
|
<Dec as codec::Decode>::decode(&mut &*bytes.0).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the storage item.
|
||||||
|
pub(crate) async fn get_storage<'a, T: codec::Decode>(
|
||||||
|
client: &WsClient,
|
||||||
|
params: JsonRpcParams<'a>,
|
||||||
|
) -> Result<Option<T>, Error> {
|
||||||
|
let maybe_bytes = rpc::<Option<sp_core::Bytes>>(client, "state_getStorage", params).await?;
|
||||||
|
if let Some(bytes) = maybe_bytes {
|
||||||
|
let decoded = <T as codec::Decode>::decode(&mut &*bytes.0)?;
|
||||||
|
Ok(Some(decoded))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use codec::{EncodeLike, FullCodec};
|
||||||
|
use frame_support::storage::{StorageMap, StorageValue};
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) async fn get_storage_value_frame_v2<'a, V: StorageValue<T>, T: FullCodec, Hash>(
|
||||||
|
client: &WsClient,
|
||||||
|
maybe_at: Option<Hash>,
|
||||||
|
) -> Result<Option<V::Query>, Error>
|
||||||
|
where
|
||||||
|
V::Query: codec::Decode,
|
||||||
|
Hash: serde::Serialize,
|
||||||
|
{
|
||||||
|
let key = <V as StorageValue<T>>::hashed_key();
|
||||||
|
get_storage::<V::Query>(&client, params! { key, maybe_at }).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) async fn get_storage_map_frame_v2<
|
||||||
|
'a,
|
||||||
|
Hash,
|
||||||
|
KeyArg: EncodeLike<K>,
|
||||||
|
K: FullCodec,
|
||||||
|
T: FullCodec,
|
||||||
|
M: StorageMap<K, T>,
|
||||||
|
>(
|
||||||
|
client: &WsClient,
|
||||||
|
key: KeyArg,
|
||||||
|
maybe_at: Option<Hash>,
|
||||||
|
) -> Result<Option<M::Query>, Error>
|
||||||
|
where
|
||||||
|
M::Query: codec::Decode,
|
||||||
|
Hash: serde::Serialize,
|
||||||
|
{
|
||||||
|
let key = <M as StorageMap<K, T>>::hashed_key_for(key);
|
||||||
|
get_storage::<M::Query>(&client, params! { key, maybe_at }).await
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Wrappers around creating a signer account.
|
||||||
|
|
||||||
|
use crate::{rpc_helpers, AccountId, Error, Index, Pair, WsClient, LOG_TARGET};
|
||||||
|
use sp_core::crypto::Pair as _;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub(crate) const SIGNER_ACCOUNT_WILL_EXIST: &'static str =
|
||||||
|
"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.";
|
||||||
|
|
||||||
|
/// Some information about the signer. Redundant at this point, but makes life easier.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Signer {
|
||||||
|
/// The account id.
|
||||||
|
pub(crate) account: AccountId,
|
||||||
|
/// The full crypto key-pair.
|
||||||
|
pub(crate) pair: Pair,
|
||||||
|
/// The raw uri read from file.
|
||||||
|
pub(crate) uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn get_account_info<T: frame_system::Config>(
|
||||||
|
client: &WsClient,
|
||||||
|
who: &T::AccountId,
|
||||||
|
maybe_at: Option<T::Hash>,
|
||||||
|
) -> Result<Option<frame_system::AccountInfo<Index, T::AccountData>>, Error> {
|
||||||
|
rpc_helpers::get_storage::<frame_system::AccountInfo<Index, T::AccountData>>(
|
||||||
|
client,
|
||||||
|
crate::params! {
|
||||||
|
sp_core::storage::StorageKey(<frame_system::Account<T>>::hashed_key_for(&who)),
|
||||||
|
maybe_at
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the signer account's uri from the given `path`.
|
||||||
|
pub(crate) async fn read_signer_uri<
|
||||||
|
P: AsRef<Path>,
|
||||||
|
T: frame_system::Config<AccountId = AccountId, Index = Index>,
|
||||||
|
>(
|
||||||
|
path: P,
|
||||||
|
client: &WsClient,
|
||||||
|
) -> Result<Signer, Error> {
|
||||||
|
let uri = std::fs::read_to_string(path)?;
|
||||||
|
|
||||||
|
// trim any trailing garbage.
|
||||||
|
let uri = uri.trim_end();
|
||||||
|
|
||||||
|
let pair = Pair::from_string(&uri, None)?;
|
||||||
|
let account = T::AccountId::from(pair.public());
|
||||||
|
let _info =
|
||||||
|
get_account_info::<T>(&client, &account, None).await?.ok_or(Error::AccountDoesNotExists)?;
|
||||||
|
log::info!(target: LOG_TARGET, "loaded account {:?}, info: {:?}", &account, _info);
|
||||||
|
Ok(Signer { account, pair, uri: uri.to_string() })
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user