-
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
Rotate parent network key takes 2 steps #1018
Changes from all commits
160f8a8
9e4da4c
4c64b8b
5175183
eddb5e5
dd4a0cb
c4310cf
2f4a731
085a0fb
80c4880
705ffa3
4a37dc5
50e02c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,7 @@ pub use entropy_protocol::{ | |
execute_protocol::{execute_protocol_generic, execute_reshare, Channels, PairWrapper}, | ||
KeyParams, KeyShareWithAuxInfo, Listener, PartyId, SessionId, ValidatorInfo, | ||
}; | ||
use entropy_shared::{OcwMessageReshare, NETWORK_PARENT_KEY}; | ||
use entropy_shared::{OcwMessageReshare, NETWORK_PARENT_KEY, NEXT_NETWORK_PARENT_KEY}; | ||
use parity_scale_codec::{Decode, Encode}; | ||
use sp_core::Pair; | ||
use std::{collections::BTreeSet, str::FromStr}; | ||
|
@@ -93,12 +93,9 @@ pub async fn new_reshare( | |
) | ||
.map_err(|e| ValidatorErr::VerifyingKeyError(e.to_string()))?; | ||
|
||
let is_proper_signer = is_signer_or_delete_parent_key( | ||
signer.account_id(), | ||
validators_info.clone(), | ||
&app_state.kv_store, | ||
) | ||
.await?; | ||
let is_proper_signer = validators_info | ||
.iter() | ||
.any(|validator_info| validator_info.tss_account == *signer.account_id()); | ||
|
||
if !is_proper_signer { | ||
return Ok(StatusCode::MISDIRECTED_REQUEST); | ||
|
@@ -176,20 +173,72 @@ pub async fn new_reshare( | |
|
||
let serialized_key_share = key_serialize(&(new_key_share, aux_info)) | ||
.map_err(|_| ProtocolErr::KvSerialize("Kv Serialize Error".to_string()))?; | ||
let network_parent_key = hex::encode(NETWORK_PARENT_KEY); | ||
// TODO: should this be a two step process? see # https://github.com/entropyxyz/entropy-core/issues/968 | ||
if app_state.kv_store.kv().exists(&network_parent_key).await? { | ||
app_state.kv_store.kv().delete(&network_parent_key).await? | ||
let next_network_parent_key = hex::encode(NEXT_NETWORK_PARENT_KEY); | ||
|
||
if app_state.kv_store.kv().exists(&next_network_parent_key).await? { | ||
app_state.kv_store.kv().delete(&next_network_parent_key).await? | ||
}; | ||
|
||
let reservation = app_state.kv_store.kv().reserve_key(network_parent_key).await?; | ||
let reservation = app_state.kv_store.kv().reserve_key(next_network_parent_key).await?; | ||
app_state.kv_store.kv().put(reservation, serialized_key_share.clone()).await?; | ||
|
||
// TODO: Error handling really complex needs to be thought about. | ||
confirm_key_reshare(&api, &rpc, &signer).await?; | ||
Ok(StatusCode::OK) | ||
} | ||
|
||
/// HTTP POST endpoint called by the off-chain worker (propagation pallet) after a network key reshare. | ||
/// | ||
/// This rotates network key, deleting the previous network parent key. | ||
#[tracing::instrument(skip_all)] | ||
pub async fn rotate_network_key( | ||
State(app_state): State<AppState>, | ||
) -> Result<StatusCode, ValidatorErr> { | ||
// validate from chain | ||
let api = get_api(&app_state.configuration.endpoint).await?; | ||
let rpc = get_rpc(&app_state.configuration.endpoint).await?; | ||
validate_rotate_network_key(&api, &rpc).await?; | ||
|
||
let (signer, _) = get_signer_and_x25519_secret(&app_state.kv_store) | ||
.await | ||
.map_err(|e| ValidatorErr::UserError(e.to_string()))?; | ||
|
||
let signers_query = entropy::storage().staking_extension().signers(); | ||
let signers = query_chain(&api, &rpc, signers_query, None) | ||
.await? | ||
.ok_or_else(|| ValidatorErr::ChainFetch("Error getting signers"))?; | ||
|
||
let validators_info = get_validators_info(&api, &rpc, signers) | ||
.await | ||
.map_err(|e| ValidatorErr::UserError(e.to_string()))?; | ||
|
||
let is_proper_signer = is_signer_or_delete_parent_key( | ||
signer.account_id(), | ||
validators_info.clone(), | ||
&app_state.kv_store, | ||
) | ||
.await?; | ||
|
||
if !is_proper_signer { | ||
return Ok(StatusCode::MISDIRECTED_REQUEST); | ||
} | ||
|
||
let network_parent_key_heading = hex::encode(NETWORK_PARENT_KEY); | ||
let next_network_parent_key_heading = hex::encode(NEXT_NETWORK_PARENT_KEY); | ||
|
||
let new_parent_key = app_state.kv_store.kv().get(&next_network_parent_key_heading).await?; | ||
|
||
if app_state.kv_store.kv().exists(&network_parent_key_heading).await? { | ||
app_state.kv_store.kv().delete(&network_parent_key_heading).await?; | ||
}; | ||
|
||
app_state.kv_store.kv().delete(&next_network_parent_key_heading).await?; | ||
|
||
let reservation = app_state.kv_store.kv().reserve_key(network_parent_key_heading).await?; | ||
app_state.kv_store.kv().put(reservation, new_parent_key).await?; | ||
Comment on lines
+226
to
+238
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. Does this mean we should get rid of the 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. mmm yes that is a good point, that should be moved to the second step |
||
Ok(StatusCode::OK) | ||
} | ||
|
||
// Validates new reshare endpoint | ||
/// Checks the chain for validity of data and block number of data matches current block | ||
pub async fn validate_new_reshare( | ||
|
@@ -236,6 +285,30 @@ pub async fn validate_new_reshare( | |
Ok(()) | ||
} | ||
|
||
/// Checks the chain that the reshare was completed recently. | ||
/// | ||
/// We only care that this happens after a reshare so we just check that message isn't stale | ||
pub async fn validate_rotate_network_key( | ||
api: &OnlineClient<EntropyConfig>, | ||
rpc: &LegacyRpcMethods<EntropyConfig>, | ||
) -> Result<(), ValidatorErr> { | ||
let latest_block_number = rpc | ||
.chain_get_header(None) | ||
.await? | ||
.ok_or_else(|| ValidatorErr::OptionUnwrapError("Failed to get block number".to_string()))? | ||
.number; | ||
Comment on lines
+295
to
+299
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. Here it makes sense to check using the finalized header 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. no , I think that would fail as this needs to match when the OCW is fired and that isn't on finalized but on block creation |
||
|
||
let rotate_keyshares_info_query = entropy::storage().staking_extension().rotate_keyshares(); | ||
let rotate_keyshare_block = query_chain(api, rpc, rotate_keyshares_info_query, None) | ||
.await? | ||
.ok_or_else(|| ValidatorErr::ChainFetch("Rotate Keyshare not in progress"))?; | ||
|
||
if latest_block_number > rotate_keyshare_block { | ||
return Err(ValidatorErr::StaleData); | ||
} | ||
|
||
Ok(()) | ||
} | ||
/// Confirms that a validator has succefully reshared. | ||
pub async fn confirm_key_reshare( | ||
api: &OnlineClient<EntropyConfig>, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,15 +101,95 @@ async fn test_reshare() { | |
let (key_share_after, aux_info_after): KeyShareWithAuxInfo = | ||
deserialize(&key_share_and_aux_data_after).unwrap(); | ||
|
||
// Check key share has not yet changed | ||
assert_eq!(serialize(&key_share_before).unwrap(), serialize(&key_share_after).unwrap()); | ||
// Check aux info has not yet changed | ||
assert_eq!(serialize(&aux_info_before).unwrap(), serialize(&aux_info_after).unwrap()); | ||
|
||
let _ = client | ||
.post(format!("http://127.0.0.1:{}/validator/rotate_network_key", validator_ports[i])) | ||
.send() | ||
.await | ||
.unwrap(); | ||
|
||
let key_share_and_aux_data_after_rotate = | ||
unsafe_get(&client, hex::encode(NETWORK_PARENT_KEY), validator_ports[i]).await; | ||
let (key_share_after_rotate, aux_info_after_rotate): KeyShareWithAuxInfo = | ||
deserialize(&key_share_and_aux_data_after_rotate).unwrap(); | ||
|
||
// Check key share has changed | ||
assert_ne!(serialize(&key_share_before).unwrap(), serialize(&key_share_after).unwrap()); | ||
assert_ne!( | ||
serialize(&key_share_before).unwrap(), | ||
serialize(&key_share_after_rotate).unwrap() | ||
); | ||
// Check aux info has changed | ||
assert_ne!(serialize(&aux_info_before).unwrap(), serialize(&aux_info_after).unwrap()); | ||
assert_ne!( | ||
serialize(&aux_info_before).unwrap(), | ||
serialize(&aux_info_after_rotate).unwrap() | ||
); | ||
|
||
// calling twice doesn't do anything | ||
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. 👍 |
||
let response = client | ||
.post(format!("http://127.0.0.1:{}/validator/rotate_network_key", validator_ports[i])) | ||
.send() | ||
.await | ||
.unwrap(); | ||
|
||
assert_eq!(response.text().await.unwrap(), "Kv error: Recv Error: channel closed"); | ||
let key_share_and_aux_data_after_rotate_twice = | ||
unsafe_get(&client, hex::encode(NETWORK_PARENT_KEY), validator_ports[i]).await; | ||
let (key_share_after_rotate_twice, aux_info_after_rotate_twice): KeyShareWithAuxInfo = | ||
deserialize(&key_share_and_aux_data_after_rotate_twice).unwrap(); | ||
|
||
// Check key share has not changed | ||
assert_eq!( | ||
serialize(&key_share_after_rotate_twice).unwrap(), | ||
serialize(&key_share_after_rotate).unwrap() | ||
); | ||
// Check aux info has not changed | ||
assert_eq!( | ||
serialize(&aux_info_after_rotate_twice).unwrap(), | ||
serialize(&aux_info_after_rotate).unwrap() | ||
); | ||
} | ||
|
||
run_to_block(&rpc, block_number + 7).await; | ||
|
||
let response_stale = | ||
client.post("http://127.0.0.1:3001/validator/rotate_network_key").send().await.unwrap(); | ||
|
||
assert_eq!(response_stale.text().await.unwrap(), "Data is stale"); | ||
|
||
// TODO #981 - test signing a message with the new keyshare set | ||
clean_tests(); | ||
} | ||
|
||
#[tokio::test] | ||
#[serial] | ||
async fn test_reshare_none_called() { | ||
initialize_test_logger().await; | ||
clean_tests(); | ||
|
||
let _cxt = test_node_process_testing_state(true).await; | ||
|
||
let add_parent_key_to_kvdb = true; | ||
let (_validator_ips, _validator_ids) = spawn_testing_validators(add_parent_key_to_kvdb).await; | ||
|
||
let validator_ports = vec![3001, 3002, 3003]; | ||
|
||
let client = reqwest::Client::new(); | ||
|
||
for i in 0..validator_ports.len() { | ||
let response = client | ||
.post(format!("http://127.0.0.1:{}/validator/rotate_network_key", validator_ports[i])) | ||
.send() | ||
.await | ||
.unwrap(); | ||
|
||
assert_eq!(response.text().await.unwrap(), "Chain Fetch: Rotate Keyshare not in progress"); | ||
} | ||
} | ||
|
||
#[tokio::test] | ||
#[serial] | ||
async fn test_reshare_validation_fail() { | ||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -71,6 +71,7 @@ pub mod pallet { | |||||||
let _ = Self::post_reshare(block_number); | ||||||||
let _ = Self::post_proactive_refresh(block_number); | ||||||||
let _ = Self::post_attestation_request(block_number); | ||||||||
let _ = Self::post_rotate_keyshare(block_number); | ||||||||
} | ||||||||
|
||||||||
fn on_initialize(_block_number: BlockNumberFor<T>) -> Weight { | ||||||||
|
@@ -96,6 +97,10 @@ pub mod pallet { | |||||||
|
||||||||
/// Attestations request message passed | ||||||||
AttestationRequestMessagePassed(OcwMessageAttestationRequest), | ||||||||
|
||||||||
/// Key Rotate Message passed to validators | ||||||||
/// parameters. [BlockNumberFor<T>] | ||||||||
Comment on lines
+101
to
+102
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.
Suggested change
|
||||||||
KeyRotatesMessagePassed(BlockNumberFor<T>), | ||||||||
} | ||||||||
|
||||||||
#[pallet::call] | ||||||||
|
@@ -259,6 +264,49 @@ pub mod pallet { | |||||||
Ok(()) | ||||||||
} | ||||||||
|
||||||||
/// Submits a request to rotate parent network key the threshold servers. | ||||||||
pub fn post_rotate_keyshare(block_number: BlockNumberFor<T>) -> Result<(), http::Error> { | ||||||||
let rotate_keyshares = pallet_staking_extension::Pallet::<T>::rotate_keyshares(); | ||||||||
if rotate_keyshares != block_number { | ||||||||
return Ok(()); | ||||||||
} | ||||||||
|
||||||||
let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); | ||||||||
let kind = sp_core::offchain::StorageKind::PERSISTENT; | ||||||||
let from_local = sp_io::offchain::local_storage_get(kind, b"rotate_keyshares") | ||||||||
.unwrap_or_else(|| b"http://localhost:3001/validator/rotate_keyshares".to_vec()); | ||||||||
let url = str::from_utf8(&from_local) | ||||||||
.unwrap_or("http://localhost:3001/validator/rotate_keyshares"); | ||||||||
|
||||||||
log::warn!("propagation::post rotate keyshare"); | ||||||||
|
||||||||
let converted_block_number: u32 = | ||||||||
BlockNumberFor::<T>::try_into(block_number).unwrap_or_default(); | ||||||||
|
||||||||
// We construct the request | ||||||||
// important: the header->Content-Type must be added and match that of the receiving | ||||||||
// party!! | ||||||||
let pending = http::Request::post(url, vec![converted_block_number.encode()]) | ||||||||
.deadline(deadline) | ||||||||
.send() | ||||||||
.map_err(|_| http::Error::IoError)?; | ||||||||
|
||||||||
// We await response, same as in fn get() | ||||||||
let response = | ||||||||
pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; | ||||||||
|
||||||||
// check response code | ||||||||
if response.code != 200 { | ||||||||
log::warn!("Unexpected status code: {}", response.code); | ||||||||
return Err(http::Error::Unknown); | ||||||||
} | ||||||||
let _res_body = response.body().collect::<Vec<u8>>(); | ||||||||
|
||||||||
Self::deposit_event(Event::KeyRotatesMessagePassed(block_number)); | ||||||||
|
||||||||
Ok(()) | ||||||||
} | ||||||||
|
||||||||
/// Submits a request for a TDX attestation. | ||||||||
pub fn post_attestation_request( | ||||||||
block_number: BlockNumberFor<T>, | ||||||||
|
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.
Why does this need to be encoded as hex?
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.
the type that the kv takes needs it, but tbh should probably make the const that