Files
zeekay 929bf86938 schain: M1 — manifest state root (multi-validator safety) + S3 object split
PART A — manifest STATE ROOT (the >1-validator prerequisite M0 omitted):
- state.Root() hashes the committed manifest keyspace deterministically via the
  zapdb prefix iterator NewIteratorWithStartAndPrefix(nil, prefixManifest),
  folding each entry length-prefixed in lexicographic key order. Mirrors
  dexvm/state/state.go:395 StateHash, narrowed to the one keyspace.
- VM.computeStateRoot folds blockHash+height with state.Root() (dexvm/vm.go:1260);
  ProcessBlock stamps BlockResult.StateRoot (dexvm/vm.go:584).
- The root travels in the block HEADER (new 32B field after parentID; block id
  commits to it). Block.Verify recomputes it and REJECTS a mismatch
  (errStateRootMismatch) + Aborts staged writes — the safety gate dexvm computed
  but never compared.
- Tests: determinism across write order, change-sensitivity (object set / etag /
  size / fileIds), Verify-rejects-tampered-root.

PART B — S3 OBJECT path, on-chain-metadata / off-chain-blob split, end to end
(schain/object/):
- Store.PutObject streams the blob to a Volume (OFF chain) -> fid, then commits
  only PutManifest{bucket,object,[fid],size,etag} to the VM through a real block
  (ON chain). GetObject reads the manifest back and reconstructs from the volume.
- object.Volume is the seam; MemVolume satisfies it for M1 with a fid shape
  mirroring hanzo/s3 needle.FileId and etag = base64(md5(blob)) (= ContentMd5).
- M2 PLUG POINT (object/volume.go): swap MemVolume for a github.com/hanzoai/s3
  s3/operation.SubmitFiles adapter; nothing in the VM or object.Store changes.
- Test proves blob bytes appear in NO accepted block, the block stays
  manifest-small, the manifest IS on chain, and GET byte-reconstructs.

luxfi packages only; zapdb-native; no protobuf in state; SDKROOT needed on macOS.
2026-06-25 13:28:14 -07:00

56 lines
2.8 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package object wires the S3 OBJECT path to the S-Chain storage VM, proving the
// on-chain-metadata / off-chain-blob split end to end:
//
// - On PUT, the blob bytes are streamed to a VOLUME server (a needle write) —
// OFF chain. The volume returns a file id (fid) per blob. The blob NEVER
// enters a block.
// - The small MANIFEST {bucket, object, fileIds, size, etag} is submitted as a
// PutManifest tx to the S-Chain VM — ON chain. Only this consensus-sized
// metadata becomes block state.
// - On GET, the manifest is read back from the VM (GetManifest), the fids it
// names are streamed from the volume, and the original blob is reconstructed.
//
// The Volume interface below is the SEAM. For M1 it is satisfied by a faithful
// in-memory stub (memvolume.go) whose Write/Read mirror the hanzo/s3 volume
// contract: Write(blob) -> fid, Read(fid) -> blob. For M2, the real hanzo/s3
// volume client (github.com/hanzoai/s3 s3/operation.SubmitFiles, which assigns a
// volume + streams the needle and returns SubmitResult.Fid) plugs in HERE, behind
// this identical interface — see the M2 PLUG POINT note on Volume. Keeping the
// seam in chains/schain (rather than importing the hanzoai/s3 module into the
// luxfi/chains module) respects the org/module boundary while proving the exact
// data model the real client will satisfy.
package object
import "errors"
var (
// ErrBlobNotFound is returned by Volume.Read when no blob is stored under fid.
ErrBlobNotFound = errors.New("object: blob not found in volume")
// ErrEmptyBlob rejects a PutObject with no bytes (M1 stores at least one fid).
ErrEmptyBlob = errors.New("object: empty blob")
)
// Volume is the OFF-CHAIN blob store seam. It is the minimal projection of the
// hanzo/s3 volume server the object path needs: write bytes, get a file id back;
// read a file id, get the bytes back. The fid is opaque to this package — it is
// the volume's own address (the SeaweedFS-style "volumeId,needleIdCookie" string
// the real s3 volume returns).
//
// M2 PLUG POINT: replace the in-memory stub (NewMemVolume) with a thin adapter
// over github.com/hanzoai/s3 s3/operation.SubmitFiles:
//
// Write(blob) -> SubmitFiles(...).Fid (assign volume + stream needle)
// Read(fid) -> GET http://<volume>/<fid> (stream needle bytes back)
//
// No code in object.go / the VM changes — only this interface's implementation.
type Volume interface {
// Write streams blob to the volume (OFF chain) and returns its file id.
Write(blob []byte) (fid string, err error)
// Read streams the blob bytes for fid back from the volume. Returns
// ErrBlobNotFound if no such blob exists.
Read(fid string) (blob []byte, err error)
}