-
Notifications
You must be signed in to change notification settings - Fork 2
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
Non persistent TSS signer and x25519 keypair #1216
Open
ameba23
wants to merge
36
commits into
master
Choose a base branch
from
peg/non-persistant-tss-keys
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
1de907a
Dont allow mnemonic to be passed in via CLI, or environment variable …
ameba23 6b97f8f
Changelog
ameba23 7bd4358
Error handling
ameba23 4fcfc30
Add endpoint giving public keys
ameba23 e1649a4
Document new endpoint
ameba23 b12149b
Changelog
ameba23 53d5175
Clippy
ameba23 7c7acc7
Merge master
ameba23 a2c9cd7
Merge master
ameba23 5b8db51
Merge master
ameba23 1de6e81
Fix lockfile
ameba23 e97aa16
Merge branch 'master' into peg/generate-mnemonic
ameba23 8a8cb52
Add keys to appstate
ameba23 542849e
Rm persisted TSS keys
ameba23 15d3bbe
Tidy following app state change
ameba23 1ca0a0c
Fixes for tests and test helpers
ameba23 1303be2
Revert commented out import
ameba23 ebc339e
Clippy
ameba23 87c6afd
Update unsafe get test
ameba23 e71cc72
Rm setup only option, tidy
ameba23 6831f49
Tidy AppState interface
ameba23 1c36ae0
Allow for entropy-tss to be put in a non-ready state
ameba23 e416800
Update node info test
ameba23 7d8690b
Make app state ready in tests
ameba23 96c6a1a
Comments
ameba23 91ac834
Fix node info test
ameba23 b59d78d
Update pre-requisite checks
ameba23 c7d5ca2
Clippy
ameba23 a05a2a3
Force getting minimum balance before start
ameba23 079c394
Clippy
ameba23 12e940d
Comments
ameba23 a71521b
Fixes, add helper
ameba23 9d2f80d
Merge master
ameba23 7cb7ac6
Changelog
ameba23 3c08c75
Improve display of failed balance check errors
ameba23 c18124e
Improve display of failed registration checks
ameba23 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,20 +17,16 @@ | |
|
||
use std::{fs, path::PathBuf}; | ||
|
||
use crate::{chain_api::entropy, helpers::substrate::query_chain, AppState}; | ||
use clap::Parser; | ||
use entropy_client::substrate::SubstrateError; | ||
use entropy_kvdb::{ | ||
encrypted_sled::PasswordMethod, | ||
kv_manager::{error::KvError, KvManager}, | ||
}; | ||
use entropy_shared::NETWORK_PARENT_KEY; | ||
use serde::Deserialize; | ||
use serde_json::json; | ||
use subxt::ext::sp_core::{ | ||
crypto::{AccountId32, Ss58Codec}, | ||
sr25519, Pair, | ||
}; | ||
|
||
use crate::helpers::validator::get_signer_and_x25519_secret; | ||
use sp_core::crypto::Ss58Codec; | ||
|
||
pub const DEFAULT_MNEMONIC: &str = | ||
"alarm mutual concert decrease hurry invest culture survey diagram crash snap click"; | ||
|
@@ -53,16 +49,7 @@ pub const LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH: &str = "LATEST_BLOCK_NUMBER_PRO | |
#[cfg(any(test, feature = "test_helpers"))] | ||
pub const DEFAULT_ENDPOINT: &str = "ws://localhost:9944"; | ||
|
||
pub const FORBIDDEN_KEYS: [&str; 4] = [ | ||
FORBIDDEN_KEY_MNEMONIC, | ||
FORBIDDEN_KEY_SHARED_SECRET, | ||
FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC, | ||
NETWORK_PARENT_KEY, | ||
]; | ||
|
||
pub const FORBIDDEN_KEY_MNEMONIC: &str = "MNEMONIC"; | ||
pub const FORBIDDEN_KEY_SHARED_SECRET: &str = "SHARED_SECRET"; | ||
pub const FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC: &str = "DH_PUBLIC"; | ||
pub const FORBIDDEN_KEYS: [&str; 1] = [NETWORK_PARENT_KEY]; | ||
|
||
// Deafult name for TSS server | ||
// Will set mnemonic and db path | ||
|
@@ -200,23 +187,6 @@ pub struct StartupArgs { | |
/// The path to a password file | ||
#[arg(short = 'f', long = "password-file")] | ||
pub password_file: Option<PathBuf>, | ||
|
||
/// Set up the key-value store (KVDB), or ensure one already exists, print setup information to | ||
/// stdout, then exit. Supply the `--password-file` option for fully non-interactive operation. | ||
/// | ||
/// Returns the AccountID and Diffie-Hellman Public Keys associated with this server. | ||
#[arg(long = "setup-only")] | ||
pub setup_only: bool, | ||
} | ||
|
||
pub async fn has_mnemonic(kv: &KvManager) -> bool { | ||
let exists = kv.kv().exists(FORBIDDEN_KEY_MNEMONIC).await.expect("issue querying DB"); | ||
|
||
if exists { | ||
tracing::debug!("Existing mnemonic found in keystore."); | ||
} | ||
|
||
exists | ||
} | ||
|
||
pub fn development_mnemonic(validator_name: &Option<ValidatorName>) -> bip39::Mnemonic { | ||
|
@@ -236,84 +206,6 @@ pub fn development_mnemonic(validator_name: &Option<ValidatorName>) -> bip39::Mn | |
.expect("Unable to parse given mnemonic.") | ||
} | ||
|
||
pub async fn setup_mnemonic(kv: &KvManager, mnemonic: bip39::Mnemonic) { | ||
if has_mnemonic(kv).await { | ||
tracing::warn!("Deleting account related keys from KVDB."); | ||
|
||
kv.kv() | ||
.delete(FORBIDDEN_KEY_MNEMONIC) | ||
.await | ||
.expect("Error deleting existing mnemonic from KVDB."); | ||
kv.kv() | ||
.delete(FORBIDDEN_KEY_SHARED_SECRET) | ||
.await | ||
.expect("Error deleting shared secret from KVDB."); | ||
kv.kv() | ||
.delete(FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC) | ||
.await | ||
.expect("Error deleting X25519 public key from KVDB."); | ||
} | ||
|
||
tracing::info!("Writing new mnemonic to KVDB."); | ||
|
||
// Write our new mnemonic to the KVDB. | ||
let reservation = kv | ||
.kv() | ||
.reserve_key(FORBIDDEN_KEY_MNEMONIC.to_string()) | ||
.await | ||
.expect("Issue reserving mnemonic"); | ||
kv.kv() | ||
.put(reservation, mnemonic.to_string().as_bytes().to_vec()) | ||
.await | ||
.expect("failed to update mnemonic"); | ||
|
||
let (pair, static_secret) = | ||
get_signer_and_x25519_secret(kv).await.expect("Cannot derive keypairs"); | ||
let x25519_public_key = x25519_dalek::PublicKey::from(&static_secret).to_bytes(); | ||
|
||
// Write the shared secret in the KVDB | ||
let shared_secret_reservation = kv | ||
.kv() | ||
.reserve_key(FORBIDDEN_KEY_SHARED_SECRET.to_string()) | ||
.await | ||
.expect("Issue reserving ss key"); | ||
kv.kv() | ||
.put(shared_secret_reservation, static_secret.to_bytes().to_vec()) | ||
.await | ||
.expect("failed to update secret share"); | ||
|
||
// Write the Diffie-Hellman key in the KVDB | ||
let diffie_hellman_reservation = kv | ||
.kv() | ||
.reserve_key(FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC.to_string()) | ||
.await | ||
.expect("Issue reserving DH key"); | ||
|
||
kv.kv() | ||
.put(diffie_hellman_reservation, x25519_public_key.to_vec()) | ||
.await | ||
.expect("failed to update dh"); | ||
|
||
// Now we write the TSS AccountID and X25519 public key to files for convenience reasons. | ||
let formatted_dh_public = format!("{x25519_public_key:?}").replace('"', ""); | ||
fs::write(".entropy/public_key", formatted_dh_public).expect("Failed to write public key file"); | ||
|
||
let id = AccountId32::new(pair.signer().public().0); | ||
fs::write(".entropy/account_id", format!("{id}")).expect("Failed to write account_id file"); | ||
|
||
tracing::debug!("Starting process with account ID: `{id}`"); | ||
} | ||
|
||
pub async fn threshold_account_id(kv: &KvManager) -> String { | ||
let mnemonic = kv.kv().get(FORBIDDEN_KEY_MNEMONIC).await.expect("Issue getting mnemonic"); | ||
let pair = <sr25519::Pair as Pair>::from_phrase( | ||
&String::from_utf8(mnemonic).expect("Issue converting mnemonic to string"), | ||
None, | ||
) | ||
.expect("Issue converting mnemonic to pair"); | ||
AccountId32::new(pair.0.public().into()).to_ss58check() | ||
} | ||
|
||
pub async fn setup_latest_block_number(kv: &KvManager) -> Result<(), KvError> { | ||
let exists_result_new_user = | ||
kv.kv().exists(LATEST_BLOCK_NUMBER_NEW_USER).await.expect("issue querying DB"); | ||
|
@@ -370,27 +262,10 @@ pub async fn setup_latest_block_number(kv: &KvManager) -> Result<(), KvError> { | |
Ok(()) | ||
} | ||
|
||
pub async fn setup_only(kv: &KvManager) { | ||
let mnemonic = kv.kv().get(FORBIDDEN_KEYS[0]).await.expect("Issue getting mnemonic"); | ||
let pair = <sr25519::Pair as Pair>::from_phrase( | ||
&String::from_utf8(mnemonic).expect("Issue converting mnemonic to string"), | ||
None, | ||
) | ||
.expect("Issue converting mnemonic to pair"); | ||
let account_id = AccountId32::new(pair.0.public().into()).to_ss58check(); | ||
|
||
let dh_public_key = kv.kv().get(FORBIDDEN_KEYS[2]).await.expect("Issue getting dh public key"); | ||
let dh_public_key = format!("{dh_public_key:?}").replace('"', ""); | ||
let output = json!({ | ||
"account_id": account_id, | ||
"dh_public_key": dh_public_key, | ||
}); | ||
|
||
println!("{}", output); | ||
} | ||
|
||
pub async fn check_node_prerequisites(url: &str, account_id: &str) { | ||
pub async fn check_node_prerequisites(app_state: AppState) { | ||
use crate::chain_api::{get_api, get_rpc}; | ||
let url = &app_state.configuration.endpoint; | ||
let account_id = app_state.account_id(); | ||
|
||
let connect_to_substrate_node = || async { | ||
tracing::info!("Attempting to establish connection to Substrate node at `{}`", url); | ||
|
@@ -406,45 +281,69 @@ pub async fn check_node_prerequisites(url: &str, account_id: &str) { | |
Ok((api, rpc)) | ||
}; | ||
|
||
// Note: By default this will wait 15 minutes before it stops retry attempts. | ||
let backoff = backoff::ExponentialBackoff::default(); | ||
match backoff::future::retry(backoff, connect_to_substrate_node).await { | ||
// Never give up trying to connect | ||
let backoff = backoff::ExponentialBackoff { max_elapsed_time: None, ..Default::default() }; | ||
|
||
match backoff::future::retry(backoff.clone(), connect_to_substrate_node).await { | ||
Ok((api, rpc)) => { | ||
tracing::info!("Sucessfully connected to Substrate node!"); | ||
|
||
tracing::info!("Checking balance of threshold server AccountId `{}`", &account_id); | ||
let balance_query = crate::validator::api::check_balance_for_fees( | ||
&api, | ||
&rpc, | ||
account_id.to_string(), | ||
entropy_shared::MIN_BALANCE, | ||
) | ||
.await | ||
.map_err(|_| Err::<bool, String>("Failed to get balance of account.".to_string())); | ||
|
||
match balance_query { | ||
Ok(has_minimum_balance) => { | ||
if has_minimum_balance { | ||
tracing::info!( | ||
"The account `{}` has enough funds for submitting extrinsics.", | ||
&account_id | ||
) | ||
} else { | ||
tracing::warn!( | ||
"The account `{}` does not meet the minimum balance of `{}`", | ||
&account_id, | ||
entropy_shared::MIN_BALANCE, | ||
) | ||
} | ||
}, | ||
Err(_) => { | ||
tracing::warn!("Unable to query the account balance of `{}`", &account_id) | ||
}, | ||
|
||
let balance_query = || async { | ||
let has_minimum_balance = crate::validator::api::check_balance_for_fees( | ||
&api, | ||
&rpc, | ||
account_id.to_ss58check().to_string(), | ||
entropy_shared::MIN_BALANCE, | ||
) | ||
.await | ||
.map_err(|e| { | ||
tracing::warn!("Account: {} {}", &account_id, e); | ||
e.to_string() | ||
})?; | ||
if !has_minimum_balance { | ||
Err("Minimum balance not met".to_string())? | ||
} | ||
Ok(()) | ||
}; | ||
|
||
if let Err(error) = backoff::future::retry(backoff.clone(), balance_query).await { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This balance check and the one below could maybe be combined into a state machine which makes both checks, but i think for now its ok to make one after the other |
||
tracing::error!("This should never happen because backoff has no permanent errors or maximum timeout: {error}"); | ||
} | ||
|
||
tracing::info!( | ||
"The account `{}` has enough funds for submitting extrinsics.", | ||
&account_id | ||
); | ||
|
||
// Now check if there exists a threshold server with our details - if there is not, | ||
// we need to wait until there is | ||
let check_for_tss_account_id = || async { | ||
let stash_address_query = entropy::storage() | ||
.staking_extension() | ||
.threshold_to_stash(subxt::utils::AccountId32(*account_id.as_ref())); | ||
|
||
let _stash_address = query_chain(&api, &rpc, stash_address_query, None) | ||
.await? | ||
.ok_or_else(|| { | ||
tracing::warn!("TSS account ID {account_id} not yet registered on-chain - you need to call `validate` or `change_threshold_accounts`"); | ||
SubstrateError::NoEvent})?; | ||
Ok(()) | ||
}; | ||
|
||
tracing::info!( | ||
"Checking if our account ID has been registered on chain `{}`", | ||
&account_id | ||
); | ||
if let Err(error) = backoff::future::retry(backoff, check_for_tss_account_id).await { | ||
tracing::error!("This should never happen because backoff has no permanent errors or maximum timeout: {error}"); | ||
} | ||
tracing::info!("TSS node passed all prerequisite checks and is ready"); | ||
app_state.make_ready(); | ||
}, | ||
Err(_err) => { | ||
tracing::error!("Unable to establish connection with Substrate node at `{}`", url); | ||
panic!("Unable to establish connection with Substrate node."); | ||
Err(error) => { | ||
tracing::error!("This should never happen because backoff has no permanent errors or maximum timeout: {error:?}"); | ||
}, | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've not looked at the
backoff
crate in too much detail but i think addingmax_elapsed_time: None
means it will keep checking indefinitely.