schain

package
v1.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 25, 2026 License: BSD-3-Clause Imports: 23 Imported by: 0

Documentation

Overview

allocate_auth.go — the cryptographic half of the leaderless pinned-writer gate.

pinning.Owner answers "WHICH validator owns this range" deterministically. This file answers the verifiable question the original gate could not: "did the ACTUAL owner authorize this allocate?" — without trusting any unverifiable proposer identity. An AllocateTx carries an ML-DSA signature by the owner's staking key; the owner's NodeID is a SHAKE256-384 commitment to that key (ids.DeriveMLDSA), so the carried public key is bound to the claimed NodeID and the signature is bound to the key. A non-owner cannot forge it: they cannot sign as the owner (no key), and claiming the owner's NodeID with their own key fails the NodeID re-derivation.

The signature uses the SAME ML-DSA scheme the validator NodeID derives from (FIPS 204 ML-DSA-65/87), so the staking identity and the allocate authorization are one key, one trust root. Verification is pure and local: it needs only the tx, the epoch-frozen member set (already required by pinning), and the chain id the NodeIDs were derived under — no network, no live validator lookup.

Package schain implements the Lux S-Chain — the STORAGE VM.

The S-Chain records object STORAGE MANIFESTS through real Lux consensus: a PutManifest mutation enters the mempool, the proposer drains it into a block, every validator deterministically applies it to a per-block version layer during Verify, and the block COMMITS the version layer to the chain's database (zapdb via the luxfi/database interface) in exactly ONE atomic batch at Accept. A manifest is durable — and GetManifest returns it — only after Accept.

M0 is the smallest end-to-end proof of that contract: no blobs, no pinning, no networking beyond the VM↔engine contract. It forks dexvm's structure and COMMIT DISCIPLINE (chains/dexvm/vm.go), stripped of the DEX's cross-chain relay/settlement machinery, which a storage VM does not have:

  • vm.db (versiondb) is the per-block state layer; vm.baseDB is the durable base it wraps (mirror of dexvm/vm.go:316-318).
  • ProcessBlock applies txs to the version layer, NO I/O, deterministic (mirror of dexvm/vm.go:542).
  • acceptBlock does ONE db.CommitBatch() + batch.Write() then db.Abort() (mirror of dexvm/vm.go:1194 — its no-shared-memory branch).

DESIGN: no background goroutines; every operation is block-driven and deterministic, so every node produces identical state from identical inputs.

Index

Constants

This section is empty.

Variables

View Source
var (
	// VMID is the unique identifier for the S-Chain storage VM. Derived from the
	// ASCII bytes of "schain", left-aligned and zero-padded to 32 bytes — the same
	// byte pattern dexvm uses (dexvm/factory.go:31, ids.ID{'d','e','x','v','m'}).
	VMID = ids.ID{'s', 'c', 'h', 'a', 'i', 'n'}
)

Functions

This section is empty.

Types

type AllocateSigner

type AllocateSigner struct {
	// contains filtered or unexported fields
}

AllocateSigner stamps an unsigned AllocateTx with the proposer's ML-DSA pinned-writer authorization. It binds one validator's staking key to the allocate gate: its NodeID is the SHAKE256-384 commitment to its public key under identityChainID, and it signs with the matching secret key. The VM installs one on the owning node; BuildBlock uses it to sign the allocates that node owns.

func NewMLDSA65Signer

func NewMLDSA65Signer(chainID ids.ID, pub *mldsa65.PublicKey, sk *mldsa65.PrivateKey) (*AllocateSigner, error)

NewMLDSA65Signer builds an AllocateSigner over an ML-DSA-65 staking key. chainID is the chain the validator NodeIDs are derived under (the same id Verify uses to re-derive and check the NodeID binding).

func NewMLDSA87Signer

func NewMLDSA87Signer(chainID ids.ID, pub *mldsa87.PublicKey, sk *mldsa87.PrivateKey) (*AllocateSigner, error)

NewMLDSA87Signer builds an AllocateSigner over an ML-DSA-87 staking key (the high-value validator scheme). Identical contract to NewMLDSA65Signer.

func (*AllocateSigner) NodeID

func (s *AllocateSigner) NodeID() ids.NodeID

NodeID is this signer's validator NodeID (the candidate range owner).

type Block

type Block struct {
	// contains filtered or unexported fields
}

Block is an S-Chain block. It wraps the deterministic ProcessBlock result and implements chain.Block. It carries only the storage VM's needs: a header and the serialized PutManifest transactions — no cross-chain carried fills (the dexvm wire-format complication a storage VM does not have).

func (*Block) Accept

func (b *Block) Accept(ctx context.Context) error

