Skip to main content

DhtService

Struct DhtService 

Source
pub struct DhtService { /* private fields */ }
Expand description

The DHT service for one node. Cloneable-by-Arc internally; wrap in Arc to share between the serving task (inbound RPC) and querying callers.

Implementations§

Source§

impl DhtService

Source

pub fn new( local_id: PeerId, local_addresses: Vec<CandidateAddr>, config: DhtConfig, transport: Arc<dyn DhtTransport>, ) -> Self

Create a service for the node identified by local_id, advertising local_addresses in the provider records it announces, driving RPC over transport.

Source

pub fn local_id(&self) -> &PeerId

This node’s id.

Source

pub async fn bootstrap( &self, peers: &[BootstrapPeer], ) -> Result<usize, DhtError>

Seed the routing table from peers and populate it by looking up this node’s own id (the canonical Kademlia bootstrap: a self-lookup fills the buckets around us). Returns the number of distinct peers now known.

Safe to call repeatedly (on reconnect / when new bootstrap peers arrive) — it merges, never resets.

Source

pub async fn add_peer(&self, peer_id: &PeerId, addresses: Vec<CandidateAddr>)

Add a single live peer to the routing table as it connects (e.g. a dig-gossip PoolEvent::PeerAdded), WITHOUT the network round-trip bootstrap does.

