mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
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.
76 lines
2.3 KiB
Go
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
|
|
}
|