Accept marks the block accepted and commits its state batch in ONE atomic write (the single commit point — dexvm/block.go:159). After this returns, the block's manifests are durable and GetManifest observes them.

func (*Block) Bytes

func (b *Block) Bytes() []byte

Bytes serializes the block. Wire format:

height[8] | timestamp[8] | parentID[32] | stateRoot[32] |
txCount[4] | txCount × ( txLen[4] | txBytes )

The txCount prefix makes the transactions self-delimiting. stateRoot is the proposer's claimed manifest state root, carried in the header so the block id (sha256 of these bytes) commits to it — a peer cannot swap the claimed root or a manifest while keeping the same id, and Verify rejects a claimed root that does not match the root recomputed from the applied txs.

func (*Block) Height

func (b *Block) Height() uint64

func (*Block) ID

func (b *Block) ID() ids.ID

func (*Block) Parent

func (b *Block) Parent() ids.ID

func (*Block) ParentID

func (b *Block) ParentID() ids.ID

func (*Block) Reject

func (b *Block) Reject(ctx context.Context) error

Reject discards the block's staged version-layer changes (dexvm/block.go:175).

func (*Block) Status

func (b *Block) Status() uint8

func (*Block) Timestamp

func (b *Block) Timestamp() time.Time

func (*Block) Verify

func (b *Block) Verify(ctx context.Context) error

Verify processes the block deterministically against the version layer, then enforces the manifest STATE ROOT: it recomputes the root from the txs it just applied and rejects the block if that computed root does not match the root the proposer claimed in the header. This is the multi-validator safety gate — a proposer (or a corrupted replica) whose post-apply manifest state diverges from what an honest validator computes cannot get its block accepted, because the claimed root (committed by the block id) and the recomputed root disagree. Verify performs NO external I/O on any node (mirror of dexvm/block.go:141); the manifest writes land in the in-memory version layer and become durable only at Accept.

type BlockContext

type BlockContext struct {
	// Members is the validator set frozen at Epoch, projected to (NodeID, Weight).
	Members []pinning.Member
	// Proposer is the NodeID that built this block. It is NO LONGER part of the
	// owner-gate trust path (the gate keys on the tx's verifiable ML-DSA signer);
	// it remains so BuildBlock knows which key signs and for logging.
	Proposer ids.NodeID
	// Epoch is the P-Chain height Members was frozen at (block.pChainHeight).
	Epoch uint64
	// IdentityChainID is the chain id the validator NodeIDs were derived under
	// (ids.DeriveMLDSA). Verify re-derives a signer's NodeID from its carried public
	// key under this id to check the key-to-NodeID binding. It is a fixed,
	// network-wide constant (the staking chain id), so this stays a pure local input.
	IdentityChainID ids.ID
}

BlockContext carries the deterministic consensus inputs an AllocateTx's owner gate needs: the validator set frozen at the block's epoch (block.pChainHeight) and the identity of the block's proposer. Both are pure inputs — every node verifying the block resolves the SAME owner against the SAME frozen set, so the gate is evaluated inside deterministic block apply with ZERO network I/O (the purity premise the whole model rests on — DESIGN_pinned_writer.md §6.4).

WIRE SEAM (master-cutover stage): in Stage 1 BlockContext is an explicit parameter threaded from the test harness / proposer. In production it must be populated from the REAL consensus runtime, ONCE, at the points the validator set + proposer are cleanly available WITHOUT a network round-trip inside Verify:

  • Members: pinning.Member projection of vm.consensusRuntime.ValidatorState.GetValidatorSet(ctx, block.pChainHeight, netID) — id+weight only. This MUST be a LOCAL lookup against already-synced P-Chain state at the historical height; if it can block on the network, resolution moves OUT of Verify (pin in BuildBlock, carry owner+fingerprint in the tx, Verify only re-checks the fingerprint). See §6.4 fallback.
  • Proposer: the block's proposer NodeID, from the consensus block header (block.pChainHeight's proposer / the engine's BuildBlock identity), NOT vm.consensusRuntime.NodeID — that is "me", which is only the proposer on the building node, not on a verifying node.
  • Epoch: block.pChainHeight, the height Members was frozen at, so a peer can recompute pinning.EpochFingerprint and reject an epoch-skewed pin.

An empty BlockContext (nil Members) is the M0/no-allocate path: a block with no AllocateTx is unaffected, so existing PutManifest blocks need no context.

type BlockContextBuilder

type BlockContextBuilder func(ctx context.Context, height uint64) (BlockContext, error)

BlockContextBuilder resolves the deterministic consensus inputs (validator set, proposer, epoch) the AllocateTx owner gate needs for a block at the given height. It MUST be a pure local computation (no network I/O) so block Verify stays deterministic. height is the S-Chain block height; the implementation maps it to the epoch's pChainHeight and the block's proposer.

