Compare commits

...

6 Commits

Author SHA1 Message Date
Jędrzej Stuczyński 58b808602a I hate DKG 2025-10-22 09:24:58 +01:00
benedetta davico 85030069f1 comment out migration 2025-10-22 09:08:11 +01:00
Jędrzej Stuczyński df512d6d3a sandbox/canary inconsistent state fix 2025-10-22 09:08:11 +01:00
Jędrzej Stuczyński e30576eed3 bugfix: update internal owner address in transferred share 2025-10-22 08:58:01 +01:00
Jędrzej Stuczyński 9f9639950b feat: expose more explicit new_with_fronted_urls builder for http API client (#6136) 2025-10-21 14:47:58 +01:00
Jędrzej Stuczyński 111a0b20b6 bugfix: update stored epoch share when changing ownership (#6135) 2025-10-21 14:10:20 +01:00
9 changed files with 98 additions and 24 deletions
+1 -1
View File
@@ -151,7 +151,7 @@ pub async fn gateways_for_init(
} }
let retry_count = retry_count.unwrap_or(DEFAULT_NYM_API_RETRIES); let retry_count = retry_count.unwrap_or(DEFAULT_NYM_API_RETRIES);
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone()) let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())?
.with_retries(retry_count) .with_retries(retry_count)
.with_bincode(); .with_bincode();
@@ -84,6 +84,10 @@ pub enum ExecuteMsg {
UpdateAnnounceAddress { UpdateAnnounceAddress {
new_address: String, new_address: String,
}, },
FixOldShare {
old_owner: String,
},
} }
#[cw_serde] #[cw_serde]
+32 -16
View File
@@ -296,6 +296,9 @@ impl std::error::Error for ReqwestErrorWrapper {}
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum HttpClientError { pub enum HttpClientError {
#[error("did not provide any valid client URLs")]
NoUrlsProvided,
#[error("failed to construct inner reqwest client: {source}")] #[error("failed to construct inner reqwest client: {source}")]
ReqwestBuildError { ReqwestBuildError {
#[source] #[source]
@@ -582,24 +585,29 @@ impl ClientBuilder {
Self::new(alt) Self::new(alt)
} else { } else {
let url = url.to_url()?; let url = url.to_url()?;
Ok(Self::new_with_urls(vec![url])) Self::new_with_urls(vec![url])
} }
} }
/// Create a client builder from network details with sensible defaults /// Create a client builder from network details with sensible defaults
#[cfg(feature = "network-defaults")] #[cfg(feature = "network-defaults")]
// deprecating function since it's not clear from its signature whether the client
// would be constructed using `nym_api_urls` or `nym_vpn_api_urls`
#[deprecated(note = "use explicit Self::new_with_fronted_urls instead")]
pub fn from_network( pub fn from_network(
network: &nym_network_defaults::NymNetworkDetails, network: &nym_network_defaults::NymNetworkDetails,
) -> Result<Self, HttpClientError> { ) -> Result<Self, HttpClientError> {
let urls = network let urls = network.nym_api_urls.as_ref().cloned().unwrap_or_default();
.nym_api_urls Self::new_with_fronted_urls(urls.clone())
.as_ref() }
.ok_or_else(|| {
HttpClientError::GenericRequestFailure( /// Create a client builder using the provided set of domain-fronted URLs
"No API URLs configured in network details".to_string(), #[cfg(feature = "network-defaults")]
) pub fn new_with_fronted_urls(
})? urls: Vec<nym_network_defaults::ApiUrl>,
.iter() ) -> Result<Self, HttpClientError> {
let urls = urls
.into_iter()
.map(|api_url| { .map(|api_url| {
// Convert ApiUrl to our Url type with fronting support // Convert ApiUrl to our Url type with fronting support
let mut url = Url::parse(&api_url.url)?; let mut url = Url::parse(&api_url.url)?;
@@ -611,15 +619,19 @@ impl ClientBuilder {
.iter() .iter()
.map(|host| format!("https://{}", host)) .map(|host| format!("https://{}", host))
.collect(); .collect();
url = Url::new(api_url.url.clone(), Some(fronts)) url = Url::new(api_url.url.clone(), Some(fronts)).map_err(|source| {
.map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?; HttpClientError::MalformedUrl {
raw: api_url.url.clone(),
source,
}
})?;
} }
Ok(url) Ok(url)
}) })
.collect::<Result<Vec<_>, HttpClientError>>()?; .collect::<Result<Vec<_>, HttpClientError>>()?;
let mut builder = Self::new_with_urls(urls); let mut builder = Self::new_with_urls(urls)?;
// Enable domain fronting by default (on retry) // Enable domain fronting by default (on retry)
#[cfg(feature = "tunneling")] #[cfg(feature = "tunneling")]
@@ -631,7 +643,11 @@ impl ClientBuilder {
} }
/// Constructs a new http `ClientBuilder` from a valid url. /// Constructs a new http `ClientBuilder` from a valid url.
pub fn new_with_urls(urls: Vec<Url>) -> Self { pub fn new_with_urls(urls: Vec<Url>) -> Result<Self, HttpClientError> {
if urls.is_empty() {
return Err(HttpClientError::NoUrlsProvided);
}
let urls = Self::check_urls(urls); let urls = Self::check_urls(urls);
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@@ -640,7 +656,7 @@ impl ClientBuilder {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = default_builder(); let reqwest_client_builder = default_builder();
ClientBuilder { Ok(ClientBuilder {
urls, urls,
timeout: None, timeout: None,
custom_user_agent: false, custom_user_agent: false,
@@ -651,7 +667,7 @@ impl ClientBuilder {
retry_limit: 0, retry_limit: 0,
serialization: SerializationFormat::Json, serialization: SerializationFormat::Json,
} })
} }
/// Add an additional URL to the set usable by this constructed `Client` /// Add an additional URL to the set usable by this constructed `Client`
+9 -1
View File
@@ -21,6 +21,10 @@ inventory::collect!(ConfigRecord);
/// Returns the default builder with all registered configurations applied. /// Returns the default builder with all registered configurations applied.
pub fn default_builder() -> ReqwestClientBuilder { pub fn default_builder() -> ReqwestClientBuilder {
let mut b = ReqwestClientBuilder::new(); let mut b = ReqwestClientBuilder::new();
#[cfg(feature = "debug-inventory")]
let mut test_client = ReqwestClientBuilder::new();
let mut records: Vec<&'static ConfigRecord> = let mut records: Vec<&'static ConfigRecord> =
inventory::iter::<ConfigRecord>.into_iter().collect(); inventory::iter::<ConfigRecord>.into_iter().collect();
records.sort_by_key(|r| r.priority); // lower runs first records.sort_by_key(|r| r.priority); // lower runs first
@@ -35,6 +39,10 @@ pub fn default_builder() -> ReqwestClientBuilder {
for r in records { for r in records {
b = (r.apply)(b); b = (r.apply)(b);
#[cfg(feature = "debug-inventory")]
{
test_client = (r.apply)(test_client);
}
} }
#[cfg(feature = "debug-inventory")] #[cfg(feature = "debug-inventory")]
@@ -47,7 +55,7 @@ pub fn default_builder() -> ReqwestClientBuilder {
eprintln!("[HTTP-INVENTORY] Building test client to verify configuration..."); eprintln!("[HTTP-INVENTORY] Building test client to verify configuration...");
// Try to build a client to see if it works // Try to build a client to see if it works
match b.try_clone().unwrap().build() { match test_client.build() {
Ok(client) => { Ok(client) => {
eprintln!("[HTTP-INVENTORY] ✓ Client built successfully"); eprintln!("[HTTP-INVENTORY] ✓ Client built successfully");
eprintln!("[HTTP-INVENTORY] Client debug info: {:#?}", client); eprintln!("[HTTP-INVENTORY] Client debug info: {:#?}", client);
+1 -1
View File
@@ -93,7 +93,7 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
let client = ClientBuilder::new_with_urls(vec![ let client = ClientBuilder::new_with_urls(vec![
"http://broken.nym.badurl".parse()?, "http://broken.nym.badurl".parse()?,
"http://example.com/".parse()?, "http://example.com/".parse()?,
]) ])?
.with_retries(3) .with_retries(3)
.build()?; .build()?;
+35 -2
View File
@@ -6,6 +6,7 @@ use crate::dealers::queries::{
query_epoch_dealers_addresses_paged, query_epoch_dealers_paged, query_epoch_dealers_addresses_paged, query_epoch_dealers_paged,
query_registered_dealer_details, query_registered_dealer_details,
}; };
use crate::dealers::storage::{ensure_dealer, EPOCH_DEALERS_MAP};
use crate::dealers::transactions::{ use crate::dealers::transactions::{
try_add_dealer, try_transfer_ownership, try_update_announce_address, try_add_dealer, try_transfer_ownership, try_update_announce_address,
}; };
@@ -18,14 +19,16 @@ use crate::epoch_state::queries::{
query_can_advance_state, query_current_epoch, query_current_epoch_threshold, query_can_advance_state, query_current_epoch, query_current_epoch_threshold,
query_epoch_at_height, query_epoch_threshold, query_epoch_at_height, query_epoch_threshold,
}; };
use crate::epoch_state::storage::save_epoch; use crate::epoch_state::storage::{load_current_epoch, save_epoch};
use crate::epoch_state::transactions::{ use crate::epoch_state::transactions::{
try_advance_epoch_state, try_initiate_dkg, try_trigger_reset, try_trigger_resharing, try_advance_epoch_state, try_initiate_dkg, try_trigger_reset, try_trigger_resharing,
}; };
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError; use crate::error::ContractError;
use crate::state::queries::query_state; use crate::state::queries::query_state;
use crate::state::storage::{DKG_ADMIN, MULTISIG, STATE}; use crate::state::storage::{DKG_ADMIN, MULTISIG, STATE};
use crate::verification_key_shares::queries::{query_vk_share, query_vk_shares_paged}; use crate::verification_key_shares::queries::{query_vk_share, query_vk_shares_paged};
use crate::verification_key_shares::storage::vk_shares;
use crate::verification_key_shares::transactions::try_commit_verification_key_share; use crate::verification_key_shares::transactions::try_commit_verification_key_share;
use crate::verification_key_shares::transactions::try_verify_verification_key_share; use crate::verification_key_shares::transactions::try_verify_verification_key_share;
use cosmwasm_std::{ use cosmwasm_std::{
@@ -134,6 +137,36 @@ pub fn execute(
ExecuteMsg::UpdateAnnounceAddress { new_address } => { ExecuteMsg::UpdateAnnounceAddress { new_address } => {
try_update_announce_address(deps, info, new_address) try_update_announce_address(deps, info, new_address)
} }
ExecuteMsg::FixOldShare { old_owner } => {
let old_owner = deps.api.addr_validate(&old_owner)?;
let epoch = load_current_epoch(deps.storage)?;
// make sure we're not mid-exchange
check_epoch_state(deps.storage, EpochState::InProgress)?;
// make sure the requester is actually a dealer for this epoch
ensure_dealer(deps.storage, &info.sender, epoch.epoch_id)?;
for epoch_id in 0..=epoch.epoch_id {
if let Some(mut vk_share) =
vk_shares().may_load(deps.storage, (&old_owner, epoch_id))?
{
vk_share.owner = info.sender.clone();
vk_shares().remove(deps.storage, (&old_owner, epoch_id))?;
vk_shares().save(deps.storage, (&info.sender, epoch_id), &vk_share)?;
}
if let Some(mut vk_share) =
vk_shares().may_load(deps.storage, (&info.sender, epoch_id))?
{
vk_share.owner = info.sender.clone();
vk_shares().save(deps.storage, (&info.sender, epoch_id), &vk_share)?;
}
}
Ok(Response::new())
}
} }
} }
@@ -259,7 +292,7 @@ pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result<Response
set_build_information!(deps.storage)?; set_build_information!(deps.storage)?;
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
crate::queued_migrations::introduce_historical_epochs(deps, env)?; // crate::queued_migrations::introduce_historical_epochs(deps, env)?;
Ok(Response::new()) Ok(Response::new())
} }
@@ -110,7 +110,7 @@ pub fn try_transfer_ownership(
DEALERS_INDICES.save(deps.storage, &transfer_to, &current_index)?; DEALERS_INDICES.save(deps.storage, &transfer_to, &current_index)?;
DEALERS_INDICES.remove(deps.storage, &info.sender); DEALERS_INDICES.remove(deps.storage, &info.sender);
// update registration detail for every epoch the current dealer has participated in the protocol // update registration detail and share information for every epoch the current dealer has participated in the protocol
// ideally, we'd have only updated the current epoch, but the way the contract is constructed // ideally, we'd have only updated the current epoch, but the way the contract is constructed
// forbids that otherwise we'd have introduced inconsistency // forbids that otherwise we'd have introduced inconsistency
for epoch_id in 0..=epoch.epoch_id { for epoch_id in 0..=epoch.epoch_id {
@@ -118,6 +118,11 @@ pub fn try_transfer_ownership(
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &info.sender)); EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &info.sender));
EPOCH_DEALERS_MAP.save(deps.storage, (epoch_id, &transfer_to), &details)?; EPOCH_DEALERS_MAP.save(deps.storage, (epoch_id, &transfer_to), &details)?;
} }
if let Some(mut vk_share) = vk_shares().may_load(deps.storage, (&info.sender, epoch_id))? {
vk_shares().remove(deps.storage, (&info.sender, epoch_id))?;
vk_share.owner = transfer_to.clone();
vk_shares().save(deps.storage, (&transfer_to, epoch_id), &vk_share)?;
}
} }
let Some(transaction_info) = env.transaction else { let Some(transaction_info) = env.transaction else {
@@ -262,6 +267,7 @@ mod tests_with_mock {
contract.run_initial_dummy_dkg(); contract.run_initial_dummy_dkg();
let old_index = DEALERS_INDICES.load(&contract, &group_member)?; let old_index = DEALERS_INDICES.load(&contract, &group_member)?;
let old_details = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?; let old_details = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?;
let old_share = vk_shares().load(&contract, (&group_member, 0))?;
let not_group_member = contract.addr_make("not_group_member"); let not_group_member = contract.addr_make("not_group_member");
let (deps, env) = contract.deps_mut_env(); let (deps, env) = contract.deps_mut_env();
@@ -291,13 +297,20 @@ mod tests_with_mock {
assert!(EPOCH_DEALERS_MAP assert!(EPOCH_DEALERS_MAP
.may_load(&contract, (0, &group_member))? .may_load(&contract, (0, &group_member))?
.is_none()); .is_none());
assert!(vk_shares()
.may_load(&contract, (&group_member, 0))?
.is_none());
let new_index = DEALERS_INDICES.load(&contract, &new_group_member)?; let new_index = DEALERS_INDICES.load(&contract, &new_group_member)?;
let new_details = EPOCH_DEALERS_MAP.load(&contract, (0, &new_group_member))?; let new_details = EPOCH_DEALERS_MAP.load(&contract, (0, &new_group_member))?;
let new_share = vk_shares().load(&contract, (&new_group_member, 0))?;
// the underlying info hasn't changed // the underlying info hasn't changed
assert_eq!(old_index, new_index); assert_eq!(old_index, new_index);
assert_eq!(old_details, new_details); assert_eq!(old_details, new_details);
assert_ne!(old_share, new_share);
assert_eq!(old_share.owner, group_member);
assert_eq!(new_share.owner, new_group_member);
assert_eq!( assert_eq!(
OWNERSHIP_TRANSFER_LOG.load( OWNERSHIP_TRANSFER_LOG.load(
@@ -57,7 +57,7 @@ async fn run(
.clone() .clone()
.expect("rust sdk mainnet default missing api_url"); .expect("rust sdk mainnet default missing api_url");
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()]) let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])?
.no_hickory_dns() .no_hickory_dns()
.with_timeout(nym_api_client_timeout) .with_timeout(nym_api_client_timeout)
.build()?; .build()?;
@@ -98,7 +98,7 @@ impl Monitor {
.expect("rust sdk mainnet default missing api_url"); .expect("rust sdk mainnet default missing api_url");
let nym_api = let nym_api =
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()]) nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])?
.no_hickory_dns() .no_hickory_dns()
.with_timeout(self.nym_api_client_timeout) .with_timeout(self.nym_api_client_timeout)
.build()?; .build()?;