Skip to content

Commit

Permalink
Validate proactive refresh endpoint (#483)
Browse files Browse the repository at this point in the history
* add to validate proactive refresh endpoint

* working tests

* clean

* docs

* fmt

* fmt

* metadata

* Update crypto/server/src/signing_client/api.rs

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

* Update crypto/server/src/signing_client/api.rs

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

* Update pallets/staking/src/lib.rs

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

* Update crypto/server/src/signing_client/tests.rs

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

* refactors

* fix test

* docs

* refactor

---------

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
  • Loading branch information
JesseAbram and HCastano authored Nov 8, 2023
1 parent fd7d439 commit 1696407
Show file tree
Hide file tree
Showing 19 changed files with 273 additions and 109 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

Binary file modified crypto/server/entropy_metadata.scale
Binary file not shown.
23 changes: 20 additions & 3 deletions crypto/server/src/helpers/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub const DEFAULT_ALICE_MNEMONIC: &str =
pub const DEFAULT_CHARLIE_MNEMONIC: &str =
"lake carry still awful point mention bike category tornado plate brass lock";

pub const LATEST_BLOCK_NUMBER_NEW_USER: &str = "LATEST_BLOCK_NUMBER_NEW_USER";
pub const LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH: &str = "LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH";

#[cfg(test)]
pub const DEFAULT_ENDPOINT: &str = "ws://localhost:9944";

Expand Down Expand Up @@ -183,11 +186,25 @@ pub async fn setup_mnemonic(kv: &KvManager, is_alice: bool, is_bob: bool) -> Res
}