type BlockResult

type BlockResult struct {
	BlockHeight uint64
	Timestamp   time.Time

	// StateRoot is the deterministic commitment over the manifest keyspace AFTER
	// this block's writes are staged, folded with the block's consensus binding
	// (blockHash + height). It travels in the block header and Block.Verify
	// recomputes it on every validator and rejects a block whose claimed root
	// does not match — the multi-validator safety gate M0 omitted.
	StateRoot ids.ID
	// contains filtered or unexported fields
}

BlockResult is the deterministic result of processing one block. For the storage VM the per-block output is simply the height/time/blockHash binding — the manifest mutations are staged directly into the version layer by ProcessBlock and committed at Accept. There is no cross-chain leg to carry.

type ChainVM

type ChainVM struct {
	// contains filtered or unexported fields
}

ChainVM wraps the functional storage VM to implement chain.ChainVM — the interface the chains manager drives. It owns the in-memory block index, mempool, and last-accepted pointers; the inner VM owns state + commit. This is the dexvm/chainvm.go boilerplate, stripped of the DEX fee gate and DAG hooks the storage VM does not need.

func NewChainVM

func NewChainVM(logger log.Logger) *ChainVM

NewChainVM constructs a ChainVM wrapping a fresh inner storage VM.

func (*ChainVM) BuildBlock

func (cvm *ChainVM) BuildBlock(ctx context.Context) (chain.Block, error)

BuildBlock drains the mempool into a new block on top of the preferred tip. The proposer chooses the block time (wall clock, clamped non-decreasing) and carries it in the bytes — the consensus-agreement point. The block id is the hash of the serialized bytes, so it commits to every drained transaction.

func (*ChainVM) Connected

func (cvm *ChainVM) Connected(ctx context.Context, nodeID ids.NodeID, v *version.Application) error

Connected is a no-op for M0 (no peer-version tracking needed).

func (*ChainVM) Disconnected

func (cvm *ChainVM) Disconnected(ctx context.Context, nodeID ids.NodeID) error

Disconnected is a no-op for M0.

func (*ChainVM) GetBlock

func (cvm *ChainVM) GetBlock(ctx context.Context, blkID ids.ID) (chain.Block, error)

GetBlock returns a block by id.

func (*ChainVM) GetBlockIDAtHeight

func (cvm *ChainVM) GetBlockIDAtHeight(ctx context.Context, height uint64) (ids.ID, error)

GetBlockIDAtHeight returns the accepted block id at a height.

func (*ChainVM) GetInnerVM

func (cvm *ChainVM) GetInnerVM() *VM

GetInnerVM exposes the inner VM for direct reads (e.g. GetManifest).

func (*ChainVM) HealthCheck

func (cvm *ChainVM) HealthCheck(ctx context.Context) (chain.HealthResult, error)

HealthCheck delegates to the inner VM.

func (*ChainVM) Initialize

func (cvm *ChainVM) Initialize(ctx context.Context, vmInit vmcore.Init) error

Initialize wires the inner VM and seeds the genesis block.

func (*ChainVM) LastAccepted

func (cvm *ChainVM) LastAccepted(ctx context.Context) (ids.ID, error)

LastAccepted returns the last accepted block id.

func (*ChainVM) NewHTTPHandler

func (cvm *ChainVM) NewHTTPHandler(ctx context.Context) (http.Handler, error)

NewHTTPHandler assembles the inner VM's handlers behind one mux.

func (*ChainVM) ParseBlock

func (cvm *ChainVM) ParseBlock(ctx context.Context, data []byte) (chain.Block, error)

ParseBlock parses a block from bytes, deduplicating against the index.

func (*ChainVM) SetAllocateSigner

func (cvm *ChainVM) SetAllocateSigner(s *AllocateSigner)

SetAllocateSigner installs the ML-DSA staking key this node signs its owned allocates with at BuildBlock. A node only ever produces VALID allocates for the ranges it owns (a signed allocate whose signer is not the HRW owner is rejected at Verify on every node), so installing a signer never lets a node write a range it does not own — it only lets it authorize the ranges it does.

func (*ChainVM) SetBlockContextBuilder

func (cvm *ChainVM) SetBlockContextBuilder(b BlockContextBuilder)

SetBlockContextBuilder installs the resolver that supplies the AllocateTx owner gate its validator set + proposer identity. Stage 1 wire point; production installs the real consensus-runtime-backed resolver here.

func (*ChainVM) SetPreference

func (cvm *ChainVM) SetPreference(ctx context.Context, blkID ids.ID) error

SetPreference sets the tip new blocks build on.

func (*ChainVM) SetState

