holochain_p2p 0.6.2

holochain specific wrapper around more generic p2p module
use super::*;
use crate::actor::*;
use std::time::Duration;

mod actor;
pub use actor::WrapEvtSender;

/// Spawn a new HolochainP2p actor.
/// Conductor will call this on initialization.
pub async fn spawn_holochain_p2p(
    config: HolochainP2pConfig,
    lair_client: holochain_keystore::MetaLairClient,
) -> HolochainP2pResult<DynHcP2p> {
    tracing::info!(?config, "Launching HolochainP2p");
    actor::HolochainP2pActor::create(config, lair_client).await
}

/// Callback function to retrieve a peer meta database handle for a dna hash.
pub type GetDbPeerMeta = Arc<
    dyn Fn(DnaHash) -> BoxFut<'static, HolochainP2pResult<DbWrite<DbKindPeerMetaStore>>>
        + 'static
        + Send
        + Sync,
>;

/// Callback function to retrieve an op store database handle for a dna hash.
pub type GetDbOpStore = Arc<
    dyn Fn(DnaHash) -> BoxFut<'static, HolochainP2pResult<DbWrite<DbKindDht>>>
        + 'static
        + Send
        + Sync,
>;

/// Callback function to retrieve a cache database handle for a dna hash.
pub type GetDbCache = Arc<
    dyn Fn(DnaHash) -> BoxFut<'static, HolochainP2pResult<DbWrite<DbKindCache>>>
        + 'static
        + Send
        + Sync,
>;

/// Callback function to retrieve a conductor database.
pub type GetDbConductor =
    Arc<dyn Fn() -> BoxFut<'static, DbWrite<DbKindConductor>> + 'static + Send + Sync>;

/// Configure reporting.
#[derive(Default)]
pub enum ReportConfig {
    /// No reporting.
    #[default]
    None,

    /// Write reports to a rotating on-disk JsonL file.
    JsonLines(hc_report::HcReportConfig),
}

/// HolochainP2p config struct.
pub struct HolochainP2pConfig {
    /// Callback function to retrieve a peer meta database handle for a dna hash.
    ///
    /// **Must be set explicitly** — the [`Default`] value panics when called. Example:
    ///
    /// ```ignore
    /// get_db_peer_meta: Arc::new(move |dna_hash| {
    ///     let res = spaces.peer_meta(&dna_hash);
    ///     Box::pin(async move { res.map_err(HolochainP2pError::other) })
    /// }),
    /// ```
    pub get_db_peer_meta: GetDbPeerMeta,

    /// Interval for a pruning task to remove expired values from the peer meta store.
    ///
    /// Default: 10 s
    pub peer_meta_pruning_interval_ms: u64,

    /// Callback function to retrieve an op store database handle for a dna hash.
    ///
    /// Called when a new space is created to open the DHT [`DbWrite`] for that DNA. Gossip uses
    /// the returned handle to read and write ops for that space.
    ///
    /// **Must be set explicitly** — the [`Default`] value panics when called. Example:
    ///
    /// ```ignore
    /// get_db_op_store: Arc::new(move |dna_hash| {
    ///     let res = spaces.dht(&dna_hash);
    ///     Box::pin(async move { res.map_err(HolochainP2pError::other) })
    /// }),
    /// ```
    pub get_db_op_store: GetDbOpStore,

    /// Callback function to retrieve a cache database handle for a dna hash.
    ///
    /// Called when a new space is created to open the cache [`DbWrite`] for that DNA.
    /// The returned handle is used alongside [`get_db_op_store`](Self::get_db_op_store)
    /// to report the total local op count to gossip.
    ///
    /// **Must be set explicitly** — the [`Default`] value panics when called. Example:
    ///
    /// ```ignore
    /// get_db_cache: Arc::new(move |dna_hash| {
    ///     let res = spaces.cache(&dna_hash);
    ///     Box::pin(async move { res.map_err(HolochainP2pError::other) })
    /// }),
    /// ```
    pub get_db_cache: GetDbCache,

    /// Callback function to retrieve the conductor database handle.
    ///
    /// **Must be set explicitly** — the [`Default`] value panics when called. Example:
    ///
    /// ```ignore
    /// get_conductor_db: Arc::new(|| {
    ///     let res = conductor_db.clone();
    ///     Box::pin(async move { Ok(res) })
    /// }),
    /// ```
    pub get_conductor_db: GetDbConductor,

    /// The arc factor to apply to target arc hints.
    pub target_arc_factor: u32,

    /// Authentication material if required by the bootstrap service.
    pub auth_material_bootstrap: Option<Vec<u8>>,

    /// Authentication material if required by the relay service.
    pub auth_material_relay: Option<Vec<u8>>,

    /// Configuration to pass to Kitsune2.
    ///
    /// This should contain module configurations such as [CoreBootstrapModConfig](kitsune2_core::factories::CoreBootstrapModConfig).
    pub network_config: Option<serde_json::Value>,

