mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
feat(platformvm/txs): wire ZAP codec selector with V1→V2 forward activation
Adds CodecVersionV2 (zapcodec, little-endian) alongside existing V0/V1
(linearcodec, big-endian) on the txs.Codec / txs.GenesisCodec managers.
The codec.Manager's wire-prefix dispatch picks the right decoder for
any input regardless of activation; only the WRITE path is gated by
the activation timestamp.
ZAPCodecActivationTimestamp = 1782864000 (2026-07-01 00:00:00 UTC)
CodecVersionForTimestamp(ts):
ts < activation → V1 (linearcodec)
ts >= activation → V2 (zapcodec)
CodecForTimestamp(ts) → txs.Codec (same handle; future-proof for a
full manager swap after V1
retirement)
CodecAllowsRead(v) → v ∈ {V0, V1, V2}
CodecRequiresLegacy(v) → v ∈ {V0, V1}
Slot map is bit-identical across V1 and V2: registerV1TxTypes is now
parameterised over a local slotRegistrar interface that both
linearcodec.Codec and zapcodec.Codec satisfy. Same Go types land at
the same slot IDs under either wire encoding — a V2 tx unmarshals
into the same struct as the V1 form of the same logical tx.
Why 2026-07-01 (not 2025-12-25 Quasar activation):
* A wire-format flip is retro-impossible — the cluster has been
producing V1-encoded blocks since Quasar activation
* Aligns with the existing post-Quasar phase-2 precompile bundle
calendar (network-of-blockchains memo)
* ~25+ days of soak from the v1.28.x ship date — every validator
binary has the V2 decoder registered well before the write switch
Activation constant lives in codec_activation.go for clear separation.
Tests (12 new, all passing — existing 2 unchanged):
TestCodecVersionForTimestamp_StrictBoundary — bit-exact ts == act flip
TestCodecForTimestamp_ManagerIsStable — manager handle is stable
TestPreActivationRoundTripV1 — ts < act → V1 round-trip
TestPostActivationRoundTripV2 — ts >= act → V2 round-trip
TestCrossVersionWireIsDistinct — V1 ≠ V2 wire bytes
TestCodecAllowsRead — read-acceptance gate
TestCodecRequiresLegacy — legacy classifier
TestV2WireIsZapNative — byte-position LE assertions
TestV1WireIsBigEndian — byte-position BE assertions
TestActivationConstantUnchanged — constant value watchdog
All existing platformvm txs / block / executor / fee / mempool /
txheap / zap_native tests still pass.
This commit is contained in:
+101
-9
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/luxfi/codec"
|
||||
"github.com/luxfi/codec/linearcodec"
|
||||
"github.com/luxfi/codec/wrappers"
|
||||
"github.com/luxfi/codec/zapcodec"
|
||||
"github.com/luxfi/node/vms/platformvm/signer"
|
||||
"github.com/luxfi/node/vms/platformvm/stakeable"
|
||||
"github.com/luxfi/utxo/secp256k1fx"
|
||||
@@ -19,17 +20,31 @@ const (
|
||||
// CodecVersionV0 is the v1.23.x ("Apricot/Banff") wire layout. It is
|
||||
// retained as a READ-ONLY decoder so that pre-codec-v1 blocks and txs
|
||||
// on disk (mainnet, testnet) continue to deserialize. All write paths
|
||||
// MUST use CodecVersionV1.
|
||||
// at ts < ZAPCodecActivationTimestamp MUST use CodecVersionV1.
|
||||
CodecVersionV0 uint16 = 0
|
||||
|
||||
// CodecVersionV1 is the current canonical wire layout used for every
|
||||
// new tx and every new block. It is the only version produced by the
|
||||
// build/sign paths.
|
||||
// CodecVersionV1 is the canonical linearcodec layout — big-endian,
|
||||
// produced by every binary up to and including the pre-ZAP cutover.
|
||||
// Read forever; written only when block.Timestamp <
|
||||
// ZAPCodecActivationTimestamp.
|
||||
CodecVersionV1 uint16 = 1
|
||||
|
||||
// CodecVersion is the canonical write version. All Marshal call sites
|
||||
// in this package use CodecVersion so that any future bump of the
|
||||
// write target updates exactly one symbol.
|
||||
// CodecVersionV2 is the ZAP-native layout — little-endian, structurally
|
||||
// identical to V1 (same slot map, same field order), only the wire
|
||||
// encoding differs. Produced when block.Timestamp >=
|
||||
// ZAPCodecActivationTimestamp. Read always.
|
||||
//
|
||||
// V2 is NOT a slot-map change; it's a wire-encoding change. The same
|
||||
// Go types are registered at the same slot IDs as V1. A V1-encoded
|
||||
// tx and a V2-encoded tx of the same logical content differ in
|
||||
// byte content (LE vs BE) but produce identical Go values after
|
||||
// Unmarshal.
|
||||
CodecVersionV2 uint16 = 2
|
||||
|
||||
// CodecVersion is the deprecated alias preserved for code that
|
||||
// historically referenced "the write version". New code MUST use
|
||||
// CodecVersionForTimestamp instead — there is no single canonical
|
||||
// write version after the ZAP cutover.
|
||||
CodecVersion = CodecVersionV1
|
||||
|
||||
// Version is retained as a deprecated alias for code that referenced
|
||||
@@ -39,6 +54,9 @@ const (
|
||||
|
||||
var (
|
||||
// Codec is the standard-size multi-version codec used for normal txs.
|
||||
// Registers V0 + V1 (linearcodec) AND V2 (zapcodec). The right write
|
||||
// version is selected via CodecVersionForTimestamp; reads dispatch on
|
||||
// the 2-byte wire prefix.
|
||||
Codec codec.Manager
|
||||
|
||||
// GenesisCodec allows txs of larger than usual size to be parsed.
|
||||
@@ -52,8 +70,10 @@ var (
|
||||
func init() {
|
||||
cV0 := linearcodec.NewDefault()
|
||||
cV1 := linearcodec.NewDefault()
|
||||
cV2 := zapcodec.NewDefault()
|
||||
gcV0 := linearcodec.NewDefault()
|
||||
gcV1 := linearcodec.NewDefault()
|
||||
gcV2 := zapcodec.NewDefault()
|
||||
|
||||
errs := wrappers.Errs{}
|
||||
errs.Add(
|
||||
@@ -61,6 +81,10 @@ func init() {
|
||||
registerV0TxTypes(gcV0),
|
||||
registerV1TxTypes(cV1),
|
||||
registerV1TxTypes(gcV1),
|
||||
// V2 reuses the V1 slot map verbatim — only the wire encoding
|
||||
// differs. Same Go types, same IDs.
|
||||
registerV1TxTypes(cV2),
|
||||
registerV1TxTypes(gcV2),
|
||||
)
|
||||
|
||||
Codec = codec.NewDefaultManager()
|
||||
@@ -68,14 +92,82 @@ func init() {
|
||||
errs.Add(
|
||||
Codec.RegisterCodec(CodecVersionV0, cV0),
|
||||
Codec.RegisterCodec(CodecVersionV1, cV1),
|
||||
Codec.RegisterCodec(CodecVersionV2, cV2),
|
||||
GenesisCodec.RegisterCodec(CodecVersionV0, gcV0),
|
||||
GenesisCodec.RegisterCodec(CodecVersionV1, gcV1),
|
||||
GenesisCodec.RegisterCodec(CodecVersionV2, gcV2),
|
||||
)
|
||||
if errs.Errored() {
|
||||
panic(errs.Err)
|
||||
}
|
||||
}
|
||||
|
||||
// slotRegistrar is the subset of {linearcodec.Codec, zapcodec.Codec}
|
||||
// that registerV0TxTypes and registerV1TxTypes need. Identifying it as
|
||||
// an in-package interface keeps the registration functions wire-
|
||||
// agnostic: they decide the SLOT MAP, the codec implementation decides
|
||||
// the WIRE FORMAT. Same slot IDs across all codec implementations.
|
||||
type slotRegistrar interface {
|
||||
RegisterType(interface{}) error
|
||||
SkipRegistrations(int)
|
||||
}
|
||||
|
||||
// CodecVersionForTimestamp returns the canonical write-codec version
|
||||
// for a transaction (or block) whose timestamp is ts. Reads should NOT
|
||||
// use this — dispatch on the wire prefix via Codec.Unmarshal directly.
|
||||
//
|
||||
// The cutover is strict — a tx with ts == ZAPCodecActivationTimestamp
|
||||
// is V2. A tx with ts == ZAPCodecActivationTimestamp - 1 is V1. There
|
||||
// is no fallback; if the wire bytes do not match the expected version
|
||||
// for the chain's current time, the tx is rejected by the codec
|
||||
// version-not-allowed gate in TxsForTimestamp.
|
||||
func CodecVersionForTimestamp(ts uint64) uint16 {
|
||||
if ts >= ZAPCodecActivationTimestamp {
|
||||
return CodecVersionV2
|
||||
}
|
||||
return CodecVersionV1
|
||||
}
|
||||
|
||||
// CodecForTimestamp returns the codec.Manager configured to write at
|
||||
// the canonical version for the given timestamp. The returned Manager
|
||||
// is always the package-level Codec — it hosts every registered
|
||||
// version. The Manager's Marshal(version, ...) method is what selects
|
||||
// the wire encoding; this helper just returns the right version too,
|
||||
// via the companion CodecVersionForTimestamp.
|
||||
//
|
||||
// Why expose both Manager AND version: callers historically pass a
|
||||
// version into Codec.Marshal/Codec.Unmarshal. They keep that pattern;
|
||||
// CodecForTimestamp is a no-op for the manager handle, kept here so
|
||||
// future evolutions can swap the manager itself (e.g. a ZAP-only
|
||||
// manager after V1 retirement) without changing call sites.
|
||||
func CodecForTimestamp(_ uint64) codec.Manager {
|
||||
return Codec
|
||||
}
|
||||
|
||||
// CodecAllowsRead reports whether v is one of the codec versions this
|
||||
// binary recognises for reads. Used by tx-parse gates: a wire whose
|
||||
// prefix is V0/V1/V2 is accepted; anything else is rejected before
|
||||
// the codec.Manager would surface ErrUnknownVersion.
|
||||
//
|
||||
// V0 reads are always allowed (legacy historical txs).
|
||||
// V1 reads are always allowed.
|
||||
// V2 reads are always allowed once the binary ships with this
|
||||
// constant — there is no "too early" rejection. The activation gate
|
||||
// only protects WRITES.
|
||||
func CodecAllowsRead(v uint16) bool {
|
||||
return v == CodecVersionV0 || v == CodecVersionV1 || v == CodecVersionV2
|
||||
}
|
||||
|
||||
// CodecRequiresLegacy reports whether wire-version v is in the
|
||||
// pre-ZAP-activation legacy set. After activation a node MAY still
|
||||
// accept a V1-encoded tx — there is no historical-bytes lookback
|
||||
// problem — but the build path MUST refuse to PRODUCE V1 once ts
|
||||
// crosses the activation threshold. This predicate distinguishes
|
||||
// "legacy but accepted" from "current write version".
|
||||
func CodecRequiresLegacy(v uint16) bool {
|
||||
return v == CodecVersionV0 || v == CodecVersionV1
|
||||
}
|
||||
|
||||
// RegisterTypes registers the v1 tx-codec types (the only version a
|
||||
// new build path uses) on the given linearcodec. Existing call sites
|
||||
// outside this package continue to compose with RegisterTypes; the v0
|
||||
@@ -100,7 +192,7 @@ func RegisterTypes(targetCodec linearcodec.Codec) error {
|
||||
// SetL1ValidatorWeightTx, IncreaseL1ValidatorBalanceTx,
|
||||
// DisableL1ValidatorTx, SlashValidatorTx, CreateAssetTx,
|
||||
// OperationTx)
|
||||
func registerV1TxTypes(targetCodec linearcodec.Codec) error {
|
||||
func registerV1TxTypes(targetCodec slotRegistrar) error {
|
||||
// Reserve 5 slots for the four canonical block types + one historical
|
||||
// slot (atomic block ID) so existing tx type IDs remain stable.
|
||||
targetCodec.SkipRegistrations(5)
|
||||
@@ -198,7 +290,7 @@ func registerV1TxTypes(targetCodec linearcodec.Codec) error {
|
||||
//
|
||||
// Pre-Etna v0 blobs that contain only slots 5..28 decode cleanly
|
||||
// without touching the post-Etna types.
|
||||
func registerV0TxTypes(targetCodec linearcodec.Codec) error {
|
||||
func registerV0TxTypes(targetCodec slotRegistrar) error {
|
||||
// Slots 0-4: reserved for block-codec block types.
|
||||
targetCodec.SkipRegistrations(5)
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package txs
|
||||
|
||||
// ZAPCodecActivationTimestamp is the unix timestamp at which the
|
||||
// canonical write codec for the P-chain transitions from
|
||||
// linearcodec (V1, big-endian) to zapcodec (V2, little-endian).
|
||||
//
|
||||
// Value: 1782864000 = 2026-07-01 00:00:00 UTC.
|
||||
//
|
||||
// Why 2026-07-01:
|
||||
//
|
||||
// - The historical Quasar activation milestone (Dec 25 2025 16:20
|
||||
// PST) is retro-impossible for a wire-format flip; the cluster
|
||||
// has been running and producing V1-encoded blocks since then.
|
||||
// - 2026-07-01 aligns with the existing post-Quasar phase-2
|
||||
// precompile activation calendar (see the network of blockchains
|
||||
// architecture memo in MEMORY.md). Bundling wire-codec flips
|
||||
// with precompile bundles reduces the number of forward-dated
|
||||
// activations operators must track.
|
||||
// - Far enough out that every validator binary in the field before
|
||||
// activation will recognise V2 as a registered codec (this
|
||||
// constant ships in v1.28.x+ and the activation gives operators
|
||||
// a multi-week soak window).
|
||||
//
|
||||
// Read path is timestamp-blind: any binary with this constant baked
|
||||
// in unmarshals both V1 and V2 bytes via codec.Manager's wire-prefix
|
||||
// dispatch. The timestamp gates only the WRITE path — which version
|
||||
// the mempool/block-builder/signer emits.
|
||||
//
|
||||
// Coordination protocol: changing this constant requires every
|
||||
// validator to ship the new binary BEFORE the new timestamp ticks.
|
||||
// Otherwise validators on the old binary will reject V2-prefixed
|
||||
// blocks produced by the new binary (codec.Manager returns
|
||||
// ErrUnknownVersion) and the chain halts. A long forward-dated
|
||||
// activation gives operators time to upgrade without coordination
|
||||
// drama.
|
||||
const ZAPCodecActivationTimestamp uint64 = 1782864000
|
||||
@@ -0,0 +1,210 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package txs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestCodecVersionForTimestamp_StrictBoundary asserts the timestamp
|
||||
// selector is bit-exact at the activation boundary. ts ==
|
||||
// ZAPCodecActivationTimestamp - 1 is V1 (linearcodec); ts ==
|
||||
// ZAPCodecActivationTimestamp is V2 (zapcodec). No fuzz, no fallback.
|
||||
func TestCodecVersionForTimestamp_StrictBoundary(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// One second BEFORE activation: must be V1.
|
||||
require.Equal(CodecVersionV1, CodecVersionForTimestamp(ZAPCodecActivationTimestamp-1),
|
||||
"ts < activation must select linearcodec (V1)")
|
||||
|
||||
// EXACTLY at activation: must be V2.
|
||||
require.Equal(CodecVersionV2, CodecVersionForTimestamp(ZAPCodecActivationTimestamp),
|
||||
"ts == activation must select zapcodec (V2)")
|
||||
|
||||
// Far after activation: still V2.
|
||||
require.Equal(CodecVersionV2, CodecVersionForTimestamp(ZAPCodecActivationTimestamp+1_000_000),
|
||||
"ts > activation must select zapcodec (V2)")
|
||||
|
||||
// Genesis (ts=0): V1, the historical write path.
|
||||
require.Equal(CodecVersionV1, CodecVersionForTimestamp(0),
|
||||
"ts == 0 must select linearcodec (V1)")
|
||||
}
|
||||
|
||||
// TestCodecForTimestamp_ManagerIsStable asserts the manager handle is
|
||||
// the same across timestamps — only the version returned by
|
||||
// CodecVersionForTimestamp varies. Callers MUST pass that version into
|
||||
// Codec.Marshal / Codec.Unmarshal.
|
||||
func TestCodecForTimestamp_ManagerIsStable(t *testing.T) {
|
||||
require := require.New(t)
|
||||
pre := CodecForTimestamp(ZAPCodecActivationTimestamp - 1)
|
||||
post := CodecForTimestamp(ZAPCodecActivationTimestamp + 1)
|
||||
// Two interface values from the same package-level variable; must
|
||||
// be identical.
|
||||
require.Equal(Codec, pre)
|
||||
require.Equal(Codec, post)
|
||||
require.Equal(pre, post)
|
||||
}
|
||||
|
||||
// TestPreActivationRoundTripV1 asserts a tx with ts < activation
|
||||
// round-trips through V1 (linearcodec): marshal at V1 → unmarshal as
|
||||
// V1 → identical Go value. The wire prefix MUST be V1.
|
||||
func TestPreActivationRoundTripV1(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
tx := &AdvanceTimeTx{Time: ZAPCodecActivationTimestamp - 1}
|
||||
v := CodecVersionForTimestamp(tx.Time)
|
||||
require.Equal(CodecVersionV1, v)
|
||||
|
||||
b, err := Codec.Marshal(v, tx)
|
||||
require.NoError(err)
|
||||
|
||||
// Wire prefix is the codec version (2 bytes BE per codec.Manager).
|
||||
require.Equal(uint16(CodecVersionV1), binary.BigEndian.Uint16(b[:2]),
|
||||
"pre-activation wire MUST be V1-prefixed")
|
||||
|
||||
out := &AdvanceTimeTx{}
|
||||
gotVersion, err := Codec.Unmarshal(b, out)
|
||||
require.NoError(err)
|
||||
require.Equal(CodecVersionV1, gotVersion)
|
||||
require.Equal(tx.Time, out.Time)
|
||||
}
|
||||
|
||||
// TestPostActivationRoundTripV2 asserts a tx with ts >= activation
|
||||
// round-trips through V2 (zapcodec): marshal at V2 → unmarshal as V2 →
|
||||
// identical Go value. Wire prefix MUST be V2.
|
||||
func TestPostActivationRoundTripV2(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
tx := &AdvanceTimeTx{Time: ZAPCodecActivationTimestamp}
|
||||
v := CodecVersionForTimestamp(tx.Time)
|
||||
require.Equal(CodecVersionV2, v)
|
||||
|
||||
b, err := Codec.Marshal(v, tx)
|
||||
require.NoError(err)
|
||||
require.Equal(uint16(CodecVersionV2), binary.BigEndian.Uint16(b[:2]),
|
||||
"post-activation wire MUST be V2-prefixed")
|
||||
|
||||
out := &AdvanceTimeTx{}
|
||||
gotVersion, err := Codec.Unmarshal(b, out)
|
||||
require.NoError(err)
|
||||
require.Equal(CodecVersionV2, gotVersion)
|
||||
require.Equal(tx.Time, out.Time)
|
||||
}
|
||||
|
||||
// TestCrossVersionWireIsDistinct asserts that V1 and V2 bytes for the
|
||||
// same logical tx differ — the only thing they share is structural
|
||||
// equivalence (same Go value out). Endianness flips the integer field
|
||||
// bytes, so wire equality must NOT hold.
|
||||
//
|
||||
// AdvanceTimeTx has a uint64 Time field whose LE and BE encodings are
|
||||
// distinct for any non-palindromic value. We pick activation_ts
|
||||
// because it's not palindromic.
|
||||
func TestCrossVersionWireIsDistinct(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
tx := &AdvanceTimeTx{Time: ZAPCodecActivationTimestamp}
|
||||
|
||||
v1Bytes, err := Codec.Marshal(CodecVersionV1, tx)
|
||||
require.NoError(err)
|
||||
v2Bytes, err := Codec.Marshal(CodecVersionV2, tx)
|
||||
require.NoError(err)
|
||||
|
||||
// Same length — V2 is a wire-encoding flip, not a slot-map change.
|
||||
require.Equal(len(v1Bytes), len(v2Bytes),
|
||||
"V1 and V2 wire MUST have identical length for the same logical tx")
|
||||
|
||||
// Prefix differs (V1 vs V2 codec version).
|
||||
require.NotEqual(v1Bytes[:2], v2Bytes[:2],
|
||||
"V1 and V2 wire prefix MUST differ")
|
||||
|
||||
// Payload (post-prefix) differs because of LE vs BE on Time uint64.
|
||||
require.NotEqual(v1Bytes[2:], v2Bytes[2:],
|
||||
"V1 and V2 wire payload MUST differ when timestamp has non-palindromic LE/BE forms")
|
||||
}
|
||||
|
||||
// TestCodecAllowsRead asserts the read-acceptance gate. V0, V1, V2 are
|
||||
// recognised; anything else is not.
|
||||
func TestCodecAllowsRead(t *testing.T) {
|
||||
require := require.New(t)
|
||||
require.True(CodecAllowsRead(CodecVersionV0))
|
||||
require.True(CodecAllowsRead(CodecVersionV1))
|
||||
require.True(CodecAllowsRead(CodecVersionV2))
|
||||
require.False(CodecAllowsRead(3))
|
||||
require.False(CodecAllowsRead(0xFFFF))
|
||||
}
|
||||
|
||||
// TestCodecRequiresLegacy asserts the legacy-version classifier.
|
||||
func TestCodecRequiresLegacy(t *testing.T) {
|
||||
require := require.New(t)
|
||||
require.True(CodecRequiresLegacy(CodecVersionV0))
|
||||
require.True(CodecRequiresLegacy(CodecVersionV1))
|
||||
require.False(CodecRequiresLegacy(CodecVersionV2))
|
||||
}
|
||||
|
||||
// TestV2WireIsZapNative asserts the canonical detection: a V2-prefixed
|
||||
// wire has the codec version 0x0002 (BE per codec.Manager) followed by
|
||||
// the zapcodec little-endian payload. The activation contract is that
|
||||
// V2 == zapcodec; if anyone changes the underlying impl, this test
|
||||
// surfaces the regression.
|
||||
//
|
||||
// We assert it structurally by checking that the marshaled byte at
|
||||
// offset 2 (first payload byte after the version prefix) is the LSB
|
||||
// of the Time field, not the MSB. AdvanceTimeTx packs Time as a
|
||||
// uint64 directly; LE means LSB-first.
|
||||
func TestV2WireIsZapNative(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Pick a Time with distinct LSB/MSB.
|
||||
tx := &AdvanceTimeTx{Time: 0x0102030405060708}
|
||||
|
||||
b, err := Codec.Marshal(CodecVersionV2, tx)
|
||||
require.NoError(err)
|
||||
|
||||
// Wire layout for AdvanceTimeTx under V2:
|
||||
// bytes 0-1: 0x0002 (BE, codec version)
|
||||
// bytes 2-9: Time uint64 in LE — LSB first
|
||||
require.Equal(byte(0x00), b[0])
|
||||
require.Equal(byte(CodecVersionV2), b[1])
|
||||
require.Equal(byte(0x08), b[2], "V2 LSB-first: byte 2 must be LSB of Time")
|
||||
require.Equal(byte(0x07), b[3])
|
||||
require.Equal(byte(0x06), b[4])
|
||||
require.Equal(byte(0x05), b[5])
|
||||
require.Equal(byte(0x04), b[6])
|
||||
require.Equal(byte(0x03), b[7])
|
||||
require.Equal(byte(0x02), b[8])
|
||||
require.Equal(byte(0x01), b[9], "V2 LSB-first: byte 9 must be MSB of Time")
|
||||
|
||||
// Cross-check via LE uint64.
|
||||
require.Equal(uint64(0x0102030405060708), binary.LittleEndian.Uint64(b[2:]),
|
||||
"V2 payload MUST be LE-decodable as the original Time value")
|
||||
}
|
||||
|
||||
// TestV1WireIsBigEndian is the mirror assertion for V1: the first
|
||||
// payload byte is the MSB of Time. If V1 ever flipped endianness, the
|
||||
// hash-stable TxIDs of every historical P-chain tx would change —
|
||||
// this test guards that invariant.
|
||||
func TestV1WireIsBigEndian(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
tx := &AdvanceTimeTx{Time: 0x0102030405060708}
|
||||
|
||||
b, err := Codec.Marshal(CodecVersionV1, tx)
|
||||
require.NoError(err)
|
||||
require.Equal(byte(CodecVersionV1), b[1])
|
||||
require.Equal(byte(0x01), b[2], "V1 MSB-first: byte 2 must be MSB of Time")
|
||||
require.Equal(byte(0x08), b[9], "V1 MSB-first: byte 9 must be LSB of Time")
|
||||
}
|
||||
|
||||
// TestActivationConstantUnchanged is the watchdog test. Anyone who
|
||||
// modifies the activation timestamp out of a consensus-coordinated
|
||||
// PR will trip this gate. The constant MUST be 1782864000 (2026-07-01
|
||||
// 00:00:00 UTC) — see comments on ZAPCodecActivationTimestamp.
|
||||
func TestActivationConstantUnchanged(t *testing.T) {
|
||||
require.Equal(t, uint64(1782864000), ZAPCodecActivationTimestamp,
|
||||
"activation constant changed without consensus coordination — "+
|
||||
"see comments on ZAPCodecActivationTimestamp for the rationale")
|
||||
}
|
||||
Reference in New Issue
Block a user