func (cvm *ChainVM) SetState(ctx context.Context, stateNum uint32) error

SetState delegates to the inner VM.

func (*ChainVM) Shutdown

func (cvm *ChainVM) Shutdown(ctx context.Context) error

Shutdown delegates to the inner VM.

func (*ChainVM) SubmitTx

func (cvm *ChainVM) SubmitTx(tx []byte) error

SubmitTx admits a transaction to the mempool and notifies the engine to build a block. This is the canonical user-mempool entry. The bytes are validated (parse + Verify) before they touch the pending pool, so a malformed manifest never enters a block.

func (*ChainVM) Version

func (cvm *ChainVM) Version(ctx context.Context) (string, error)

Version delegates to the inner VM.

func (*ChainVM) WaitForEvent

func (cvm *ChainVM) WaitForEvent(ctx context.Context) (vmcore.Message, error)

WaitForEvent blocks until an event should trigger block building. M0 triggers builds via SubmitTx -> PendingTxs on toEngine, so this simply parks until the context is cancelled (mirror of dexvm/chainvm.go:408).

type Factory

type Factory struct{}

Factory creates new S-Chain VM instances for the chains manager.

func (*Factory) New

func (f *Factory) New(logger log.Logger) (interface{}, error)

New implements vms.Factory. It returns a ChainVM (which implements chain.ChainVM) for the chains manager to drive. Unlike dexvm, the storage VM allocates no GPU session — there is no latency-critical compute on its hot path; manifest commits are pure database writes.

type Status

type Status uint8

Status represents block status.

const (
	StatusUnknown Status = iota
	StatusProcessing
	StatusAccepted
	StatusRejected
)

type VM

type VM struct {
	// contains filtered or unexported fields
}

VM is the inner functional storage VM. It holds the dual-DB layering and the typed manifest state; the chain.ChainVM wrapper (ChainVM) drives it.

func (*VM) CreateHandlers

func (vm *VM) CreateHandlers(ctx context.Context) (map[string]http.Handler, error)

CreateHandlers returns the VM's HTTP handlers. M0 exposes none (the VM contract + commit discipline is the deliverable; the S3 API surface is M1+).

func (*VM) GetBlockHeight

func (vm *VM) GetBlockHeight() uint64

GetBlockHeight returns the current block height.

func (*VM) GetLastBlockTime

func (vm *VM) GetLastBlockTime() time.Time

GetLastBlockTime returns the timestamp of the last processed block.

func (*VM) GetManifest

func (vm *VM) GetManifest(bucket, object string) (state.Manifest, bool, error)

GetManifest returns a committed manifest for (bucket, object). Reads go through the version layer, so this observes only state already committed by an accepted block (the in-memory staging from an unaccepted block is the proposer's transient view, not durable). The M0 proof asserts a manifest is visible here ONLY after Accept.

func (*VM) HealthCheck

func (vm *VM) HealthCheck(ctx context.Context) (chain.HealthResult, error)

HealthCheck reports the VM healthy once initialized.

func (*VM) Initialize

func (vm *VM) Initialize(ctx context.Context, vmInit vmcore.Init) error

Initialize sets up the VM with the consensus runtime, database, and channels. It establishes the dual-DB layering (baseDB durable, db versiondb) the commit discipline depends on — the exact wiring of dexvm/vm.go:311-321.

func (*VM) ProcessBlock

func (vm *VM) ProcessBlock(ctx context.Context, blockHeight uint64, blockTime time.Time, blockTxs [][]byte, blockCtx BlockContext) (*BlockResult, error)

ProcessBlock deterministically applies a block's transactions to the version layer. It performs NO external I/O — the same inputs produce identical state on every node, so Verify is a pure function (mirror of dexvm/vm.go:542). The manifest writes land in vm.db's in-memory layer and become durable only when acceptBlock commits the batch.

func (*VM) SetState

func (vm *VM) SetState(ctx context.Context, stateNum uint32) error

SetState is a no-op for M0 (no bootstrap state machine to drive).

func (*VM) Shutdown

func (vm *VM) Shutdown(ctx context.Context) error

Shutdown closes the database.

func (*VM) Version

func (vm *VM) Version(ctx context.Context) (string, error)

Version returns the VM version string.

Directories

Path Synopsis
cmd
plugin command
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:
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:
Package pinning is the deterministic single-writer assignment for the S-Chain storage VM.
Package pinning is the deterministic single-writer assignment for the S-Chain storage VM.
Package state manages persistent state for the S-Chain — the Lux storage VM.
Package state manages persistent state for the S-Chain — the Lux storage VM.
Package txs defines the transaction surface for the S-Chain — the Lux STORAGE VM.
Package txs defines the transaction surface for the S-Chain — the Lux STORAGE VM.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL