1mod chain_wiring;
30
31use std::{future::Future, pin::Pin, sync::Arc, time::Duration};
32
33use futures::{FutureExt, StreamExt};
34pub use hopr_api::types::crypto::{
35 keypairs::Keypair,
36 prelude::{ChainKeypair, OffchainKeypair},
37};
38use hopr_api::{
39 chain::{AnnouncementError, HoprChainApi, SafeRegistrationError, StateSyncOptions},
40 ct::{CoverTrafficGeneration, ProbingTrafficGeneration},
41 graph::HoprGraphApi,
42 network::{BoxedProcessFn, NetworkStreamControl, NetworkView},
43 node::{AtomicHoprState, HoprState, NodeOnchainIdentity, PixEvent, TicketEvent},
44 tickets::{TicketFactory, TicketManagement},
45 types::{chain::chain_events::ChainEvent, internal::prelude::ChannelDirection, primitive::prelude::Address},
46};
47use hopr_transport::{HoprTransport, IncomingSession};
48use hopr_utils::{
49 network_types::{
50 addr::is_public_address,
51 crossfire_sink::{CrossfireSink, bounded_sink_channel},
52 },
53 runtime::{AbortableList, prelude::spawn},
54};
55use validator::Validate;
56
57use crate::{
58 Hopr, HoprLibError, HoprLibProcess, MIN_NATIVE_BALANCE, NODE_READY_TIMEOUT, SUGGESTED_NATIVE_BALANCE,
59 config::HoprLibConfig, constants, helpers::BroadcastSenderSink,
60};
61
62#[cfg(all(feature = "telemetry", not(test)))]
63lazy_static::lazy_static! {
64 static ref METRIC_PROCESS_START_TIME: hopr_api::types::telemetry::SimpleGauge = hopr_api::types::telemetry::SimpleGauge::new(
65 "hopr_start_time",
66 "The unix timestamp in seconds at which the process was started"
67 ).unwrap();
68 static ref METRIC_HOPR_LIB_VERSION: hopr_api::types::telemetry::MultiGauge = hopr_api::types::telemetry::MultiGauge::new(
69 "hopr_lib_version",
70 "Executed version of hopr-lib",
71 &["version"]
72 ).unwrap();
73 static ref METRIC_HOPR_NODE_INFO: hopr_api::types::telemetry::MultiGauge = hopr_api::types::telemetry::MultiGauge::new(
74 "hopr_node_addresses",
75 "Node on-chain and off-chain addresses",
76 &["peerid", "address", "safe_address", "module_address"]
77 ).unwrap();
78}
79
80const PEER_DISCOVERY_CHANNEL_CAPACITY: usize = 2048;
81
82type PeerDiscoveryRx =
83 Arc<parking_lot::Mutex<Option<futures::stream::BoxStream<'static, (hopr_api::PeerId, Vec<hopr_api::Multiaddr>)>>>>;
84
85type Factory<T> = Box<dyn FnOnce(&BuildCtx) -> T + Send>;
87type AsyncFactory<T> = Box<dyn FnOnce(BuildCtx) -> Pin<Box<dyn Future<Output = T> + Send>> + Send>;
88
89#[derive(Clone)]
91pub struct BuildCtx {
92 pub chain_key: ChainKeypair,
94 pub packet_key: OffchainKeypair,
96 pub cfg: HoprLibConfig,
98 peer_discovery_rx: PeerDiscoveryRx,
99}
100
101impl BuildCtx {
102 pub fn take_peer_discovery_rx(
104 &self,
105 ) -> Option<futures::stream::BoxStream<'static, (hopr_api::PeerId, Vec<hopr_api::Multiaddr>)>> {
106 self.peer_discovery_rx.lock().take()
107 }
108}
109
110#[derive(Default)]
116pub struct HoprBuilder;
117
118impl HoprBuilder {
119 pub fn with_identity(self, chain_key: &ChainKeypair, offchain_key: &OffchainKeypair) -> HoprBuilderWithIdentity {
121 HoprBuilderWithIdentity {
122 chain_key: chain_key.clone(),
123 packet_key: offchain_key.clone(),
124 }
125 }
126}
127
128pub struct HoprBuilderWithIdentity {
130 chain_key: ChainKeypair,
131 packet_key: OffchainKeypair,
132}
133
134impl HoprBuilderWithIdentity {
135 pub fn with_config(self, cfg: HoprLibConfig) -> HoprBuilderConfigured {
137 let (peer_discovery_tx, peer_discovery_rx) =
138 bounded_sink_channel::<(hopr_api::PeerId, Vec<hopr_api::Multiaddr>)>(PEER_DISCOVERY_CHANNEL_CAPACITY);
139 HoprBuilderConfigured {
140 ctx: BuildCtx {
141 chain_key: self.chain_key,
142 packet_key: self.packet_key,
143 cfg,
144 peer_discovery_rx: Arc::new(parking_lot::Mutex::new(Some(peer_discovery_rx))),
145 },
146 safe_and_module: None,
147 chain_factory: None,
148 graph_factory: None,
149 network_factory: None,
150 ct_factory: None,
151 peer_discovery_tx,
152 }
153 }
154}
155
156pub struct HoprBuilderConfigured<Chain = (), Graph = (), Net = (), Ct = ()> {
168 ctx: BuildCtx,
169 safe_and_module: Option<(Address, Address)>,
170 chain_factory: Option<Factory<Chain>>,
171 graph_factory: Option<Factory<Graph>>,
172 network_factory: Option<AsyncFactory<Result<(Net, BoxedProcessFn), HoprLibError>>>,
173 ct_factory: Option<Factory<Ct>>,
174 peer_discovery_tx: CrossfireSink<(hopr_api::PeerId, Vec<hopr_api::Multiaddr>)>,
175}
176
177impl<Chain, Graph, Net, Ct> HoprBuilderConfigured<Chain, Graph, Net, Ct> {
178 pub fn with_safe_module(mut self, safe: &Address, module: &Address) -> Self {
180 self.safe_and_module = Some((*safe, *module));
181 self
182 }
183
184 pub fn with_chain_api<NewChain>(
186 self,
187 f: impl FnOnce(&BuildCtx) -> NewChain + Send + 'static,
188 ) -> HoprBuilderConfigured<NewChain, Graph, Net, Ct> {
189 HoprBuilderConfigured {
190 ctx: self.ctx,
191 safe_and_module: self.safe_and_module,
192 chain_factory: Some(Box::new(f)),
193 graph_factory: self.graph_factory,
194 network_factory: self.network_factory,
195 ct_factory: self.ct_factory,
196 peer_discovery_tx: self.peer_discovery_tx,
197 }
198 }
199
200 pub fn with_graph<NewGraph>(
202 self,
203 f: impl FnOnce(&BuildCtx) -> NewGraph + Send + 'static,
204 ) -> HoprBuilderConfigured<Chain, NewGraph, Net, Ct> {
205 HoprBuilderConfigured {
206 ctx: self.ctx,
207 safe_and_module: self.safe_and_module,
208 chain_factory: self.chain_factory,
209 graph_factory: Some(Box::new(f)),
210 network_factory: self.network_factory,
211 ct_factory: self.ct_factory,
212 peer_discovery_tx: self.peer_discovery_tx,
213 }
214 }
215
216 pub fn with_network<NewNet>(
222 self,
223 f: impl FnOnce(BuildCtx) -> Pin<Box<dyn Future<Output = Result<(NewNet, BoxedProcessFn), HoprLibError>> + Send>>
224 + Send
225 + 'static,
226 ) -> HoprBuilderConfigured<Chain, Graph, NewNet, Ct> {
227 HoprBuilderConfigured {
228 ctx: self.ctx,
229 safe_and_module: self.safe_and_module,
230 chain_factory: self.chain_factory,
231 graph_factory: self.graph_factory,
232 network_factory: Some(Box::new(f)),
233 ct_factory: self.ct_factory,
234 peer_discovery_tx: self.peer_discovery_tx,
235 }
236 }
237
238 pub fn with_cover_traffic<NewCt>(
240 self,
241 f: impl FnOnce(&BuildCtx) -> NewCt + Send + 'static,
242 ) -> HoprBuilderConfigured<Chain, Graph, Net, NewCt> {
243 HoprBuilderConfigured {
244 ctx: self.ctx,
245 safe_and_module: self.safe_and_module,
246 chain_factory: self.chain_factory,
247 graph_factory: self.graph_factory,
248 network_factory: self.network_factory,
249 ct_factory: Some(Box::new(f)),
250 peer_discovery_tx: self.peer_discovery_tx,
251 }
252 }
253
254 #[cfg(feature = "session-server")]
259 pub fn with_session_server(
260 self,
261 server: impl hopr_api::node::HoprSessionServer<Session = IncomingSession, Error: std::fmt::Display>
262 + Clone
263 + Send
264 + 'static,
265 ) -> HoprBuilderWithSession<Chain, Graph, Net, Ct> {
266 let incoming_session_capacity = self.ctx.cfg.incoming_session_capacity.max(1);
267
268 let (session_tx, session_rx) = futures::channel::mpsc::channel::<IncomingSession>(incoming_session_capacity);
269
270 tracing::debug!(capacity = incoming_session_capacity, "spawning session server");
271 let session_diag = hopr_utils::runtime::diagnostics::ConcurrentDiagnostics::new(
272 "session_server_for_each_concurrent",
273 module_path!(),
274 file!(),
275 line!(),
276 );
277 let handle = hopr_utils::spawn_as_abortable_named!(
278 "hopr_lib_session_server",
279 session_rx
280 .for_each_concurrent(None, move |session| {
281 let server = server.clone();
282 let session_diag = session_diag.clone();
283 session_diag.wrap(|| async move {
284 let session_id = *session.session.id();
285 match server.process(session).await {
286 Ok(()) => tracing::debug!(?session_id, "session processed successfully"),
287 Err(error) => {
288 tracing::error!(?session_id, %error, "session processing failed")
289 }
290 }
291 })
292 })
293 .inspect(|_| tracing::warn!(
294 task = %HoprLibProcess::SessionServer,
295 "long-running background task finished"
296 ))
297 );
298
299 HoprBuilderWithSession {
300 inner: self,
301 session_tx,
302 session_handle: handle,
303 }
304 }
305}
306
307#[cfg(feature = "session-server")]
315pub struct HoprBuilderWithSession<Chain = (), Graph = (), Net = (), Ct = ()> {
316 inner: HoprBuilderConfigured<Chain, Graph, Net, Ct>,
317 session_tx: futures::channel::mpsc::Sender<IncomingSession>,
318 session_handle: hopr_utils::runtime::AbortHandle,
319}
320
321struct PreHopr<Chain, Graph, Net, Ct> {
326 chain_id: ChainKeypair,
327 transport_id: OffchainKeypair,
328 cfg: HoprLibConfig,
329 state: Arc<AtomicHoprState>,
330 transport_api: HoprTransport<Chain, Graph, Net>,
331 chain_api: Chain,
332
333 ticket_event_subscribers: (
334 async_broadcast::Sender<TicketEvent>,
335 async_broadcast::InactiveReceiver<TicketEvent>,
336 ),
337 pix_event_subscribers: (
338 async_broadcast::Sender<PixEvent>,
339 async_broadcast::InactiveReceiver<PixEvent>,
340 ),
341 processes: AbortableList<HoprLibProcess>,
342 session_tx: futures::channel::mpsc::Sender<IncomingSession>,
343 cover_traffic: Ct,
344 network: Net,
345 network_process: BoxedProcessFn,
346}
347
348async fn drain_incoming_data<S: futures::Stream + Unpin>(mut reader: S) {
355 let mut received: u64 = 0;
356 let mut last_report = std::time::Instant::now();
357 while reader.next().await.is_some() {
358 received += 1;
359 if last_report.elapsed().as_secs() >= 60 {
360 tracing::info!(
361 received,
362 "incoming-data drain: unrelated packets discarded in last ~1 min"
363 );
364 received = 0;
365 last_report = std::time::Instant::now();
366 }
367 }
368}
369
370async fn pre_build_inner<Chain, Graph, Net, Ct>(
371 configured: HoprBuilderConfigured<Chain, Graph, Net, Ct>,
372 session_tx: futures::channel::mpsc::Sender<IncomingSession>,
373 mut processes: AbortableList<HoprLibProcess>,
374) -> Result<PreHopr<Chain, Graph, Net, Ct>, HoprLibError>
375where
376 Chain: HoprChainApi + Clone + Send + Sync + 'static,
377 Graph: HoprGraphApi<HoprNodeId = hopr_api::OffchainPublicKey> + Clone + Send + Sync + 'static,
378 <Graph as hopr_api::graph::NetworkGraphTraverse>::Observed:
379 hopr_api::graph::traits::EdgeObservableRead + Send + 'static,
380 <Graph as hopr_api::graph::NetworkGraphWrite>::Observed: hopr_api::graph::traits::EdgeObservableWrite + Send,
381 Net: NetworkView + NetworkStreamControl + Send + Sync + Clone + 'static,
382 Ct: ProbingTrafficGeneration + CoverTrafficGeneration + Send + Sync + 'static,
383{
384 let peer_discovery_tx = Some(configured.peer_discovery_tx);
385 let ctx = configured.ctx;
386 ctx.cfg.validate()?;
387
388 let chain_api = (configured
389 .chain_factory
390 .ok_or(HoprLibError::BuilderError("missing chain factory"))?)(&ctx);
391 let graph = (configured
392 .graph_factory
393 .ok_or(HoprLibError::BuilderError("missing graph factory"))?)(&ctx);
394 let (network, network_process) =
395 (configured
396 .network_factory
397 .ok_or(HoprLibError::BuilderError("missing network factory"))?)(ctx.clone())
398 .await?;
399 let cover_traffic = (configured
400 .ct_factory
401 .ok_or(HoprLibError::BuilderError("missing cover traffic factory"))?)(&ctx);
402
403 let (chain_id, transport_id) = (ctx.chain_key.clone(), ctx.packet_key.clone());
404
405 let transport_api = HoprTransport::new(
406 (&chain_id, &transport_id),
407 chain_api.clone(),
408 graph.clone(),
409 vec![(&ctx.cfg.host).try_into().map_err(HoprLibError::TransportError)?],
410 ctx.cfg.protocol.clone(),
411 )
412 .map_err(HoprLibError::TransportError)?;
413
414 #[cfg(all(feature = "telemetry", not(test)))]
415 {
416 use hopr_api::types::primitive::traits::AsUnixTimestamp;
417 METRIC_PROCESS_START_TIME.set(std::time::SystemTime::now().as_unix_timestamp().as_secs_f64());
418 METRIC_HOPR_LIB_VERSION.set(
419 &[const_format::formatcp!("{}", constants::APP_VERSION)],
420 const_format::formatcp!(
421 "{}.{}",
422 env!("CARGO_PKG_VERSION_MAJOR"),
423 env!("CARGO_PKG_VERSION_MINOR")
424 )
425 .parse()
426 .unwrap_or(0.0),
427 );
428 }
429
430 let (mut new_tickets_tx, new_tickets_rx) = async_broadcast::broadcast(2048);
431 new_tickets_tx.set_await_active(false);
432 new_tickets_tx.set_overflow(true);
433
434 let (mut ssa_tx, ssa_rx) = async_broadcast::broadcast(2048);
435 ssa_tx.set_await_active(false);
436 ssa_tx.set_overflow(true);
437
438 let me_onchain = chain_id.public().to_address();
439
440 #[cfg(feature = "testing")]
441 tracing::warn!("!! FOR TESTING ONLY !! Node is running with some safety checks disabled!");
442
443 tracing::info!(
444 address = %me_onchain,
445 minimum_balance = %*SUGGESTED_NATIVE_BALANCE,
446 "node is not started, please fund this node",
447 );
448
449 tracing::info!(
450 suggested_minimum_balance = %*SUGGESTED_NATIVE_BALANCE,
451 "node about to start, checking for funds",
452 );
453 let funding_timeout = Duration::from_secs(200);
454 crate::helpers::wait_for_balance(*MIN_NATIVE_BALANCE, funding_timeout, me_onchain, &chain_api)
455 .await
456 .map_err(|_| {
457 HoprLibError::InsufficientFunds(format!(
458 "failed to fund the node within {} seconds",
459 funding_timeout.as_secs()
460 ))
461 })?;
462
463 tracing::info!("starting HOPR node...");
464 let balance: hopr_api::types::primitive::prelude::XDaiBalance =
465 chain_api.balance(me_onchain).await.map_err(HoprLibError::chain)?;
466 let minimum_balance = *constants::MIN_NATIVE_BALANCE;
467
468 tracing::info!(address = %me_onchain, %balance, %minimum_balance, "node information");
469
470 if balance.le(&minimum_balance) {
471 return Err(HoprLibError::InsufficientFunds(
472 "cannot start the node without a sufficiently funded wallet".into(),
473 ));
474 }
475
476 #[cfg(debug_assertions)]
477 let skip_protocol_checks = ctx.cfg.disable_protocol_checks;
478 #[cfg(not(debug_assertions))]
479 let skip_protocol_checks = false;
480
481 let network_min_ticket_price = chain_api.minimum_ticket_price().await.map_err(HoprLibError::chain)?;
482 let configured_ticket_price = ctx.cfg.protocol.packet.codec.outgoing_ticket_price;
483 if !skip_protocol_checks && configured_ticket_price.is_some_and(|c| c < network_min_ticket_price) {
484 return Err(HoprLibError::GeneralError(format!(
485 "configured outgoing ticket price < network minimum: {configured_ticket_price:?} < \
486 {network_min_ticket_price}"
487 )));
488 }
489
490 let network_min_win_prob = chain_api
491 .minimum_incoming_ticket_win_prob()
492 .await
493 .map_err(HoprLibError::chain)?;
494 let configured_win_prob = ctx.cfg.protocol.packet.codec.outgoing_win_prob;
495
496 if !skip_protocol_checks && configured_win_prob.is_some_and(|c| c.approx_cmp(&network_min_win_prob).is_lt()) {
497 return Err(HoprLibError::GeneralError(format!(
498 "configured outgoing win probability < network minimum: {configured_win_prob:?} < {network_min_win_prob}"
499 )));
500 }
501
502 tracing::info!(
503 peer_id = %transport_id.public().to_peerid_str(),
504 address = %me_onchain,
505 version = constants::APP_VERSION,
506 "Node information"
507 );
508
509 let safe_addr = ctx.cfg.safe_module.safe_address;
510 if me_onchain == safe_addr {
511 return Err(HoprLibError::GeneralError(
512 "cannot use self as staking safe address".into(),
513 ));
514 }
515
516 tracing::info!(%safe_addr, "registering safe with this node");
517 match chain_api.register_safe(&safe_addr).await {
518 Ok(awaiter) => {
519 awaiter.await.map_err(|error| {
520 tracing::error!(%safe_addr, %error, "safe registration failed");
521 HoprLibError::chain(error)
522 })?;
523 tracing::info!(%safe_addr, "safe successfully registered with this node");
524 }
525 Err(SafeRegistrationError::AlreadyRegistered(registered_safe)) => {
526 if registered_safe == safe_addr {
527 tracing::info!(%safe_addr, "this safe is already registered with this node");
528 } else {
529 tracing::error!(%safe_addr, %registered_safe, "node registered with different safe");
530 return Err(HoprLibError::GeneralError("node registered with different safe".into()));
531 }
532 }
533 Err(error) => {
534 tracing::error!(%safe_addr, %error, "safe registration failed");
535 return Err(HoprLibError::chain(error));
536 }
537 }
538
539 let multiaddresses_to_announce = if ctx.cfg.publish {
540 transport_api.announceable_multiaddresses()
541 } else {
542 Vec::new()
543 };
544
545 multiaddresses_to_announce
546 .iter()
547 .filter(|a| !is_public_address(a))
548 .for_each(|multi_addr| tracing::warn!(?multi_addr, "announcing private multiaddress"));
549
550 if ctx.cfg.publish {
552 let key_binding_fee = chain_api.key_binding_fee().await.map_err(HoprLibError::chain)?;
553 if !key_binding_fee.is_zero() {
554 let safe_hopr_balance: hopr_api::types::primitive::prelude::HoprBalance =
555 chain_api.balance(safe_addr).await.map_err(HoprLibError::chain)?;
556 if safe_hopr_balance < key_binding_fee
561 && chain_api
562 .await_key_binding(transport_id.public(), Duration::from_secs(1))
563 .await
564 .is_err()
565 {
566 tracing::warn!(
567 %safe_addr,
568 safe_balance = %safe_hopr_balance,
569 required = %key_binding_fee,
570 "the safe does not hold enough wxHOPR to pay the node announcement (key-binding) fee, waiting for \
571 it to be funded before announcing the node",
572 );
573 let announce_timeout = Duration::from_secs(200);
574 crate::helpers::wait_for_balance(key_binding_fee, announce_timeout, safe_addr, &chain_api)
575 .await
576 .map_err(|_| {
577 HoprLibError::InsufficientFunds(format!(
578 "the safe {safe_addr} does not hold enough wxHOPR (needs at least {key_binding_fee}) to \
579 announce the node on chain; fund the safe with wxHOPR and restart the node"
580 ))
581 })?;
582 }
583 }
584 }
585
586 let chain_api_clone = chain_api.clone();
587 let me_offchain = *transport_id.public();
588 let node_ready = spawn(async move {
589 chain_api_clone
590 .await_key_binding(&me_offchain, NODE_READY_TIMEOUT)
591 .await
592 });
593
594 tracing::info!(?multiaddresses_to_announce, "announcing node on chain");
595 match chain_api.announce(&multiaddresses_to_announce, &transport_id).await {
596 Ok(awaiter) => {
597 awaiter.await.map_err(|error| {
598 tracing::error!(
599 ?multiaddresses_to_announce,
600 %error,
601 "node announcement failed; this is commonly caused by the safe not holding enough wxHOPR to pay \
602 the key-binding fee",
603 );
604 HoprLibError::chain(error)
605 })?;
606 tracing::info!(?multiaddresses_to_announce, "node announced successfully");
607 }
608 Err(AnnouncementError::AlreadyAnnounced) => {
609 tracing::info!("node already announced on chain");
610 }
611 Err(error) => {
612 tracing::error!(%error, "failed to transmit node announcement");
613 return Err(HoprLibError::chain(error));
614 }
615 }
616
617 let this_node_account = node_ready
618 .await
619 .map_err(HoprLibError::other)?
620 .map_err(HoprLibError::chain)?;
621 if this_node_account.chain_addr != me_onchain || this_node_account.safe_address.is_none_or(|a| a != safe_addr) {
622 tracing::error!(%this_node_account, "account key-binding mismatch");
623 return Err(HoprLibError::GeneralError("account key-binding mismatch".into()));
624 }
625
626 tracing::info!(%this_node_account, "node account is ready");
627
628 {
630 let network_events = network.subscribe_network_events();
631 let graph_updater = graph.clone();
632 spawn(async move {
633 network_events
634 .for_each(|event| {
635 let graph_updater = graph_updater.clone();
636 async move {
637 let (peer_id, connected) = match event {
638 hopr_api::network::NetworkEvent::PeerConnected(p) => (p, true),
639 hopr_api::network::NetworkEvent::PeerDisconnected(p) => (p, false),
640 };
641 if let Ok(opk) = hopr_api::OffchainPublicKey::from_peerid(&peer_id) {
642 graph_updater.record_edge(hopr_api::graph::MeasurableEdge::<
643 hopr_transport::NeighborTelemetry,
644 hopr_transport::PathTelemetry,
645 >::ConnectionStatus {
646 peer: opk,
647 connected,
648 });
649 } else {
650 tracing::error!(%peer_id, "failed to convert peer ID to public key for graph update");
651 }
652 }
653 })
654 .await;
655 });
656 }
657
658 {
660 let chain_events = chain_api
661 .subscribe_with_state_sync([StateSyncOptions::PublicAccounts, StateSyncOptions::OpenedChannels])
662 .map_err(HoprLibError::chain)?;
663
664 let graph_updater = graph.clone();
665 let chain_reader = chain_api.clone();
666
667 let own_chain_addr = me_onchain;
668 let own_packet_key = *transport_id.public();
669
670 let ticket_price = Arc::new(parking_lot::RwLock::new(
671 chain_reader.minimum_ticket_price().await.unwrap_or_default(),
672 ));
673 let win_probability = Arc::new(parking_lot::RwLock::new(
674 chain_reader
675 .minimum_incoming_ticket_win_prob()
676 .await
677 .unwrap_or_default(),
678 ));
679
680 let proc = chain_wiring::process_chain_events(
681 chain_reader,
682 graph_updater,
683 transport_api.surb_store().clone(),
684 chain_events,
685 own_chain_addr,
686 own_packet_key,
687 ticket_price,
688 win_probability,
689 peer_discovery_tx,
690 )
691 .inspect(|_| {
692 tracing::warn!(
693 task = "chain-to-graph event wiring",
694 "long-running background task finished"
695 )
696 });
697 processes.insert(HoprLibProcess::ChannelEvents, hopr_utils::spawn_as_abortable!(proc));
698 }
699
700 Ok(PreHopr {
701 chain_id,
702 transport_id,
703 cfg: ctx.cfg,
704 state: Arc::new(AtomicHoprState::new(HoprState::Uninitialized)),
705 transport_api,
706 chain_api,
707 ticket_event_subscribers: (new_tickets_tx, new_tickets_rx.deactivate()),
708 pix_event_subscribers: (ssa_tx, ssa_rx.deactivate()),
709 processes,
710 session_tx,
711 cover_traffic,
712 network,
713 network_process,
714 })
715}
716
717macro_rules! impl_build_methods {
722 () => {
723 pub async fn build_edge<TFact>(
725 self,
726 ticket_factory: TFact,
727 ) -> Result<Hopr<Chain, Graph, Net, ()>, HoprLibError>
728 where
729 TFact: TicketFactory + Clone + Send + Sync + 'static,
730 {
731 let (configured, session_tx, processes) = self.into_parts();
732 let pre = pre_build_inner(configured, session_tx, processes).await?;
733
734 tracing::info!("starting transport for edge (entry) node");
735 let (socket, transport_processes) = pre
736 .transport_api
737 .run_entry(
738 pre.cover_traffic,
739 pre.network,
740 pre.network_process,
741 ticket_factory,
742 Some(BroadcastSenderSink(pre.pix_event_subscribers.0.clone())),
743 )
744 .await?;
745
746 spawn(drain_incoming_data(socket.reader()));
748
749 let mut processes = pre.processes;
750 processes.flat_map_extend_from(transport_processes, HoprLibProcess::Transport);
751
752 let hopr = Hopr {
753 chain_id: NodeOnchainIdentity {
754 node_address: pre.chain_id.public().to_address(),
755 safe_address: pre.cfg.safe_module.safe_address,
756 module_address: pre.cfg.safe_module.module_address,
757 },
758 cfg: pre.cfg,
759 state: pre.state.clone(),
760 ticket_event_subscribers: pre.ticket_event_subscribers,
761 pix_event_subscribers: pre.pix_event_subscribers,
762 transport_id: pre.transport_id,
763 transport_api: pre.transport_api,
764 chain_api: pre.chain_api,
765 processes,
766 ticket_manager: (),
767 };
768
769 hopr.state.store(HoprState::Running, std::sync::atomic::Ordering::Relaxed);
770 tracing::info!(
771 id = %hopr.transport_id.public().to_peerid_str(),
772 version = constants::APP_VERSION,
773 "EDGE NODE STARTED AND RUNNING"
774 );
775
776 Ok(hopr)
777 }
778
779 pub async fn build_entry<TFact>(
784 self,
785 ticket_factory: TFact,
786 ) -> Result<Hopr<Chain, Graph, Net, ()>, HoprLibError>
787 where
788 TFact: TicketFactory + Clone + Send + Sync + 'static,
789 {
790 let (configured, session_tx, processes) = self.into_parts();
791 let pre = pre_build_inner(configured, session_tx, processes).await?;
792
793 tracing::info!("starting transport for entry node");
794 let (socket, transport_processes) = pre
795 .transport_api
796 .run_entry(
797 pre.cover_traffic,
798 pre.network,
799 pre.network_process,
800 ticket_factory,
801 Some(BroadcastSenderSink(pre.pix_event_subscribers.0.clone())),
802 )
803 .await?;
804
805 spawn(drain_incoming_data(socket.reader()));
807
808 let mut processes = pre.processes;
809 processes.flat_map_extend_from(transport_processes, HoprLibProcess::Transport);
810
811 let hopr = Hopr {
812 chain_id: NodeOnchainIdentity {
813 node_address: pre.chain_id.public().to_address(),
814 safe_address: pre.cfg.safe_module.safe_address,
815 module_address: pre.cfg.safe_module.module_address,
816 },
817 cfg: pre.cfg,
818 state: pre.state.clone(),
819 ticket_event_subscribers: pre.ticket_event_subscribers,
820 pix_event_subscribers: pre.pix_event_subscribers,
821 transport_id: pre.transport_id,
822 transport_api: pre.transport_api,
823 chain_api: pre.chain_api,
824 processes,
825 ticket_manager: (),
826 };
827
828 hopr.state.store(HoprState::Running, std::sync::atomic::Ordering::Relaxed);
829 tracing::info!(
830 id = %hopr.transport_id.public().to_peerid_str(),
831 version = constants::APP_VERSION,
832 "ENTRY NODE STARTED AND RUNNING"
833 );
834
835 Ok(hopr)
836 }
837
838 pub async fn build_exit<TFact>(
843 self,
844 ticket_factory: TFact,
845 ) -> Result<Hopr<Chain, Graph, Net, ()>, HoprLibError>
846 where
847 TFact: TicketFactory + Clone + Send + Sync + 'static,
848 {
849 let (configured, session_tx, processes) = self.into_parts();
850 let pre = pre_build_inner(configured, session_tx, processes).await?;
851
852 tracing::info!("starting transport for exit node");
853 let (socket, transport_processes) = pre
854 .transport_api
855 .run_exit::<TFact, Ct, _>(
856 pre.cover_traffic,
857 pre.network,
858 pre.network_process,
859 ticket_factory,
860 Some(BroadcastSenderSink(pre.pix_event_subscribers.0.clone())),
861 pre.session_tx,
862 )
863 .await?;
864
865 spawn(drain_incoming_data(socket.reader()));
867
868 let mut processes = pre.processes;
869 processes.flat_map_extend_from(transport_processes, HoprLibProcess::Transport);
870
871 let hopr = Hopr {
872 chain_id: NodeOnchainIdentity {
873 node_address: pre.chain_id.public().to_address(),
874 safe_address: pre.cfg.safe_module.safe_address,
875 module_address: pre.cfg.safe_module.module_address,
876 },
877 cfg: pre.cfg,
878 state: pre.state.clone(),
879 ticket_event_subscribers: pre.ticket_event_subscribers,
880 pix_event_subscribers: pre.pix_event_subscribers,
881 transport_id: pre.transport_id,
882 transport_api: pre.transport_api,
883 chain_api: pre.chain_api,
884 processes,
885 ticket_manager: (),
886 };
887
888 hopr.state.store(HoprState::Running, std::sync::atomic::Ordering::Relaxed);
889 tracing::info!(
890 id = %hopr.transport_id.public().to_peerid_str(),
891 version = constants::APP_VERSION,
892 "EXIT NODE STARTED AND RUNNING"
893 );
894
895 Ok(hopr)
896 }
897
898 pub async fn build_full<TMgr, TFact>(
900 self,
901 ticket_manager: TMgr,
902 ticket_factory: TFact,
903 ) -> Result<Hopr<Chain, Graph, Net, TMgr>, HoprLibError>
904 where
905 TMgr: TicketManagement + Clone + Send + Sync + 'static,
906 TFact: TicketFactory + Clone + Send + Sync + 'static,
907 {
908 let (configured, session_tx, processes) = self.into_parts();
909 let pre = pre_build_inner(configured, session_tx, processes).await?;
910 let mut processes = pre.processes;
911
912 tracing::info!("starting ticket events processor");
913 let (tickets_tx, tickets_rx) = bounded_sink_channel::<TicketEvent>(8192);
914
915 let (tickets_rx_stream, tickets_handle) = hopr_utils::runtime::DropAbortable::new(tickets_rx);
917
918 processes.insert(HoprLibProcess::TicketEvents, tickets_handle);
919 let new_ticket_tx = pre.ticket_event_subscribers.0.clone();
920 let tmgr_clone = ticket_manager.clone();
921 spawn(
922 hopr_utils::runtime::diagnostics::instrument(
923 tickets_rx_stream
924 .for_each(move |event| {
925 if let TicketEvent::WinningTicket(ticket) = &event
926 && let Err(error) = tmgr_clone.insert_incoming_ticket(**ticket)
927 {
928 tracing::error!(%error, "failed to insert incoming ticket");
929 }
930 if let Err(error) = new_ticket_tx.try_broadcast(event) {
931 tracing::error!(%error, "failed to broadcast ticket event");
932 }
933 futures::future::ready(())
934 })
935 .inspect(|_| {
936 tracing::warn!(task = %HoprLibProcess::TicketEvents, "long-running background task finished")
937 }),
938 "hopr_lib_ticket_events",
939 module_path!(),
940 file!(),
941 line!(),
942 ),
943 );
944
945 {
946 let chain_for_neglect = pre.chain_api.clone();
947 let tmgr_for_neglect = ticket_manager.clone();
948 let events = pre.chain_api.subscribe().map_err(HoprLibError::chain)?;
949 let (neglect_handle, neglect_reg) = hopr_utils::runtime::AbortHandle::new_pair();
950 let neglect_task = futures::stream::Abortable::new(
951 events.filter_map(move |event| {
952 futures::future::ready(match event {
953 ChainEvent::ChannelClosed(ch) => Some(ch),
954 _ => None,
955 })
956 }),
957 neglect_reg,
958 )
959 .for_each(move |closed_channel| {
960 let chain = chain_for_neglect.clone();
961 let tmgr = tmgr_for_neglect.clone();
962 async move {
963 match closed_channel.direction(chain.me()) {
964 Some(ChannelDirection::Incoming) => {
965 match tmgr.neglect_tickets(closed_channel.get_id(), None) {
966 Ok(neglected) if !neglected.is_empty() => {
967 tracing::warn!(
968 num_neglected = neglected.len(),
969 %closed_channel,
970 "tickets on incoming closed channel were neglected"
971 );
972 }
973 Ok(_) => {}
974 Err(error) => {
975 tracing::error!(
976 %error, %closed_channel,
977 "failed to neglect tickets on closed channel"
978 );
979 }
980 }
981 }
982 Some(ChannelDirection::Outgoing) => {}
983 _ => {}
984 }
985 }
986 })
987 .inspect(|_| {
988 tracing::warn!(
989 task = %HoprLibProcess::ChannelClosureNeglect,
990 "channel closure ticket neglect task finished"
991 )
992 });
993 spawn(hopr_utils::runtime::diagnostics::instrument(
994 neglect_task,
995 "hopr_lib_channel_closure_neglect",
996 module_path!(),
997 file!(),
998 line!(),
999 ));
1000 processes.insert(HoprLibProcess::ChannelClosureNeglect, neglect_handle);
1001 }
1002
1003 tracing::info!("starting transport for full (relay) node");
1004 let (socket, transport_processes) = pre
1005 .transport_api
1006 .run_relay(
1007 pre.cover_traffic,
1008 pre.network,
1009 pre.network_process,
1010 tickets_tx,
1011 ticket_factory,
1012 Some(BroadcastSenderSink(pre.pix_event_subscribers.0.clone())),
1013 pre.session_tx,
1014 )
1015 .await?;
1016 spawn(drain_incoming_data(socket.reader()));
1018 processes.flat_map_extend_from(transport_processes, HoprLibProcess::Transport);
1019
1020 let hopr = Hopr {
1021 chain_id: NodeOnchainIdentity {
1022 node_address: pre.chain_id.public().to_address(),
1023 safe_address: pre.cfg.safe_module.safe_address,
1024 module_address: pre.cfg.safe_module.module_address,
1025 },
1026 cfg: pre.cfg,
1027 state: pre.state.clone(),
1028 ticket_event_subscribers: pre.ticket_event_subscribers,
1029 pix_event_subscribers: pre.pix_event_subscribers,
1030 transport_id: pre.transport_id,
1031 transport_api: pre.transport_api,
1032 chain_api: pre.chain_api,
1033 processes,
1034 ticket_manager,
1035 };
1036
1037 hopr.state.store(HoprState::Running, std::sync::atomic::Ordering::Relaxed);
1038
1039 tracing::info!(
1040 id = %hopr.transport_id.public().to_peerid_str(),
1041 version = constants::APP_VERSION,
1042 "FULL NODE STARTED AND RUNNING"
1043 );
1044
1045 #[cfg(all(feature = "telemetry", not(test)))]
1046 METRIC_HOPR_NODE_INFO.set(
1047 &[
1048 &hopr.transport_id.public().to_peerid_str(),
1049 &hopr.chain_id.node_address.to_string(),
1050 &hopr.chain_id.safe_address.to_string(),
1051 &hopr.chain_id.module_address.to_string(),
1052 ],
1053 1.0,
1054 );
1055
1056 Ok(hopr)
1057 }
1058 };
1059}
1060
1061#[cfg(feature = "session-server")]
1063impl<Chain, Graph, Net, Ct> HoprBuilderWithSession<Chain, Graph, Net, Ct>
1064where
1065 Chain: HoprChainApi + Clone + Send + Sync + 'static,
1066 Graph: HoprGraphApi<HoprNodeId = hopr_api::OffchainPublicKey> + Clone + Send + Sync + 'static,
1067 <Graph as hopr_api::graph::NetworkGraphTraverse>::Observed:
1068 hopr_api::graph::traits::EdgeObservableRead + Send + 'static,
1069 <Graph as hopr_api::graph::NetworkGraphWrite>::Observed: hopr_api::graph::traits::EdgeObservableWrite + Send,
1070 Net: NetworkView + NetworkStreamControl + Send + Sync + Clone + 'static,
1071 Ct: ProbingTrafficGeneration + CoverTrafficGeneration + Send + Sync + 'static,
1072{
1073 impl_build_methods!();
1074
1075 fn into_parts(
1076 self,
1077 ) -> (
1078 HoprBuilderConfigured<Chain, Graph, Net, Ct>,
1079 futures::channel::mpsc::Sender<IncomingSession>,
1080 AbortableList<HoprLibProcess>,
1081 ) {
1082 let mut processes = AbortableList::<HoprLibProcess>::default();
1083 processes.insert(HoprLibProcess::SessionServer, self.session_handle);
1084 (self.inner, self.session_tx, processes)
1085 }
1086}
1087
1088#[cfg(not(feature = "session-server"))]
1090impl<Chain, Graph, Net, Ct> HoprBuilderConfigured<Chain, Graph, Net, Ct>
1091where
1092 Chain: HoprChainApi + Clone + Send + Sync + 'static,
1093 Graph: HoprGraphApi<HoprNodeId = hopr_api::OffchainPublicKey> + Clone + Send + Sync + 'static,
1094 <Graph as hopr_api::graph::NetworkGraphTraverse>::Observed:
1095 hopr_api::graph::traits::EdgeObservableRead + Send + 'static,
1096 <Graph as hopr_api::graph::NetworkGraphWrite>::Observed: hopr_api::graph::traits::EdgeObservableWrite + Send,
1097 Net: NetworkView + NetworkStreamControl + Send + Sync + Clone + 'static,
1098 Ct: ProbingTrafficGeneration + CoverTrafficGeneration + Send + Sync + 'static,
1099{
1100 impl_build_methods!();
1101
1102 fn into_parts(
1103 self,
1104 ) -> (
1105 HoprBuilderConfigured<Chain, Graph, Net, Ct>,
1106 futures::channel::mpsc::Sender<IncomingSession>,
1107 AbortableList<HoprLibProcess>,
1108 ) {
1109 let (tx, _rx) = futures::channel::mpsc::channel::<IncomingSession>(1);
1110 let processes = AbortableList::<HoprLibProcess>::default();
1111 (self, tx, processes)
1112 }
1113}