Files
node/vms/platformvm/state/codec_helpers_test.go
T
Hanzo AI b4f240d285 vms/platformvm: rip luxfi/codec via pcodecs (wave 2D — final)
Thirty platformvm files migrated. Every codec.Manager / codec.Registry /
linearcodec / wrappers / zapcodec reference inside vms/platformvm now
flows through node/vms/pcodecs, completing wave 2D of the codec rip
(#101). zero direct luxfi/codec imports remain anywhere in node/vms
outside vms/pcodecs/{pcodecs.go,pcodecsmock/manager.go}.

Codec construction sites (singletons preserved for external API
compatibility — wallet, indexer, network — pcodecs is the construction
layer only):
  platformvm/txs/codec.go       — V0+V1 linearcodec + V2 zapcodec
                                   via pcodecs.NewLinearCodec /
                                   NewZAPCodec / NewDefaultManager /
                                   NewMaxInt32Manager helpers
  platformvm/block/codec.go     — v1 + v0 read-only + GenesisCodec
                                   via pcodecs helpers
  platformvm/warp/codec.go      — Codec via pcodecs.NewMaxIntManager
  platformvm/warp/message/codec.go — Codec via pcodecs.NewMaxIntManager
  platformvm/warp/payload/codec.go — Codec via pcodecs.NewManager
  platformvm/state/metadata_codec.go — MetadataCodec via pcodecs

Production code typed through pcodecs:
  platformvm/txs/{tx,initial_state,operation}.go — codec.Manager →
                                                   pcodecs.Manager
  platformvm/txs/fee/complexity.go — wrappers.{LongLen,IntLen,ShortLen,
                                     ByteLen} + codec.VersionSize →
                                     pcodecs.{LongLen,IntLen,ShortLen,
                                     ByteLen,VersionSize}
  platformvm/state/{state,codec_helpers,metadata_validator}.go —
                                   pcodecs.Manager / pcodecs.Registry /
                                   pcodecs.{VersionSize,LongLen,IntLen,
                                   BoolLen}
  platformvm/block/{parse,v0/block}.go — pcodecs.Manager + LinearCodec
                                          + Errs
  platformvm/metrics/metrics.go — wrappers.Errs → pcodecs.Errs
  platformvm/vm.go — codec.Registry / linearcodec.NewDefault →
                     pcodecs.Registry / pcodecs.NewLinearCodec

Tests:
  platformvm/block/codec_multiversion_test.go — codec.ErrUnknownVersion
                                                 → pcodecs
  platformvm/state/{codec_helpers,metadata_validator,
                    metadata_delegator,state_v0_codec}_test.go —
                                   codec sentinel errors → pcodecs;
                                   linearcodec.NewDefault /
                                   codec.NewDefaultManager → pcodecs
                                   helpers
  platformvm/txs/{fee/complexity,tx_fuzz}_test.go — pcodecs.VersionSize
                                                    / NewLinearCodec /
                                                    Packer
  platformvm/warp/{message,unsigned_message}_test.go — pcodecs.ErrUnknownVersion
  platformvm/warp/message/payload_test.go — pcodecs.ErrUnknownVersion
  platformvm/warp/payload/{addressed_call,hash,payload}_test.go —
                                   pcodecs.ErrUnknownVersion

Build: go build ./vms/...
Tests: go test ./vms/platformvm/... — all green.

Pre-existing failures in vms/xvm (TestTxAcceptAfterParseTx,
TestIssueImportTx, TestIssueNFT, TestIssueProperty, TestVerifyFxUsage)
are unrelated to wave 2D — they fail on the baseline branch without any
change to xvm. Independent fix.

Wave 2D complete:
  - Components:  8 files migrated
  - Proposervm:  9 files migrated
  - EVM:         5 files migrated
  - XSVM:        2 files migrated
  - RPCchainvm:  1 file migrated
  - XVM:        18 files migrated
  - Platformvm: 30 files migrated
  --
  Total:        73 files migrated to pcodecs.

vms/pcodecs is the single canonical construction site.
2026-06-06 02:55:23 -07:00

137 lines
5.5 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package state
import (
"encoding/binary"
"math"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/vms/components/gas"
"github.com/luxfi/node/vms/pcodecs"
"github.com/luxfi/node/vms/platformvm/block"
)
// TestMultiVersionUnmarshalAcceptsBothVersions documents that the
// defensive helper is a transparent pass-through for the canonical
// codec — neither v0 nor v1 reads should observe any behavioral
// difference from raw codec.Manager.Unmarshal once the codec has both
// versions registered.
func TestMultiVersionUnmarshalAcceptsBothVersions(t *testing.T) {
require := require.New(t)
original := gas.State{Capacity: 100, Excess: 200}
for _, version := range []uint16{block.CodecVersionV0, block.CodecVersionV1} {
bytesAtVersion, err := block.GenesisCodec.Marshal(version, original)
require.NoError(err)
var decoded gas.State
gotVersion, err := multiVersionUnmarshal(block.GenesisCodec, bytesAtVersion, &decoded)
require.NoError(err)
require.Equal(version, gotVersion)
require.Equal(original, decoded)
}
}
// TestMultiVersionUnmarshalSurfacesSingleVersionCodec is the test for
// the soft warning path. We build a single-version codec.Manager (only
// v1 registered) and call multiVersionUnmarshal on it. The Unmarshal
// itself MUST still succeed for v1 bytes (the helper is a soft guard,
// not a hard one), but the post-condition probe MUST observe the
// missing v0 slot.
//
// We can't directly intercept log.Warn (it's a global logger), so we
// instead exercise the underlying probe deterministically: marshaling
// the empty `probe` struct at v0 on a single-version codec must return
// pcodecs.ErrUnknownVersion. This is the exact condition that triggers
// the warning emission.
func TestMultiVersionUnmarshalSurfacesSingleVersionCodec(t *testing.T) {
require := require.New(t)
// Build a single-version codec by hand — only v1 registered.
singleC := pcodecs.NewLinearCodec()
singleVersion := pcodecs.NewDefaultManager()
require.NoError(singleVersion.RegisterCodec(block.CodecVersionV1, singleC))
// Confirm v1 Unmarshal still succeeds (helper is non-blocking).
v1Bytes, err := singleVersion.Marshal(block.CodecVersionV1, gas.State{Capacity: 1, Excess: 2})
require.NoError(err)
var decoded gas.State
_, err = multiVersionUnmarshal(singleVersion, v1Bytes, &decoded)
require.NoError(err, "multiVersionUnmarshal must not block successful v1 reads on a single-version codec")
require.Equal(uint16(2), uint16(decoded.Excess))
// Confirm the underlying detection mechanism would fire: v0
// Marshal on this codec returns ErrUnknownVersion, which is what
// the post-condition check picks up.
_, err = singleVersion.Marshal(block.CodecVersionV0, probe{})
require.ErrorIs(err, pcodecs.ErrUnknownVersion,
"single-version codec must surface ErrUnknownVersion on the v0 probe — this is the warning trigger")
}
// TestMultiVersionUnmarshalSurfacesSingleVersionCodec_Idempotent
// documents that the warning probe fires AT MOST once per codec
// pointer, so a long-running canary boot that hits dozens of state
// reads through the same single-version codec produces a single log
// line, not a flood.
func TestMultiVersionUnmarshalSurfacesSingleVersionCodec_Idempotent(t *testing.T) {
require := require.New(t)
singleC := pcodecs.NewLinearCodec()
singleVersion := pcodecs.NewDefaultManager()
require.NoError(singleVersion.RegisterCodec(block.CodecVersionV1, singleC))
v1Bytes, err := singleVersion.Marshal(block.CodecVersionV1, gas.State{Capacity: 5, Excess: 6})
require.NoError(err)
// First call: warning probe runs (sync.Map records the codec).
var d1 gas.State
_, err = multiVersionUnmarshal(singleVersion, v1Bytes, &d1)
require.NoError(err)
// Confirm the sync.Map remembers this codec — the LoadOrStore
// returns observed=true on subsequent calls.
_, observedAgain := multiVersionProbeOnce.LoadOrStore(singleVersion, struct{}{})
require.True(observedAgain, "probe must record the codec on first observation")
// Second call: warning probe SKIPS the marshal probe entirely.
// Unmarshal still succeeds.
var d2 gas.State
_, err = multiVersionUnmarshal(singleVersion, v1Bytes, &d2)
require.NoError(err)
}
// TestMultiVersionUnmarshalRejectsUnknownVersionUpstream confirms the
// helper does NOT swallow pcodecs.ErrUnknownVersion — if the input bytes
// carry a prefix that the codec does not have registered (e.g. a future
// codec v2 prefix on a v0+v1 codec), the original error surfaces so the
// caller can decide whether to treat it as a corruption or a soft skip.
func TestMultiVersionUnmarshalRejectsUnknownVersionUpstream(t *testing.T) {
require := require.New(t)
bogus := make([]byte, 18)
binary.BigEndian.PutUint16(bogus[:2], math.MaxUint16)
var sink gas.State
_, err := multiVersionUnmarshal(block.GenesisCodec, bogus, &sink)
require.ErrorIs(err, pcodecs.ErrUnknownVersion)
}
// TestBlockGenesisCodecPassesMultiVersionProbe pins the post-v1.28.2
// invariant: the canonical state-side codec MUST register both v0 and
// v1. If a future refactor accidentally drops a version slot, this test
// trips immediately and the canary doesn't have to.
func TestBlockGenesisCodecPassesMultiVersionProbe(t *testing.T) {
require := require.New(t)
for _, version := range []uint16{block.CodecVersionV0, block.CodecVersionV1} {
_, err := block.GenesisCodec.Marshal(version, probe{})
require.NoErrorf(err, "block.GenesisCodec must register version %d", version)
}
}