Files
nym/nym-api/src/support/cli/run.rs
T
Jędrzej Stuczyński e2dd8ac743 feat: localnet v2 (#6277)
* squashing localnet-v2 commits (again)

cargo fmt

fixes to localnet purge

provide path in the error message

output args

log failed exec

print based on tty

check-prerequisites cmd

checked iptables update

basic kernel features check

enable ipv6 rules

add forwarding rules

squashing localnet-v2 commits

additional changes

propagate custom-dns flag to all run containers

remove is_mock from EcashManager

another localnet squash

unused import

chore: remove redundant testnet manager

missing impl

additional linux fixes

command to rebuild container image

wait for at least 2 blocks

additional node startup fixes

added --custom-dns flag to nym node setup

add gateway probe + wait for DKG magic file

fixed localnet down on linux

container ls

re-enable state resync

additional feature locking

macos adjustments

working nyxd startup on linux

wip linux box

wip

separating network inspect betweewn macos and linux

initial linux feature locking

moved all container commands into a single location

finally working initial node performance

squashing orchestrator commits

cleanup

fixed condition for naive rearrangement

added cache of cosmwasm contracts for speed up on subsequent runs

'down' command

refreshing described cache after nodes are bonded

nym nodes setup + wip on nym api refresh

nodes setup WIP

first pass cleanup

placeholder for nym-node setup

bypassing the dkg

further progress on nym-api setup

wip: api setup

up/down/purge placeholders

persisting contract setup data

fix contract upload by forcing amd64 container platform

wip: contracts setup4

wip: contracts setup3

wip: contracts setup2

wip: contracts setup

include network setup

init and spawn nyxd

build nyxd image in dedicated orchestrator

build nyxd image

squashed cherry-picked lp changes

Bits and bobs to make everything work

Title

MacOS setup instructions

Docker/Container localnet

* clippy

* fixes on non-unix targets

---------

Co-authored-by: durch <durch@users.noreply.github.com>
2026-03-12 14:46:00 +00:00

437 lines
17 KiB
Rust

// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::client::Client;
use crate::ecash::comm::QueryCommunicationChannel;
use crate::ecash::dkg::controller::keys::{
can_validate_coconut_keys, load_bte_keypair, load_ecash_keypair_if_exists,
};
use crate::ecash::dkg::controller::DkgController;
use crate::ecash::state::EcashState;
use crate::epoch_operations::EpochAdvancer;
use crate::key_rotation::KeyRotationController;
use crate::mixnet_contract_cache::cache::MixnetContractCache;
use crate::network::models::NetworkDetails;
use crate::node_describe_cache::cache::DescribedNodes;
use crate::node_performance::provider::contract_provider::ContractPerformanceProvider;
use crate::node_performance::provider::legacy_storage_provider::LegacyStoragePerformanceProvider;
use crate::node_performance::provider::NodePerformanceProvider;
use crate::node_status_api::handlers::unstable;
use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater;
use crate::node_status_api::NodeStatusCache;
use crate::status::{ApiStatusState, SignerState};
use crate::support::caching::cache::SharedCache;
use crate::support::config::helpers::try_load_current_config;
use crate::support::config::{Config, DEFAULT_CHAIN_STATUS_CACHE_TTL};
use crate::support::http::state::chain_status::ChainStatusCache;
use crate::support::http::state::contract_details::ContractDetailsCache;
use crate::support::http::state::force_refresh::ForcedRefresh;
use crate::support::http::state::mixnet_contract_cache::MixnetContractCacheState;
use crate::support::http::state::node_annotations_cache::NodeAnnotationsCache;
use crate::support::http::state::AppState;
use crate::support::http::{RouterBuilder, TASK_MANAGER_TIMEOUT_S};
use crate::support::nyxd;
use crate::support::storage::runtime_migrations::m001_directory_services_v2_1::migrate_to_directory_services_v2_1;
use crate::support::storage::NymApiStorage;
use crate::unstable_routes::v1::account::cache::AddressInfoCache;
use crate::{
ecash, epoch_operations, mixnet_contract_cache, network_monitor, node_describe_cache,
node_performance, node_status_api, signers_cache,
};
use anyhow::{bail, Context};
use nym_config::defaults::NymNetworkDetails;
use nym_sphinx::receiver::SphinxMessageReceiver;
use nym_task::ShutdownManager;
use nym_validator_client::nyxd::Coin;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tracing::info;
#[derive(clap::Args, Debug)]
pub(crate) struct Args {
/// Id of the nym-api we want to run.if unspecified, a default value will be used.
/// default: "default"
#[clap(long, default_value = "default", env = "NYMAPI_ID_ARG")]
pub(crate) id: String,
/// Specifies whether network monitoring is enabled on this API
/// default: None - config value will be used instead
#[clap(short = 'm', long, env = "NYMAPI_ENABLE_MONITOR_ARG")]
pub(crate) enable_monitor: Option<bool>,
/// Specifies whether network rewarding is enabled on this API
/// default: None - config value will be used instead
#[clap(
short = 'r',
long,
requires = "enable_monitor",
requires = "mnemonic",
env = "NYMAPI_ENABLE_REWARDING_ARG"
)]
pub(crate) enable_rewarding: Option<bool>,
/// Endpoint to nyxd instance used for contract information.
/// default: None - config value will be used instead
#[clap(long, env = "NYMAPI_NYXD_VALIDATOR_ARG")]
pub(crate) nyxd_validator: Option<url::Url>,
/// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions
/// default: None - config value will be used instead
#[clap(long, env = "NYMAPI_MNEMONIC_ARG")]
pub(crate) mnemonic: Option<bip39::Mnemonic>,
/// Flag to indicate whether coconut signer authority is enabled on this API
/// default: None - config value will be used instead
#[clap(
long,
requires = "mnemonic",
requires = "announce_address",
alias = "enable_coconut",
env = "NYMAPI_ENABLE_ZK_NYM_ARG"
)]
pub(crate) enable_zk_nym: Option<bool>,
/// Announced address that is going to be put in the DKG contract where zk-nym clients will connect
/// to obtain their credentials
/// default: None - config value will be used instead
#[clap(long, env = "NYMAPI_ANNOUNCE_ADDRESS_ARG")]
pub(crate) announce_address: Option<url::Url>,
/// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement
/// default: None - config value will be used instead
#[clap(long, env = "NYMAPI_MONITOR_CREDENTIALS_MODE_ARG")]
pub(crate) monitor_credentials_mode: Option<bool>,
/// Socket address this api will use for binding its http API.
/// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release`
#[clap(long)]
pub(crate) bind_address: Option<SocketAddr>,
/// account/address cache TTL: should be lower than epoch length (1 hour)
/// because, at worst, data will be stale for <epoch_length> + <cache_ttl> seconds
#[clap(long, env = "ADDRESS_CACHE_REFRESH_INTERVAL_S")]
pub(crate) address_cache_ttl_seconds: Option<u64>,
/// number of addresses that are cached on account/address endpoint
#[clap(long, env = "ADDRESS_CACHE_CAPACITY")]
pub(crate) address_cache_capacity: Option<u64>,
#[clap(hide = true, long, default_value_t = false)]
pub(crate) allow_illegal_ips: bool,
/// Bearer token for exposing and accessing additional utility routes
#[clap(long, env = "NYMAPI_UTILITY_ROUTES_BEARER_ARG")]
pub(crate) utility_routes_bearer: Option<String>,
}
pub(crate) async fn initialise_storage(config: &Config) -> anyhow::Result<NymApiStorage> {
let nyxd_client = nyxd::Client::new(config)?;
let storage = NymApiStorage::init(&config.node_status_api.storage_paths.database_path).await?;
// try to perform any needed migrations of the storage
migrate_to_directory_services_v2_1(&storage, &nyxd_client).await?;
Ok(storage)
}
async fn start_nym_api_tasks(mut config: Config) -> anyhow::Result<ShutdownManager> {
let shutdown_manager = ShutdownManager::build_new_default()?
.with_shutdown_duration(Duration::from_secs(TASK_MANAGER_TIMEOUT_S));
let nyxd_client = nyxd::Client::new(&config)?;
let connected_nyxd = config.get_nyxd_url();
let nym_network_details = NymNetworkDetails::new_from_env();
let network_details = NetworkDetails::new(connected_nyxd.to_string(), nym_network_details);
let ecash_keypair_wrapper = ecash::keys::KeyPair::new();
// if the keypair doesn't exist (because say this API is running in the caching mode), nothing will happen
if let Some(loaded_keys) = load_ecash_keypair_if_exists(&config.ecash_signer)? {
let issued_for = loaded_keys.issued_for_epoch;
ecash_keypair_wrapper.set(loaded_keys).await;
if can_validate_coconut_keys(&nyxd_client, issued_for).await? {
ecash_keypair_wrapper.validate()
}
}
let storage = initialise_storage(&config).await?;
let identity_keypair = config.base.storage_paths.load_identity()?;
let identity_public_key = *identity_keypair.public_key();
let mix_denom = network_details.network.chain_details.mix_denom.base.clone();
let storage_cfg = &config.base.storage_paths;
// ===== START: attempt to build up initial caches based on prior data
//
// MIXNET CONTRACT
let mixnet_path = storage_cfg.cache_file("mixnet_contract");
let ttl = config.mixnet_contract_cache.debug.caching_interval;
let mixnet_contract_cache_state = MixnetContractCache::new(&mixnet_path, ttl);
let mixnet_contract_cache_refresher = mixnet_contract_cache::build_refresher(
&config.mixnet_contract_cache,
&mixnet_contract_cache_state.clone(),
nyxd_client.clone(),
mixnet_path,
);
// DESCRIBED NODES
let described_path = storage_cfg.cache_file("described_nodes");
let ttl = config.describe_cache.debug.caching_interval;
let described_nodes_cache =
SharedCache::<DescribedNodes>::new_with_persistent(&described_path, ttl, None);
let describe_cache_refresher = node_describe_cache::provider::new_provider_with_initial_value(
&config.describe_cache,
mixnet_contract_cache_state.clone(),
described_nodes_cache.clone(),
described_path,
);
// NODES ANNOTATIONS
let annotations_path = storage_cfg.cache_file("node_annotations");
let ttl = config.node_status_api.debug.caching_interval;
let node_status_cache_state = NodeStatusCache::new(&annotations_path, ttl);
// note: can't create the cache refresher the same way as above as it's using a different structure
// unfortunately
// ===== END: attempt to build up initial caches based on prior data
// not a 'persistent' cache that's updated on a timer like the above - it's just use for retrieving database information
// for unstable routes regarding test runs.
let node_info_cache = unstable::NodeInfoCache::default();
// not as data sensitive as others
// check if signers cache is enabled, and if so, start the refresher
let ecash_signers_cache = if config.signers_cache.enabled {
signers_cache::start_refresher(
&config.signers_cache,
nyxd_client.clone(),
&shutdown_manager,
)
} else {
SharedCache::new()
};
let mixnet_contract_cache_refresh_requester =
mixnet_contract_cache_refresher.refresh_requester();
let ecash_contract = nyxd_client
.get_ecash_contract_address()
.await
.context("e-cash contract address is required to setup the nym-api routes")?;
let comm_channel = QueryCommunicationChannel::new(nyxd_client.clone());
let encoded_identity = identity_keypair.public_key().to_base58_string();
let mut ecash_state = EcashState::new(
&config,
ecash_contract,
nyxd_client.clone(),
identity_keypair,
ecash_keypair_wrapper.clone(),
comm_channel,
storage.clone(),
&shutdown_manager,
);
// if ecash signer is enabled, there are additional constraints on the nym-api,
// such as having sufficient token balance
let signer_information = if config.ecash_signer.enabled {
let cosmos_address = nyxd_client.address().await?;
// make sure we have some tokens to cover multisig fees
let balance = nyxd_client.balance(&mix_denom).await?;
if balance.amount < ecash::MINIMUM_BALANCE {
let min = Coin::new(ecash::MINIMUM_BALANCE, mix_denom);
bail!("the account ({cosmos_address}) doesn't have enough funds to cover verification fees. it has {balance} while it needs at least {min}")
}
let announce_address = config
.ecash_signer
.announce_address
.clone()
.map(|u| u.to_string())
.unwrap_or_default();
Some(SignerState {
cosmos_address: cosmos_address.to_string(),
identity: encoded_identity,
announce_address,
ecash_keypair: ecash_keypair_wrapper.clone(),
})
} else {
None
};
ecash_state.spawn_background_cleaner();
let describe_cache_refresh_requester = describe_cache_refresher.refresh_requester();
let describe_cache_watcher =
describe_cache_refresher.start_with_watcher(shutdown_manager.clone_shutdown_token());
let performance_provider = if config.performance_provider.use_performance_contract_data {
if network_details
.network
.contracts
.performance_contract_address
.is_none()
{
bail!("can't use performance contract data without setting the address of the contract")
}
let performance_contract_cache = node_performance::contract_cache::start_cache_refresher(
&config.performance_provider,
nyxd_client.clone(),
mixnet_contract_cache_state.clone(),
&shutdown_manager,
)
.await?;
let provider = ContractPerformanceProvider::new(
&config.performance_provider,
performance_contract_cache,
);
Box::new(provider) as Box<dyn NodePerformanceProvider + Send + Sync>
} else {
Box::new(LegacyStoragePerformanceProvider::new(
storage.clone(),
mixnet_contract_cache_state.clone(),
))
};
// start all the caches first
let contract_cache_watcher =
mixnet_contract_cache_refresher.start_with_watcher(shutdown_manager.clone_shutdown_token());
let node_status_cache_refresh_requester = node_status_api::start_cache_refresh(
&config.node_status_api,
&mixnet_contract_cache_state,
&described_nodes_cache,
&node_status_cache_state,
performance_provider,
contract_cache_watcher.clone(),
describe_cache_watcher,
annotations_path,
&shutdown_manager,
);
// start dkg task
if config.ecash_signer.enabled {
let dkg_bte_keypair = load_bte_keypair(&config.ecash_signer)?;
DkgController::start(
&config.ecash_signer,
nyxd_client.clone(),
ecash_keypair_wrapper,
dkg_bte_keypair,
identity_public_key,
rand::rngs::OsRng,
&shutdown_manager,
)?;
}
let has_performance_data =
config.network_monitor.enabled || config.performance_provider.use_performance_contract_data;
// and then only start the uptime updater (and the monitor itself, duh)
// if the monitoring is enabled
if config.network_monitor.enabled {
network_monitor::start::<SphinxMessageReceiver>(
&config,
&mixnet_contract_cache_state,
described_nodes_cache.clone(),
node_status_cache_state.clone(),
&storage,
nyxd_client.clone(),
&shutdown_manager,
)
.await;
HistoricalUptimeUpdater::start(storage.to_owned(), &shutdown_manager);
}
// start 'rewarding' if its enabled and there exists source for performance data
if config.rewarding.enabled && has_performance_data {
epoch_operations::ensure_rewarding_permission(&nyxd_client).await?;
EpochAdvancer::start(
nyxd_client.clone(),
&mixnet_contract_cache_state,
mixnet_contract_cache_refresh_requester.clone(),
&node_status_cache_state,
described_nodes_cache.clone(),
&storage,
&shutdown_manager,
);
}
// finally start a background task watching the contract changes and requesting
// self-described cache refresh upon being close to key rotation rollover
KeyRotationController::new(
describe_cache_refresh_requester,
contract_cache_watcher,
mixnet_contract_cache_state.clone(),
)
.start(shutdown_manager.clone_shutdown_token());
let mixnet_contract_cache = MixnetContractCacheState::new(
mixnet_contract_cache_state,
mixnet_contract_cache_refresh_requester,
);
let node_annotations_cache =
NodeAnnotationsCache::new(node_status_cache_state, node_status_cache_refresh_requester);
let router = RouterBuilder::with_default_routes(
config.network_monitor.enabled,
config.base.utility_routes_bearer.take(),
)
.with_state(AppState {
nyxd_client,
chain_status_cache: ChainStatusCache::new(DEFAULT_CHAIN_STATUS_CACHE_TTL),
ecash_signers_cache,
address_info_cache: AddressInfoCache::new(
config.address_cache.time_to_live,
config.address_cache.capacity,
),
forced_refresh: ForcedRefresh::new(config.describe_cache.debug.allow_illegal_ips),
mixnet_contract_cache,
node_annotations_cache,
storage,
described_nodes_cache,
network_details,
node_info_cache,
contract_info_cache: ContractDetailsCache::new(config.contracts_info_cache.time_to_live),
api_status: ApiStatusState::new(signer_information),
ecash_state: Arc::new(ecash_state),
});
let bind_address = config.base.bind_address.to_owned();
let server = router.build_server(&bind_address).await?;
let http_shutdown = shutdown_manager.clone_shutdown_token();
tokio::spawn(async move {
{
info!("Started Axum HTTP V2 server on {bind_address}");
server.run(http_shutdown).await
}
});
shutdown_manager.close_tracker();
Ok(shutdown_manager)
}
pub(crate) async fn execute(args: Args) -> anyhow::Result<()> {
// args take precedence over env
let mut config = try_load_current_config(&args.id)?
.override_with_env()
.override_with_args(args);
config.validate_and_fixup()?;
let mut shutdown_manager = start_nym_api_tasks(config).await?;
shutdown_manager.run_until_shutdown().await;
Ok(())
}