mirror of
https://github.com/luxfi/proto.git
synced 2026-07-27 07:04:45 +00:00
Forward-only ZAP. No backwards-compat shims: - zap_codec multi.go: drop BE-fallback read path - zap_codec zapcodec.go: drop BE-fallback read path + peekVersionBE helper - zap_native codec_select.go: rip LegacyEnabled var + LUXD_ENABLE_LEGACY_CODEC env knob + ErrLegacyCodecDisabled. Comment block simplified. ZAPActivationUnix stays at 1766708400 (Dec 25 2025 16:20 PST — Quasar activation timestamp, same as all other forks). ShouldUseZAPForWrite returns true unconditionally — ZAP is the only wire format. Tests cleaned: drop LegacyEnabled toggle scenarios in TestShouldUseZAPForWrite and TestRed_V15. Pin ZAPActivationUnix value to the Quasar timestamp. Validators upgrading from v1.28.x DBs cannot mount in this layout (pre-LP-023 BE-encoded P-chain state is unreadable). Mainnet/testnet/ devnet operators must rebootstrap from genesis + RLP re-import.
260 lines
9.1 KiB
Go
260 lines
9.1 KiB
Go
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package zap_codec
|
|
|
|
import (
|
|
"math"
|
|
|
|
"github.com/luxfi/utils/wrappers"
|
|
"github.com/luxfi/zapcodec"
|
|
)
|
|
|
|
// Codec is the local wire-codec interface — Marshal/Unmarshal/Size on
|
|
// a *wrappers.Packer. Structurally identical to the legacy
|
|
// github.com/luxfi/codec.Codec interface; every zapcodec.Codec instance
|
|
// satisfies it by embedding the same three methods. Consumers of
|
|
// proto/zap_codec hold this local type rather than importing the
|
|
// upstream codec package.
|
|
type Codec interface {
|
|
MarshalInto(interface{}, *wrappers.Packer) error
|
|
UnmarshalFrom(*wrappers.Packer, interface{}) error
|
|
Size(value interface{}) (int, error)
|
|
}
|
|
|
|
// Registry is the local type-registration interface. Structurally
|
|
// identical to github.com/luxfi/codec.Registry. Every zapcodec.Codec
|
|
// instance satisfies it via the embedded zapcodec.RegisterType.
|
|
type Registry interface {
|
|
RegisterType(interface{}) error
|
|
}
|
|
|
|
// LinearRegistry extends Registry with the slot-skipping operation used
|
|
// by historical PVM/XVM type layouts. zapcodec.Codec satisfies this
|
|
// (its SkipRegistrations bumps the next-type-id counter).
|
|
type LinearRegistry interface {
|
|
Registry
|
|
SkipRegistrations(int)
|
|
}
|
|
|
|
// GeneralCodec is the Codec + Registry union — every concrete codec
|
|
// instance the platform uses (currently zapcodec) satisfies it.
|
|
type GeneralCodec interface {
|
|
Codec
|
|
Registry
|
|
}
|
|
|
|
// LinearCodec is the GeneralCodec + SkipRegistrations surface every VM
|
|
// consumer holds. The name "Linear" refers to LINEAR TYPE-ID
|
|
// ASSIGNMENT (sequential ids assigned in registration order), NOT to
|
|
// the historical linearcodec big-endian wire format. The underlying
|
|
// implementation is luxfi/codec/zapcodec — little-endian on the wire.
|
|
type LinearCodec = zapcodec.Codec
|
|
|
|
// ZAPCodec is an explicit alias for the same zapcodec.Codec surface
|
|
// callers reach for when they want to emphasise the ZAP-native wire
|
|
// layout (e.g. platformvm txs V2). Same backing type as LinearCodec;
|
|
// the alias exists for readability at call sites.
|
|
type ZAPCodec = zapcodec.Codec
|
|
|
|
// Packer re-exports wrappers.Packer for callers that need to drive
|
|
// MarshalInto / UnmarshalFrom byte streams directly (fuzz tests,
|
|
// size-accounting code).
|
|
type Packer = wrappers.Packer
|
|
|
|
// Errs re-exports wrappers.Errs — a multi-error accumulator used by
|
|
// per-VM codec.go files that fan in many RegisterCodec / RegisterType
|
|
// calls under a single error tap.
|
|
type Errs = wrappers.Errs
|
|
|
|
// ErrInsufficientLength is the zapcodec underflow sentinel. Callers
|
|
// asserting on the codec's "read past buffer end" condition do so via
|
|
// this re-export so they don't need to import the zapcodec subpackage.
|
|
//
|
|
// IMPORTANT: this points to zapcodec.ErrInsufficientLength, not
|
|
// wrappers.ErrInsufficientLength. The zapcodec internal packer raises
|
|
// its own sentinel which is propagated unchanged through
|
|
// Manager.Unmarshal — assertions against wrappers.ErrInsufficientLength
|
|
// would not match the chain.
|
|
var ErrInsufficientLength = zapcodec.ErrInsufficientLength
|
|
|
|
// Byte-length constants for the wire-size accounting code in node/vms.
|
|
// Identical values to wrappers.{Byte,Short,Int,Long,Bool}Len — re-
|
|
// exported here so consumers don't reach across into luxfi/codec.
|
|
const (
|
|
ByteLen = wrappers.ByteLen
|
|
ShortLen = wrappers.ShortLen
|
|
IntLen = wrappers.IntLen
|
|
LongLen = wrappers.LongLen
|
|
BoolLen = wrappers.BoolLen
|
|
)
|
|
|
|
// DefaultMaxSize is the default wire-payload budget when the caller
|
|
// doesn't size their Manager explicitly. Matches the legacy
|
|
// codec.DefaultMaxSize (1 MiB).
|
|
const DefaultMaxSize = 1024 * 1024
|
|
|
|
// NewLinearCodec returns a fresh zapcodec-backed Codec instance with
|
|
// the default ("serialize") struct tag. The name "Linear" refers to
|
|
// LINEAR TYPE-ID ASSIGNMENT (sequential ids), NOT to the historical
|
|
// linearcodec big-endian wire layout — this codec emits LE wire bytes.
|
|
func NewLinearCodec() LinearCodec {
|
|
return zapcodec.NewDefault()
|
|
}
|
|
|
|
// NewLinearCodecWithTags returns a fresh zapcodec-backed Codec instance
|
|
// that honours the supplied struct-tag names. Used by the metadata
|
|
// codec wiring where v0:"true" / v1:"true" tags select per-version
|
|
// field sets.
|
|
func NewLinearCodecWithTags(tags ...string) LinearCodec {
|
|
return zapcodec.New(tags)
|
|
}
|
|
|
|
// NewZAPCodec is an alias for NewLinearCodec — both return a
|
|
// zapcodec-backed Codec. Call sites that want to emphasise the ZAP
|
|
// wire layout use this name; otherwise they're interchangeable.
|
|
func NewZAPCodec() ZAPCodec {
|
|
return zapcodec.NewDefault()
|
|
}
|
|
|
|
// NewDefaultManager returns a multi-version Manager with the default
|
|
// 1 MiB wire-payload budget. Equivalent to NewManager(DefaultMaxSize).
|
|
func NewDefaultManager() MultiManager {
|
|
return NewManager(DefaultMaxSize)
|
|
}
|
|
|
|
// NewManager returns a multi-version Manager with the supplied wire-
|
|
// payload budget. Versions are registered after construction via
|
|
// MultiManager.RegisterCodec.
|
|
func NewManager(maxSize uint64) MultiManager {
|
|
size := maxSize
|
|
if size > math.MaxInt {
|
|
size = math.MaxInt
|
|
}
|
|
return &multiManager{
|
|
maxSize: int(size),
|
|
codecs: make(map[uint16]Codec),
|
|
}
|
|
}
|
|
|
|
// NewMaxInt32Manager returns a multi-version Manager sized for
|
|
// genesis-style blobs (math.MaxInt32 budget). VM genesis codec wiring
|
|
// reaches for this rather than re-deriving the budget at every call
|
|
// site.
|
|
func NewMaxInt32Manager() MultiManager {
|
|
return NewManager(math.MaxInt32)
|
|
}
|
|
|
|
// NewMaxIntManager returns a multi-version Manager sized for warp /
|
|
// proposervm-block style payloads (math.MaxInt budget — effectively
|
|
// unbounded; the p2p layer caps real-world wire sizes well below this).
|
|
func NewMaxIntManager() MultiManager {
|
|
return NewManager(math.MaxInt)
|
|
}
|
|
|
|
// MultiManager is a multi-codec-version dispatcher. It owns the
|
|
// version-prefix outer layer and routes Marshal/Unmarshal to the
|
|
// registered inner Codec for the supplied version. Wire layout:
|
|
//
|
|
// [uint16 LE codec version] [inner codec body]
|
|
//
|
|
// The version-prefix byte order is LE to match the inner zapcodec body
|
|
// byte order. This deliberately diverges from the legacy
|
|
// luxfi/codec.Manager (which prepends BE) — see proto/zap_codec/README.md
|
|
// for the wire-format break note.
|
|
//
|
|
// Thread-safety: RegisterCodec is NOT goroutine-safe — call it once at
|
|
// init time before any Marshal/Unmarshal. After that, Marshal /
|
|
// Unmarshal / Size are concurrency-safe to the extent the inner Codec
|
|
// implementations are (zapcodec.Codec is, via its RWMutex-protected
|
|
// type registry).
|
|
type MultiManager interface {
|
|
RegisterCodec(version uint16, codec Codec) error
|
|
Marshal(version uint16, source interface{}) ([]byte, error)
|
|
Unmarshal(bytes []byte, dest interface{}) (uint16, error)
|
|
Size(version uint16, value interface{}) (int, error)
|
|
}
|
|
|
|
type multiManager struct {
|
|
maxSize int
|
|
codecs map[uint16]Codec
|
|
}
|
|
|
|
// RegisterCodec registers an inner Codec at the supplied version.
|
|
// Returns ErrUnknownVersion if the version is already registered (the
|
|
// upstream codec.Manager returns codec.ErrDuplicateType for this case;
|
|
// we surface ErrUnknownVersion to match the rest of the package's
|
|
// sentinel choices, but both indicate the same invariant violation).
|
|
func (m *multiManager) RegisterCodec(version uint16, codec Codec) error {
|
|
if _, exists := m.codecs[version]; exists {
|
|
return ErrUnknownVersion
|
|
}
|
|
m.codecs[version] = codec
|
|
return nil
|
|
}
|
|
|
|
// Marshal serializes source under the codec registered at the supplied
|
|
// version. The wire bytes start with the version (uint16 LE), followed
|
|
// by the inner codec body.
|
|
func (m *multiManager) Marshal(version uint16, source interface{}) ([]byte, error) {
|
|
c, exists := m.codecs[version]
|
|
if !exists {
|
|
return nil, ErrUnknownVersion
|
|
}
|
|
p := &wrappers.Packer{MaxSize: m.maxSize}
|
|
if err := writeVersionLE(p, version); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := c.MarshalInto(source, p); err != nil {
|
|
return nil, err
|
|
}
|
|
if p.Err != nil {
|
|
return nil, p.Err
|
|
}
|
|
return p.Bytes[:p.Offset], nil
|
|
}
|
|
|
|
// Unmarshal deserializes bytes into dest using the inner codec
|
|
// registered for the version embedded in the wire prefix. Returns the
|
|
// version read from the wire, even on error (so callers can log the
|
|
// version that failed). Rejects buffers larger than maxSize, version
|
|
// not registered, and trailing bytes past the end of the inner body.
|
|
func (m *multiManager) Unmarshal(bytes []byte, dest interface{}) (uint16, error) {
|
|
if len(bytes) < VersionSize {
|
|
return 0, ErrCantUnpackVersion
|
|
}
|
|
if len(bytes) > m.maxSize {
|
|
return 0, ErrMaxSizeExceeded
|
|
}
|
|
p := &wrappers.Packer{Bytes: bytes, MaxSize: m.maxSize}
|
|
version, err := readVersionLE(p)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
c, exists := m.codecs[version]
|
|
if !exists {
|
|
return version, ErrUnknownVersion
|
|
}
|
|
if err := c.UnmarshalFrom(p, dest); err != nil {
|
|
return version, err
|
|
}
|
|
if p.Offset != len(bytes) {
|
|
return version, ErrExtraSpace
|
|
}
|
|
return version, nil
|
|
}
|
|
|
|
// Size returns the on-wire size of value under the codec registered at
|
|
// the supplied version, INCLUDING the manager's version-prefix bytes.
|
|
func (m *multiManager) Size(version uint16, value interface{}) (int, error) {
|
|
c, exists := m.codecs[version]
|
|
if !exists {
|
|
return 0, ErrUnknownVersion
|
|
}
|
|
size, err := c.Size(value)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return VersionSize + size, nil
|
|
}
|