Skip to content

Commit

Permalink
Split jumpstart and register flows (#952)
Browse files Browse the repository at this point in the history
* Use `/generate_network_key` endpoint instead of `/user/new` when jumpstarting

These flows are going to be different since the user registration flow shouldn't involve any DKG
going forward.

* Make implementation for jumpstart and user registration endpoints the same

* Change offchain worker local storage entry for registration

* Small cleanups

* Add the registration flow to offchain worker hook

* Add different codepath when validating user data

* Appease Clippy

* Add `CHANGELOG` entry

* Fix OCW HTTP test

Tbh don't know what the right values for this are, and we should really be encoding the data
stucture instead of hardcoding it, but 🤷

* Add some docs

* Use correct DKG call in jumpstart test
  • Loading branch information
HCastano authored Jul 19, 2024
1 parent 9b38db1 commit 3c04c96
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 24 deletions.
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ At the moment this project **does not** adhere to

## [Unreleased](https://github.com/entropyxyz/entropy-core/compare/release/v0.2.0...master)

### Added
- Jumpstart network ([#918](https://github.com/entropyxyz/entropy-core/pull/918))
- Add Signer groups and rotation ([#938](https://github.com/entropyxyz/entropy-core/pull/938))

### Breaking Changes
- In [#938](https://github.com/entropyxyz/entropy-core/pull/938), the chainspec got a couple of new
fields, `pallet_staking_extension::initial_signers`, `pallet_parameters::total_signers`, and
`pallet_parameters::threshold`, which are used to set up the initial threshold signing
configuration for the network.

### Added
- Jumpstart network ([#918](https://github.com/entropyxyz/entropy-core/pull/918))
- Add Signer groups and rotation ([#938](https://github.com/entropyxyz/entropy-core/pull/938))
- Split jumpstart and register flows ([#952](https://github.com/entropyxyz/entropy-core/pull/952))

## [0.2.0](https://github.com/entropyxyz/entropy-core/compare/release/v0.1.0...release/v0.2.0) - 2024-07-11

### Breaking Changes
Expand Down
Binary file modified crates/client/entropy_metadata.scale
Binary file not shown.
3 changes: 2 additions & 1 deletion crates/threshold-signature-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,9 @@ impl AppState {

pub fn app(app_state: AppState) -> Router {
let mut routes = Router::new()
.route("/user/sign_tx", post(sign_tx))
.route("/generate_network_key", post(generate_network_key))
.route("/user/new", post(new_user))
.route("/user/sign_tx", post(sign_tx))
.route("/signer/proactive_refresh", post(proactive_refresh))
.route("/healthz", get(healthz))
.route("/version", get(get_version))
Expand Down
51 changes: 47 additions & 4 deletions crates/threshold-signature-server/src/user/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ use crate::{
pub use entropy_client::user::{get_signers_from_chain, UserSignatureRequest};
pub const REQUEST_KEY_HEADER: &str = "REQUESTS";

/// Used to differentiate different flows which perform distributed key generation.
enum DkgFlow {
Jumpstart,
Registration,
}

/// Type for validators to send user key's back and forth
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -228,7 +234,25 @@ pub async fn sign_tx(
Ok((StatusCode::OK, Body::from_stream(response_rx)))
}

/// HTTP POST endpoint called by the off-chain worker (propagation pallet) during user registration.
/// HTTP POST endpoint called by the off-chain worker (Propagation pallet) during the network
/// jumpstart.
///
/// The HTTP request takes a Parity SCALE encoded [OcwMessageDkg] which indicates which validators
/// are in the validator group.
///
/// This will trigger the Distributed Key Generation (DKG) process.
#[tracing::instrument(skip_all, fields(block_number))]
pub async fn generate_network_key(
State(app_state): State<AppState>,
encoded_data: Bytes,
) -> Result<StatusCode, UserErr> {
let data = OcwMessageDkg::decode(&mut encoded_data.as_ref())?;
tracing::Span::current().record("block_number", data.block_number);

distributed_key_generation(app_state, data, DkgFlow::Jumpstart).await
}

/// HTTP POST endpoint called by the off-chain worker (Propagation pallet) during user registration.
///
/// The HTTP request takes a Parity SCALE encoded [OcwMessageDkg] which indicates which validators
/// are in the validator group.
Expand All @@ -242,12 +266,25 @@ pub async fn new_user(
let data = OcwMessageDkg::decode(&mut encoded_data.as_ref())?;
tracing::Span::current().record("block_number", data.block_number);

distributed_key_generation(app_state, data, DkgFlow::Registration).await
}

/// An internal helper which kicks off the distributed key generation (DKG) process.
///
/// Since the jumpstart and registration flows are both doing DKG at the moment, we've split this
/// out. In the future though, only the jumpstart flow will require this.
async fn distributed_key_generation(
app_state: AppState,
data: OcwMessageDkg,
flow: DkgFlow,
) -> Result<StatusCode, UserErr> {
if data.sig_request_accounts.is_empty() {
return Ok(StatusCode::NO_CONTENT);
}
let api = get_api(&app_state.configuration.endpoint).await?;
let rpc = get_rpc(&app_state.configuration.endpoint).await?;
let (signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store).await?;

let in_registration_group =
check_in_registration_group(&data.validators_info, signer.account_id());

Expand All @@ -261,7 +298,7 @@ pub async fn new_user(
return Ok(StatusCode::MISDIRECTED_REQUEST);
}

validate_new_user(&data, &api, &rpc, &app_state.kv_store).await?;
validate_new_user(&data, &api, &rpc, &app_state.kv_store, flow).await?;

// Do the DKG protocol in another task, so we can already respond
tokio::spawn(async move {
Expand Down Expand Up @@ -308,6 +345,7 @@ async fn setup_dkg(
data.block_number,
)
.await?;

let verifying_key = key_share.verifying_key().to_encoded_point(true).as_bytes().to_vec();
let string_verifying_key = if sig_request_account == NETWORK_PARENT_KEY.encode() {
hex::encode(NETWORK_PARENT_KEY)
Expand Down Expand Up @@ -396,11 +434,12 @@ pub async fn confirm_registered(

/// Validates new user endpoint
/// Checks the chain for validity of data and block number of data matches current block
pub async fn validate_new_user(
async fn validate_new_user(
chain_data: &OcwMessageDkg,
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
kv_manager: &KvManager,
flow: DkgFlow,
) -> Result<(), UserErr> {
let last_block_number_recorded = kv_manager.kv().get(LATEST_BLOCK_NUMBER_NEW_USER).await?;
if u32::from_be_bytes(
Expand Down Expand Up @@ -428,7 +467,11 @@ pub async fn validate_new_user(
let chain_data_hash = hasher_chain_data.finalize();
let mut hasher_verifying_data = Blake2s256::new();

let verifying_data_query = entropy::storage().registry().dkg(chain_data.block_number);
let verifying_data_query = match flow {
DkgFlow::Jumpstart => entropy::storage().registry().jumpstart_dkg(chain_data.block_number),
DkgFlow::Registration => entropy::storage().registry().dkg(chain_data.block_number),
};

let verifying_data = query_chain(api, rpc, verifying_data_query, None).await?;
hasher_verifying_data.update(verifying_data.encode());

Expand Down
2 changes: 1 addition & 1 deletion crates/threshold-signature-server/src/user/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ async fn test_jumpstart_network() {
.iter()
.map(|port| {
client
.post(format!("http://127.0.0.1:{}/user/new", port))
.post(format!("http://127.0.0.1:{}/generate_network_key", port))
.body(onchain_user_request.clone().encode())
.send()
})
Expand Down
5 changes: 5 additions & 0 deletions node/cli/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@ pub fn new_full_base(
offchain_db.local_storage_set(
sp_core::offchain::StorageKind::PERSISTENT,
b"propagation",
&format!("{}/generate_network_key", endpoint).into_bytes(),
);
offchain_db.local_storage_set(
sp_core::offchain::StorageKind::PERSISTENT,
b"registration",
&format!("{}/user/new", endpoint).into_bytes(),
);
offchain_db.local_storage_set(
Expand Down
66 changes: 65 additions & 1 deletion pallets/propagation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub mod pallet {
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn offchain_worker(block_number: BlockNumberFor<T>) {
let _ = Self::post_dkg(block_number);
let _ = Self::post_user_registration(block_number);
let _ = Self::post_proactive_refresh(block_number);
}

Expand All @@ -82,13 +83,75 @@ pub mod pallet {
impl<T: Config> Pallet<T> {}

impl<T: Config> Pallet<T> {
/// Submits a distributed key generation request to jumpstart the network to the threshold
/// servers.
pub fn post_dkg(block_number: BlockNumberFor<T>) -> Result<(), http::Error> {
let messages = pallet_registry::Pallet::<T>::jumpstart_dkg(
block_number.saturating_sub(1u32.into()),
);

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"propagation")
.unwrap_or_else(|| b"http://localhost:3001/generate_network_key".to_vec());
let url =
str::from_utf8(&from_local).unwrap_or("http://localhost:3001/generate_network_key");

log::warn!("propagation::post::messages: {:?}", &messages);
let converted_block_number: u32 =
BlockNumberFor::<T>::try_into(block_number).unwrap_or_default();
let servers_info =
pallet_registry::Pallet::<T>::get_validators_info().unwrap_or_default();
let validators_info = servers_info
.iter()
.map(|server_info| ValidatorInfo {
x25519_public_key: server_info.x25519_public_key,
ip_address: server_info.endpoint.clone(),
tss_account: server_info.tss_account.encode(),
})
.collect::<Vec<_>>();
// the data is serialized / encoded to Vec<u8> by parity-scale-codec::encode()
let req_body = OcwMessageDkg {
// subtract 1 from blocknumber since the request is from the last block
block_number: converted_block_number.saturating_sub(1),
sig_request_accounts: messages,
validators_info,
};

log::warn!("propagation::post::req_body: {:?}", &[req_body.encode()]);
// 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![req_body.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::DkgMessagePassed(req_body));

Ok(())
}

/// Submits a distributed key generation request to register a set of users to the threshold
/// servers.
pub fn post_user_registration(block_number: BlockNumberFor<T>) -> Result<(), http::Error> {
let messages =
pallet_registry::Pallet::<T>::dkg(block_number.saturating_sub(1u32.into()));

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"propagation")
let from_local = sp_io::offchain::local_storage_get(kind, b"registration")
.unwrap_or_else(|| b"http://localhost:3001/user/new".to_vec());
let url = str::from_utf8(&from_local).unwrap_or("http://localhost:3001/user/new");

Expand Down Expand Up @@ -138,6 +201,7 @@ pub mod pallet {
Ok(())
}

/// Submits a request to perform a proactive refresh to the threshold servers.
pub fn post_proactive_refresh(block_number: BlockNumberFor<T>) -> Result<(), http::Error> {
let refresh_info = pallet_staking_extension::Pallet::<T>::proactive_refresh();
if refresh_info.validators_info.is_empty() {
Expand Down
17 changes: 10 additions & 7 deletions pallets/propagation/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fn knows_how_to_mock_several_http_calls() {
let mut t = offchain_worker_env(|state| {
state.expect_request(testing::PendingRequest {
method: "POST".into(),
uri: "http://localhost:3001/user/new".into(),
uri: "http://localhost:3001/generate_network_key".into(),
sent: true,
response: Some([].to_vec()),
body: [
Expand All @@ -44,21 +44,22 @@ fn knows_how_to_mock_several_http_calls() {
.to_vec(),
..Default::default()
});

state.expect_request(testing::PendingRequest {
method: "POST".into(),
uri: "http://localhost:3001/user/new".into(),
uri: "http://localhost:3001/generate_network_key".into(),
sent: true,
response: Some([].to_vec()),
body: [
3, 0, 0, 0, 8, 32, 1, 0, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 4, 10, 32, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 11, 32, 4, 0, 0, 0, 0, 0, 0,
0,
3, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 10, 32, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4,
11, 32, 4, 0, 0, 0, 0, 0, 0, 0,
]
.to_vec(),
..Default::default()
});

state.expect_request(testing::PendingRequest {
method: "POST".into(),
uri: "http://localhost:3001/signer/proactive_refresh".into(),
Expand Down Expand Up @@ -97,8 +98,10 @@ fn knows_how_to_mock_several_http_calls() {
.unwrap();
assert_ok!(Registry::register(RuntimeOrigin::signed(1), 2, programs_info.clone(),));
assert_ok!(Registry::register(RuntimeOrigin::signed(2), 3, programs_info,));

// full send
Propagation::post_dkg(4).unwrap();

// test pruning
assert_eq!(Registry::dkg(3).len(), 2);
Propagation::on_initialize(5);
Expand Down
23 changes: 18 additions & 5 deletions pallets/registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@ pub mod pallet {
pub type Registering<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, RegisteringDetails<T>, OptionQuery>;

/// Used for triggering a network wide distributed key generation request via an offchain
/// worker.
#[pallet::storage]
#[pallet::getter(fn jumpstart_dkg)]
pub type JumpstartDkg<T: Config> =
StorageMap<_, Blake2_128Concat, BlockNumberFor<T>, Vec<Vec<u8>>, ValueQuery>;

/// Used to store requests and trigger distributed key generation for users via an offchain
/// worker.
#[pallet::storage]
#[pallet::getter(fn dkg)]
pub type Dkg<T: Config> =
Expand Down Expand Up @@ -284,11 +293,15 @@ pub mod pallet {
},
_ => return Err(Error::<T>::JumpStartProgressNotReady.into()),
};

// TODO (#923): Add checks for network state.
Dkg::<T>::try_mutate(current_block_number, |messages| -> Result<_, DispatchError> {
messages.push(NETWORK_PARENT_KEY.encode());
Ok(())
})?;
JumpstartDkg::<T>::try_mutate(
current_block_number,
|messages| -> Result<_, DispatchError> {
messages.push(NETWORK_PARENT_KEY.encode());
Ok(())
},
)?;
JumpStartProgress::<T>::put(JumpStartDetails {
jump_start_status: JumpStartStatus::InProgress(converted_block_number),
confirmations: vec![],
Expand Down Expand Up @@ -373,7 +386,7 @@ pub mod pallet {
/// network.
#[pallet::call_index(2)]
#[pallet::weight({
<T as Config>::WeightInfo::register( <T as Config>::MaxProgramHashes::get())
<T as Config>::WeightInfo::register(<T as Config>::MaxProgramHashes::get())
})]
pub fn register(
origin: OriginFor<T>,
Expand Down
2 changes: 1 addition & 1 deletion pallets/registry/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn it_jumps_the_network() {
);
assert_ok!(Registry::jump_start_network(RuntimeOrigin::signed(1)));
assert_eq!(
Registry::dkg(0),
Registry::jumpstart_dkg(0),
vec![NETWORK_PARENT_KEY.encode()],
"ensures a dkg message for the jump start network is prepped"
);
Expand Down

0 comments on commit 3c04c96

Please sign in to comment.