Pushes name the peer by ticket, and an allowance can confirm its push

PeerAllowPush and Push take a blob ticket the peer issued as well as a
NodeId; the ticket's addresses are remembered, so the push dials
directly. A peer allowed to push may observe too: the sender confirms a
push by observing it, and without that every allowance-only push
reported failure though it had landed. The allowance test now fails
instead of skipping on anything but an unreachable peer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-10-03 13:29:03 +02:00
co-authored by Claude Opus 5.5
parent 4d9985c9cb
commit c2b9167e1e
5 changed files with 56 additions and 14 deletions
+22 -6
View File
@@ -335,6 +335,26 @@ impl Daemon {
}
}
/// The peer a request names: a NodeId, or a blob ticket from it (whose
/// addresses are remembered, so dialing it needs no discovery). An app
/// that knows peers by the tickets they hand out - redoal does - can
/// allow and push without unpacking them.
fn peer_of(&self, peer: &str) -> Result<iroh::EndpointId, OpError> {
if let Ok(id) = peer.parse::<iroh::EndpointId>() {
return Ok(id);
}
match crate::transfer::parse_ticket(peer) {
Ok((_, _, addr)) => {
let id = addr.id;
self.transfer.remember(addr);
Ok(id)
}
Err(_) => Err(OpError::InvalidArgument(format!(
"{peer:?} is neither a node id nor a ticket"
))),
}
}
/// Trusted peers seen on the LAN recently.
fn present_trusted_peers(&self) -> Vec<PeerInfo> {
let peers = self.lan_peers.lock().expect("lan peers lock");
@@ -574,9 +594,7 @@ impl Daemon {
Ok(ResponseData::Done {})
}
Request::PeerAllowPush { node_id, ttl_secs } => {
let parsed: iroh::EndpointId = node_id
.parse()
.map_err(|e| OpError::InvalidArgument(format!("invalid node id: {e}")))?;
let parsed = self.peer_of(&node_id)?;
let ttl = ttl_secs.min(varde_proto::MAX_PUSH_TTL_SECS);
self.meta
.allow_push(&parsed.to_string(), crate::meta::unix_now() + ttl)?;
@@ -588,9 +606,7 @@ impl Daemon {
}),
Request::Push { hash, node_id } => {
let parsed = parse_hash(&hash)?;
let peer: iroh::EndpointId = node_id
.parse()
.map_err(|e| OpError::InvalidArgument(format!("invalid node id: {e}")))?;
let peer = self.peer_of(&node_id)?;
// Consent, sending side: we only push to peers we trust or
// hold a push allowance for. (The receiver independently
// requires the same of us.)
+4 -3
View File
@@ -200,11 +200,12 @@ pub fn provider_events(gate: Gate) -> EventSender {
ProviderMessage::ObserveRequestReceived(m) => {
// Presence is information too: gate it like data.
// Bitfields are metadata, not payload, so trusted
// peers may observe even while serving is off or the
// connection is metered (push confirmation needs it).
// peers - and peers allowed to push, whose push is
// confirmed by observing it - may observe even while
// serving is off or the connection is metered.
let trusted = conns
.get(&m.inner.connection_id)
.map(|i| i.trusted)
.map(|i| i.trusted || i.may_push)
.unwrap_or(false);
let allowed = trusted
|| allow(&gate, &conns, m.inner.connection_id, [m.inner.request.hash]);
+9
View File
@@ -298,6 +298,15 @@ impl Transfer {
Ok(ticket.to_string())
}
/// Teach the endpoint where a peer can be reached (the addresses a
/// ticket carries), so a push dials it directly instead of waiting on
/// discovery.
pub fn remember(&self, addr: EndpointAddr) {
if !addr.addrs.is_empty() {
self.known_addrs.add_endpoint_info(addr);
}
}
/// Queue a background fetch of `content` from `providers`. Emits
/// progress events and [`Event::PinComplete`] when fully verified.
pub fn spawn_fetch(&self, content: HashAndFormat, providers: Vec<EndpointAddr>) {