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

76 lines
2.3 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package object
import (
"fmt"
"sync"
)
// MemVolume is a faithful in-memory stand-in for the hanzo/s3 volume server,
// used to prove the object round-trip for M1 without a running volume daemon. It
// honours the Volume contract exactly: Write assigns a fresh file id in the
// SeaweedFS "volumeId,needleIdCookie" form the real volume returns, stores the
// bytes under it (the needle), and Read returns those bytes verbatim. It is the
// OFF-CHAIN store — nothing here ever touches a block.
//
// M2 swaps this for the real volume client; the VM and object.go are unaffected
// because both sides speak only the Volume interface.
type MemVolume struct {
mu sync.Mutex
volumeID uint32
nextKey uint64
blobs map[string][]byte
}
// NewMemVolume returns an empty in-memory volume bound to a single logical
// volumeId (a real deployment shards across many; one suffices for the M1 proof).
func NewMemVolume() *MemVolume {
return &MemVolume{
volumeID: 1,
blobs: make(map[string][]byte),
}
}
// Write stores blob under a fresh fid and returns the fid. The fid format mirrors
// needle.FileId.String() ("volumeId,needleIdCookie") so the manifest's fileIds
// are byte-identical in shape to what the real volume yields — the M2 client is a
// drop-in.
func (v *MemVolume) Write(blob []byte) (string, error) {
if len(blob) == 0 {
return "", ErrEmptyBlob
}
v.mu.Lock()
defer v.mu.Unlock()
v.nextKey++
// cookie is a fixed nonzero stand-in; the real volume randomises it.
fid := fmt.Sprintf("%d,%x%x", v.volumeID, v.nextKey, uint32(0x2a))
stored := make([]byte, len(blob))
copy(stored, blob)
v.blobs[fid] = stored
return fid, nil
}
// Read returns the bytes stored under fid, or ErrBlobNotFound.
func (v *MemVolume) Read(fid string) ([]byte, error) {
v.mu.Lock()
defer v.mu.Unlock()
blob, ok := v.blobs[fid]
if !ok {
return nil, ErrBlobNotFound
}
out := make([]byte, len(blob))
copy(out, blob)
return out, nil
}
// Has reports whether fid is present — used by tests to assert the blob lives in
// the volume (off chain) and not in any block.
func (v *MemVolume) Has(fid string) bool {
v.mu.Lock()
defer v.mu.Unlock()
_, ok := v.blobs[fid]
return ok
}