Skip to content

fix(manifest): infer refBytesSize at marshal time when never set#5484

Open
mfw78 wants to merge 1 commit into
ethersphere:masterfrom
nxm-rs:fix/mantaray-ref-size-zero-encoder
Open

fix(manifest): infer refBytesSize at marshal time when never set#5484
mfw78 wants to merge 1 commit into
ethersphere:masterfrom
nxm-rs:fix/mantaray-ref-size-zero-encoder

Conversation

@mfw78
Copy link
Copy Markdown
Collaborator

@mfw78 mfw78 commented Jun 1, 2026

Fixes #5483.

Add() defers setting n.refBytesSize until the first non-empty entry is added. Bee calls Add with an empty entry at pkg/api/bzz.go:266 for root metadata; if that node accumulates forks, MarshalBinary writes refBytesSize = 0 while the fork bodies still carry full-width refs, and the v0.2 reader at marshal.go:280 silently drops them on reload.

This patch infers refBytesSize at the top of MarshalBinary — from the node's own entry width if present, else the first child fork's saved ref width. The existing v0.2 read-side guard is left in place as backward-compat for already-persisted manifests.

TestPersistDirectoryOnlyAdds covers the directory-only-adds path; it fails on master with entry on 'foo' ('666f6f'): not found (silent fork loss) and passes here.

Checklist

  • make build clean
  • go test ./pkg/manifest/... passes
  • golangci-lint run ./pkg/manifest/mantaray/... clean
  • gofumpt clean on touched lines

AI Disclosure

  • This PR contains suggestions and code generated by an LLM.
  • I have reviewed the AI generated content thoroughly.
  • I possess the technical expertise to responsibly review the AI generated content mentioned in this PR.

`Add()` defers setting `n.refBytesSize` until the first non-empty entry is
added. When the only adds against a node use an empty entry (e.g. the
bzz endpoint's `m.Add(ctx, manifest.RootPath, manifest.NewEntry(swarm.ZeroAddress, rootMetadata))`
at `pkg/api/bzz.go:266`), the field stays at zero. If such a node is then
populated with non-empty children, `MarshalBinary` writes a header with
`refBytesSize = 0` while the fork bodies still carry full-width refs, and
the v0.2 `UnmarshalBinary` early-return at the (previous) `marshal.go:285`
silently drops every fork. The mantaray-js reference impl documents this
with an explicit FIXME ("in Bee, if one uploads a file on the bzz endpoint,
the node under `/` gets 0 refsize"); downstream Rust consumers (nectar,
isheika) hit it in the wild and have to add bee-tolerance paths in their
decoders.

Infer the correct width at the top of `MarshalBinary`: use the node's own
entry width if present, else the first child ref's width. The early-return
guard in `UnmarshalBinary` is left in place as backward-compat for any
already-persisted manifests that pre-date this fix.

Adds `TestPersistDirectoryOnlyAdds` exercising the directory-only-adds
path; without the inference, the test fails with `entry on 'foo': not found`
after save+reload (silent fork loss).
mfw78 added a commit to nxm-rs/nectar that referenced this pull request Jun 1, 2026
## What does this PR do?
Tolerates bee's `ref_size = 0` mantaray wire form on decode so nectar
can read real-world bee-produced manifests.

## Changes
- Add `decode_empty_terminal_node` to handle `ref_size = 0`; invoked
from both `decode_v01` and `decode_v02`. Accepts the shape only when the
forks bitfield is empty; rejects `ref_size = 0` with non-empty forks as
malformed.
- Tag every tolerance site with grep-able `BEE-WORKAROUND(bee#5483)`.
Documents the convention in `crates/mantaray/src/lib.rs` so future
maintainers can enumerate sites with `git grep -n BEE-WORKAROUND` once
the upstream bee fix has propagated.
- Four new regression tests: empty terminal node decode for v0.1 and
v0.2; rejection of `ref_size = 0` plus non-empty forks; an encoder pin
asserting nectar never emits `ref_size = 0`.

## Breaking changes
None. The encoder is unchanged; the decoder strictly broadens what it
accepts.

## Testing
- [x] Unit tests pass (56/56 in `nectar-mantaray --lib`, 4 of them new)
- [x] Integration and doctests pass
- [x] Behaviour cross-verified against the bee root-cause fix at
ethersphere/bee#5484

## Related issues
- Fixes #36
- Upstream root cause: ethersphere/bee#5483 (fix in
ethersphere/bee#5484)

## AI assistance disclosure
AI Assistance: Claude Code used for the architectural review (parallel
agents reading bee Go, nectar Rust, and the spec / mantaray-js
reference), drafting the patch, writing tests, and authoring this PR
description.

## Notes for reviewers
- The uniform-ref-width rule (`ref_size` governs both entry slot and
every fork ref slot) is what the bee spec doc
(`bee/pkg/manifest/mantaray/docs/format/node.md`) and every reference
impl (bee, mantaray-js, nectar) say; the HAZMAT block above
`decode_empty_terminal_node` captures the context.
- Decode tolerance is bounded: only the empty-forks-bitfield case.
Anything else with `ref_size = 0` errors out rather than silently
dropping forks like bee's v0.2 reader does.

## Checklist
- [x] Code follows project style
- [x] Self-review completed
- [x] Tests added (4 new regression tests)
- [x] No debug code left behind
- [x] PR title is descriptive
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.

mantaray: directory-only Add() leaves refBytesSize=0; forks silently lost on round-trip

1 participant