txs

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: 4 Imported by: 0

Documentation

Overview

Package txs defines the transaction surface for the S-Chain — the Lux STORAGE VM. M0 has exactly one mutation: PutManifest, which records the (bucket, object) -> manifest mapping for an object whose file blobs already exist elsewhere (the S-Chain does NOT carry blobs in M0; it carries the content manifest that names them).

Wire format mirrors dexvm/txs exactly — a single type byte followed by the JSON body of the concrete struct (dexvm/txs/tx.go:621). The encoding is deterministic (encoding/json emits struct fields in declaration order and these types contain no maps), so the same logical transaction always serializes to identical bytes and therefore the same TxID. One codec, one way to read a transaction off the wire.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidTxType is returned when the leading type byte names no known tx.
	ErrInvalidTxType = errors.New("invalid transaction type")
	// ErrEmptyBucket / ErrEmptyObject reject a manifest with no addressable key.
	ErrEmptyBucket = errors.New("manifest: empty bucket")
	ErrEmptyObject = errors.New("manifest: empty object")
	// ErrNoFileIDs rejects a manifest that names no file blobs (M0 carries the
	// content manifest; an object with zero files has nothing to commit).
	ErrNoFileIDs = errors.New("manifest: no file ids")
	// ErrEmptyRange rejects an allocation with no addressable partition key.
	ErrEmptyRange = errors.New("allocate: empty range")
	// ErrZeroCount rejects an allocation that requests no ids (a no-op write that
	// would still consume a tx slot and move the counter by 0 — forbidden so every
	// AllocateTx advances the counter and yields a non-empty id range).
	ErrZeroCount = errors.New("allocate: zero count")
)

Functions

func AllocateSigningBytes

func AllocateSigningBytes(rng string, count uint32, epoch, nonce uint64, fingerprint ids.ID) []byte

AllocateSigningBytes is the canonical message an AllocateTx's owner signs: the SP 800-185-framed encoding of (domain, Range, Count, Epoch, Nonce, Fingerprint). Every field is length-framed (left_encode of its bit length) so concatenation is unambiguous — a verifier cannot be tricked by a Range whose bytes spell another field's payload. The Signer/PubKey/Sig fields are deliberately NOT covered: the signature authenticates the key, the key re-derives the NodeID, and signing the Sig would be circular.

func Marshal

func Marshal[T any](tx *T, txType TxType) ([]byte, error)

Marshal serializes a concrete transaction into wire bytes: type byte + JSON body. The single codec used by both constructors and the parser.

Types

type AllocateTx

type AllocateTx struct {
	BaseTx
	Range string `json:"range"`
	Count uint32 `json:"count"`

	// Epoch / Nonce / Fingerprint are proposer-stamped at BuildBlock and bound
	// into the signature. Zero on an unsigned mempool intent.
	Epoch       uint64 `json:"epoch"`
	Nonce       uint64 `json:"nonce"`
	Fingerprint ids.ID `json:"fingerprint"`

	// Signer / SignerScheme / SignerPubKey / Sig are the ML-DSA pinned-writer
	// authorization. Empty on an unsigned intent.
	Signer       ids.NodeID `json:"signer"`
	SignerScheme uint8      `json:"signerScheme"`
	SignerPubKey []byte     `json:"signerPubKey"`
	Sig          []byte     `json:"sig"`
}

AllocateTx reserves Count ids in the per-range allocator counter alloc/<Range>. Range is the pinned key-range / partition (a volume-collection or bucket-shard) the allocation belongs to; the HRW owner of Range is the ONLY validator permitted to emit this tx, and a block containing an AllocateTx not signed by that owner is rejected at Verify (the leaderless pinned-writer safety gate — see DESIGN_pinned_writer.md §2-3). The id range it reserves is a pure function of the committed counter: [base, base+Count), where base is the counter's value before this tx — so every validator derives identical ids.

