Skip to content

feat(seeder, worker/studio): the NFX file store, and a free upload packaged into it - #36

Closed
SovereignTechnology wants to merge 3 commits into
mainfrom
feat/nfx-store
Closed

SovereignTechnology wants to merge 3 commits into
mainfrom
feat/nfx-store

Conversation

@SovereignTechnology

Copy link
Copy Markdown
Owner

Phase 1 slice 2b of the NFX interop plan (ADR 0020): where NFX files live on a seeder, and the studio step that puts a free upload there. Stacked work from #32 and #33, which are now on main.

What's in it

  • NfxFileStore (packages/seeder/src/store/nfx-files.ts):
    • Keeps each NFX video's files under their sha256 in <dataDir>/nfx/files/, apart from every Hypercore, so none of them is served on the legacy swarm.
    • A put checks the hash list against root, the address and segs, then each file's size and sha256 against the list, and keeps nothing on a mismatch.
    • A file two videos list is stored and counted once. A list giving one sha256 two sizes, or a stored file another size, is refused.
    • Hash lists over 16 MiB are refused, and buffers are copied before they're checked.
  • One disk cap: the seeder sizes its DiskCap over the Hyperblobs payload plus these files.
  • Crashes:
    • A put is recorded as pending before its first write, and a load removes its files.
    • Removals delete files before they save the index.
    • Temporary files have fixed names that a load cleans up.
    • At load, the size on disk decides which records are true.
    • A malformed index entry is dropped on its own; an unreadable index is set aside, not overwritten.
    • A store that can't load leaves NFX off instead of failing the seeder.
  • Studio (packages/app-desktop/src/worker/studio/nfx.ts):
    • A free upload whose renditions are all H.264 in MP4 is packaged (media.packageNfx, stream copy) and stored before the legacy publish.
    • It is removed again if that publish fails.
    • NFX never fails an upload.

Off until slice 5

runWorkerUpload packages only when given WorkerNfxDeps (the network and a video-id source), and the host doesn't pass them yet. Uploads stay legacy only.

The free switch is decided: an option for uploaders to publish free and for seeders to seed for free. It comes with the slice 5 settings, since the Studio today requires at least 1 sat.

Review before push

An independent adversarial review of the first two commits found 14 items. The third commit fixes every one that applies to this slice, with tests:

  • a compile error;
  • a crafted list naming a stored file at a false size, which deleted another video at the next restart;
  • a sha listed at two sizes, which skewed the cap;
  • crash leftovers;
  • whole-index loss on one bad entry;
  • load failures stopping the seeder;
  • the rollback removing a video it hadn't stored;
  • a throwing id source failing the upload.

Left for slice 5, and noted in the code:

  • Verified manifests: a peer's video/root must come from a manifest the caller verified, because the first put for an address wins.
  • Bounds: limits on file count and video count for peer-supplied lists.
  • Liveness: removing the NFX copy when its legacy video is re-priced or deleted.

Load checks sizes, not content. A same-size corruption is caught by the receiver's sha256 check (NFX-05 §4).

CI

Not yet run. This PR is the first run.

Merge

Squash, locally as sovtech.

NfxFileStore keeps NFX videos' files (ADR 0020, NFX-05) under their sha256 in
<dataDir>/nfx/files, apart from every Hypercore, so none of them is served on
the legacy swarm. A put verifies the hash list against root, address and segs,
then each file's size and sha256 against the list, and keeps nothing on a
mismatch. A file two videos list is stored and counted once. The index records
a put as pending before its first file is written, so a load removes what an
interrupted put left; a load also drops a video whose files are missing or of
the wrong size. The seeder sizes its one DiskCap over the Hyperblobs payload
and these files.
…egacy publish

storeNfxUpload remuxes the renditions the studio already encoded into NFX-05
content (media.packageNfx, no second encode) and stores it in the seeder's
NFX file store. runWorkerUpload does this only for a free upload whose
renditions are all H.264 in MP4 (ADR 0020: Phase 1 is free videos only, so a
free NFX copy never undercuts a paid video), and only when it is given
WorkerNfxDeps; the host passes none yet, so uploads stay legacy only until NFX
has its settings (slice 5). NFX never fails an upload: a failure is logged and
the upload goes on, and a failed legacy publish removes the NFX video again.
…afe order, never fail the seeder

From the pre-push adversarial review of the NFX file store:
- put() declared `listed` twice (a compile error); the verified list is now `verified`.
- A list naming a stored file (or a stored hash list) at another size, or one sha256
  at two sizes, is refused: before, it made the index disagree with the disk, and the
  next load dropped every video sharing that file, or the cap counted a false size.
- At load the size on disk decides: only the records it contradicts are dropped.
- Removals and rollbacks delete files before saving the index, and temporary files
  have one fixed name per sha256 that a load removes, so a crash never leaves a file
  no record names.
- A malformed index entry is dropped on its own; an index that is not one, or of
  another version, is set aside instead of written over.
- load() never throws: a failure leaves NFX off ('unavailable'), not the seeder down.
- Hash lists over 16 MiB are refused, and buffers are copied before they are checked.
- storeNfxUpload catches a throwing video-id source, and returns (and so rolls back)
  only a video it stored itself.
- putVideo documents that a peer's video and root must come from a verified manifest.
@SovereignTechnology

Copy link
Copy Markdown
Owner Author

Superseded: #35 merged its own store first; the review fixes from this PR are ported to it in the follow-up PR (fix/nfx-store-review).

@SovereignTechnology
SovereignTechnology deleted the feat/nfx-store branch October 25, 2026 03:16
SovereignTechnology added a commit that referenced this pull request Oct 25, 2026
…als, a load that never stops the seeder (#37)

The fixes from #36's adversarial review, ported to the store #35 merged:
- A list naming a stored file (or the stored hash list) at another size, or one sha256 at two
  sizes, is refused; before, the record took the false size and skewed totalBytes, file()
  sizes and later cap frees.
- A put is saved as a pending record before its first write, and a load removes a pending
  put's files and .part files; a removal deletes files before it saves the records.
- At load the size on disk decides which records are true; a malformed record is dropped on
  its own; a videos.json that is not one, or of another version, is set aside.
- load() never throws: a store that cannot load is off (loadError, puts 'unavailable').
- Hash lists over 16 MiB are refused; buffers are copied before they are checked.
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