use std::sync::Arc;
use ntk_common::{Naddr, Topology};
use ntk_hooking::{HookingConfig, HookingHandle, HookingOrigin};
use ntk_peerservices::RoutingEnv;
use ntk_qspn::QspnHandle;
use tokio::task::JoinSet;
use tokio_util::sync::CancellationToken;
use crate::node::adapters::Live;
use crate::node::adapters::{
CoordinatorClientAdapter, CoordinatorMapAdapter, CoordinatorStubFactoryAdapter, EnterArbiter,
EnterHandlersAdapter, NetworkInfo, PropagationHandlerAdapter, QspnViewAdapter,
RoutingEnvAdapter,
};
use crate::node::peers::PeerLinks;
use crate::node::registry::LinkRegistry;
use crate::node::stubs::HookingStubFactoryAdapter;
#[derive(Debug)]
pub struct ProcessScope<'a> {
pub cancel: &'a CancellationToken,
pub tasks: &'a mut JoinSet<()>,
}
#[derive(Debug)]
pub struct HookingRetarget {
view: Arc<QspnViewAdapter>,
coordinator: Arc<CoordinatorClientAdapter>,
stubs: Arc<HookingStubFactoryAdapter>,
}
impl HookingRetarget {
pub fn retarget(
&self,
qspn: &QspnHandle,
coordinator: &ntk_coordinator::Handle,
coordinator_client: &ntk_coordinator::CoordinatorClient,
) {
self.view.retarget(qspn.clone());
self.coordinator.retarget(
coordinator_client.clone(),
coordinator.clone(),
qspn.clone(),
);
self.stubs.qspn.set(qspn.clone());
}
}
#[derive(Debug)]
pub enum HookingProvenance {
Fresh(HookingOrigin),
Carried(HookingHandle),
}
#[derive(Debug)]
pub struct Services {
pub peers: ntk_peerservices::Handle,
pub coordinator: ntk_coordinator::Handle,
pub coordinator_client: ntk_coordinator::CoordinatorClient,
pub andna: ntk_andna::Handle,
pub hooking: HookingHandle,
pub qspn_view: Arc<QspnViewAdapter>,
pub retarget: Option<HookingRetarget>,
}
#[must_use]
fn peers_config(require_auth: bool) -> ntk_peerservices::Config {
ntk_peerservices::Config {
participation_reannounce_interval: Some(std::time::Duration::from_secs(300)),
require_auth,
..ntk_peerservices::Config::default()
}
}
#[must_use]
pub(crate) fn coordinator_config() -> ntk_coordinator::Config {
ntk_coordinator::Config {
n_nodes_cache_ttl: std::time::Duration::from_millis(200),
..ntk_coordinator::Config::default()
}
}
#[must_use]
fn hooking_config() -> HookingConfig {
let base = HookingConfig::default();
let merge_reject_wait = base.restart_wait(1);
HookingConfig {
merge_reject_wait,
..base
}
}
#[allow(clippy::too_many_arguments)]
pub async fn spawn(
topology: Topology,
my_naddr: Naddr,
hooking: HookingProvenance,
qspn: QspnHandle,
registry: Arc<LinkRegistry>,
links: Arc<PeerLinks>,
net: Arc<NetworkInfo>,
tasks: &mut JoinSet<()>,
cancel: &CancellationToken,
signing_key: Option<ed25519_dalek::SigningKey>,
require_auth: bool,
coordinator_handoff: Option<ntk_coordinator::HandOff>,
process: Option<ProcessScope<'_>>,
) -> Services {
let joining = matches!(&hooking, HookingProvenance::Carried(_));
let retrieved_below_level = if joining { 0 } else { topology.levels() };
let routing_env: Arc<dyn RoutingEnv> = Arc::new(RoutingEnvAdapter {
qspn: qspn.clone(),
registry: registry.clone(),
links: links.clone(),
});
let (peers_manager, peers) = ntk_peerservices::Manager::new(
topology.clone(),
my_naddr.clone(),
routing_env.clone(),
peers_config(require_auth),
retrieved_below_level,
);
let peers = match signing_key {
Some(key) => peers.with_signing_key(key),
None => peers,
};
tasks.spawn(peers_manager.run(cancel.child_token()));
let coordinator_client =
ntk_coordinator::CoordinatorClient::new(peers.clone(), coordinator_config());
let coordinator_map = Arc::new(CoordinatorMapAdapter {
qspn: qspn.clone(),
net: net.clone(),
});
let coordinator_stub_factory = Arc::new(CoordinatorStubFactoryAdapter {
links: links.clone(),
});
let (hooking_tx, hooking_rx) = tokio::sync::watch::channel(match &hooking {
HookingProvenance::Carried(existing) => Some(existing.clone()),
HookingProvenance::Fresh(_) => None,
});
let propagation_handler = Arc::new(PropagationHandlerAdapter {
hooking: hooking_rx,
});
let (coordinator_service_tx, coordinator_service_rx) =
tokio::sync::watch::channel(None::<Arc<ntk_coordinator::CoordinatorService>>);
let enter_arbiter = Arc::new(EnterArbiter::new());
let enter_adapter = Arc::new(EnterHandlersAdapter {
arbiter: enter_arbiter,
qspn: qspn.clone(),
net: net.clone(),
coordinator_service: coordinator_service_rx,
});
let enter_handlers = ntk_coordinator::EnterHandlers {
evaluate_enter: enter_adapter.clone(),
begin_enter: enter_adapter.clone(),
completed_enter: enter_adapter.clone(),
abort_enter: enter_adapter,
};
let (coordinator_manager, coordinator) = ntk_coordinator::Manager::new(
topology.clone(),
coordinator_map,
coordinator_stub_factory,
propagation_handler,
enter_handlers,
coordinator_config(),
coordinator_handoff,
);
tasks.spawn(coordinator_manager.run(cancel.child_token()));
let coordinator_service = Arc::new(ntk_coordinator::CoordinatorService::new(
coordinator.clone(),
peers.clone(),
));
let _ = coordinator_service_tx.send(Some(coordinator_service.clone()));
peers.register(coordinator_service).await;
let (hooking_cancel, hooking_tasks): (CancellationToken, &mut JoinSet<()>) = match process {
Some(scope) => (scope.cancel.child_token(), scope.tasks),
None => (cancel.child_token(), tasks),
};
let qspn_view = Arc::new(QspnViewAdapter::spawn(
qspn.clone(),
net.clone(),
hooking_tasks,
hooking_cancel.clone(),
));
let mut retarget = None;
let hooking = match hooking {
HookingProvenance::Fresh(origin) => {
let coordinator_client_adapter = Arc::new(CoordinatorClientAdapter::new(
coordinator_client.clone(),
coordinator.clone(),
qspn.clone(),
net.clone(),
coordinator_config().n_nodes_cache_ttl,
));
let hooking_stub_factory = Arc::new(HookingStubFactoryAdapter {
qspn: Live::new(qspn.clone()),
links: links.clone(),
registry: registry.clone(),
});
let (h, hooking_join) = ntk_hooking::spawn(
origin,
qspn_view.clone() as Arc<dyn ntk_hooking::QspnView>,
coordinator_client_adapter.clone(),
hooking_stub_factory.clone(),
hooking_config(),
hooking_cancel,
);
hooking_tasks.spawn(async move {
if let Err(err) = hooking_join.await
&& err.is_panic()
{
tracing::error!(%err, "hooking actor panicked");
}
});
let _ = hooking_tx.send(Some(h.clone()));
retarget = Some(HookingRetarget {
view: qspn_view.clone(),
coordinator: coordinator_client_adapter,
stubs: hooking_stub_factory,
});
h
}
HookingProvenance::Carried(existing) => existing,
};
let substrate = Arc::new(peers.clone()) as Arc<dyn ntk_andna::AndnaSubstrate>;
let (andna_manager, andna) = ntk_andna::Manager::new(substrate, ntk_andna::Config::default());
tasks.spawn(andna_manager.run(cancel.child_token()));
andna.register_services().await;
tasks.spawn(ntk_andna::run_expiry_reclaimer(
andna.clone(),
cancel.child_token(),
));
if joining {
let peers_for_seed = peers.clone();
tasks.spawn(async move {
for neighbor in routing_env.neighbors() {
match neighbor.ask_participant_maps().await {
Ok(incoming) => {
peers_for_seed.apply_participant_set(incoming).await;
}
Err(err) => tracing::debug!(
%err,
"peerservices: ask_participant_maps failed while bootstrapping a joined generation's participation knowledge"
),
}
}
});
}
Services {
peers,
coordinator,
coordinator_client,
andna,
hooking,
qspn_view,
retarget,
}
}