make gateway cache master verification keys between requests

This commit is contained in:
Jędrzej Stuczyński
2024-01-26 12:07:51 +00:00
parent 3510ee8df6
commit 991cc3fa01
4 changed files with 130 additions and 41 deletions
@@ -226,4 +226,8 @@ impl EpochState {
pub fn is_final(&self) -> bool {
*self == EpochState::InProgress
}
pub fn is_in_progress(&self) -> bool {
matches!(self, EpochState::InProgress)
}
}
@@ -229,26 +229,11 @@ where
iv,
)?;
// Get the latest coconut signers and their VK
let credential_api_clients = self
let aggregated_verification_key = self
.inner
.coconut_verifier
.all_coconut_api_clients(*credential.epoch_id())
.verification_key(*credential.epoch_id())
.await?;
let current_api_clients = self
.inner
.coconut_verifier
.all_current_coconut_api_clients()
.await?;
if credential_api_clients.is_empty() || current_api_clients.is_empty() {
return Err(RequestHandlingError::NotEnoughNymAPIs {
received: 0,
needed: 1,
});
}
let aggregated_verification_key =
nym_credentials::obtain_aggregate_verification_key(&credential_api_clients).await?;
if !credential.verify(&aggregated_verification_key) {
return Err(RequestHandlingError::InvalidBandwidthCredential(
@@ -256,9 +241,15 @@ where
));
}
let api_clients = self
.inner
.coconut_verifier
.api_clients(*credential.epoch_id())
.await?;
self.inner
.coconut_verifier
.release_funds(current_api_clients, &credential)
.release_funds(&api_clients, &credential)
.await?;
let bandwidth = Bandwidth::from(credential);
@@ -1,57 +1,151 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use super::authenticated::RequestHandlingError;
use log::*;
use nym_coconut_interface::Credential;
use nym_coconut_interface::{Credential, VerificationKey};
use nym_validator_client::coconut::all_coconut_api_clients;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::MultisigQueryClient;
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::{
nyxd::{
contract_traits::{
CoconutBandwidthSigningClient, DkgQueryClient, MultisigQueryClient,
MultisigSigningClient,
},
contract_traits::{CoconutBandwidthSigningClient, DkgQueryClient, MultisigSigningClient},
cosmwasm_client::logs::{find_attribute, BANDWIDTH_PROPOSAL_ID},
Coin,
},
CoconutApiClient, DirectSigningHttpRpcNyxdClient,
};
use std::collections::HashMap;
use std::ops::Deref;
use tokio::sync::RwLock;
use tokio::sync::{RwLock, RwLockReadGuard};
pub(crate) struct CoconutVerifier {
address: AccountId,
nyxd_client: RwLock<DirectSigningHttpRpcNyxdClient>,
// **CURRENTLY** api client addresses don't change during the epochs
api_clients: RwLock<HashMap<EpochId, Vec<CoconutApiClient>>>,
// keys never change during epochs
master_keys: RwLock<HashMap<EpochId, VerificationKey>>,
mix_denom_base: String,
}
impl CoconutVerifier {
pub async fn new(nyxd_client: DirectSigningHttpRpcNyxdClient) -> Self {
pub async fn new(
nyxd_client: DirectSigningHttpRpcNyxdClient,
) -> Result<Self, RequestHandlingError> {
let mix_denom_base = nyxd_client.current_chain_details().mix_denom.base.clone();
let address = nyxd_client.address();
CoconutVerifier {
let mut master_keys = HashMap::new();
let mut api_clients = HashMap::new();
// might as well obtain the key for the current epoch, if applicable
let current_epoch = nyxd_client.get_current_epoch().await?;
if current_epoch.state.is_in_progress() {
// note: even though we're constructing clients here, we will NOT be making any network requests
let epoch_api_clients =
all_coconut_api_clients(&nyxd_client, current_epoch.epoch_id).await?;
let threshold = nyxd_client.get_current_epoch_threshold().await?;
// SAFETY:
// if epoch state is in the 'in progress' state, it means the threshold value MUST HAVE
// been established. if it wasn't, there's an underlying issue with the DKG contract in which
// case we shouldn't continue anyway because here be dragons
#[allow(clippy::expect_used)]
let threshold = threshold.expect("unavailable threshold value") as usize;
if epoch_api_clients.len() < threshold {
return Err(RequestHandlingError::NotEnoughNymAPIs {
received: epoch_api_clients.len(),
needed: threshold,
});
}
let aggregated_verification_key =
nym_credentials::obtain_aggregate_verification_key(&epoch_api_clients).await?;
api_clients.insert(current_epoch.epoch_id, epoch_api_clients);
master_keys.insert(current_epoch.epoch_id, aggregated_verification_key);
}
Ok(CoconutVerifier {
address,
nyxd_client: RwLock::new(nyxd_client),
api_clients: RwLock::new(api_clients),
master_keys: RwLock::new(master_keys),
mix_denom_base,
}
})
}
pub async fn all_current_coconut_api_clients(
pub async fn api_clients(
&self,
) -> Result<Vec<CoconutApiClient>, RequestHandlingError> {
let epoch_id = self
.nyxd_client
.read()
.await
.get_current_epoch()
.await?
.epoch_id;
self.all_coconut_api_clients(epoch_id).await
epoch_id: EpochId,
) -> Result<RwLockReadGuard<Vec<CoconutApiClient>>, RequestHandlingError> {
let guard = self.api_clients.read().await;
// the key was already in the map
if let Ok(mapped) = RwLockReadGuard::try_map(guard, |clients| clients.get(&epoch_id)) {
return Ok(mapped);
}
let api_clients = self.query_api_clients(epoch_id).await?;
// EDGE CASE:
// if this epoch is from the past, we can't query for its threshold
// we can only hope that enough valid keys were submitted
// the best we can do is check if we have at least a api
if api_clients.is_empty() {
return Err(RequestHandlingError::NotEnoughNymAPIs {
received: 0,
needed: 1,
});
}
let mut guard = self.api_clients.write().await;
guard.insert(epoch_id, api_clients);
let guard = guard.downgrade();
// SAFETY:
// we just inserted the entry into the map while NEVER dropping the lock (only downgraded it)
// so it MUST exist and thus the unwrap is fine
#[allow(clippy::unwrap_used)]
Ok(RwLockReadGuard::map(guard, |clients| {
clients.get(&epoch_id).unwrap()
}))
}
pub async fn all_coconut_api_clients(
pub async fn verification_key(
&self,
epoch_id: EpochId,
) -> Result<RwLockReadGuard<VerificationKey>, RequestHandlingError> {
let guard = self.master_keys.read().await;
// the key was already in the map
if let Ok(mapped) = RwLockReadGuard::try_map(guard, |keys| keys.get(&epoch_id)) {
return Ok(mapped);
}
let api_clients = self.api_clients(epoch_id).await?;
let mut guard = self.master_keys.write().await;
let aggregated_verification_key =
nym_credentials::obtain_aggregate_verification_key(&api_clients).await?;
guard.insert(epoch_id, aggregated_verification_key);
let guard = guard.downgrade();
// SAFETY:
// we just inserted the entry into the map while NEVER dropping the lock (only downgraded it)
// so it MUST exist and thus the unwrap is fine
#[allow(clippy::unwrap_used)]
Ok(RwLockReadGuard::map(guard, |keys| {
keys.get(&epoch_id).unwrap()
}))
}
pub async fn query_api_clients(
&self,
epoch_id: u64,
) -> Result<Vec<CoconutApiClient>, RequestHandlingError> {
@@ -60,7 +154,7 @@ impl CoconutVerifier {
pub async fn release_funds(
&self,
api_clients: Vec<CoconutApiClient>,
api_clients: &[CoconutApiClient],
credential: &Credential,
) -> Result<(), RequestHandlingError> {
let res = self
+1 -1
View File
@@ -457,7 +457,7 @@ impl<St> Gateway<St> {
let coconut_verifier = {
let nyxd_client = self.random_nyxd_client()?;
CoconutVerifier::new(nyxd_client).await
};
}?;
let mix_forwarding_channel =
self.start_packet_forwarder(shutdown.subscribe().named("PacketForwarder"));