    /// The compat params to use.
    pub compat: NetworkCompatParams,

    /// The amount of time to elapse before a request times out.
    ///
    /// Defaults to 60 seconds.
    pub request_timeout: Duration,

    /// Configure reporting.
    ///
    /// If `None`, will not report.
    pub report: ReportConfig,

    /// The maximum number of incoming requests that will be handled concurrently.
    ///
    /// Additional incoming requests over this limit are ignored.
    /// This is only applied to incoming authority requests, i.e.
    /// `get`, `get_links`, `count_links`, `get_agent_activity`, and `must_get_agent_activity`.
    pub incoming_request_concurrency_limit: u16,

    /// If true, will disable the default bootstrap module.
    ///
    /// This flag is only used in tests.
    #[cfg(feature = "test_utils")]
    pub disable_bootstrap: bool,

    /// If true, will replace the default publish module with a no-op module.
    ///
    /// This flag is only used in tests.
    #[cfg(feature = "test_utils")]
    pub disable_publish: bool,

    /// If true, will leave the default no-op gossip module in place rather than replacing it with
    /// the real gossip module.
    ///
    /// This flag is only used in tests.
    #[cfg(feature = "test_utils")]
    pub disable_gossip: bool,

    /// Request using the in-memory bootstrap module instead of the real one.
    ///
    /// This flag is only used in tests.
    #[cfg(feature = "test_utils")]
    pub mem_bootstrap: bool,
}

impl std::fmt::Debug for HolochainP2pConfig {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut dbg = f.debug_struct("HolochainP2pConfig");
        dbg.field("compat", &self.compat);
        dbg.field(
            "auth_material_bootstrap",
            &self.auth_material_bootstrap.as_ref().map(|_| "<redacted>"),
        );
        dbg.field(
            "auth_material_relay",
            &self.auth_material_relay.as_ref().map(|_| "<redacted>"),
        );
        dbg.field("request_timeout", &self.request_timeout);
        dbg.field("target_arc_factor", &self.target_arc_factor);
        dbg.field("network_config", &self.network_config);

        #[cfg(feature = "test_utils")]
        {
            dbg.field("disable_bootstrap", &self.disable_bootstrap)
                .field("mem_bootstrap", &self.mem_bootstrap)
                .field("disable_publish", &self.disable_publish)
                .field("disable_gossip", &self.disable_gossip);
        }

        dbg.finish()
    }
}

impl Default for HolochainP2pConfig {
    /// Returns a config with placeholder values.
    ///
    /// The database callbacks (`get_db_peer_meta`, `get_db_op_store`, `get_db_cache`,
    /// `get_conductor_db`) all panic with `unimplemented!()` when invoked — they will
    /// be called the first time a space is created, so **always supply concrete
    /// implementations** before passing this config to [`spawn_holochain_p2p`].
    /// Use struct-update syntax rather than relying on this default entirely:
    ///
    /// ```ignore
    /// let config = HolochainP2pConfig {
    ///     get_db_op_store: Arc::new(move |dna_hash| { ... }),
    ///     get_db_cache: Arc::new(move |dna_hash| { ... }),
    ///     ..HolochainP2pConfig::default()
    /// };
    /// ```
    fn default() -> Self {
        Self {
            get_db_peer_meta: Arc::new(|_| unimplemented!()),
            peer_meta_pruning_interval_ms: 10_000,
            get_db_op_store: Arc::new(|_| unimplemented!()),
            get_db_cache: Arc::new(|_| unimplemented!()),
            get_conductor_db: Arc::new(|| unimplemented!()),
            target_arc_factor: 1,
            auth_material_bootstrap: None,
            auth_material_relay: None,
            network_config: None,
            compat: Default::default(),
            request_timeout: Duration::from_secs(60),
            report: ReportConfig::default(),
            incoming_request_concurrency_limit: 4,
            #[cfg(feature = "test_utils")]
            disable_bootstrap: false,
            #[cfg(feature = "test_utils")]
            disable_publish: false,
            #[cfg(feature = "test_utils")]
            disable_gossip: false,
            #[cfg(feature = "test_utils")]
            mem_bootstrap: true,
        }
    }
}

/// See [NetworkCompatParams::proto_ver].
pub const HCP2P_PROTO_VER: u32 = 2;

/// Some parameters used as part of a protocol compatibility check during tx5 preflight
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
pub struct NetworkCompatParams {
    /// The current protocol version. This should be incremented whenever
    /// any breaking protocol changes are made to prevent incompatible
    /// nodes from talking to each other.
    pub proto_ver: u32,
}

impl std::fmt::Debug for NetworkCompatParams {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("NetworkCompatParams")
            .field("proto_ver", &self.proto_ver)
            .finish()
    }
}

impl Default for NetworkCompatParams {
    fn default() -> Self {
        Self {
            proto_ver: HCP2P_PROTO_VER,
        }
    }
}