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 ¶
- Variables
- type AllocateSigner
- type Block
- func (b *Block) Accept(ctx context.Context) error
- func (b *Block) Bytes() []byte
- func (b *Block) Height() uint64
- func (b *Block) ID() ids.ID
- func (b *Block) Parent() ids.ID
- func (b *Block) ParentID() ids.ID
- func (b *Block) Reject(ctx context.Context) error
- func (b *Block) Status() uint8
- func (b *Block) Timestamp() time.Time
- func (b *Block) Verify(ctx context.Context) error
- type BlockContext
- type BlockContextBuilder
- type BlockResult
- type ChainVM
- func (cvm *ChainVM) BuildBlock(ctx context.Context) (chain.Block, error)
- func (cvm *ChainVM) Connected(ctx context.Context, nodeID ids.NodeID, v *version.Application) error
- func (cvm *ChainVM) Disconnected(ctx context.Context, nodeID ids.NodeID) error
- func (cvm *ChainVM) GetBlock(ctx context.Context, blkID ids.ID) (chain.Block, error)
- func (cvm *ChainVM) GetBlockIDAtHeight(ctx context.Context, height uint64) (ids.ID, error)
- func (cvm *ChainVM) GetInnerVM() *VM
- func (cvm *ChainVM) HealthCheck(ctx context.Context) (chain.HealthResult, error)
- func (cvm *ChainVM) Initialize(ctx context.Context, vmInit vmcore.Init) error
- func (cvm *ChainVM) LastAccepted(ctx context.Context) (ids.ID, error)
- func (cvm *ChainVM) NewHTTPHandler(ctx context.Context) (http.Handler, error)
- func (cvm *ChainVM) ParseBlock(ctx context.Context, data []byte) (chain.Block, error)
- func (cvm *ChainVM) SetAllocateSigner(s *AllocateSigner)
- func (cvm *ChainVM) SetBlockContextBuilder(b BlockContextBuilder)
- func (cvm *ChainVM) SetPreference(ctx context.Context, blkID ids.ID) error
- func (cvm *ChainVM) SetState(ctx context.Context, stateNum uint32) error
- func (cvm *ChainVM) Shutdown(ctx context.Context) error
- func (cvm *ChainVM) SubmitTx(tx []byte) error
- func (cvm *ChainVM) Version(ctx context.Context) (string, error)
- func (cvm *ChainVM) WaitForEvent(ctx context.Context) (vmcore.Message, error)
- type Factory
- type Status
- type VM
- func (vm *VM) CreateHandlers(ctx context.Context) (map[string]http.Handler, error)
- func (vm *VM) GetBlockHeight() uint64
- func (vm *VM) GetLastBlockTime() time.Time
- func (vm *VM) GetManifest(bucket, object string) (state.Manifest, bool, error)
- func (vm *VM) HealthCheck(ctx context.Context) (chain.HealthResult, error)
- func (vm *VM) Initialize(ctx context.Context, vmInit vmcore.Init) error
- func (vm *VM) ProcessBlock(ctx context.Context, blockHeight uint64, blockTime time.Time, ...) (*BlockResult, error)
- func (vm *VM) SetState(ctx context.Context, stateNum uint32) error
- func (vm *VM) Shutdown(ctx context.Context) error
- func (vm *VM) Version(ctx context.Context) (string, error)
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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) Reject ¶
Reject discards the block's staged version-layer changes (dexvm/block.go:175).
func (*Block) Verify ¶
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 ¶
NewChainVM constructs a ChainVM wrapping a fresh inner storage VM.
func (*ChainVM) BuildBlock ¶
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) Disconnected ¶
Disconnected is a no-op for M0.
func (*ChainVM) GetBlockIDAtHeight ¶
GetBlockIDAtHeight returns the accepted block id at a height.
func (*ChainVM) GetInnerVM ¶
GetInnerVM exposes the inner VM for direct reads (e.g. GetManifest).
func (*ChainVM) HealthCheck ¶
HealthCheck delegates to the inner VM.
func (*ChainVM) Initialize ¶
Initialize wires the inner VM and seeds the genesis block.
func (*ChainVM) LastAccepted ¶
LastAccepted returns the last accepted block id.
func (*ChainVM) NewHTTPHandler ¶
NewHTTPHandler assembles the inner VM's handlers behind one mux.
func (*ChainVM) ParseBlock ¶
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 ¶
SetPreference sets the tip new blocks build on.
func (*ChainVM) SubmitTx ¶
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) WaitForEvent ¶
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.
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 ¶
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 ¶
GetBlockHeight returns the current block height.
func (*VM) GetLastBlockTime ¶
GetLastBlockTime returns the timestamp of the last processed block.
func (*VM) GetManifest ¶
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 ¶
HealthCheck reports the VM healthy once initialized.
func (*VM) Initialize ¶
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.
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. |