WeaveDocs
Weft

Fetch

Reconstruct a Weft payload from chunks served by one or more peers, verifying against the manifest root.

What this page covers

The consumer-side flow: given a root (and access to a manifest), fetch the chunks, verify them against the manifest, and assemble the original bytes.

The fetch model

A fetch needs three things:

  1. A WeftManifest — proves the structure and chunk ids of the payload.
  2. A ChunkSource — knows how to retrieve a chunk by id from one or more peers.
  3. Availability information — a map from chunk index to the peers that have it.

The fetch_manifest function performs the work, verifying each chunk's BLAKE3 id before accepting it.

Note

Every chunk is verified against its BLAKE3 id before delivery. If a peer serves bytes that don't match the id, the response is discarded and the fetcher tries another peer from the availability list — corrupted or adversarial peers cannot poison a fetch.

Local fetch (one peer, in-process)

The simplest case: a consumer in the same process as the producer.

use weft::{
    chunk::{ChunkConfig, DEFAULT_CHUNK_SIZE},
    discovery::PeerId,
    local::LocalWeftStore,
    manifest::{ArtifactMetadata, ManifestProducer},
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let store = LocalWeftStore::in_memory();
    let payload = b"the quick brown fox jumps over the lazy dog".to_vec();

    let producer = ManifestProducer {
        did: "did:oas:agent:demo".into(),
        signing_key_id: None,
        signature: vec![0u8; 64],
    };
    let artifact = ArtifactMetadata {
        artifact_id:          None,
        media_type:           "text/plain".into(),
        schema:               "weave.text.v1".into(),
        mars_asset_id:        None,
        sigil_commitment_cid: None,
    };

    let published = store.publish_bytes(
        &payload,
        ChunkConfig::new(DEFAULT_CHUNK_SIZE)?,
        producer,
        artifact,
        PeerId::new("did:peer:demo")?,
    )?;

    // Consumer side: given just the root, fetch the bytes back.
    let max_parallelism = 4;
    let bytes = store.fetch_payload(published.manifest.root, max_parallelism).await?;
    assert_eq!(bytes, payload);

    Ok(())
}

LocalWeftStore::fetch_payload builds a FetchRequest internally, calls fetch_manifest, and returns the assembled bytes.

Fetch through a custom ChunkSource

Real network fetches plug in a ChunkSource implementation:

use async_trait::async_trait;
use std::sync::Arc;
use weft::discovery::PeerId;
use weft::fetch::{fetch_manifest, ChunkSource, FetchRequest, InMemoryChunkSource};
use weft::{WeftHash, Result};

#[async_trait]
trait MyChunkAdapter: Send + Sync {
    async fn fetch(&self, peer: &PeerId, id: WeftHash) -> std::result::Result<Vec<u8>, String>;
}

struct AdapterSource<A: MyChunkAdapter> {
    adapter: A,
}

#[async_trait]
impl<A: MyChunkAdapter + 'static> ChunkSource for AdapterSource<A> {
    async fn fetch(&self, peer: &PeerId, id: WeftHash) -> Result<Vec<u8>> {
        self.adapter
            .fetch(peer, id)
            .await
            .map_err(|e| weft::WeftError::Fetch(e))
    }
}

The ChunkSource trait is the entire surface a network adapter must implement. The fetch engine in fetch_manifest does the rest: scheduling, verification, retry on WeftError::Hash mismatches.

What fetch_manifest does

fetch_manifest(request, source)

   ├─► For each chunk index 0..chunk_count:
   │     candidates = request.availability.get(chunk_index)
   │     for each candidate peer in candidates:
   │       try:
   │         bytes = source.fetch(peer, chunk_id).await?
   │         verified_id = blake3(domain || bytes)
   │         if verified_id != chunk_id: continue   // try next peer
   │         buffer.extend(bytes)
   │         break
   │     else:
   │       return Err(no peer served chunk index)

   └─► Ok(buffer)

The max_parallelism field in FetchRequest bounds in-flight chunk fetches. Verification happens after each chunk lands, so a malicious peer cannot inject content under a wrong id.

Errors

ErrorCauseRecovery
WeftError::InvalidHashReturned bytes did not hash to the expected chunk idTry the next advertised peer
WeftError::Fetch(String)Network adapter failedRetry with backoff
WeftError::Manifest("...")Manifest itself failed validationThe manifest is the wrong file; re-fetch from the producer
WeftError::Unsupported(...)ParitySpec set, or DHT adapter not linkedDisable parity in publishing; link a DHT adapter

Tuning fetches

VariableWhat it controls
FetchRequest::max_parallelismConcurrent chunk fetches
Availability map sizePeer choice; more peers means more redundancy
Chunk size on the producer sideBigger chunks → fewer round trips, more bytes per retry

Practical patterns

Resumable fetch

The LocalWeftStore and WeftDhtStore both keep chunks once stored. A second fetch_payload call uses the cached chunks first. To resume an interrupted fetch, point the same store at the same root and call fetch_payload again — already-fetched chunks are skipped.

Verifying before serving

If you are a downstream peer mirroring an artifact, you should fetch and verify before serving. Both stores do this automatically: stored chunks carry their canonical id as the key, and the chunk id is recomputed from the bytes on insert.