Files
node/vms/platformvm/stakeable/stakeable_lock.go
T
zeekayandHanzo Dev 2d227dd3aa Full-codec-kill wave 2: proposervm + xsvm + components + evm + platformvm-residual → native ZAP
Kills pcodecs from 3 complete subsystems (verified go test ./... = 155 ok / 0 FAIL):
- proposervm: block/state/summary struct-is-wire (blockwire.go + statewire.go;
  deleted block/state/summary codec.go). blockKind bytes: reserved=0 signed=1
  option=2. Epoch inlined in unsigned object. proposer/windower chainSource seed
  = binary.LittleEndian (byte-identical to old wrappers.Packer.UnpackLong).
- example/xsvm: tx/block/genesis native Marshal (deleted 3 codec.go); create-chain
  example uses genesis.Marshal().
- components: message.Tx native (deleted codec.go); keystore codec shell removed;
  index pcodecs.LongLen → local const.
- evm/{predicate,lp176}: off pcodecs.
- platformvm residual: state metadata + genesis + metrics + signer + stakeable
  native, vestigial serialize tags removed.

REMAINING (careful solo, agents weekly-capped): warp (reverted — agent left an
ID≠Marshal consensus inconsistency in ChainToL1Conversion; needs proper review,
not a golden-regen) + xvm (reverted — barely started). pcodecs package stays
until those 2 land. /ext/→/v1/ endpoint change also pending.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-11 17:19:20 -07:00

128 lines
4.0 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package stakeable
import (
"errors"
"fmt"
luxcomp "github.com/luxfi/node/vms/components/lux"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/wire"
"github.com/luxfi/vm/components/verify"
)
var (
errInvalidLocktime = errors.New("invalid locktime")
errNestedStakeableLocks = errors.New("shouldn't nest stakeable locks")
)
// init registers the LockedOutput wire handler with the cross-fx
// dispatcher in node/vms/components/lux. Without this registration the
// dispatcher returns "unknown (TypeKind=0x00, ShapeKind=0x0F)" for every
// vesting-allocation UTXO in genesis — observed live as
// `platform.getBalance = 0` for every funded P-chain address on
// mainnet/testnet/devnet despite the genesis allocations being
// well-formed and the supply tally matching the sum of allocations.
//
// The cycle that forced this registration pattern: stakeable.LockOut
// embeds utxo.TransferableOut so node/vms/components/lux cannot import
// stakeable directly (would import the embed source through the embed,
// breaking the dispatcher package's "no fx-specific deps" property).
// The registration lets stakeable own the *LockOut reconstruction
// without flipping the dependency graph.
func init() {
luxcomp.RegisterLockedOutputHandler(func(b []byte) (verify.State, error) {
lo, err := wire.WrapLockedOutput(b)
if err != nil {
return nil, fmt.Errorf("wire.WrapLockedOutput: %w", err)
}
innerState, err := luxcomp.WrapOutputBytes(lo.TransferOutBytes())
if err != nil {
return nil, fmt.Errorf("inner output dispatch: %w", err)
}
inner, ok := innerState.(lux.TransferableOut)
if !ok {
return nil, fmt.Errorf("inner state is %T, not lux.TransferableOut — LockOut envelope corrupted", innerState)
}
return &LockOut{
Locktime: lo.Locktime(),
TransferableOut: inner,
}, nil
})
}
type LockOut struct {
Locktime uint64 `json:"locktime"`
lux.TransferableOut `json:"output"`
}
func (s *LockOut) InitRuntime(rt *runtime.Runtime) {
// Initialize the context for the underlying output if it supports it
if contextOutput, ok := s.TransferableOut.(interface{ InitRuntime(*runtime.Runtime) }); ok {
contextOutput.InitRuntime(rt)
}
}
func (s *LockOut) Addresses() [][]byte {
if addressable, ok := s.TransferableOut.(lux.Addressable); ok {
return addressable.Addresses()
}
return nil
}
func (s *LockOut) Verify() error {
if s.Locktime == 0 {
return errInvalidLocktime
}
if _, nested := s.TransferableOut.(*LockOut); nested {
return errNestedStakeableLocks
}
return s.TransferableOut.Verify()
}
// Bytes returns the ZAP-native wire envelope for this LockOut.
// Envelope = (TypeKindReserved, ShapeKindLockedOutput, ZAP message)
// where the ZAP message carries Locktime and the inner TransferableOut's
// own wire envelope (which carries its own TypeKind+ShapeKind+message).
//
// The inner TransferableOut MUST satisfy the wireSerializable contract
// (Bytes() []byte) — every fx package's wire.go adapter does. Nested
// LockOut is forbidden by Verify() and panics here to surface the bug.
func (s *LockOut) Bytes() []byte {
type wireSerializable interface {
Bytes() []byte
}
inner, ok := s.TransferableOut.(wireSerializable)
if !ok {
panic("stakeable.LockOut: inner TransferableOut does not implement wire-serializable (Bytes() []byte)")
}
return wire.NewLockedOutput(wire.LockedOutputInput{
Locktime: s.Locktime,
TransferOutBytes: inner.Bytes(),
})
}
type LockIn struct {
Locktime uint64 `json:"locktime"`
lux.TransferableIn `json:"input"`
}
func (s *LockIn) Verify() error {
if s.Locktime == 0 {
return errInvalidLocktime
}
if _, nested := s.TransferableIn.(*LockIn); nested {
return errNestedStakeableLocks
}
return s.TransferableIn.Verify()
}
func (s *LockIn) InitRuntime(rt *runtime.Runtime) {
if contextInput, ok := s.TransferableIn.(interface{ InitRuntime(*runtime.Runtime) }); ok {
contextInput.InitRuntime(rt)
}
}