pub async fn setup_latest_block_number(kv: &KvManager) -> Result<(), KvError> {
let exists_result = kv.kv().exists("LATEST_BLOCK_NUMBER").await.expect("issue querying DB");
if !exists_result {
let exists_result_new_user =
kv.kv().exists(LATEST_BLOCK_NUMBER_NEW_USER).await.expect("issue querying DB");
if !exists_result_new_user {
let reservation = kv
.kv()
.reserve_key(LATEST_BLOCK_NUMBER_NEW_USER.to_string())
.await
.expect("Issue reserving latest block number");
kv.kv()
.put(reservation, 0u32.to_be_bytes().to_vec())
.await
.expect("failed to update latest block number");
}
let exists_result_proactive_refresh =
kv.kv().exists(LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH).await.expect("issue querying DB");
if !exists_result_proactive_refresh {
let reservation = kv
.kv()
.reserve_key("LATEST_BLOCK_NUMBER".to_string())
.reserve_key(LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH.to_string())
.await
.expect("Issue reserving latest block number");
kv.kv()
Expand Down
12 changes: 10 additions & 2 deletions crypto/server/src/helpers/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,22 @@ use crate::{
AppState,
};

pub async fn setup_client() {
pub async fn setup_client() -> KvManager {
let kv_store =
KvManager::new(get_db_path(true).into(), PasswordMethod::NoPassword.execute().unwrap())
.unwrap();
let _ = setup_mnemonic(&kv_store, true, false).await;
let _ = setup_latest_block_number(&kv_store).await;
let listener_state = ListenerState::default();
let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
let app_state = AppState { listener_state, configuration, kv_store };
let app_state = AppState { listener_state, configuration, kv_store: kv_store.clone() };
let app = app(app_state).into_make_service();
let listener = TcpListener::bind("0.0.0.0:3001").unwrap();

tokio::spawn(async move {
axum::Server::from_tcp(listener).unwrap().serve(app).await.unwrap();
});
kv_store
}

pub async fn create_clients(
Expand Down Expand Up @@ -191,6 +192,13 @@ pub async fn check_if_confirmation(
assert_eq!(is_registered.unwrap().key_visibility, Static(KeyVisibility::Public));
}

pub async fn run_to_block(rpc: &LegacyRpcMethods<EntropyConfig>, block_run: u32) {
let mut current_block = 0;
while current_block < block_run {
current_block = rpc.chain_get_header(None).await.unwrap().unwrap().number;
}
}

#[tokio::test]
#[serial]
async fn test_get_signing_group() {
Expand Down
2 changes: 1 addition & 1 deletion crypto/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
//! [crate::user::api::new_user()]
//!
//! Called by the off-chain worker (propagation pallet) during user registration.
//! This takes a parity scale encoded [entropy_shared::types::OcwMessage] which tells us which
//! This takes a parity scale encoded [entropy_shared::types::OcwMessageDkg] which tells us which
//! validators are in the registration group and will perform a DKG.
//!
//! ### For other instances of the threshold server
Expand Down
66 changes: 53 additions & 13 deletions crypto/server/src/signing_client/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ use axum::{
http::StatusCode,
response::IntoResponse,
};
use blake2::{Blake2s256, Digest};
use entropy_protocol::{
execute_protocol::{execute_proactive_refresh, Channels},
KeyParams, ValidatorInfo,
};
use entropy_shared::SETUP_TIMEOUT_SECONDS;
use kvdb::kv_manager::helpers::{deserialize, serialize as key_serialize};
use parity_scale_codec::Encode;

use entropy_shared::{OcwMessageProactiveRefresh, SETUP_TIMEOUT_SECONDS};
use kvdb::kv_manager::{
helpers::{deserialize, serialize as key_serialize},
KvManager,
};
use parity_scale_codec::Decode;
use sp_core::crypto::AccountId32;
use subxt::{
Expand All @@ -27,6 +33,7 @@ use tokio::time::timeout;
use crate::{
chain_api::{entropy, get_api, get_rpc, EntropyConfig},
helpers::{
launch::LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH,
substrate::{get_subgroup, return_all_addresses_of_subgroup},
user::{check_in_registration_group, send_key},
validator::{get_signer, get_subxt_signer},
Expand All @@ -49,15 +56,14 @@ pub async fn proactive_refresh(
State(app_state): State<AppState>,
encoded_data: Bytes,
) -> Result<StatusCode, ProtocolErr> {
let validators_info = Vec::<entropy_shared::ValidatorInfo>::decode(&mut encoded_data.as_ref())?;
let ocw_data = OcwMessageProactiveRefresh::decode(&mut encoded_data.as_ref())?;
let api = get_api(&app_state.configuration.endpoint).await?;
let rpc = get_rpc(&app_state.configuration.endpoint).await?;
let signer =
get_signer(&app_state.kv_store).await.map_err(|e| ProtocolErr::UserError(e.to_string()))?;
validate_proactive_refresh(&api, &rpc).await?;
check_in_registration_group(&validators_info, signer.account_id())
check_in_registration_group(&ocw_data.validators_info, signer.account_id())
.map_err(|e| ProtocolErr::UserError(e.to_string()))?;
// TODO: validate this endpoint
validate_proactive_refresh(&api, &rpc, &app_state.kv_store, &ocw_data).await?;
// TODO batch the network keys into smaller groups per session
let all_keys =
get_all_keys(&api, &rpc).await.map_err(|e| ProtocolErr::ValidatorErr(e.to_string()))?;
Expand All @@ -83,7 +89,7 @@ pub async fn proactive_refresh(
.ok_or_else(|| ProtocolErr::Deserialization("Failed to load KeyShare".into()))?;

let new_key_share = do_proactive_refresh(
&validators_info,
&ocw_data.validators_info,
&signer,
&app_state.listener_state,
sig_request_address,
Expand Down Expand Up @@ -191,27 +197,61 @@ pub async fn do_proactive_refresh(
Ok(result)
}

/// Validates if proactive refresh was called for by chain.
///
/// # TODO
/// Validates proactive refresh call.
///
/// In the future we should check validity of message integrity. See https://github.com/entropyxyz/entropy-core/issues/454
/// It checks that:
/// - the data matches what is on-chain
/// - the data is not repeated on-chain
pub async fn validate_proactive_refresh(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
kv_manager: &KvManager,
ocw_data: &OcwMessageProactiveRefresh,
) -> Result<(), ProtocolErr> {
let last_block_number_recorded =
kv_manager.kv().get(LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH).await?;

let latest_block_number = rpc
.chain_get_header(None)
.await?
.ok_or_else(|| ProtocolErr::OptionUnwrapError("Failed to get block number".to_string()))?
.number;
// prevents multiple repeated messages being sent
if u32::from_be_bytes(
last_block_number_recorded
.try_into()
.map_err(|_| ProtocolErr::Conversion("Block number conversion"))?,
) >= latest_block_number
&& latest_block_number != 0
{
return Err(ProtocolErr::RepeatedData);
}

let block_hash = rpc
.chain_get_block_hash(None)
.await?
.ok_or_else(|| ProtocolErr::OptionUnwrapError("Error getting block hash".to_string()))?;
let proactive_info_query = entropy::storage().staking_extension().proactive_refresh();
let proactive_info =
api.storage().at(block_hash).fetch(&proactive_info_query).await?.ok_or_else(|| {
ProtocolErr::OptionUnwrapError("Error getting Proactive Refresh trigger".to_string())
ProtocolErr::OptionUnwrapError("Error getting Proactive Refresh data".to_string())
})?;

if !proactive_info {
return Err(ProtocolErr::NoProactiveRefresh);
let mut hasher_chain_data = Blake2s256::new();
hasher_chain_data.update(ocw_data.validators_info.encode());
let chain_data_hash = hasher_chain_data.finalize();
let mut hasher_verifying_data = Blake2s256::new();
hasher_verifying_data.update(proactive_info.encode());
let verifying_data_hash = hasher_verifying_data.finalize();
// checks validity of data
if verifying_data_hash != chain_data_hash {
return Err(ProtocolErr::InvalidData);
}

kv_manager.kv().delete(LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH).await?;
let reservation =
kv_manager.kv().reserve_key(LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH.to_string()).await?;
kv_manager.kv().put(reservation, latest_block_number.to_be_bytes().to_vec()).await?;
Ok(())
}
8 changes: 5 additions & 3 deletions crypto/server/src/signing_client/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub enum ProtocolErr {
BadSubscribeMessage(String),
#[error("From Hex Error: {0}")]
FromHex(#[from] hex::FromHexError),
#[error("Vec<u8> Conversion Error: {0}")]
#[error("Conversion Error: {0}")]
Conversion(&'static str),
#[error("Could not open ws connection: {0}")]
ConnectionError(#[from] tokio_tungstenite::tungstenite::Error),
Expand All @@ -83,8 +83,10 @@ pub enum ProtocolErr {
StringError(&'static str),
#[error("Option Unwrap error: {0}")]
OptionUnwrapError(String),
#[error("Proactive Refresh not called for")]
NoProactiveRefresh,
#[error("Proactive Refresh data incorrect")]
InvalidData,
#[error("Data is repeated")]
RepeatedData,
}

impl IntoResponse for ProtocolErr {
Expand Down
Loading

0 comments on commit 1696407

Please sign in to comment.