Skip to content

feat(api): server-side manifest listing endpoint (read path for #5535) - #5537

Open
petfold wants to merge 3 commits into
ethersphere:masterfrom
petfold:feat/manifest-listing
Open

feat(api): server-side manifest listing endpoint (read path for #5535)#5537
petfold wants to merge 3 commits into
ethersphere:masterfrom
petfold:feat/manifest-listing

Conversation

@petfold

@petfold petfold commented Jul 14, 2026

Copy link
Copy Markdown

Summary

Adds a read-only, server-side manifest listing endpoint, implementing the read half of #5535.

GET /manifest/{address}/{prefix}

Bee currently has no way to enumerate a manifest's contents over HTTP — clients must download and walk the Mantaray trie themselves, chunk by chunk via /bytes (one round trip per node). This endpoint walks the trie on the node and returns entries as JSON, turning O(trie nodes) round trips into O(pages). Semantics mirror S3 ListObjectsV2:

Param Default Meaning
delimiter none / → shallow listing: direct entries + commonPrefixes. Unset → recursive.
limit 1000 Max entries+prefixes per page. Bounds per-request buffered memory, not total enumeration (unbounded via after/nextMarker). Hard cap 1000000; a higher value is rejected with 400 rather than silently clamped, and a non-positive value falls back to the default.
after none Continuation token — return entries strictly after this path.
sizes false Resolve each entry's byte length from its root-chunk span (one extra chunk read per entry).

Response: { entries[], commonPrefixes[], truncated, nextMarker }.

This is deliberately listing only. The companion server-side mutation endpoint sketched in #5535 is a separate follow-up.

Implementation

Everything needed already existed in the tree:

  • pkg/manifest — a small optional EntryWalker interface (+ ErrStopWalk), implemented as WalkEntry on the mantaray manifest over the existing sorted WalkNode. This keeps the core manifest.Interface and simple.go untouched and doesn't expose the trie representation to callers.
  • pkg/api/manifest.gomanifestListHandler, mirroring the /bzz download path's manifest resolution (loadsave.NewReadonlyNewDefaultManifestReference) and reusing the ACT decryption chain, so encrypted/ACT manifests resolve unchanged. Pagination uses a last-consumed-path marker that is correct for both recursive and delimiter modes.
  • openapi/ — path + ManifestList/ManifestListEntry schemas.

No new storage, protocol, or incentive-layer behavior — purely a local read amplification of data the node already serves.

Testing

  • Unit (pkg/api/manifest_test.go): recursive, delimiter=/, prefix, multi-page pagination round-trip, missing-prefix 404, and over-cap-limit 400, over a real mantaray fixture (incl. the empty root-metadata entry). go test ./pkg/api/ ./pkg/manifest/... green; go vet and gofmt clean.
  • Live: verified end-to-end against a funded Bee 2.8.1 light node on mainnet, listing a manifest built by Bee's own POST /bzz uploader. All cases correct, and sizes=true returned exact byte lengths from real chunk spans. The live run caught one bug the synthetic test missed — the RootPath metadata entry carries a 32-zero-byte reference (not swarm.ZeroAddress, which has nil bytes), so it leaked as a / entry until the skip was aligned with the IterateAddresses convention.

Known limitations / scope (draft — feedback welcome on the API shape)

Opening as a draft to agree the API before polishing. Deliberately out of scope here, happy to address based on review:

  1. Delimiter mode still descends full subtrees. WalkNode has no pruning hook, so delimiter=/ walks a subtree fully to emit one commonPrefixes entry — output is correct but not yet the cheap shallow listing it should be. The clean fix is a pruning-aware walk in the mantaray layer.
  2. Each page re-walks from the start (after is a post-filter), so paginating N pages is ~O(N × subtree) node loads. A trie-seek to after would fix it.
  3. prefix is directory-boundary-only, not an arbitrary S3 byte-prefix (e.g. prefix=da 404s; data/ works). Documented as such; could add HasPrefix-style handling.
  4. No per-request node-load budget yet (the DoS mitigation noted in API: server-side manifest listing endpoint (S3-style), with optional manifest mutation as a follow-up #5535); sizes=true on encrypted refs is silently unsupported. The limit hard cap bounds buffered response memory but not the number of trie nodes walked to build a page.
  5. Root-level manifest metadata (e.g. website-index-document) is currently dropped from the listing; API: server-side manifest listing endpoint (S3-style), with optional manifest mutation as a follow-up #5535 proposes surfacing it as a top-level manifestMetadata — easy to add if wanted.

Refs #5535.

Add GET /manifest/{address}/{path}, which walks the Mantaray trie
server-side and returns entries as JSON, so clients no longer need to
fetch and traverse the trie chunk by chunk just to list a collection.
Semantics follow S3 ListObjectsV2: optional path prefix, an optional
delimiter for shallow (pseudo-directory) listings, lexicographic
pagination via limit/after, and opt-in per-entry sizes.

- pkg/manifest: add the optional EntryWalker interface (+ ErrStopWalk)
  and implement WalkEntry on the mantaray manifest over the existing
  sorted WalkNode, without expanding the core Interface.
- pkg/api: manifestListHandler + route, reusing the /bzz manifest
  resolution and ACT decryption chain; per-entry size via the root
  chunk span.
- openapi: document the path plus ManifestList/ManifestListEntry schemas.
- tests: recursive, delimiter, prefix, pagination and 404 cases over a
  real mantaray fixture, including the empty root-metadata entry.

Refs ethersphere#5535

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
petfold and others added 2 commits July 14, 2026 23:41
OpenAPI requires every in:path parameter to be required:true; an
optional path parameter fails spec validation and broke the
OpenAPI Preview CI check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Raise the manifest listing page cap to 1000000 and document what it
bounds (per-request buffered memory, not total enumeration, which stays
unbounded via after/nextMarker). An over-cap limit now returns 400
rather than being silently clamped, so the client keeps an accurate
view of its page size; a non-positive limit falls back to the default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant