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

Add validator helpers to cli #870

Merged
merged 9 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all 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 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>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I already said with the other ones, i think --mnemonic-option is not a good name for this argument. In the context of this CLI this a mandatory argument. If they don't give it the command will not work.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we shouldn't even have this as an Option then, eh?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no it will work if the mnemonic is in the environment, a mnemonic is needed but it is not needed to be passed in

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok yeah you are right.

},
/// 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be a bit more specific with what we're printing here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well this one returns an event that is pretty descriptive and has the extra data needed

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