Protocol v2: trusted-peer push, truthful partials, split downloads

Three capabilities the iroh-blobs 0.103 line makes possible:

- Push (new API surface, protocol v2): `Push { hash, node_id }` hands
  fully-present content to a trusted peer, unprompted. Consent is
  mutual — the sender pushes only to peers it trusts, and the receiver
  (which now accepts connections unconditionally and gates per request)
  admits pushes only from peers *it* trusts, deferring with RateLimited
  while metered. An accepted push is pinned by the receiver (default
  policy, format inferred from the request ranges) once its transfer
  completes, and surfaces as a PushReceived event. Because QUIC writes
  are fire-and-forget, the sender confirms delivery by observing the
  receiver's bitfields (root + last hashseq child) before replying —
  Pushed { bytes } means verified received, not merely sent. New
  varde-ctl `push` command.

- Truthful partial presence: Status/List report bytes actually present
  and verified for partial blobs, from the store's bitfields via
  observe, instead of the old "0 until complete".

- Split downloads: multi-provider fetches stripe one request across
  providers (SplitStrategy::Split) instead of trying them serially.

Trusted peers may observe bitfields even when serving is disabled or
metered — bitfields are metadata, and push confirmation rides on them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bendik Lynghaug
2026-08-16 14:52:27 +02:00
co-authored by Claude Fable 5
parent ad69f7d884
commit 4033c0ffab
9 changed files with 474 additions and 33 deletions
+52
View File
@@ -125,6 +125,29 @@ impl Daemon {
}
});
// Inbound pushes: content a trusted peer handed us, fully
// verified. Pin it (default policy) so it survives gc, and tell
// subscribers where it came from.
if let Some(mut pushes) = daemon.transfer.take_pushes() {
let weak = Arc::downgrade(&daemon);
tokio::spawn(async move {
while let Some(push) = pushes.recv().await {
let Some(daemon) = weak.upgrade() else { break };
let hex = push.hash.to_hex().to_string();
let from = push.from.clone().unwrap_or_default();
info!(hash = %hex, from = %from, "push received, pinning");
if let Err(e) = daemon.meta.record_format(&hex, push.format) {
warn!(hash = %hex, error = %e, "recording pushed format");
}
if let Err(e) = daemon.meta.pin(&hex, varde_proto::PinPolicy::default()) {
warn!(hash = %hex, error = %e, "pinning pushed content");
}
daemon.recompute_open_set().await;
let _ = daemon.events.send(Event::PushReceived { hash: hex, from });
}
});
}
// Discovery: raw mdns sightings feed presence tracking, and are
// re-emitted as signed announcements through the LanDiscovery
// provider — the same seam a future gossip provider plugs into.
@@ -513,6 +536,35 @@ impl Daemon {
Request::PeerList {} => Ok(ResponseData::Peers {
peers: self.present_trusted_peers(),
}),
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}")))?;
// Consent, sending side: we only push to peers we trust.
// (The receiver independently requires that it trusts us.)
if !self.meta.is_trusted(&peer.to_string()) {
return Err(OpError::InvalidArgument(format!(
"{peer} is not a trusted peer; trust it before pushing"
)));
}
let (_, _, complete) = self.store.presence(parsed).await?;
if !complete {
return Err(OpError::NotFound(format!(
"{} is not fully present; cannot push",
parsed.to_hex()
)));
}
let content = HashAndFormat {
hash: parsed,
format: blob_format(self.meta.format_of(&parsed.to_hex())),
};
let bytes = self.transfer.push_to(peer, content).await?;
Ok(ResponseData::Pushed {
hash: parsed.to_hex().to_string(),
bytes,
})
}
Request::Subscribe {} => Ok(ResponseData::Done {}),
}
}