Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 867acfef26 | |||
| e4996dc0ce | |||
| a6e23a210b | |||
| 88c4e0ce6c | |||
| 5a817e1df1 | |||
| a07a24db00 | |||
| a0cb812eff | |||
| 923c1fa184 | |||
| 35ea7e4926 | |||
| d1cb9afaf0 | |||
| 79d4b4b2e3 | |||
| 8460b33946 | |||
| ae6539e07c | |||
| 18cebdfedc | |||
| c448ec823a | |||
| a266137278 | |||
| 6f4dfd1dab | |||
| 57719787db | |||
| 29a57bf172 | |||
| db813b6e3e | |||
| 1be5ba310a |
@@ -54,7 +54,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --lib --manifest-path contracts/Cargo.toml
|
||||
args: --lib --manifest-path contracts/Cargo.toml --all-features
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
Generated
-1
@@ -6790,7 +6790,6 @@ dependencies = [
|
||||
"nym-bandwidth-controller",
|
||||
"nym-credential-storage",
|
||||
"nym-credentials-interface",
|
||||
"nym-http-api-client",
|
||||
"nym-ip-packet-client",
|
||||
"nym-registration-common",
|
||||
"nym-sdk",
|
||||
|
||||
@@ -119,6 +119,7 @@ where
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -178,6 +178,7 @@ where
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -218,7 +218,6 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
shutdown: Option<ShutdownTracker>,
|
||||
event_tx: Option<EventSender>,
|
||||
user_agent: Option<UserAgent>,
|
||||
custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
|
||||
@@ -248,7 +247,6 @@ where
|
||||
shutdown: None,
|
||||
event_tx: None,
|
||||
user_agent: None,
|
||||
custom_nym_api_client: None,
|
||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
@@ -256,12 +254,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_nym_api_client(mut self, client: nym_http_api_client::Client) -> Self {
|
||||
self.custom_nym_api_client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_derivation_material(
|
||||
mut self,
|
||||
@@ -873,17 +865,7 @@ where
|
||||
fn construct_nym_api_client(
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
custom_client: Option<nym_http_api_client::Client>,
|
||||
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
||||
// If a custom client was provided (e.g., with domain fronting support), use it
|
||||
if let Some(client) = custom_client {
|
||||
tracing::debug!("Using custom nym-api HTTP client");
|
||||
return Ok(client);
|
||||
}
|
||||
|
||||
tracing::debug!("Creating default nym-api HTTP client from config");
|
||||
|
||||
// Otherwise, create a basic client
|
||||
let mut nym_api_urls = config.get_nym_api_endpoints();
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
@@ -979,11 +961,7 @@ where
|
||||
.dkg_query_client
|
||||
.map(|client| BandwidthController::new(credential_store, client));
|
||||
|
||||
let nym_api_client = Self::construct_nym_api_client(
|
||||
&self.config,
|
||||
self.user_agent.clone(),
|
||||
self.custom_nym_api_client,
|
||||
)?;
|
||||
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone())?;
|
||||
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
|
||||
|
||||
let topology_provider = Self::setup_topology_provider(
|
||||
|
||||
@@ -45,6 +45,7 @@ type WsConn = JSWebsocket;
|
||||
|
||||
const CONCURRENT_GATEWAYS_MEASURED: usize = 20;
|
||||
const MEASUREMENTS: usize = 3;
|
||||
const DEFAULT_NYM_API_RETRIES: usize = 3;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
@@ -89,22 +90,16 @@ async fn get_all_basic_entry_nodes_with_metadata(
|
||||
client: &nym_http_api_client::Client,
|
||||
use_bincode: bool,
|
||||
) -> Result<SkimmedNodesWithMetadata, ClientCoreError> {
|
||||
// Get ALL nodes (not just entry-assigned) because in some environments (like sandbox),
|
||||
// nodes may be capable of entry gateway role but not currently assigned to it
|
||||
// Get first page to obtain metadata
|
||||
let mut page = 0;
|
||||
let res = client
|
||||
.get_basic_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
// Filter for entry-capable nodes (nodes with supported_roles.entry == true)
|
||||
let entry_nodes: Vec<_> = nodes
|
||||
.into_iter()
|
||||
.filter(|n| n.supported_roles.entry)
|
||||
.collect();
|
||||
return Ok(SkimmedNodesWithMetadata::new(entry_nodes, metadata));
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
@@ -112,7 +107,7 @@ async fn get_all_basic_entry_nodes_with_metadata(
|
||||
// Collect remaining pages
|
||||
loop {
|
||||
let mut res = client
|
||||
.get_basic_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
@@ -129,13 +124,7 @@ async fn get_all_basic_entry_nodes_with_metadata(
|
||||
}
|
||||
}
|
||||
|
||||
// Filter for entry-capable nodes (nodes with supported_roles.entry == true)
|
||||
let entry_nodes: Vec<_> = nodes
|
||||
.into_iter()
|
||||
.filter(|n| n.supported_roles.entry)
|
||||
.collect();
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(entry_nodes, metadata))
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
@@ -149,19 +138,21 @@ pub async fn gateways_for_init(
|
||||
user_agent: Option<UserAgent>,
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
retry_count: Option<usize>,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
// Build client with ALL URLs for fallback support
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = nym_apis
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
if nym_api_urls.is_empty() {
|
||||
return Err(ClientCoreError::ListOfNymApisIsEmpty);
|
||||
}
|
||||
|
||||
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())
|
||||
.with_retries(3)
|
||||
.with_retries(retry_count)
|
||||
.with_bincode();
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
@@ -442,7 +433,7 @@ mod tests {
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(nym_api_urls.len(), 1, "Should have exactly one URL");
|
||||
@@ -458,7 +449,7 @@ mod tests {
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(nym_api_urls.len(), 3, "Should have all three URLs");
|
||||
@@ -474,118 +465,9 @@ mod tests {
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert!(nym_api_urls.is_empty(), "Empty list should remain empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gateway_filtering_logic() {
|
||||
// NOTE: This test validates the filtering logic in isolation.
|
||||
// It does NOT test the actual implementation in get_all_basic_entry_nodes_with_metadata
|
||||
// or gateways_for_init (which would require mocking the HTTP client).
|
||||
// The real proof is building and running the daemon.
|
||||
//
|
||||
// Test the core filtering logic used in gateways_for_init:
|
||||
// 1. Filter by supported_roles.entry (not by epoch role assignment)
|
||||
// 2. Filter by performance
|
||||
//
|
||||
// This test verifies the fix where nodes have role=Inactive
|
||||
// but supported_roles.entry=true
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestNode {
|
||||
id: u32,
|
||||
entry_capable: bool, // supported_roles.entry
|
||||
mixnode_capable: bool, // supported_roles.mixnode
|
||||
performance: u8, // 0-100
|
||||
}
|
||||
|
||||
let nodes = [
|
||||
// Node 53: entry-capable, good performance
|
||||
TestNode {
|
||||
id: 53,
|
||||
entry_capable: true,
|
||||
mixnode_capable: false,
|
||||
performance: 100,
|
||||
},
|
||||
// Node 97: entry-capable (but role=Inactive in sandbox), good performance
|
||||
TestNode {
|
||||
id: 97,
|
||||
entry_capable: true,
|
||||
mixnode_capable: false,
|
||||
performance: 100,
|
||||
},
|
||||
// Node 75: NOT entry-capable (mixnode only)
|
||||
TestNode {
|
||||
id: 75,
|
||||
entry_capable: false,
|
||||
mixnode_capable: true,
|
||||
performance: 100,
|
||||
},
|
||||
// Node 99: entry-capable but low performance
|
||||
TestNode {
|
||||
id: 99,
|
||||
entry_capable: true,
|
||||
mixnode_capable: false,
|
||||
performance: 0,
|
||||
},
|
||||
];
|
||||
|
||||
let minimum_performance = 50;
|
||||
let ignore_epoch_roles = true;
|
||||
|
||||
// Step 1: Filter by supported_roles.entry (this is what the fix enables)
|
||||
let entry_capable: Vec<_> = nodes.iter().filter(|n| n.entry_capable).collect();
|
||||
|
||||
assert_eq!(
|
||||
entry_capable.len(),
|
||||
3,
|
||||
"Should have 3 entry-capable nodes (53, 97, 99) - this includes Inactive nodes!"
|
||||
);
|
||||
|
||||
// Step 2: Filter by role (exclude mixnode-capable if not ignoring epoch roles)
|
||||
let after_role_filter: Vec<_> = entry_capable
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.mixnode_capable)
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
after_role_filter.len(),
|
||||
3,
|
||||
"All entry-capable nodes pass role filter with ignore_epoch_roles=true"
|
||||
);
|
||||
|
||||
// Step 3: Filter by performance
|
||||
let after_performance_filter: Vec<_> = after_role_filter
|
||||
.iter()
|
||||
.filter(|g| g.performance >= minimum_performance)
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
after_performance_filter.len(),
|
||||
2,
|
||||
"Should have 2 nodes after performance filter (53 and 97, excluding 99 with 0%)"
|
||||
);
|
||||
|
||||
// Verify the correct nodes made it through
|
||||
let node_ids: Vec<u32> = after_performance_filter.iter().map(|n| n.id).collect();
|
||||
assert!(
|
||||
node_ids.contains(&53),
|
||||
"Node 53 (actively assigned entry gateway) should be included"
|
||||
);
|
||||
assert!(
|
||||
node_ids.contains(&97),
|
||||
"Node 97 (Inactive but entry-capable) should be included - THIS IS THE FIX!"
|
||||
);
|
||||
assert!(
|
||||
!node_ids.contains(&75),
|
||||
"Node 75 (mixnode-only, not entry-capable) should be excluded"
|
||||
);
|
||||
assert!(
|
||||
!node_ids.contains(&99),
|
||||
"Node 99 (low performance) should be excluded"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,11 @@ impl Url {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the underlying URL
|
||||
pub fn inner_url(&self) -> &url::Url {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Returns true if the URL has a front domain set
|
||||
pub fn has_front(&self) -> bool {
|
||||
if let Some(fronts) = &self.fronts {
|
||||
@@ -201,6 +206,11 @@ impl Url {
|
||||
.and_then(|url| url.host_str())
|
||||
}
|
||||
|
||||
/// Returns the fronts
|
||||
pub fn fronts(&self) -> Option<&[url::Url]> {
|
||||
self.fronts.as_deref()
|
||||
}
|
||||
|
||||
/// Return the string representation of the host (domain or IP address) for this URL, if any.
|
||||
pub fn host_str(&self) -> Option<&str> {
|
||||
self.url.host_str()
|
||||
|
||||
@@ -160,8 +160,14 @@ pub async fn setup_gateway_from_api(
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let gateways =
|
||||
gateways_for_init(nym_apis, None, minimum_performance, ignore_epoch_roles).await?;
|
||||
let gateways = gateways_for_init(
|
||||
nym_apis,
|
||||
None,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
||||
}
|
||||
|
||||
@@ -176,6 +182,7 @@ pub async fn current_gateways_wasm(
|
||||
user_agent,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result<Response
|
||||
set_build_information!(deps.storage)?;
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::epoch_state::storage::{load_current_epoch, save_epoch};
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::STATE;
|
||||
use crate::verification_key_shares::storage::vk_shares;
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::{Deps, DepsMut, Env, Event, MessageInfo, Response};
|
||||
use nym_coconut_dkg_common::dealer::{DealerRegistrationDetails, OwnershipTransfer};
|
||||
@@ -109,7 +110,7 @@ pub fn try_transfer_ownership(
|
||||
DEALERS_INDICES.save(deps.storage, &transfer_to, ¤t_index)?;
|
||||
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
|
||||
// forbids that otherwise we'd have introduced inconsistency
|
||||
for epoch_id in 0..=epoch.epoch_id {
|
||||
@@ -117,6 +118,11 @@ pub fn try_transfer_ownership(
|
||||
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &info.sender));
|
||||
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 {
|
||||
@@ -161,6 +167,14 @@ pub fn try_update_announce_address(
|
||||
details.announce_address = new_address.clone();
|
||||
EPOCH_DEALERS_MAP.save(deps.storage, (epoch.epoch_id, &info.sender), &details)?;
|
||||
|
||||
let mut contract_share = vk_shares().load(deps.storage, (&info.sender, epoch.epoch_id))?;
|
||||
contract_share.announce_address = new_address.clone();
|
||||
vk_shares().save(
|
||||
deps.storage,
|
||||
(&info.sender, epoch.epoch_id),
|
||||
&contract_share,
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(
|
||||
Event::new("dkg-announce-address-update")
|
||||
.add_attribute("dealer", info.sender)
|
||||
@@ -228,9 +242,14 @@ pub(crate) mod tests {
|
||||
#[cfg(feature = "testable-dkg-contract")]
|
||||
mod tests_with_mock {
|
||||
use super::*;
|
||||
use crate::testable_dkg_contract::{init_contract_tester, DkgContractTesterExt};
|
||||
use crate::testable_dkg_contract::{
|
||||
init_contract_tester, init_contract_tester_with_group_members, DkgContractTesterExt,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use cosmwasm_std::testing::message_info;
|
||||
use nym_contracts_common_testing::ContractOpts;
|
||||
use nym_coconut_dkg_common::msg::QueryMsg;
|
||||
use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse;
|
||||
use nym_contracts_common_testing::{ChainOpts, ContractOpts};
|
||||
|
||||
#[test]
|
||||
fn transferring_ownership() -> anyhow::Result<()> {
|
||||
@@ -248,6 +267,7 @@ mod tests_with_mock {
|
||||
contract.run_initial_dummy_dkg();
|
||||
let old_index = DEALERS_INDICES.load(&contract, &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 (deps, env) = contract.deps_mut_env();
|
||||
@@ -277,13 +297,20 @@ mod tests_with_mock {
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (0, &group_member))?
|
||||
.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_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
|
||||
assert_eq!(old_index, new_index);
|
||||
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!(
|
||||
OWNERSHIP_TRANSFER_LOG.load(
|
||||
@@ -436,9 +463,91 @@ mod tests_with_mock {
|
||||
assert_eq!(old_details1, new_details1);
|
||||
assert_eq!(old_details2, new_details2);
|
||||
|
||||
// most recent entry is updated
|
||||
// most recent entry is updated
|
||||
assert_eq!(new_details3.announce_address, new_address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_announce_address_updates_vk_shares() -> anyhow::Result<()> {
|
||||
let mut contract = init_contract_tester_with_group_members(3);
|
||||
let group_member = contract.random_group_member();
|
||||
|
||||
contract.run_initial_dummy_dkg(); // => epoch 0
|
||||
contract.run_reset_dkg(); // => epoch 1
|
||||
|
||||
// LEAVE DKG MEMBERSHIP
|
||||
contract.remove_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 2
|
||||
|
||||
// COME BACK
|
||||
contract.add_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 3
|
||||
|
||||
let old_address = EPOCH_DEALERS_MAP
|
||||
.load(&contract, (3, &group_member))?
|
||||
.announce_address;
|
||||
|
||||
let old_share0 = vk_shares().load(&contract, (&group_member, 0))?;
|
||||
let old_share1 = vk_shares().load(&contract, (&group_member, 1))?;
|
||||
let old_share2 = vk_shares().may_load(&contract, (&group_member, 2))?;
|
||||
assert!(old_share2.is_none());
|
||||
let old_share3 = vk_shares().may_load(&contract, (&group_member, 3))?;
|
||||
assert!(old_share3.is_some());
|
||||
|
||||
let new_address = "https://new-address.com".to_string();
|
||||
try_update_announce_address(
|
||||
contract.deps_mut(),
|
||||
message_info(&group_member, &[]),
|
||||
new_address.clone(),
|
||||
)?;
|
||||
|
||||
let new_share0 = vk_shares().load(&contract, (&group_member, 0))?;
|
||||
let new_share1 = vk_shares().load(&contract, (&group_member, 1))?;
|
||||
let new_share2 = vk_shares().may_load(&contract, (&group_member, 2))?;
|
||||
assert!(new_share2.is_none());
|
||||
let new_share3 = vk_shares().load(&contract, (&group_member, 3))?;
|
||||
|
||||
// old epoch data is unchanged
|
||||
assert_eq!(old_share0, new_share0);
|
||||
assert_eq!(old_share1, new_share1);
|
||||
assert_eq!(old_share2, new_share2);
|
||||
|
||||
// most recent entry is updated
|
||||
assert_eq!(new_share3.announce_address, new_address);
|
||||
|
||||
// finally an integration check against query endpoint
|
||||
let epoch0_shares: PagedVKSharesResponse =
|
||||
contract.query(&QueryMsg::GetVerificationKeys {
|
||||
epoch_id: 0,
|
||||
limit: None,
|
||||
start_after: None,
|
||||
})?;
|
||||
assert_eq!(epoch0_shares.shares.len(), 3);
|
||||
|
||||
let member_share = epoch0_shares
|
||||
.shares
|
||||
.iter()
|
||||
.find(|s| s.owner == group_member)
|
||||
.context("failed to find member's share")?;
|
||||
assert_eq!(member_share.announce_address, old_address);
|
||||
|
||||
let epoch0_shares: PagedVKSharesResponse =
|
||||
contract.query(&QueryMsg::GetVerificationKeys {
|
||||
epoch_id: 3,
|
||||
limit: None,
|
||||
start_after: None,
|
||||
})?;
|
||||
assert_eq!(epoch0_shares.shares.len(), 3);
|
||||
|
||||
let member_share = epoch0_shares
|
||||
.shares
|
||||
.iter()
|
||||
.find(|s| s.owner == group_member)
|
||||
.context("failed to find member's share")?;
|
||||
assert_eq!(member_share.announce_address, new_address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +62,18 @@ impl TestableNymContract for DkgContract {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
init_contract_tester_with_group_members(DEFAULT_GROUP_MEMBERS)
|
||||
init_contract_tester()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_contract_tester() -> ContractTester<DkgContract> {
|
||||
DkgContract::init().with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
init_contract_tester_with_group_members(DEFAULT_GROUP_MEMBERS)
|
||||
}
|
||||
|
||||
pub fn init_contract_tester_with_group_members(members: usize) -> ContractTester<DkgContract> {
|
||||
prepare_contract_tester_builder_with_group_members(members)
|
||||
.build()
|
||||
.with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
}
|
||||
|
||||
pub fn prepare_contract_tester_builder_with_group_members<C>(
|
||||
@@ -137,12 +143,6 @@ where
|
||||
builder
|
||||
}
|
||||
|
||||
pub fn init_contract_tester_with_group_members(members: usize) -> ContractTester<DkgContract> {
|
||||
prepare_contract_tester_builder_with_group_members(members)
|
||||
.build()
|
||||
.with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
}
|
||||
|
||||
pub trait DkgContractTesterExt:
|
||||
ContractOpts<ExecuteMsg = ExecuteMsg, QueryMsg = QueryMsg, ContractError = ContractError>
|
||||
+ ChainOpts
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn legacy_mixnode_bonding() {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,3 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod transactions;
|
||||
|
||||
// the purpose of that module is to keep track of tests of legacy features that will eventually be phased out
|
||||
// such as standalone mixnode/gateway bonding
|
||||
pub(crate) mod legacy;
|
||||
|
||||
@@ -23,7 +23,6 @@ nym-authenticator-client = { path = "../nym-authenticator-client" }
|
||||
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
|
||||
nym-credential-storage = { path = "../common/credential-storage" }
|
||||
nym-credentials-interface = { path = "../common/credentials-interface" }
|
||||
nym-http-api-client = { path = "../common/http-api-client" }
|
||||
nym-ip-packet-client = { path = "../nym-ip-packet-client" }
|
||||
nym-registration-common = { path = "../common/registration" }
|
||||
nym-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
|
||||
@@ -34,7 +34,6 @@ pub struct BuilderConfig {
|
||||
pub two_hops: bool,
|
||||
pub user_agent: UserAgent,
|
||||
pub custom_topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
pub custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
pub network_env: NymNetworkDetails,
|
||||
pub cancel_token: CancellationToken,
|
||||
#[cfg(unix)]
|
||||
@@ -57,9 +56,9 @@ pub struct MixnetClientConfig {
|
||||
}
|
||||
|
||||
impl BuilderConfig {
|
||||
/// Create a new BuilderConfig without domain fronting support
|
||||
/// Creates a new BuilderConfig with all required parameters.
|
||||
///
|
||||
/// For domain fronting support, set `custom_nym_api_client` to Some(client) after creation
|
||||
/// However, consider using `BuilderConfig::builder()` instead.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
entry_node: NymNodeWithKeys,
|
||||
@@ -81,7 +80,6 @@ impl BuilderConfig {
|
||||
two_hops,
|
||||
user_agent,
|
||||
custom_topology_provider,
|
||||
custom_nym_api_client: None,
|
||||
network_env,
|
||||
cancel_token,
|
||||
#[cfg(unix)]
|
||||
@@ -89,10 +87,20 @@ impl BuilderConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a custom nym-api HTTP client (for domain fronting support)
|
||||
pub fn with_nym_api_client(mut self, client: nym_http_api_client::Client) -> Self {
|
||||
self.custom_nym_api_client = Some(client);
|
||||
self
|
||||
/// Creates a builder for BuilderConfig
|
||||
///
|
||||
/// This is the preferred way to construct a BuilderConfig.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let config = BuilderConfig::builder()
|
||||
/// .entry_node(entry)
|
||||
/// .exit_node(exit)
|
||||
/// .user_agent(agent)
|
||||
/// .build()?;
|
||||
/// ```
|
||||
pub fn builder() -> BuilderConfigBuilder {
|
||||
BuilderConfigBuilder::default()
|
||||
}
|
||||
|
||||
pub fn mixnet_client_debug_config(&self) -> DebugConfig {
|
||||
@@ -147,7 +155,7 @@ impl BuilderConfig {
|
||||
RememberMe::new_mixnet()
|
||||
};
|
||||
|
||||
let mut builder = builder
|
||||
let builder = builder
|
||||
.with_user_agent(self.user_agent)
|
||||
.request_gateway(self.entry_node.node.identity.to_string())
|
||||
.network_details(self.network_env)
|
||||
@@ -156,11 +164,6 @@ impl BuilderConfig {
|
||||
.with_remember_me(remember_me)
|
||||
.custom_topology_provider(self.custom_topology_provider);
|
||||
|
||||
if let Some(nym_api_client) = self.custom_nym_api_client {
|
||||
tracing::debug!("Using custom nym-api HTTP client");
|
||||
builder = builder.with_nym_api_client(nym_api_client);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
let builder = builder.with_connection_fd_callback(self.connection_fd_callback);
|
||||
|
||||
@@ -251,6 +254,144 @@ fn true_to_disabled(val: bool) -> &'static str {
|
||||
if val { "disabled" } else { "enabled" }
|
||||
}
|
||||
|
||||
/// Error type for BuilderConfig validation
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum BuilderConfigError {
|
||||
#[error("entry_node is required")]
|
||||
MissingEntryNode,
|
||||
#[error("exit_node is required")]
|
||||
MissingExitNode,
|
||||
#[error("mixnet_client_config is required")]
|
||||
MissingMixnetClientConfig,
|
||||
#[error("user_agent is required")]
|
||||
MissingUserAgent,
|
||||
#[error("custom_topology_provider is required")]
|
||||
MissingTopologyProvider,
|
||||
#[error("network_env is required")]
|
||||
MissingNetworkEnv,
|
||||
#[error("cancel_token is required")]
|
||||
MissingCancelToken,
|
||||
#[cfg(unix)]
|
||||
#[error("connection_fd_callback is required")]
|
||||
MissingConnectionFdCallback,
|
||||
}
|
||||
|
||||
/// Builder for `BuilderConfig`
|
||||
///
|
||||
/// This provides a more convenient way to construct a `BuilderConfig` compared to the
|
||||
/// `new()` constructor with many arguments.
|
||||
#[derive(Default)]
|
||||
pub struct BuilderConfigBuilder {
|
||||
entry_node: Option<NymNodeWithKeys>,
|
||||
exit_node: Option<NymNodeWithKeys>,
|
||||
data_path: Option<PathBuf>,
|
||||
mixnet_client_config: Option<MixnetClientConfig>,
|
||||
two_hops: bool,
|
||||
user_agent: Option<UserAgent>,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
network_env: Option<NymNetworkDetails>,
|
||||
cancel_token: Option<CancellationToken>,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl BuilderConfigBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn entry_node(mut self, entry_node: NymNodeWithKeys) -> Self {
|
||||
self.entry_node = Some(entry_node);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exit_node(mut self, exit_node: NymNodeWithKeys) -> Self {
|
||||
self.exit_node = Some(exit_node);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn data_path(mut self, data_path: Option<PathBuf>) -> Self {
|
||||
self.data_path = data_path;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mixnet_client_config(mut self, mixnet_client_config: MixnetClientConfig) -> Self {
|
||||
self.mixnet_client_config = Some(mixnet_client_config);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn two_hops(mut self, two_hops: bool) -> Self {
|
||||
self.two_hops = two_hops;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn user_agent(mut self, user_agent: UserAgent) -> Self {
|
||||
self.user_agent = Some(user_agent);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn custom_topology_provider(
|
||||
mut self,
|
||||
custom_topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
) -> Self {
|
||||
self.custom_topology_provider = Some(custom_topology_provider);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn network_env(mut self, network_env: NymNetworkDetails) -> Self {
|
||||
self.network_env = Some(network_env);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cancel_token(mut self, cancel_token: CancellationToken) -> Self {
|
||||
self.cancel_token = Some(cancel_token);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn connection_fd_callback(
|
||||
mut self,
|
||||
connection_fd_callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
||||
) -> Self {
|
||||
self.connection_fd_callback = Some(connection_fd_callback);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the `BuilderConfig`.
|
||||
///
|
||||
/// Returns an error if any required field is missing.
|
||||
pub fn build(self) -> Result<BuilderConfig, BuilderConfigError> {
|
||||
Ok(BuilderConfig {
|
||||
entry_node: self
|
||||
.entry_node
|
||||
.ok_or(BuilderConfigError::MissingEntryNode)?,
|
||||
exit_node: self.exit_node.ok_or(BuilderConfigError::MissingExitNode)?,
|
||||
data_path: self.data_path,
|
||||
mixnet_client_config: self
|
||||
.mixnet_client_config
|
||||
.ok_or(BuilderConfigError::MissingMixnetClientConfig)?,
|
||||
two_hops: self.two_hops,
|
||||
user_agent: self
|
||||
.user_agent
|
||||
.ok_or(BuilderConfigError::MissingUserAgent)?,
|
||||
custom_topology_provider: self
|
||||
.custom_topology_provider
|
||||
.ok_or(BuilderConfigError::MissingTopologyProvider)?,
|
||||
network_env: self
|
||||
.network_env
|
||||
.ok_or(BuilderConfigError::MissingNetworkEnv)?,
|
||||
cancel_token: self
|
||||
.cancel_token
|
||||
.ok_or(BuilderConfigError::MissingCancelToken)?,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: self
|
||||
.connection_fd_callback
|
||||
.ok_or(BuilderConfigError::MissingConnectionFdCallback)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -265,21 +406,52 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_config_has_custom_client_field() {
|
||||
// Verify that BuilderConfig has the custom_nym_api_client field
|
||||
// by creating a simple HTTP client
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
fn test_builder_config_builder_fails_without_required_fields() {
|
||||
// Building without any fields should fail with specific error
|
||||
let result = BuilderConfig::builder().build();
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(BuilderConfigError::MissingEntryNode) => (), // Expected
|
||||
Err(e) => panic!("Expected MissingEntryNode, got: {}", e),
|
||||
Ok(_) => panic!("Expected error, got Ok"),
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the client works
|
||||
let urls = http_client.base_urls();
|
||||
assert!(
|
||||
!urls.is_empty() && urls[0].as_str().contains("validator.nymtech.net"),
|
||||
"HTTP client should be configured with correct URL"
|
||||
);
|
||||
#[test]
|
||||
fn test_builder_config_builder_validates_all_required_fields() {
|
||||
// Test that each required field is validated
|
||||
let result = BuilderConfig::builder().build();
|
||||
assert!(result.is_err());
|
||||
|
||||
// Short-circuits at first missing field, so we just verify it's one of the expected errors
|
||||
#[allow(unreachable_patterns)] // All variants are covered, but keeping catch-all for safety
|
||||
match result {
|
||||
Err(BuilderConfigError::MissingEntryNode)
|
||||
| Err(BuilderConfigError::MissingExitNode)
|
||||
| Err(BuilderConfigError::MissingMixnetClientConfig)
|
||||
| Err(BuilderConfigError::MissingUserAgent)
|
||||
| Err(BuilderConfigError::MissingTopologyProvider)
|
||||
| Err(BuilderConfigError::MissingNetworkEnv)
|
||||
| Err(BuilderConfigError::MissingCancelToken) => (),
|
||||
#[cfg(unix)]
|
||||
Err(BuilderConfigError::MissingConnectionFdCallback) => (),
|
||||
Err(e) => panic!("Unexpected error: {}", e),
|
||||
Ok(_) => panic!("Expected validation error, got Ok"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_config_builder_method_chaining() {
|
||||
// Test that builder methods chain properly and return Self
|
||||
let builder = BuilderConfig::builder();
|
||||
|
||||
// Verify the builder returns itself for chaining
|
||||
let builder = builder.two_hops(true);
|
||||
let builder = builder.two_hops(false);
|
||||
let builder = builder.data_path(None);
|
||||
|
||||
// Builder should still fail because required fields are missing
|
||||
let result = builder.build();
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+3
-24
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "NymWallet"
|
||||
@@ -773,7 +773,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bls12_381"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp%2Fexperimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
@@ -1723,15 +1723,6 @@ dependencies = [
|
||||
"dirs-sys 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
@@ -1752,18 +1743,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.4.6",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
@@ -4100,7 +4079,7 @@ dependencies = [
|
||||
name = "nym-config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"dirs 6.0.0",
|
||||
"handlebars",
|
||||
"log",
|
||||
"nym-network-defaults",
|
||||
|
||||
@@ -54,7 +54,6 @@ pub struct MixnetClientBuilder<S: MixnetClientStorage = Ephemeral> {
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send + Sync>>,
|
||||
custom_shutdown: Option<ShutdownTracker>,
|
||||
custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
event_tx: Option<EventSender>,
|
||||
force_tls: bool,
|
||||
user_agent: Option<UserAgent>,
|
||||
@@ -94,7 +93,6 @@ impl MixnetClientBuilder<OnDiskPersistent> {
|
||||
socks5_config: None,
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_nym_api_client: None,
|
||||
storage: storage_paths
|
||||
.initialise_default_persistent_storage()
|
||||
.await?,
|
||||
@@ -133,7 +131,6 @@ where
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
custom_nym_api_client: None,
|
||||
custom_shutdown: None,
|
||||
event_tx: None,
|
||||
force_tls: false,
|
||||
@@ -158,7 +155,6 @@ where
|
||||
wait_for_gateway: self.wait_for_gateway,
|
||||
custom_topology_provider: self.custom_topology_provider,
|
||||
custom_gateway_transceiver: self.custom_gateway_transceiver,
|
||||
custom_nym_api_client: self.custom_nym_api_client,
|
||||
custom_shutdown: self.custom_shutdown,
|
||||
event_tx: self.event_tx,
|
||||
force_tls: self.force_tls,
|
||||
@@ -298,12 +294,6 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_nym_api_client(mut self, client: nym_http_api_client::Client) -> Self {
|
||||
self.custom_nym_api_client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_statistics_reporting(mut self, config: StatsReporting) -> Self {
|
||||
self.config.debug_config.stats_reporting = config;
|
||||
@@ -348,7 +338,6 @@ where
|
||||
|
||||
client.custom_gateway_transceiver = self.custom_gateway_transceiver;
|
||||
client.custom_topology_provider = self.custom_topology_provider;
|
||||
client.custom_nym_api_client = self.custom_nym_api_client;
|
||||
client.custom_shutdown = self.custom_shutdown;
|
||||
client.wait_for_gateway = self.wait_for_gateway;
|
||||
client.force_tls = self.force_tls;
|
||||
@@ -398,9 +387,6 @@ where
|
||||
/// advanced usage of custom gateways
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send + Sync>>,
|
||||
|
||||
/// Custom nym-api HTTP client (for domain fronting support)
|
||||
custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
|
||||
/// Attempt to wait for the selected gateway (if applicable) to come online if its currently not bonded.
|
||||
wait_for_gateway: bool,
|
||||
|
||||
@@ -473,7 +459,6 @@ where
|
||||
storage,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
custom_nym_api_client: None,
|
||||
wait_for_gateway: false,
|
||||
force_tls: false,
|
||||
custom_shutdown: None,
|
||||
@@ -585,6 +570,7 @@ where
|
||||
user_agent,
|
||||
topology_cfg.minimum_gateway_performance,
|
||||
topology_cfg.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -731,11 +717,6 @@ where
|
||||
base_builder = base_builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
if let Some(nym_api_client) = self.custom_nym_api_client {
|
||||
tracing::debug!("Using custom nym-api HTTP client");
|
||||
base_builder = base_builder.with_nym_api_client(nym_api_client);
|
||||
}
|
||||
|
||||
if let Some(topology_provider) = self.custom_topology_provider {
|
||||
base_builder = base_builder.with_topology_provider(topology_provider);
|
||||
}
|
||||
@@ -913,63 +894,4 @@ mod tests {
|
||||
"Builder should succeed without custom client"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_builder_with_custom_client() {
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral().with_nym_api_client(http_client);
|
||||
|
||||
assert!(
|
||||
builder.build().is_ok(),
|
||||
"Builder should succeed with custom client"
|
||||
);
|
||||
}
|
||||
|
||||
// Note: Tests for entry_capable_nodes() vs entry_gateways() are in nym-topology crate
|
||||
// These tests verify the builder functionality only
|
||||
|
||||
#[test]
|
||||
fn test_custom_client_transfer_through_build() {
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral().with_nym_api_client(http_client);
|
||||
let disconnected_client = builder.build();
|
||||
|
||||
assert!(
|
||||
disconnected_client.is_ok(),
|
||||
"Build should transfer custom_nym_api_client successfully"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_storage_transfer_includes_custom_client() {
|
||||
use nym_client_core::client::base_client::storage::Ephemeral;
|
||||
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral().with_nym_api_client(http_client);
|
||||
let new_storage = Ephemeral::default();
|
||||
let builder_with_new_storage = builder.set_storage(new_storage);
|
||||
|
||||
assert!(
|
||||
builder_with_new_storage.build().is_ok(),
|
||||
"set_storage should preserve custom_nym_api_client"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user