Skip to main content

PeerDiagnosticsResult

Struct PeerDiagnosticsResult 

Source
pub struct PeerDiagnosticsResult {
    pub nickname: String,
    pub principal: String,
    pub user_id: Option<String>,
    pub paired_at: Option<String>,
    pub last_addr: Option<String>,
    pub hint_addrs: Vec<String>,
    pub hint_usable: bool,
    pub reachability: Option<PeerReachability>,
    pub known_addrs: Option<Vec<KnownAddr>>,
    pub hint_addrs_unknown_to_iroh: Vec<String>,
    pub iroh_addrs_not_in_hint: Vec<String>,
}
Expand description

Result of Request::PeerDiagnostics (#140): the DURABLE per-peer state this node carries, for diagnosing why a specific long-lived pairing behaves differently from a fresh one.

This surface carries a PEER’s transport coordinates on purpose. The rendered porcelain is address-free everywhere — nicknames and path KINDS — because that discipline keeps a peer’s coordinates out of screenshots. (SelfNetwork.direct_addrs already returns this node’s OWN addresses on status; what is new here is another endpoint’s.) The question this answers is “what address is this node about to dial, and where did it come from”, which has no answer without the address. It is your own store’s record of your own paired peers. Do not render it in ordinary porcelain, and read it before pasting it anywhere public.

The intended use is a paired capture: run it on BOTH ends of a stuck pairing and compare the stored hint against the live path each side reports.

Fields§

§nickname: String

The peer’s nickname as this node stores it.

§principal: String

The peer’s stable eid: device principal.

§user_id: Option<String>

The peer’s b64u: user_id if it proved a device→user binding at pairing.

§paired_at: Option<String>

When the pairing was written (epoch seconds as a string), if recorded. A LONG-LIVED pairing is exactly what #140 is about, so the age is part of the evidence.

§last_addr: Option<String>

The persisted dial HINT, verbatim as stored — the durable state a freshly paired identity does not have. None for a peer added without one.

It is MERGED with discovery rather than replacing it — iroh inserts it as one more candidate path (Source::App) and then triggers address lookup.

But that lookup is skipped when a path is already selected. iroh’s trigger_address_lookup returns early if selected_path.is_some(), and a selected path is cleared only when the last connection to that peer closes. So on a pair that already holds an open RELAYED connection — live sessions, dial-backs, a working relay — discovery does NOT re-run, and this hint is the only addressing the dial contributes. Do not read “merged, so a stale hint is harmless” as unconditional; it is least true in exactly the state a stuck pairing is in.

It is the only durable per-peer state ON THIS NODE’S DISK that the dial path reads, which is what makes it the first thing to compare between two ends. It is not the only durable state a long-lived identity carries — a published discovery record under the same key, and SelfNetwork::identity_conflict_epoch, live elsewhere.

§hint_addrs: Vec<String>

The addresses parsed out of last_addr, for reading without a JSON round trip: IP addresses verbatim, relay URLs as relay <url> and SANITIZED to scheme+host+port (an operator’s relay URL can carry a userinfo token, and this output is meant to be pasted into an issue). Empty when the hint is absent, unparseable, or for a different endpoint — all of which degrade to an id-only dial.

A relay … entry with no IP alongside it is worth noticing: that hint can never punch.

§hint_usable: bool

Whether last_addr parses AND its embedded id matches this peer. A false here with a present last_addr means the hint is being silently discarded at every dial.

§reachability: Option<PeerReachability>

This node’s LIVE view of the peer, read straight from the reachability cache — the same values status reports, repeated here so one capture holds both the durable and the live side. None when this peer has never been probed, which is the honest answer on a freshly restarted daemon; it is not the same as unreachable.

Read from the cache rather than through status’s projection deliberately: that projection spawns a background probe for every stale peer, which would make this diagnostic a participant in the reproduction it is meant to observe.

§known_addrs: Option<Vec<KnownAddr>>

What iroh currently holds for this endpoint, as opposed to what this node stored (#140, api_minor >= 56). The other half of the capture, and the half the standing hypothesis lives in.

Everything above describes our own disk. Until this field there was no way to see what iroh made of it — whether the hint’s addresses are in its remote map at all, whether discovery contributed anything alongside them, or which address is carrying traffic. Read straight off Endpoint::remote_info, a point read of the remote map: no dial, no probe, no address lookup, so this stays safe to run ON a live reproduction.

None means iroh currently holds NO ENTRY — which is not the same as an empty list, and is not the same as “never heard of”. iroh reaps a remote’s state about 60 seconds after the last connection to it closes (ACTOR_MAX_IDLE_TIMEOUT), so None is the normal answer both for a peer never dialled AND for one talked to a few minutes ago. An empty list means iroh has an entry and holds no address in it.

This distinction matters most to the question #140 is asking, which is about durability over time: reading None as “iroh never knew this peer” would be wrong on any daemon that has been idle, which is most of them.

§hint_addrs_unknown_to_iroh: Vec<String>

Addresses in hint_addrs that iroh does not currently hold.

The hint is written whole: set_last_addr REPLACES the stored value with one built from the live connection’s open IP paths, so these are not accumulated cruft — they are addresses that were real at the last successful connection and are absent from iroh’s view now. (An earlier version of this doc said #124 “amends but never removes”; that is false, and dial_hint.rs says so in as many words.)

Empty whenever known_addrs is None, because there is no view to difference against. Reporting the whole hint as unknown when iroh simply has no entry would call a current, correct hint stale on every idle daemon.

INFERRED by set difference, not read: iroh 1.0.3’s TransportAddrInfo carries no provenance, so “did this come from our hint or from discovery?” cannot be answered directly. Naming it an inference is the honest version.

§iroh_addrs_not_in_hint: Vec<String>

The converse: what iroh holds that our stored hint does not name — inferred the same way, and empty for the same reason when known_addrs is None.

Trait Implementations§

Source§

impl Clone for PeerDiagnosticsResult

Source§

fn clone(&self) -> PeerDiagnosticsResult

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PeerDiagnosticsResult

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PeerDiagnosticsResult

Source§

fn default() -> PeerDiagnosticsResult

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for PeerDiagnosticsResult

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for PeerDiagnosticsResult

Source§

impl PartialEq for PeerDiagnosticsResult

Source§

fn eq(&self, other: &PeerDiagnosticsResult) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for PeerDiagnosticsResult

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for PeerDiagnosticsResult

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<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<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. 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<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