This is the LIVE seam the one-shot pre-connect bootstrap cannot cover: in a freshly-formed network the pool is empty when bootstrap runs, so routing stays empty and find_providers finds nobody. Feeding each connected peer here populates routing as the pool fills, which is what makes cross-node discovery work (#1574). Idempotent — re-adding a known peer merges its address(es) via the routing table’s insert policy; adding this node’s own id is a no-op.

Source

pub async fn remove_peer(&self, peer_id_hex: &str) -> bool

Remove a peer from the routing table as it leaves (a dig-gossip PoolEvent::PeerRemoved), keeping routing accurate so lookups don’t seed from a dead contact. Returns whether it was present. peer_id_hex is the 64-char hex id (as carried on Contact::provider_peer_id / PeerId::to_hex).

Source

pub async fn find_node( &self, peer_id: &PeerId, ) -> Result<Vec<Contact>, DhtError>

Find the k peers closest to peer_id (the routing primitive). Runs an iterative find_node lookup and returns the converged closest contacts.

Source

pub async fn find_providers( &self, content: &ContentId, ) -> Result<Vec<ProviderRecord>, DhtError>

Find the providers of content — the peers holding it. Answers from this node’s discovery cache when a recent lookup for the same key is still live (SPEC §6.8); otherwise runs an iterative find_providers lookup toward the content key, caches what it learns, and returns every live provider record collected (deduped by provider). The node then connects to those providers over dig-nat and fetches via the L7 peer RPC.

Cached answers are what make a later direct dial free (dig_ecosystem#3128 requirement 7): a .dig fetch issues many requests against the same store, and without the cache each one paid a fresh Kademlia walk. A live cache entry is treated as evidence that this node completed a lookup for the key recently, so the walk is skipped entirely — records this node holds AUTHORITATIVELY are deliberately NOT such evidence, since they may be its own announce and short-circuiting on them would stop a publisher ever learning the other holders of its own content.

A cached holder is a claim by an untrusted peer, so a dial to it may fail. That costs one failed dial, never a wrong answer — the content is accepted because it verifies against the merkle root, never because a peer supplied it (NC-12). A caller that finds every cached candidate undialable calls forget_discovered and asks again, which re-runs the full walk.

Returns an empty vec (not an error) when the content simply has no known providers; returns DhtError::NoPeers only when there is no one to ask (empty routing table + no bootstrap).

Source

pub async fn cached_providers(&self, content: &ContentId) -> Vec<ProviderRecord>

The provider records this node has CACHED for content from its own lookups, live as of now — the direct-dial shortcut requirement 7 exists to provide, with no network round-trip and no fallback walk.

§These records MUST NOT be re-served to anyone

They are hearsay: some peer along a lookup said that some other peer holds this content, and nothing authenticated that claim — unlike an authoritative record, which either names the mTLS-verified caller that announced it or was signature-checked by the caller of ingest_verified_provider. Hearsay belongs on the FETCH path, where a wrong candidate is merely a wasted dial because the merkle bind catches it. On the ASSERTION path — an inbound find_providers, a redirect answer, anything a stranger reads — it becomes THIS NODE’S claim about the world, and re-serving it would launder an attacker’s fabricated holder into an answer other nodes trust. This node therefore never serves the cache (see handle_request_from, which reads the authoritative store only) and never publishes it (see provider_snapshot).

Source

pub async fn forget_discovered(&self, content: &ContentId) -> usize

Forget every cached provider for content, so the next find_providers runs a real lookup again. Returns how many cached records were dropped.

This is what keeps a cache miss CHEAP and keeps it from being mistaken for absence: a caller that has tried every cached candidate and reached none of them calls this and asks again, rather than concluding the content has no providers. It touches only this node’s own cache — never the authoritative store, so it can neither censor a key this node serves nor be observed by any other peer.

Source

pub async fn announce_provider( &self, content: &ContentId, ) -> Result<usize, DhtError>

Announce that THIS node holds content: build a provider record (this node’s peer_id + addresses, expiring at now + provider_ttl), store it locally, remember to republish it, and PUT it at the k nodes closest to the content key. Returns how many peers accepted the PUT.

Called when the node’s inventory gains content (a new capsule/root/resource it now serves).

Source

pub async fn withdraw_provider(&self, content: &ContentId) -> bool

Stop announcing content (the node no longer holds it). The record ages out of the DHT via TTL; we just stop republishing it. Returns whether it was being announced.

This is the passive withdraw: it leaves this node’s own local provider record in place (it only expires with TTL) and merely stops re-publishing it, so a find_providers on this node may still return self until the local record’s TTL elapses. For an immediate own-retract — the local-state half of the #1423 evict+retract step — use retract_own_provider.

Source

pub async fn ingest_verified_provider( &self, record: ProviderRecord, ) -> PutOutcome

Ingest a provider record for a THIRD-PARTY holder that the caller has ALREADY verified was signed by record.provider_peer_id — the inbound-add half of the real-time holdings map (SPEC §6.5). Returns the store admission outcome.

This is the authenticated push path a node’s announce receiver calls after verifying a signed HoldingsAnnounce (dig-gossip opcode 222): the holder’s signature has replaced mTLS attribution as the proof of who provides the content, so — unlike the serving-side add_provider (§6.4) — this method bypasses the mTLS self-announce identity check (the caller, not the DHT, established authenticity). dig-dht itself stays crypto-free (SPEC §15): it NEVER verifies a signature; passing an unverified record here is a caller bug that poisons the local provider set.

Every other admission guard still applies exactly as for add_provider: the address list is capped (MAX_ADDRESSES_PER_RECORD), expires_at is clamped to min(record.expires_at, now + provider_ttl) (§6.2), and the per-key / global admission caps (§6.3) are enforced — an over-capacity ingest returns PutOutcome::RejectedOverCapacity and stores nothing. On acceptance the holder is folded into the routing table so this node can reach it.

Source

pub async fn remove_provider_record( &self, content_key: &str, provider_peer_id: &str, ) -> bool

Remove exactly the local provider record for (content_key, provider_peer_id) — the inbound-retract half of the real-time holdings map (SPEC §6.6). Returns whether a record was removed.

content_key and provider_peer_id are the 64-hex forms as they appear on a ProviderRecord (contentcontent.to_key().to_hex(); the holder’s peer_id hex). The caller MUST have verified the retract was signed by that same provider_peer_id (authenticated retract): a retract signed by one holder removes ONLY that holder’s record and can never evict another provider of the same key (censorship-resistance, §6.6). dig-dht does not verify the signature (SPEC §15) — that is the caller’s responsibility.

Source

pub async fn provider_snapshot(&self, max_keys: usize) -> ProviderSnapshot

A bounded, AGGREGATED view of this node’s provider store — content keys and their live provider COUNTS, with no provider identities (dig_ecosystem #1935).

Exposed so a node can answer the relay’s RLY-009 get_dht_records without the caller needing access to the store itself. Because a Kademlia node holds records for keys near its OWN peer_id, this describes MANY OTHER peers’ content rather than what this node caches — which is what makes the union across nodes a usable view of the network’s content layer.

max_keys bounds the result; see ProviderStore::snapshot for the truncation and privacy contract. Expired records are excluded as of the current time, so the counts agree with what find_providers would actually return.

Source

pub async fn retract_own_provider(&self, content: &ContentId) -> bool

Actively retract THIS node’s own provider record for content: remove the local record AND stop republishing it, so find_providers on this node stops returning self as a holder immediately (SPEC §6.6). Returns whether this node was providing the content (a local record existed or the key was being announced).

This is the local-state half of the #1423 atomic evict + retract step (on an LRU cache eviction the node no longer serves the content). Unlike the passive withdraw_provider (which leaves the local record to expire via TTL), this deletes it now. The copies previously PUT at the k closest peers are NOT deleted by this call — they age out via TTL, or are removed sooner when dig-node floods the signed retract announce and each recipient calls remove_provider_record.

Source

pub async fn holders_of( &self, content: &ContentId, ) -> Result<Vec<PeerId>, DhtError>

The peer_ids of the peers that hold content — a thin, address-free convenience over find_providers for callers that only need “which peers hold X” (e.g. an RPC holder-set query) and do not dial the holders themselves.

find_providers remains the PRIMARY API: it returns full ProviderRecords with candidate addresses, which dig-download needs to actually connect and fetch. This method runs the same distributed iterative lookup and simply projects each record to its holder peer_id (records with a malformed peer id are skipped; the set is already deduped by provider).

Source

pub async fn republish(&self) -> usize

Republish every content key this node still announces — re-runs the announce PUT so provider records never expire while the node is online. Call on the DhtConfig::republish_interval. Returns the number of content keys republished.

Source

pub async fn refresh_buckets(&self) -> usize

Refresh populated buckets by looking up a random key in each — keeps the routing table fresh as peers churn. Call on the DhtConfig::refresh_interval. Returns the number of buckets refreshed.

Source

pub async fn gc(&self) -> usize

Drop expired provider records from BOTH the authoritative store and the discovery cache (SPEC §6.8). Call periodically (piggy-backs on republish/refresh). Returns the total number of records removed.

One now for both sweeps, so a maintenance tick cannot leave the two stores disagreeing about which instant it ran at.

Source

pub async fn ping(&self, peer: &Contact) -> bool

Ping a peer for liveness; on failure, evict it from the routing table. Used by the ping-and-replace maintenance when a bucket is full. Returns whether the peer is alive.

Source

pub async fn handle_request(&self, request: DhtRequest) -> DhtResponse

Answer an inbound DHT request from another node, without a known caller identity. Prefer handle_request_from on an authenticated transport (it lets the responder learn the caller and populate its routing table bidirectionally, the way Kademlia tables fill).

Source

pub async fn handle_request_from( &self, caller: Option<Contact>, request: DhtRequest, ) -> DhtResponse

Answer an inbound DHT request, folding the authenticated caller into the routing table.

This is the server half — a dig-node wires it to inbound DHT streams, passing the caller’s mTLS-verified Contact as caller. Learning the caller from every inbound RPC is how a Kademlia node discovers peers without an explicit announce: a node that talks to you becomes a candidate in your table. The caller MUST come from the authenticated transport (the mTLS peer_id), never from the request body — identity is not self-asserted.

It reads/writes only local state (routing table + provider store) and never makes outbound RPCs, so it cannot recurse or block on the network.

Source

pub async fn known_closest(&self, target: &Key) -> Vec<Contact>

The contacts currently in this node’s routing table closest to target (diagnostic / introspection — the peers this node knows without any network round-trip).

Source

pub async fn routing_len(&self) -> usize

The number of peers currently in this node’s routing table (diagnostic / metrics).

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Conv for T

Source§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> FmtForward for T

Source§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
Source§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
Source§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
Source§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
Source§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
Source§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
Source§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
Source§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
Source§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pipe for T
where T: ?Sized,

Source§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
Source§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
Source§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
Source§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows self, then passes self.as_ref() into the pipe function.
Source§

fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.as_mut() into the pipe function.
Source§

fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
Source§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Tap for T

Source§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
Source§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
Source§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
Source§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
Source§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
Source§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
Source§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
Source§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
Source§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
Source§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .tap_borrow() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .tap_ref() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
Source§

impl<T> TryConv for T

Source§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more