pkarr 7.0.0

Public-Key Addressable Resource Records (Pkarr); publish and resolve DNS records over Mainline DHT
Documentation
use crate::dht::{DhtClient, ReportPolicy, ResolveOutcome, ResolveReport, ResolveResponse};
use crate::{PublicKey, SignedPacket, StoredNodeCount};

use crate::client::{PublishError, ResolveError};

use super::{
    resolve_result_accumulator::ResolveResultAccumulator, BackendResolvePolicy, CacheContext,
};

#[derive(Debug)]
pub(in crate::client) struct DhtBackend {
    client: DhtClient,
    report_policy: ReportPolicy,
}

impl DhtBackend {
    pub(super) fn new(client: DhtClient, report_policy: ReportPolicy) -> Self {
        Self {
            client,
            report_policy,
        }
    }

    pub(super) async fn publish(
        &self,
        signed_packet: &SignedPacket,
    ) -> Result<StoredNodeCount, PublishError> {
        let stored_on = self.client.publish(signed_packet).await?;
        self.log_publish_warnings(&signed_packet.public_key(), stored_on);
        Ok(stored_on)
    }

    pub(super) async fn resolve(
        &self,
        public_key: &PublicKey,
        policy: BackendResolvePolicy<'_>,
    ) -> Result<SignedPacket, ResolveError> {
        match policy {
            BackendResolvePolicy::CacheOnly => Err(ResolveError::NotFound),
            BackendResolvePolicy::CacheFirst(context) => {
                self.resolve_cache_first(public_key, context).await
            }
            BackendResolvePolicy::NetworkOnly => {
                let response = self.client.resolve(public_key, None).await;
                let outcome = self.complete_resolve(public_key, response).await;
                outcome.most_recent.map_err(Into::into)
            }
        }
    }

    async fn resolve_cache_first(
        &self,
        public_key: &PublicKey,
        cache_context: CacheContext<'_>,
    ) -> Result<SignedPacket, ResolveError> {
        let response = self
            .client
            .resolve(public_key, cache_context.dht_request_lower_bound())
            .await;

        let mut accumulator = ResolveResultAccumulator::new(Some(cache_context));
        if let Some(packet) = response.first() {
            if accumulator.record_result(Ok(packet.clone())) {
                return accumulator.into_result();
            }
        }

        let outcome = self.complete_resolve(public_key, response).await;
        accumulator.record_result(outcome.most_recent.map_err(Into::into));
        accumulator.into_result()
    }

    async fn complete_resolve(
        &self,
        public_key: &PublicKey,
        response: ResolveResponse,
    ) -> ResolveOutcome {
        let outcome = response.complete().await;
        self.log_resolve_warnings(public_key, &outcome.report);
        outcome
    }

    fn log_publish_warnings(&self, public_key: &PublicKey, stored_on: StoredNodeCount) {
        let warnings = self.report_policy.classify_publish_result(stored_on);
        if !warnings.is_empty() {
            tracing::warn!(
                ?public_key,
                ?warnings,
                "DHT publish completed with warnings"
            );
        }
    }

    fn log_resolve_warnings(&self, public_key: &PublicKey, report: &ResolveReport) {
        let warnings = self.report_policy.classify_resolve_report(report);
        if !warnings.is_empty() {
            tracing::warn!(
                ?public_key,
                ?warnings,
                "DHT resolve completed with warnings"
            );
        }
    }
}