Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use subxt-signer to reduce the number of deps #720

Merged
merged 5 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,822 changes: 738 additions & 1,084 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,20 @@ tokio = { version = "1.34", features = ["macros", "rt-multi-thread", "sync", "si
pin-project-lite = "0.2"

# subxt
subxt = { version = "0.32.1", features = ["substrate-compat"] }
scale-value = "0.12.0"
subxt = "0.32.1"
subxt-signer = { version = "0.32.1", features = ["subxt"] }

# polkadot-sdk
frame-election-provider-support = "26.0.0"
pallet-election-provider-multi-phase = "25.0.0"
sp-npos-elections = "24.0.0"
frame-support = "26.0.0"
sp-npos-elections = "24.0.0"
# Both `sp-runtime` and `sp-core` has plenty of dependencies
# and because `pallet-election-provider-multi-phase` is depending
# on them it's not much we can do it about it.
sp-runtime = "29.0.0"
sp-core = "26.0.0"

# prometheus
prometheus = "0.13"
Expand Down
44 changes: 33 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
# Staking Miner v2
# Polkadot staking miner

[![Daily compatibility check against latest polkadot](https://github.com/paritytech/staking-miner-v2/actions/workflows/nightly.yml/badge.svg)](https://github.com/paritytech/staking-miner-v2/actions/workflows/nightly.yml)
[![Daily compatibility check against latest polkadot](https://github.com/paritytech/polkadot-staking-miner/actions/workflows/nightly.yml/badge.svg)](https://github.com/paritytech/polkadot-staking-miner/actions/workflows/nightly.yml)

This is a re-write of the [polkadot staking miner](https://github.com/paritytech/polkadot/tree/master/utils/staking-miner) using [subxt](https://github.com/paritytech/subxt) to avoid hard dependency to each runtime version.
This is a re-write of the [staking miner](https://github.com/paritytech/polkadot/tree/master/utils/staking-miner) using [subxt](https://github.com/paritytech/subxt) to avoid hard dependency to each runtime version.

The binary itself embeds [static metadata](./artifacts/metadata.scale) to
generate a rust codegen at compile-time that [subxt provides](https://github.com/paritytech/subxt).

Runtime upgrades are handled by staking-miner-v2 by upgrading storage constants
and that will work unless there is a breaking change in any of pallets used by
the staking-miner (mainly pallet-election-provider and pallet-system are used).
Runtime upgrades are handled by the polkadot-staking-miner by upgrading storage constants
and that will work unless there is a breaking change in the pallet `pallet-election-provider-multi-phase`
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
or `frame_system`.

Because detecting breaking changes when connection to RPC node when using
Because detecting breaking changes when connecting to a RPC node when using
`polkadot-staking-miner` is hard, this repo performs daily integration tests
against `polkadot master` and in most cases [update the metadata](#update-metadata)
against `polkadot master` and in most cases [updating the metadata](#update-metadata)
and fixing the compile errors are sufficient.

It's also possible to use the `info` command to check whether the metadata
embedded in the binary is compatible with a remote node, [see the info the command for further information](#info-command)

Each release will specify which runtime version it was tested against but
it's not possible to know in advance which runtimes it will work with.

Thus, it's important to subscribe to releases to this repo or
add some logic that triggers an alert once the staking-miner-v2 crashes.
add some logic that triggers an alert once the polkadot-staking-miner crashes.

## Usage

You can check the help with:

```bash
$ staking-miner --help
$ polkadot-staking-miner --help
```

### Monitor
Expand Down Expand Up @@ -67,6 +70,25 @@ Mine a solution that can be submitted as an emergency solution.
$ cargo run --release -- --uri ws://localhost:9944 emergency-solution --at 0xba86a0ba663df496743eeb077d004ef86bd767716e0d8cb935ab90d3ae174e85 seq-phragmen
```

### Info command

Check if the polkadot-staking-miner's metadata is compatible with a remote node.

```bash
$ cargo run --release -- --uri wss://rpc.polkadot.io info
Remote_node:
{
"spec_name": "polkadot",
"impl_name": "parity-polkadot",
"spec_version": 9431,
"impl_version": 0,
"authoring_version": 0,
"transaction_version": 24,
"state_version": 0
}
Compatible: YES
```

### Prepare your SEED

While you could pass your seed directly to the cli or Docker, this is highly **NOT** recommended. Instead, you should use an ENV variable.
Expand Down Expand Up @@ -114,7 +136,7 @@ To update the metadata you need to connect to a polkadot, kusama or westend node
$ cargo install --locked subxt-cli
# Download the metadata from a local node and replace the current metadata
# See `https://github.com/paritytech/subxt/tree/master/cli` for further documentation of the `subxt-cli` tool.
$ subxt metadata -f bytes > artifacts/metadata.scale
$ subxt metadata --url wss://rpc.polkadot.io --pallets "ElectionProviderMultiPhase,System" -f bytes > artifacts/metadata.scale
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
# Inspect the generated code
$ subxt codegen --file artifacts/metadata.scale | rustfmt > code.rs
```
Expand Down
24 changes: 15 additions & 9 deletions src/commands/dry_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@

//! The dry-run command.

use pallet_election_provider_multi_phase::RawSolution;

use crate::{
client::Client, epm, error::Error, helpers::storage_at, opt::Solver, prelude::*,
signer::Signer, static_types,
client::Client,
epm,
error::Error,
helpers::{signer_from_seed_or_path, storage_at},
opt::Solver,
prelude::*,
static_types,
};
use clap::Parser;
use codec::Encode;
use pallet_election_provider_multi_phase::RawSolution;

#[derive(Debug, Clone, Parser)]
#[cfg_attr(test, derive(PartialEq))]
Expand Down Expand Up @@ -99,19 +103,21 @@ where
// If an account seed or path is provided, then do a dry run to the node. Otherwise,
// we've logged the solution above and we do nothing else.
if let Some(seed_or_path) = &config.seed_or_path {
let signer = Signer::new(seed_or_path)?;
let signer = signer_from_seed_or_path(seed_or_path)?;
let account_info = storage
.fetch(&runtime::storage().system().account(signer.account_id()))
.fetch(&runtime::storage().system().account(signer.public_key().to_account_id()))
.await?
.ok_or(Error::AccountDoesNotExists)?;

log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer, account_info);
log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer.public_key().to_account_id(), account_info);

let nonce = client.rpc_system_account_next_index(signer.account_id()).await?;
let nonce = client
.rpc_system_account_next_index(&signer.public_key().to_account_id())
.await?;
let tx = epm::signed_solution(raw_solution)?;
let xt = client.chain_api().tx().create_signed_with_nonce(
&tx,
&*signer,
&signer,
nonce,
Default::default(),
)?;
Expand Down
17 changes: 16 additions & 1 deletion src/commands/emergency_solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::{
use clap::Parser;
use codec::Encode;
use sp_core::hexdisplay::HexDisplay;
use sp_npos_elections::Support;
use std::io::Write;
use subxt::tx::TxPayload;

Expand Down Expand Up @@ -74,7 +75,21 @@ where
let encoded_size = ready_solution.encoded_size();
let score = ready_solution.score;

let mut supports = ready_solution.supports.into_inner();
// subxt doesn't implement `scale_info::TypeInfo` for AccountId32
// that's why all AccountId32's below are converted to the inner array.
let mut supports: Vec<_> = ready_solution
.supports
.into_inner()
.into_iter()
.map(|(a, s)| {
let supports = Support {
voters: s.voters.into_iter().map(|(a, w)| (a.0, w)).collect(),
total: s.total,
};

(a.0, supports)
})
.collect();

// maybe truncate.
if let Some(force_winner_count) = config.force_winner_count {
Expand Down
24 changes: 12 additions & 12 deletions src/commands/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ use crate::{
client::Client,
epm,
error::Error,
helpers::{kill_main_task_if_critical_err, TimedFuture},
helpers::{kill_main_task_if_critical_err, signer_from_seed_or_path, TimedFuture},
opt::Solver,
prelude::*,
prometheus,
signer::Signer,
static_types,
prometheus, static_types,
};
use clap::Parser;
use codec::{Decode, Encode};
Expand Down Expand Up @@ -170,10 +168,10 @@ where
+ 'static,
T::Solution: Send,
{
let signer = Signer::new(&config.seed_or_path)?;
let signer = signer_from_seed_or_path(&config.seed_or_path)?;

let account_info = {
let addr = runtime::storage().system().account(signer.account_id());
let addr = runtime::storage().system().account(signer.public_key().to_account_id());
client
.chain_api()
.storage()
Expand All @@ -184,7 +182,7 @@ where
.ok_or(Error::AccountDoesNotExists)?
};

log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer, account_info);
log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer.public_key().to_account_id(), account_info);

if config.dry_run {
// if we want to try-run, ensure the node supports it.
Expand Down Expand Up @@ -242,7 +240,7 @@ where
.storage()
.at_latest()
.await?
.fetch(&runtime::storage().system().account(signer.account_id()))
.fetch(&runtime::storage().system().account(signer.public_key().to_account_id()))
.await?
.ok_or(Error::AccountDoesNotExists)?;
// this is lossy but fine for now.
Expand Down Expand Up @@ -271,7 +269,9 @@ where
// NOTE: as we try to send at each block then the nonce is used guard against
// submitting twice. Because once a solution has been accepted on chain
// the "next transaction" at a later block but with the same nonce will be rejected
let nonce = client.rpc_system_account_next_index(signer.account_id()).await?;
let nonce = client
.rpc_system_account_next_index(&signer.public_key().to_account_id())
.await?;

ensure_signed_phase(client.chain_api(), block_hash)
.inspect_err(|e| {
Expand Down Expand Up @@ -300,7 +300,7 @@ where
ensure_no_previous_solution::<T::Solution>(
client.chain_api(),
block_hash,
&signer.account_id().0.into(),
&signer.public_key().0.into(),
)
.inspect_err(|e| {
log::debug!(
Expand Down Expand Up @@ -382,7 +382,7 @@ where
ensure_no_previous_solution::<T::Solution>(
client.chain_api(),
best_head,
&signer.account_id().0.into(),
&signer.public_key().0.into(),
)
.inspect_err(|e| {
log::debug!(
Expand Down Expand Up @@ -526,7 +526,7 @@ async fn submit_and_watch_solution<T: MinerConfig + Send + Sync + 'static>(

let xt = client.chain_api().tx().create_signed_with_nonce(
&tx,
&*signer,
&signer,
nonce as u64,
Default::default(),
)?;
Expand Down
8 changes: 4 additions & 4 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use crate::prelude::*;

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Failed to parse log directive: `{0}´")]
Expand All @@ -26,8 +24,10 @@ pub enum Error {
RpcError(#[from] jsonrpsee::core::Error),
#[error("subxt error: `{0}`")]
Subxt(#[from] subxt::Error),
#[error("Crypto error: `{0:?}`")]
Crypto(sp_core::crypto::SecretStringError),
#[error("SecretUri error: `{0}`")]
SecretUri(#[from] subxt_signer::SecretUriError),
#[error("Keypair error: `{0}`")]
Keypair(#[from] subxt_signer::sr25519::Error),
#[error("Codec error: `{0}`")]
Codec(#[from] codec::Error),
#[error("Incorrect phase")]
Expand Down
28 changes: 28 additions & 0 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use serde::Deserialize;
use std::{
future::Future,
pin::Pin,
str::FromStr,
task::{Context, Poll},
time::{Duration, Instant},
};
Expand Down Expand Up @@ -152,3 +153,30 @@ pub async fn storage_at(
api.storage().at_latest().await.map_err(Into::into)
}
}

pub fn signer_from_seed_or_path(seed_or_path: &str) -> Result<Signer, Error> {
let seed_or_path = seed_or_path.trim();

let unchecked_secret = match std::fs::read(seed_or_path) {
Ok(s) => String::from_utf8(s).map_err(|e| Error::Other(e.to_string()))?,
Err(_) => seed_or_path.to_string(),
};

let secret = subxt_signer::SecretUri::from_str(&unchecked_secret)?;
Signer::from_uri(&secret).map_err(Into::into)
}

#[cfg(test)]
#[test]
fn signer_parsing_works() {
assert!(signer_from_seed_or_path("//Alice").is_ok());
assert!(signer_from_seed_or_path(
"0x1122334455667788112233445566778811223344556677881122334455667788"
)
.is_ok());
assert!(signer_from_seed_or_path(
"1122334455667788112233445566778811223344556677881122334455667788"
)
.is_err());
assert!(signer_from_seed_or_path("0x0").is_err());
}
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,4 @@ pub mod helpers;
pub mod opt;
pub mod prelude;
pub mod prometheus;
pub mod signer;
pub mod static_types;
1 change: 0 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ mod helpers;
mod opt;
mod prelude;
mod prometheus;
mod signer;
mod static_types;

use clap::Parser;
Expand Down
3 changes: 1 addition & 2 deletions src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
use crate::error::Error;

use clap::*;
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use sp_npos_elections::BalancingConfig;
use sp_runtime::DeserializeOwned;
use std::{collections::HashMap, fmt, str::FromStr};
use subxt::backend::legacy::rpc_methods as subxt_rpc;

Expand Down
16 changes: 6 additions & 10 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,18 @@

// re-exports.
pub use pallet_election_provider_multi_phase::{Miner, MinerConfig};
pub use subxt::ext::sp_core;
/// The account id type.
pub type AccountId = sp_runtime::AccountId32;
pub type AccountId = subxt::utils::AccountId32;
/// The header type. We re-export it here, but we can easily get it from block as well.
pub type Header =
subxt::config::substrate::SubstrateHeader<u32, subxt::config::substrate::BlakeTwo256>;
/// The header type. We re-export it here, but we can easily get it from block as well.
pub type Hash = sp_core::H256;
pub type Hash = subxt::utils::H256;
/// Balance type
pub type Balance = u128;
pub use subxt::ext::sp_runtime::traits::{Block as BlockT, Header as HeaderT};
/// Signer type
/// The key pair type being used. We "strongly" assume sr25519 for simplicity.
pub type Signer = subxt_signer::sr25519::Keypair;

/// Default URI to connect to.
///
Expand All @@ -42,17 +43,12 @@ pub const DEFAULT_URI: &str = "ws://127.0.0.1:9944";
pub const DEFAULT_PROMETHEUS_PORT: u16 = 9999;
/// The logging target.
pub const LOG_TARGET: &str = "polkadot-staking-miner";

/// The key pair type being used. We "strongly" assume sr25519 for simplicity.
pub type Pair = sp_core::sr25519::Pair;

/// The accuracy that we use for election computation.
pub type Accuracy = sp_runtime::Perbill;

/// Rpc client.
pub type RpcClient = subxt::backend::legacy::LegacyRpcMethods<subxt::PolkadotConfig>;
/// Subxt client used by the staking miner on all chains.
pub type ChainClient = subxt::OnlineClient<subxt::PolkadotConfig>;

/// Config used by the staking-miner
pub type Config = subxt::PolkadotConfig;

Expand Down
Loading