SIGNED PINNED-WRITER AUTHORIZATION (the property that replaces raft's serialized writer). The original gate keyed on the block's "proposer" identity, which a verifying node CANNOT check — a Lux block carries no verifiable proposer, and resolving one may need a network call, so the gate was unenforceable for >1 validator. The fix makes ownership SELF-ATTESTED and cryptographically verifiable inside the pure, local block apply:

  • Signer — the claimed HRW owner's NodeID. Verify recomputes pinning.Owner(Range, V@Epoch) and requires Signer == owner.
  • SignerScheme — the NodeIDScheme byte (0x42 ML-DSA-65 / 0x43 ML-DSA-87) the NodeID was derived under.
  • SignerPubKey — the signer's ML-DSA public key. Verify RE-DERIVES the NodeID from this key (NodeID = SHAKE256-384(domain‖chainID‖scheme‖ pubkey)[:20]) and requires it to equal Signer. The NodeID is thus a binding commitment to the key — a forger cannot supply a different key for the owner's NodeID (~2^160 second-preimage on the truncated SHAKE; the same bound the identity system already rests on).
  • Sig — an ML-DSA signature over SigningBytes() (the canonical, SP 800-185-framed encoding of Range‖Count‖Epoch‖Nonce‖ Fingerprint — NOT over the Sig/Signer/PubKey fields, which would be circular). Only the holder of the owner's secret key can produce it.
  • Epoch — the P-Chain height the validator set was frozen at. Verify requires it to equal the block's epoch so ownership is resolved against the agreed set, and binding it into the signature stops cross-epoch replay where ownership differs.
  • Nonce — a per-emission uniquifier (the proposer stamps the block height) so two allocations of the same Range/Count in different blocks sign distinct messages.
  • Fingerprint — pinning.EpochFingerprint(Epoch, members): the signer's commitment to the EXACT validator set it pinned against. Verify recomputes it from its OWN local snapshot and rejects a mismatch (DESIGN §6.4) — so a proposer that pinned against a set the verifier does not hold cannot get its block accepted, and Verify never has to fetch a set over the network.

An AllocateTx enters the mempool as an UNSIGNED intent (Range+Count only); the owning proposer stamps Epoch/Nonce/Fingerprint and signs it at BuildBlock with its ML-DSA staking key.

func NewAllocateTx

func NewAllocateTx(rng string, count uint32) *AllocateTx

NewAllocateTx builds a wire-ready UNSIGNED Allocate intent (Range + Count). The proposer stamps Epoch/Nonce/Fingerprint and the ML-DSA authorization at BuildBlock via WithAuthorization. finalize stamps the deterministic wire bytes + TxID, so the returned tx is immediately Parse-round-trippable (mirrors NewPutManifestTx and every dexvm New*Tx).

func (*AllocateTx) IsSigned

func (tx *AllocateTx) IsSigned() bool

IsSigned reports whether the pinned-writer authorization has been stamped.

func (*AllocateTx) SigningBytes

func (tx *AllocateTx) SigningBytes() []byte

SigningBytes returns this tx's canonical signing message (see AllocateSigningBytes).

func (*AllocateTx) Verify

func (tx *AllocateTx) Verify() error

Verify validates an Allocate in isolation: it must name a non-empty range and reserve at least one id. No state is consulted and the OWNER GATE is NOT checked here — Verify is pure and per-tx, but ownership (and the signature over the validator set) is a function of the BLOCK's frozen validator set, which a single tx cannot see. The owner + signature gate lives in the VM's block-level apply (see schain.VM.applyAllocate), the same discipline that keeps PutManifestTx.Verify state-free.

func (*AllocateTx) WithAuthorization

func (tx *AllocateTx) WithAuthorization(
	epoch, nonce uint64,
	fingerprint ids.ID,
	signer ids.NodeID,
	scheme uint8,
	pub, sig []byte,
) *AllocateTx

WithAuthorization returns a NEW finalized, wire-ready AllocateTx carrying the proposer-stamped Epoch/Nonce/Fingerprint and the ML-DSA pinned-writer authorization (Signer/SignerScheme/SignerPubKey/Sig). The receiver supplies only Range/Count; the returned tx is the authoritative signed image that travels in the block (its TxID covers the authorization, so a peer cannot strip or swap it).

type BaseTx

type BaseTx struct {
	TxID   ids.ID `json:"-"`
	TxType TxType `json:"type"`
	// contains filtered or unexported fields
}

BaseTx carries the fields common to every S-Chain transaction.

TxID is intentionally NOT serialized (json:"-"): the id is the checksum of the wire bytes, so embedding it in those bytes would be circular. It is always (re)derived from the wire on Parse and stamped by finalize on construction — the exact discipline dexvm/txs/tx.go:107 uses.

func (*BaseTx) Bytes

func (tx *BaseTx) Bytes() []byte

func (*BaseTx) ID

func (tx *BaseTx) ID() ids.ID

func (*BaseTx) Type

func (tx *BaseTx) Type() TxType

type PutManifestTx

type PutManifestTx struct {
	BaseTx
	Bucket  string   `json:"bucket"`
	Object  string   `json:"object"`
	FileIDs []string `json:"fileIds"`
	Size    int64    `json:"size"`
	ETag    string   `json:"etag"`
}

PutManifestTx records the manifest for one object. FileIDs names the content blobs (their ids in whatever blob store M1 wires in); Size and ETag are the object-level metadata an S3 HEAD returns. M0 commits these verbatim.

func NewPutManifestTx

func NewPutManifestTx(bucket, object string, fileIDs []string, size int64, etag string) *PutManifestTx

NewPutManifestTx builds a wire-ready PutManifest transaction. finalize stamps the deterministic wire bytes + TxID, so the returned tx is immediately Parse-round-trippable (mirrors every dexvm New*Tx constructor).

func (*PutManifestTx) Verify

func (tx *PutManifestTx) Verify() error

Verify validates a PutManifest in isolation: it must name an addressable (bucket, object) and at least one file blob. No state is consulted (Verify is pure — the same discipline the VM relies on for deterministic block Verify).

type Tx

type Tx interface {
	// ID returns the deterministic transaction identifier (checksum of wire bytes).
	ID() ids.ID
	// Type returns the transaction type.
	Type() TxType
	// Bytes returns the serialized transaction (type byte + JSON body).
	Bytes() []byte
	// Verify validates the transaction in isolation (no state access).
	Verify() error
}

Tx is the interface every S-Chain transaction satisfies.

type TxParser

type TxParser struct{}

TxParser parses raw transaction bytes off the wire.

func (*TxParser) Parse

func (p *TxParser) Parse(data []byte) (Tx, error)

Parse decodes a transaction from its wire bytes (type byte + JSON body).

type TxType

type TxType uint8

TxType is the transaction discriminator (the leading wire byte).

const (
	// TxPutManifest records a (bucket, object) -> manifest mapping. The single
	// mutation of M0.
	TxPutManifest TxType = iota
	// TxAllocate reserves a contiguous, monotonic id range in a per-range
	// allocator counter — the leaderless pinned-writer replacement for raft's
	// global volume-id / fileId sequence. Emitted ONLY by the HRW owner of the
	// range; the owner gate is enforced at block Verify, not in the tx codec.
	TxAllocate
)

func (TxType) String

func (t TxType) String() string

Jump to

Keyboard shortcuts

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