Skip to content

Commit

Permalink
Add validator helpers to cli (#870)
Browse files Browse the repository at this point in the history
* Add validator helpers to cli

* add change threshold account

* changelog

* Apply suggestions from code review

Co-authored-by: peg <peg@magmacollective.org>
Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* pull logic out to client from cli

* add tests

* lint

* lint

* clean

---------

Co-authored-by: peg <peg@magmacollective.org>
Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 3, 2024
1 parent 62230f0 commit a1ac1f3
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ At the moment this project **does not** adhere to
### Added
- Add a way to change program modification account ([#843](https://github.com/entropyxyz/entropy-core/pull/843))
- Add support for `--mnemonic-file` and `THRESHOLD_SERVER_MNEMONIC` ([#864](https://github.com/entropyxyz/entropy-core/pull/864))
- Add validator helpers to cli ([#870](https://github.com/entropyxyz/entropy-core/pull/870))

### Changed
- Move TSS mnemonic out of keystore [#853](https://github.com/entropyxyz/entropy-core/pull/853)
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ reqwest ={ version="0.12.4", features=["json", "stream"], optional=true
base64 ={ version="0.22.0", optional=true }
synedrion ={ version="0.1", optional=true }
hex ={ version="0.4.3", optional=true }
anyhow ="1.0.86"

# Only for the browser
js-sys={ version="0.3.68", optional=true }

[dev-dependencies]
tokio ="1.38"
serial_test="3.1.1"
tokio ="1.38"
serial_test ="3.1.1"
entropy-testing-utils={ path="../testing-utils" }
sp-keyring ="34.0.0"

[features]
default=["native", "full-client-native"]
Expand Down
43 changes: 43 additions & 0 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ pub use crate::{
chain_api::{get_api, get_rpc},
errors::ClientError,
};
use anyhow::anyhow;
pub use entropy_protocol::{sign_and_encrypt::EncryptedSignedMessage, KeyParams};
pub use entropy_shared::{KeyVisibility, SIGNING_PARTY_SIZE};
use std::str::FromStr;
pub use synedrion::KeyShare;

use crate::{
Expand All @@ -35,6 +37,7 @@ use crate::{
},
EntropyConfig,
},
client::entropy::staking_extension::events::{EndpointChanged, ThresholdAccountChanged},
substrate::{query_chain, submit_transaction_with_pair},
user::{get_current_subgroup_signers, UserSignatureRequest},
Hasher,
Expand Down Expand Up @@ -434,3 +437,43 @@ async fn select_validator_from_subgroup(
};
Ok(address.clone())
}

/// Changes the endpoint of a validator
pub async fn change_endpoint(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
user_keypair: sr25519::Pair,
new_endpoint: String,
) -> anyhow::Result<EndpointChanged> {
let change_endpoint_tx = entropy::tx().staking_extension().change_endpoint(new_endpoint.into());
let in_block =
submit_transaction_with_pair(api, rpc, &user_keypair, &change_endpoint_tx, None).await?;
let result_event = in_block
.find_first::<entropy::staking_extension::events::EndpointChanged>()?
.ok_or(anyhow!("Error with transaction"))?;
Ok(result_event)
}

/// Changes the threshold account info of a validator
pub async fn change_threshold_accounts(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
user_keypair: sr25519::Pair,
new_tss_account: String,
new_x25519_public_key: String,
) -> anyhow::Result<ThresholdAccountChanged> {
let tss_account = SubxtAccountId32::from_str(&new_tss_account)?;
let change_threshold_accounts = entropy::tx().staking_extension().change_threshold_accounts(
tss_account,
hex::decode(new_x25519_public_key)?
.try_into()
.map_err(|_| anyhow!("X25519 pub key needs to be 32 bytes"))?,
);
let in_block =
submit_transaction_with_pair(api, rpc, &user_keypair, &change_threshold_accounts, None)
.await?;
let result_event = in_block
.find_first::<entropy::staking_extension::events::ThresholdAccountChanged>()?
.ok_or(anyhow!("Error with transaction"))?;
Ok(result_event)
}
3 changes: 3 additions & 0 deletions crates/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ pub mod user;
pub mod util;
pub use util::Hasher;

#[cfg(test)]
mod tests;

#[cfg(feature = "full-client")]
pub mod client;
#[cfg(feature = "full-client")]
Expand Down
70 changes: 70 additions & 0 deletions crates/client/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::{
chain_api::{
entropy::{
runtime_types::pallet_staking_extension::pallet::ServerInfo, staking_extension::events,
},
get_api, get_rpc,
},
change_endpoint, change_threshold_accounts,
};
use entropy_testing_utils::substrate_context::test_context_stationary;
use serial_test::serial;
use sp_core::Pair;
use sp_keyring::AccountKeyring;
use subxt::utils::AccountId32;

#[tokio::test]
#[serial]
async fn test_change_endpoint() {
let one = AccountKeyring::AliceStash;
let substrate_context = test_context_stationary().await;

let api = get_api(&substrate_context.node_proc.ws_url).await.unwrap();
let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap();

let result = change_endpoint(&api, &rpc, one.into(), "new_endpoint".to_string()).await.unwrap();
assert_eq!(
format!("{:?}", result),
format!(
"{:?}",
events::EndpointChanged(
AccountId32(one.pair().public().0),
"new_endpoint".as_bytes().to_vec()
)
)
);
}

#[tokio::test]
#[serial]
async fn test_change_threhsold_accounts() {
let one = AccountKeyring::AliceStash;
let substrate_context = test_context_stationary().await;

let api = get_api(&substrate_context.node_proc.ws_url).await.unwrap();
let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap();
let x25519_public_key = [0u8; 32];
let result = change_threshold_accounts(
&api,
&rpc,
one.into(),
AccountId32(one.pair().public().0.into()).to_string(),
hex::encode(x25519_public_key),
)
.await
.unwrap();
assert_eq!(
format!("{:?}", result),
format!(
"{:?}",
events::ThresholdAccountChanged(
AccountId32(one.pair().public().0),
ServerInfo {
tss_account: AccountId32(one.pair().public().0),
x25519_public_key,
endpoint: "127.0.0.1:3001".as_bytes().to_vec()
}
)
)
);
}
97 changes: 73 additions & 24 deletions crates/test-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Simple CLI to test registering, updating programs and signing
use std::{
fmt::{self, Display},
fs,
path::PathBuf,
};

use anyhow::{anyhow, ensure};
use clap::{Parser, Subcommand};
use colored::Colorize;
Expand All @@ -31,12 +25,18 @@ use entropy_client::{
EntropyConfig,
},
client::{
get_accounts, get_api, get_programs, get_rpc, register, sign, store_program,
update_programs, KeyParams, KeyShare, KeyVisibility, VERIFYING_KEY_LENGTH,
change_endpoint, change_threshold_accounts, get_accounts, get_api, get_programs, get_rpc,
register, sign, store_program, update_programs, KeyParams, KeyShare, KeyVisibility,
VERIFYING_KEY_LENGTH,
},
};
use sp_core::{sr25519, DeriveJunction, Hasher, Pair};
use sp_runtime::traits::BlakeTwo256;
use std::{
fmt::{self, Display},
fs,
path::PathBuf,
};
use subxt::{
backend::legacy::LegacyRpcMethods,
utils::{AccountId32 as SubxtAccountId32, H256},
Expand Down Expand Up @@ -82,10 +82,10 @@ enum CliCommand {
/// interface. If no such file exists, it is assumed the program has no configuration
/// interface.
programs: Vec<String>,
/// A name from which to generate a program modification keypair, eg: "Bob"
/// This is used to send the register extrinsic and so it must be funded
///
/// Optionally may be preceeded with "//" eg: "//Bob"
/// A name or mnemonic from which to derive a program modification keypair.
/// This is used to send the register extrinsic so it must be funded
/// If giving a name it must be preceded with "//", eg: "--mnemonic-option //Alice"
/// If giving a mnemonic it must be enclosed in quotes, eg: "--mnemonic-option "alarm mutual concert...""
#[arg(short, long)]
mnemonic_option: Option<String>,
},
Expand All @@ -97,11 +97,7 @@ enum CliCommand {
message: String,
/// Optional auxiliary data passed to the program, given as hex
auxilary_data: Option<String>,
/// A name from which to generate a keypair, eg: "Alice"
/// This is only needed when using private mode.
///
/// Optionally may be preceeded with "//", eg: "//Alice"
#[arg(short, long)]
/// The mnemonic to use for the call
mnemonic_option: Option<String>,
},
/// Update the program for a particular account
Expand All @@ -120,9 +116,7 @@ enum CliCommand {
/// interface. If no such file exists, it is assumed the program has no configuration
/// interface.
programs: Vec<String>,
/// A name from which to generate a program modification keypair, eg: "Bob"
///
/// Optionally may be preceeded with "//", eg: "//Bob"
/// The mnemonic to use for the call
#[arg(short, long)]
mnemonic_option: Option<String>,
},
Expand All @@ -134,9 +128,25 @@ enum CliCommand {
config_interface_file: Option<PathBuf>,
/// The path to a file containing the program aux interface (defaults to empty)
aux_data_interface_file: Option<PathBuf>,
/// A name from which to generate a keypair, eg: "Alice"
///
/// Optionally may be preceeded with "//", eg: "//Alice"
/// The mnemonic to use for the call
#[arg(short, long)]
mnemonic_option: Option<String>,
},
/// Allows a validator to change their endpoint
ChangeEndpoint {
/// New endpoint to change to (ex. "127.0.0.1:3001")
new_endpoint: String,
/// The mnemonic for the validator stash account to use for the call, should be stash address
#[arg(short, long)]
mnemonic_option: Option<String>,
},
/// Allows a validator to change their threshold accounts
ChangeThresholdAccounts {
/// New threshold account
new_tss_account: String,
/// New x25519 public key
new_x25519_public_key: String,
/// The mnemonic for the validator stash account to use for the call, should be stash address
#[arg(short, long)]
mnemonic_option: Option<String>,
},
Expand Down Expand Up @@ -241,7 +251,7 @@ pub async fn run_command(
// If an account name is not provided, use the Alice key
let user_keypair = <sr25519::Pair as Pair>::from_string(&mnemonic, None)?;

println!("User account: {}", user_keypair.public());
println!("User account for current call: {}", user_keypair.public());

let auxilary_data =
if let Some(data) = auxilary_data { Some(hex::decode(data)?) } else { None };
Expand Down Expand Up @@ -404,6 +414,45 @@ pub async fn run_command(

Ok("Got status".to_string())
},
CliCommand::ChangeEndpoint { new_endpoint, mnemonic_option } => {
let mnemonic = if let Some(mnemonic_option) = mnemonic_option {
mnemonic_option
} else {
passed_mnemonic.expect("No Mnemonic set")
};

let user_keypair = <sr25519::Pair as Pair>::from_string(&mnemonic, None)?;
println!("User account for current call: {}", user_keypair.public());

let result_event = change_endpoint(&api, &rpc, user_keypair, new_endpoint).await?;
println!("Event result: {:?}", result_event);
Ok("Endpoint changed".to_string())
},
CliCommand::ChangeThresholdAccounts {
new_tss_account,
new_x25519_public_key,
mnemonic_option,
} => {
let mnemonic = if let Some(mnemonic_option) = mnemonic_option {
mnemonic_option
} else {
passed_mnemonic.expect("No Mnemonic set")
};
let user_keypair = <sr25519::Pair as Pair>::from_string(&mnemonic, None)?;
println!("User account for current call: {}", user_keypair.public());

let result_event = change_threshold_accounts(
&api,
&rpc,
user_keypair,
new_tss_account,
new_x25519_public_key,
)
.await?;
println!("Event result: {:?}", result_event);

Ok("Threshold accounts changed".to_string())
},
}
}

Expand Down

0 comments on commit a1ac1f3

Please sign in to comment.