209 Commits
Author SHA1 Message Date
zeekay e22009db91 chore(node): one way only — delete four dead duplicate paths
Each removed package was a SECOND way to do something that already has a
canonical first way. Zero importers workspace-wide; verified identical build
output before/after (pre-existing keyutil breakage unchanged).

- chains/rpc      1250 lines whose own header says 'This replaces lines
                  941-990' of chains/manager.go. The replacement never
                  happened; manager.go still registers handlers inline.
- node/config.go  265-line shadow of the canonical node/config/node/config.go.
- vms/mpcvm       self-described 'thin backward-compatibility alias' wrappers
- vms/dexvm       over github.com/luxfi/chains/*. No backwards compatibility,
                  only forwards perfection.

Simple made easy: one name, one home, one path.
2026-07-25 12:26:36 -07:00
zeekayandHanzo Dev 52de39485d fix(platformvm): gate P-chain uptime state.Commit on an actual write (v1.36.28)
Disconnect/updateUptimeLocked now return (mutated bool, err); VM.Disconnected commits only when the flush wrote uptime state. Before StartTracking (bootstrap churn) and for non-validator peers the flush is a no-op, so the prior unconditional Commit was an empty full-write+fsync on every such disconnect. Skipping it is correct: every writer under stateLock commits its own diff, so no orphaned write depends on the disconnect path.

RED round-2 verdict: SHIP-READY (H1 phantom verified, mutated-gate correct, H2 pre-existing/not-worsened, block-height orthogonal). Isolated from the uncommitted staking-KMS WIP, which is quarantined on feat/staking-kms-native pending its own Blue->Red.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-24 04:20:00 -07:00
zeekayandHanzo Dev e70f687c10 fix(node): serialize P-chain Connected/Disconnected + plumb real peer version (RED CRITICAL #1/#2)
The uptime event-delivery plumbing (node/chain_router.go + chains/manager.go
blockHandler) dropped two invariants avalanchego's handler upholds — the
consensus lock and the peer version — each a mainnet-fleet crash. The uptime
tracker itself (vms/platformvm/uptime_tracker.go) is RED-cleared and UNCHANGED.

CRITICAL #2 — P-chain state race (concurrent map writes -> fatal):
chainRouter dispatched Connected/Disconnected on the peer-lifecycle goroutine,
where VM.Disconnected -> tracker.Disconnect (state.SetUptime) + state.Commit
(state.write) ran concurrently with the block acceptor's state.CommitBatch
(state.write) on the engine accept goroutine — no shared lock, so ordinary peer
churn triggered Go "concurrent map writes" and crashed the P-chain node.
Fix: one VM-owned stateLock serializes every commit of shared platform state
that originates outside the engine's lock-free VM.Accept call-out —
  - block DECISION: block/executor Block.Accept/Reject hold &vm.stateLock
    (supplied to executor.NewManager) around the whole acceptor visit;
  - peer/lifecycle: VM.Disconnected and the onReady/onBootstrapStarted/Shutdown
    Start/StopTracking uptime flushes hold vm.stateLock.
This is avalanchego's ctx.Lock invariant (accept serialized with
engine.Connected/Disconnected), scoped to the state the platform VM owns and
implemented at the VM: the Lux engine invokes VM.Accept as a lock-free call-out
(no ctx.Lock over accept exists) and a chain-agnostic blockHandler cannot reach a
per-VM accept lock, so routing "through the engine" is not feasible.

CRITICAL #1 — C-Chain nil-version panic on state sync:
blockHandler.Connected passed connector.Connected(ctx, nodeID, nil). proposervm
promotes Connected to coreth, whose state-sync peer tracker compares peer
versions; a nil version deref panics any C-Chain node running state sync (a fresh
join OR a validator rejoining after falling behind — the launch's core invariant).
Fix: plumb the REAL peer version through a versionedConnector capability —
chainRouter.Connected converts the node peer version (luxfi/node/version) to the
VM boundary type (luxfi/version = chain.VersionInfo) and delivers it via
blockHandler.ConnectedWithVersion -> connector.Connected. Audit: geth in-process
Connected is a no-op stub (nil-safe); xvm stores the pointer without deref
(nil-safe); the real coreth plugin derefs -> fixed by feeding the real version.

Tests (SDKROOT + CGO_ENABLED=1, -race):
- vms/platformvm/uptime_state_race_test.go: state.Commit (VM.Disconnected path)
  concurrent with state.CommitBatch (acceptor path) under the shared lock — no
  race/fatal; verified meaningful (an unlocked probe trips DATA RACE).
- chains/blockhandler_connected_version_test.go + node/chain_router_connected_version_test.go:
  the real, converted version reaches the connector non-nil (dedup covered).
Uptime tracker 13/13 and the block/executor reward gate untouched and green;
go build ./... and go vet clean.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-24 00:34:00 -07:00
zeekayandHanzo Dev 88bb914cd6 fix(platformvm): accrue P-chain validator uptime for a stable set (reward gate)
All 5 mainnet validators read uptime=0.0000 / connected=null even for peers
connected 24h+, so prefersCommit (block/executor/options.go) always saw
0% < threshold and withheld staking rewards (~165M LUX gate).

Root causes (four, all fixed):

1. StartTracking never existed/called. The custom uptimeTracker was created
   late (onReady) and never baselined validator records, so a long-running
   validator's stored upDuration stayed 0 and CalculateUptimePercentFrom
   returned 0/total = 0.
2. upDuration flushed only on Disconnect/Shutdown, so a continuously-connected
   validator's persisted uptime never grew.
3. service.go left `connected` hard-nil ("IsConnected no longer exists").
4. ROOT: VM.Connected never fired. network.Connected -> chainRouter.Connected
   only added to a connectedPeers set and logged; it never dispatched to chain
   handlers, and blockHandler.Connected was a no-op. So tracker.Connect was
   never called and the connected map was always empty — nothing to accrue.

Fix (faithful port of avalanchego snow/uptime.Manager semantics, luxfi pkgs):

- vms/platformvm/uptime_tracker.go: rewrite as a startedTracking-gated tracker.
  StartTracking/StopTracking/StartedTracking/IsConnected added; CalculateUptime
  folds the live connected session forward to now using the persisted
  lastUpdated (reconstructed from the second-granular uptime.State encoding), so
  a connected validator accrues uptime WITHOUT a Disconnect. Before tracking, a
  validator is assumed online since its last update (avalanchego baseline);
  after tracking, only genuine sessions accrue. Second-granular clock matches
  storage. CalculateUptimePercentFrom is the clean upDuration/(now-from) form,
  clamped to [0,1].
- vms/platformvm/vm.go: create+register the tracker at Initialize (so bootstrap
  Connect events are captured), StartTracking(primary validators) at onReady,
  StopTracking on re-bootstrap and Shutdown. Mirrors avalanchego's
  onNormalOperationsStarted lifecycle.
- vms/platformvm/service.go: populate `connected` via tracker.IsConnected.
- node/chain_router.go: chainRouter.Connected/Disconnected now dispatch to every
  registered chain handler (snapshot under lock, call outside).
- chains/manager.go: blockHandler forwards Connected/Disconnected to its chain's
  VM (engineVM) exactly once (dedup set), so the P-chain uptime tracker — and
  every VM's peer set — finally observes connectivity.

Consensus safety: prefersCommit is a per-node PREFERENCE feeding oracle-block
voting, not a deterministic value. Fixing the local uptime measurement (which
was uniformly 0) changes only which reward option each node prefers; consensus
still converges by preference voting. No staking/reward math changed.

Tests (vms/platformvm/uptime_tracker_test.go, -race green):
- TestUptimeTrackerLongRunningValidatorAccruesUptime: a 30-day validator reports
  ~100%. Uses only the shared Calculator API; run against the OLD tracker it
  returns 0.0 (FAIL — the exact mainnet symptom), against the fix 1.0 (PASS).
- Continuously-connected-climbs, StartTracking-baselines, flush-on-disconnect,
  never-connected-gets-zero, StopTracking-flushes, idempotent double-connect,
  dedup, concurrency. block/executor (reward gate) suite green.

Follow-on (NOT in this commit): devnet 5-node uptime-climb verification ->
gated release -> owner-gated one-at-a-time mainnet roll. No image built, no
deploy, no live validator touched.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-23 21:44:08 -07:00
zeekayandHanzo Dev 45a3dcfff1 fix(proposervm): millisecond-resolution block timestamps (unblocks sub-second cadence)
The proposervm block wire stored the timestamp as Unix SECONDS (timestamp.Unix()), so the
sub-second window/granularity work was silently truncated to whole seconds when written to the
block: buildChild computed the proposer slot from sub-second time, but verify read back
second-resolution and computed a DIFFERENT slot → errUnexpectedProposer verify-drops at any
window < 1s. Store timestamp.UnixMilli() and read time.UnixMilli() so sub-second timestamps
round-trip and build/verify agree on the slot. Measured at a 100ms window: errUnexpectedProposer
frequent→0, 108→149 TPS, 2.8s→2.2s cadence, 5/5 byte-identical. Forward-only wire change (the
outer proposervm timestamp; the inner EVM block timestamp is independent). Remaining sub-second
floor is now the EVM targetBlockRate, not the proposervm.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-16 09:00:21 -07:00
zeekayandHanzo Dev c3d6105804 feat(proposervm): configurable MinBlkDelay + timestamp granularity tracks WindowDuration (sub-second cadence foundation)
Two coupled knobs for differentiated cadence — co-located D-Chain/DEX (fast) vs public
C-Chain (standard):

1. MinBlkDelay was hardcoded to the 1s DefaultMinBlockDelay in chains/manager.go, ignoring
   its own --proposervm-min-block-delay flag. Now wired node.Config → ManagerConfig →
   proposervm.Config (0 ⇒ 1s default). High-throughput nets set it low.

2. Block timestamps were Truncate(time.Second) at 4 sites, quantizing cadence to 1s
   regardless of WindowDuration — so a sub-second window inflated slot numbers without finer
   time resolution and could NOT produce blocks faster than 1/s. Truncation now tracks
   proposer.TimestampGranularity() = min(1s, WindowDuration): mainnet (>=1s) keeps exact
   1-second block times; sub-second windows get matching sub-second timestamps.

Measured: 1s window/delay → ~95-104 TPS, byte-identical 5/5 finality (unchanged from before,
no regression). NOTE: true sub-second cadence additionally requires a slot-handoff fix — at
100ms the slot recomputed in buildChild from a drifted 'now' can differ from the slot
timeToBuild scheduled, causing errUnexpectedProposer verify drops; that + multi-sender
saturation is the next step. These knobs are the necessary foundation.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-15 22:33:01 -07:00
zeekayandHanzo Dev d83191e286 feat(proposervm): configurable window duration (fast local cadence) + strict-PQ classical-proposer refusal
Two changes, both node-local:

1. CADENCE — proposer.WindowDuration (the proposer-slot spacing, a validator's min build
   delay = slot index x WindowDuration) was a hardcoded 5s const tuned for mainnet-scale
   validator sets, flooring small/local block cadence at 5s per slot. It is now a
   startup-configurable var (default 5s, unchanged for mainnet) set via the new
   --proposervm-window-duration flag → node.Config → ManagerConfig → proposervm.Config →
   proposer.SetWindowDuration at VM init. Read by both the windower delay math and
   TimeToSlot, so they stay consistent. Measured on a 5-node strict-PQ net: 5s→1s took
   sustained C-Chain from 44 TPS / 14s-per-block to 104 TPS / 3.8s-per-block, still 5/5
   byte-identical finality.

2. SECURITY (cryptographer MEDIUM-1) — postForkCommonComponents.Verify now refuses a block
   carrying a CLASSICAL secp256k1 proposer identity when the chain is strict-PQ
   (StakingMLDSASigner set), UNCONDITIONALLY (before the consensusState==Ready gate, so it
   also holds during bootstrap/state-sync). Fills the documented-but-unwired 'proposer'
   SchemeGate site: the downgrade defense is now an explicit fail-closed in-perimeter gate,
   not merely emergent from 20-byte NodeID collision-resistance + upstream enforcement.
   Adds SignedBlock.HasClassicalProposer(). Mirrors contract.RefuseUnderStrictPQ.

Pins consensus v1.36.7 (consume-on-error build loop — kills the non-leader BuildBlock spin).
Block + proposervm VM tests green.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-15 22:24:36 -07:00
zeekayandHanzo Dev 13b6e80a78 fix(proposervm): strict-PQ proposer identity — sign+derive with ML-DSA-65 to match the windower
Under strict-PQ the canonical NodeID is ML-DSA-65-derived (config/node DeriveNodeID →
DeriveMLDSA), so the P-chain validator set and the proposervm windower are ML-DSA-keyed.
But proposervm signed post-fork blocks with the classical TLS leaf and derived the block
Proposer() via ids.NodeIDFromCert — a different value than DeriveMLDSA — so every signed
block (height ≥ 2) failed verifyPostDurangoBlockDelay with errUnexpectedProposer, was
dropped, and rebuilt in an unbounded storm (block 1 survived only because the unsigned
transition block skips the proposer comparison).

The block's offCert slot now carries a scheme-tagged proposer identity [scheme:1B|identity]:
0x90 classical (cert DER → NodeIDFromCert, ECDSA verify) or 0x42 strict-PQ (raw ML-DSA-65
pubkey → DeriveMLDSA(ids.Empty,pub), ML-DSA verify). ML-DSA signing/verification uses a
FIPS 204 §5.2 domain-separation context ('lux-proposervm-block-v1') so a proposer signature
can never be replayed as another ML-DSA message. proposervm.Config gains StakingMLDSASigner
/StakingMLDSAPub, plumbed from StakingConfig through ManagerConfig; exactly one scheme is
active per chain. K=1 nets are unaffected (transition + no-window blocks are unsigned).

Proven on a 5-node strict-PQ local net: sustained multi-block production, every block
built once, gossiped, and finalized byte-identical on all 5 (no automine). Pins consensus
v1.36.6 (engine logger + logged build-drops) which made this diagnosable.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-15 17:59:16 -07:00
zeekayandHanzo Dev 375dadde31 platformvm: delete dead weak SharedMemory interface declarations
Both executor backends re-declared a local SharedMemory interface with the
weak Apply(map[ids.ID]interface{}, ...interface{}) signature — but nothing
referenced it. The real atomic path uses Runtime.SharedMemory (= the narrow
atomic.SharedMemory: Apply(map[ids.ID]*atomic.Requests, ...database.Batch)).
Pure dead code; removed both. 28 platformvm packages green.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-14 15:18:48 -07:00
zeekayandHanzo Dev 4d712c3798 xvm: drop reflect from fx dispatch — closed-sum type switch + dense tag array
The fx set is a CLOSED sum type (secp256k1fx | nftfx | propertyfx), fixed at
compile time — no hot-loading, no genesis-configured fx. So the runtime
'which fx owns this value' dispatch needs no reflection: it is a total match on
the variant tag. Replaced reflect.TypeOf(val) + map[reflect.Type]int with:
  - fxKindOf(val): a Go type switch over the closed set of fx primitive types
    (compiler-checked exhaustive; lowers to a jump on the interface type tag),
    returning the value's wire.TypeKind — the same family tag the wire envelope
    already carries.
  - FxIndex: a dense [16]int array indexed by that TypeKind (one bounds-checked
    load; -1 = unregistered), filled by the SAME fx.(type) switch NewCustomParser
    already ran — no separate reflect registration.
getFx (semantic verifier + tx_init) is now fxKindOf → array index: zero reflect,
zero map-hash, compile-time-checked. Deleted registerFxTypes + all
map[reflect.Type]int fields/params (parser, block/parser, vm, backend). Node-only
(uses already-imported utxo fx types + wire.TypeKind); no dep cascade.

Full xvm suite green (11 pkgs — secp/nft/property verify dispatch exercised).
This removes the LAST reflection from the X-chain tx/verify path.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-14 10:02:52 -07:00
zeekayandHanzo Dev 3f65eb486d v1.36.8: zap v1.2.4 value ObjectBuilder — faster wire build node-wide
zap v1.2.4 (eager-reserve + value-type ObjectBuilder: zero defer-slice, zero
per-StartObject heap alloc) + utxo v0.5.7. Byte-identical wire — 21 platformvm/
xvm/components-lux/da packages green. Node-side setEnvelope/setValidator/setID/
setOwner/setSecurity/writeIDInto helpers take zap.ObjectBuilder by value (its
methods mutate through ob.b, so value + pointer are equivalent). Speeds every
ZAP object build (P/X txs, blocks, warp, da), not just X-tx. X-tx wire composite
922->655ns / 11->5 allocs.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-14 09:37:11 -07:00
zeekayandHanzo Dev 97e0a0b704 v1.36.7: xvm native-nested composites — 2.8x X-tx build, import/export too
Consume utxo v0.5.6 + zap v1.2.3. X-chain BaseTx / ExportTx / ImportTx now build
their transferable out/in lists as native ZAP AddObjectPtr object-lists (no
per-container envelope prefix, no blob concat, no length lists) via
wire.AppendTransferable{Out,In} + TransferableXFromObject. baseTxWire composes
wire.XVMTransferOut/In directly. Removed the standalone transferableOut/InBytes
node helpers. Composite money-move build 2551ns/37allocs (byte-blob) ->
913ns/11allocs (native-nested), 2.8x. Full xvm suite green (round-trip/state/
block/executor/import/export); components-lux + wallet-x + platformvm-txs green.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-14 06:38:42 -07:00
zeekayandHanzo Dev 6b25706366 wallet/x + components/lux: complete xvm native-wire consumer
Tail of the xvm codec kill (ddb3fbca93): the X-chain wallet builder + signer
now rebuild signed wire bytes as unsigned ‖ fx credential envelopes over the
native luxfi/utxo/wire form, and components/lux parses fx Inputs from their wire
envelope by concrete type. No linearcodec, no reflection on the wallet path.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-12 16:54:12 -07:00
zeekayandHanzo Dev ddb3fbca93 xvm -> native ZAP struct-is-wire: node-side codec kill COMPLETE
X-Chain fully off pcodecs (the last node consumer). kind.go (xkind 1-5), tx.go
(wire.SignedTx envelope), parser (reflect-map + dual LinearCodec DELETED, fx
dispatch by envelope TypeKind/ShapeKind), base/create_asset/operation/import/
export tx, initial_state, operation, block/{standard,parser,builder}, genesis
(native genesis_wire.go: marshalGenesis/parseGenesis + txs.ParseUnsignedTx),
vm/service/static_service/wallet_service/utxo-spender, metrics (pcodecs.Errs ->
errors.Join). ALL xvm test codec threading removed; xvm tests green.

Block-build invariant: writeTxList requires Initialized txs (mempool/parse hand
the builder canonical bytes) — errors on empty tx-bytes rather than a hidden
Initialize side-effect; state_test updated to Initialize its fixtures.

Transport foundation: rpcchainvm NewListener unix-socket fast path gated
LUXD_VM_UNIX_SOCKET=1 (default TCP, non-breaking; api/zap 159b27a infers unix
from socket-path addr). Consumes upstream utxo TransferableOut/In (4ce6a6e).

ZERO real node-side pcodecs consumers remain. vms/pcodecs kept only for 3
external luxfi/chains VMs (Wave B: zkvm/bridgevm/mpcvm) pending their migration.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-12 11:26:36 -07:00
zeekayandHanzo Dev 3f890bc98e Integrate main's Quasar-export + EVM v1.104.8 content into the codec-kill branch
The non-codec main changes (Dockerfile EVM_VERSION v1.104.8, chains/manager
GetContext size-chunking for behind-validator resync + Quasar EXPORT frontier
bridge, warp/signature, rpcchainvm/zap client) that the branch lacked. The
codec-era main files (codec.go/parse.go/tx.go etc.) are intentionally
superseded by this branch's native-ZAP struct-is-wire — not reintroduced.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-11 22:48:27 -07:00
zeekayandHanzo Dev 44d7651d16 block: reject trailing bytes in Parse (malleability guard) + port determinism test to native ZAP
RED-verified consensus-safety fix: zap.Parse truncates to the header size
field, so a block buffer with extra tail bytes wraps the SAME message but
ID=hash(bytes) differs — a block-hash malleability / fork vector. setID now
rejects msg.Size() != len(bytes) with ErrExtraSpace. (The P-chain TX envelope
already guards this at tx.go:66; only block Parse had the gap.)

Renamed codec_determinism_test.go -> block_determinism_test.go and converted
its assertions to native ZAP: New*Block + Parse(b) (no Codec), native golden
AbortBlock bytes (65B: zap header + kind/parent/height/time object), byte-
stability + BlockID-stability + trailing-bytes-rejected. Block + txs green.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-11 22:45:44 -07:00
zeekayandHanzo Dev 0ba5d05bd8 Retire /ext/ endpoint prefix: /v1/ is THE endpoint
Route registration, client URL builders, and log lines all move to /v1/
(wallet primary api, chains/rpc handler_manager+chain_integration, xvm
client+wallet_client, xsvm api client, multi-network example, manager logs).
Zero /ext/ literals remain. One endpoint namespace, no transition alias.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-11 19:14:51 -07:00
zeekayandHanzo Dev 6a766516b1 warp -> native ZAP struct-is-wire: kill the registration-order codec (hard-fork footgun)
All 3 warp codec registries deleted (codec.go, message/codec.go,
payload/codec.go). Every dispatched type now carries an EXPLICIT 1-byte
discriminator at object offset 0 — the invariant is DATA, not registration
order:

  warp wkind:    0x00 BitSetSignature, 0x01 CoronaSignature,
                 0x02 EncryptedWarpPayload, 0x03 HybridBLSCoronaSignature,
                 0x04 TeleportMessage, 0x05 TeleportTransferPayload,
                 0x06 TeleportAttestPayload   (ids = old registration order)
  message mkind: 0 ChainToL1Conversion, 1 RegisterL1Validator,
                 2 L1ValidatorRegistration, 3 L1ValidatorWeight
  payload pkind: 0 Hash, 1 AddressedCall

CONSENSUS-CRITICAL invariant made structural: ChainToL1ConversionID =
sha256(ChainToL1ConversionData.Marshal()) — the ID calls the SAME encoder, so
ID/Marshal skew (the bug in the reverted agent attempt) is impossible. The
native hash preimage is pinned as a golden with field-offset assertions
(verified by the L1 staking contract => consensus surface).

Message = {unsigned bytes, wkind-tagged sig bytes} container object;
UnsignedMessage = {networkID u32, sourceChainID 32B, payload} @44B header.
wire_baseline_test.go rewritten: pins the native hex golden for all 7 wkinds
+ structural discriminator checks + signature dispatch round-trips. Old-codec
sentinels -> zap errors. Whole node builds; warp+platformvm+proposervm green.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-11 17:44:13 -07:00
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
zeekayandHanzo Dev 7123b7399e Restore+convert remaining txs tests to struct-is-wire + dispatch-safety guard
Converts the per-type P-chain tx tests to native New*Tx + accessor methods +
roundTrip/SyntacticVerify (agents, verified): add_validator, add_chain_validator,
create_blockchain, disable/increase/register/set_weight/remove_chain/
transfer_ownership L1, add_permissionless_delegator, transform_chain. Every
error-path sentinel preserved by driving the bad value THROUGH the constructor
(pure byte-writer). Mock-based cases (fxmock/luxmock/verifymock, impossible on
an immutable zap buffer) reproduced with REAL unspendable owners / unsorted
inputs → real sentinels (ErrOutputUnspendable, ErrInputIndicesNotSortedUnique).
Only the un-reproducible 'already verified' cached-flag case dropped per file.

NEW staker_dispatch_guard_test.go pins the interface-satisfaction invariant the
staker type-switches depend on: struct-is-wire made *AddValidatorTx satisfy both
ValidatorTx+DelegatorTx (safe — ValidatorTx checked first everywhere), but
*AddDelegatorTx must NOT satisfy ValidatorTx (verified: lacks
ValidationRewardsOwner/Shares) or delegators would mis-route. Guard fails loudly
if a future edit breaks either property.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-11 05:46:10 -07:00
zeekayandHanzo Dev 43cbbac511 Restore+convert genesis/state/network tests to struct-is-wire (4 files, zero coverage dropped)
- genesis: field->method on *txs.AddValidatorTx (.Validator().NodeID/.End/
  .StakeOuts()); 3 error-path builders preserved (errUTXOHasNoValue,
  errValidatorHasZeroWeight, errValidatorAlreadyExited).
- state/staker: generateStakerTx via NewAddPermissionlessValidatorTx; the
  mutable-Signer-mock trick (impossible on immutable zap buffer) ->
  NewMockScheduledStaker.PublicKey()=errCustom, errCustom preserved exactly.
- network/gossip+network: nil-buffer &txs.BaseTx{} -> newBaseTx helper (avoids
  InputIDs() panic); SetBytes 2-arg->1-arg; all expectedErr preserved
  (ErrDuplicateTx, ErrTxTooLarge, ErrConflictsWithOtherTx, ErrMempoolFull...).
go test ./genesis/ ./state/ ./network/ = all ok.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-11 05:45:21 -07:00
zeekayandHanzo Dev 7ea0c0383f Restore+convert fee/mempool/txheap tests to struct-is-wire (6 files, green)
- fee/complexity: kept all 5 component tests + error paths; dropped codec
  byte-cross-check + dead pre-LP-023 BE-hex fixtures, REPLACED with native
  TestTxComplexity (visitor single-path + batch-additivity + ErrUnsupportedTx).
  Removed TestConvertNetworkToL1ValidatorComplexity (fn deleted; per-validator
  convert complexity now inline in complexityVisitor.ConvertNetworkTx).
- fee/static + dynamic_calculator: skipped hex-loops -> native (TxFee/
  CreateChainTxFee/positive-priced supported, ErrUnsupportedTx unsupported).
- mempool/{auth,mempool}: native New*Tx + Initialize(); strict-PQ credential
  gate (ErrLegacyCredentialUnderStrictPQ) + Add/Get/Peek/Remove/DropExpired.
- txheap/by_end_time: NewAddValidatorTx + heap-ordering assertions.
go test ./txs/fee/ ./txs/mempool/ ./txs/txheap/ = ok (17 tests, no skips).

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-11 05:40:20 -07:00
zeekayandHanzo Dev c5e999487c Green the 8 post-bump test failures (real fixes, no delete-to-hide)
- spending.go: harden spendingTx.Bytes() against nil msg (uninitialized tx no
  longer panics the spend-verification path; production always sets msg).
- config/genesis-builder tests: codec-era Codec.Marshal -> native g.Bytes();
  AddValidator field access -> methods (Weight()/Validator()/StakeOuts());
  drop unused pchaintxs imports.
- config test: consensus params K=30 alpha 16->20/25 to satisfy v1.36's tighter
  BFT bound (2*alpha-K >= floor((K-1)/3)+1); assertions updated.
- version/compatibility.json: register Current v1.32.11 under RPCChainVM proto 42
  (pre-existing registration gap).
- cevm e2e: genesis fixture completed with gasLimit+difficulty (strict geth
  genesis validation in the installed CEvm plugin; pre-existing fixture drift).

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 21:20:17 -07:00
zeekayandHanzo Dev 87a57925f3 components/lux UTXO value tree -> native ZAP (Wave-A Substrate)
The shared UTXO value tree (UTXO/TransferableOutput/TransferableInput/Asset/
UTXOID/BaseTx/Metadata + OutputOwners path) gets native struct-is-wire
Marshal/Unmarshal (new marshal.go), replacing every pcodecs.Manager. Codec
param dropped from utxo_state / atomic_utxos / transferables (Sort* sorts on
inner fx wire Bytes()); flow_checker pcodecs.Errs -> errors.Join.

Node consensus persistence uses luxfi/utxo (unchanged); components/lux is the
tx-builder/#58 surface — encoding-only change, type tree NOT collapsed (#58
respected). Both P and X share the encoding => internally consistent, re-genesis
safe. Whole node builds EXIT 0; 8 components/lux round-trips + P-chain txs/block
tests green.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 21:01:11 -07:00
zeekayandHanzo Dev 511a804016 Decomplect: CreateChainTx is the sole chain constructor (kill genesis-chains stub)
CreateNetworkTx no longer carries genesis chains — the executor never created
them, so tx.Chains() was a latent stub (declared, verified, silently dropped).
Model A resolves it by decomplection, not by implementing a second chain path:

  - CreateNetworkTx = ∅→Network birth (owner + security.Mode + own validator set).
  - CreateChainTx = the ONE chain constructor. An L1 spawn is a BLOCK of
    CreateNetworkTx + N CreateChainTx (block-atomic, not tx-atomic).
  - Removed NetworkChain, its wire (writeNetworkChains/readNetworkChains/
    ncStride/sliceIDs), the chain error set, MaxNetworkChains, the chains param.
  - managerChainIdx (index into genesis chains) -> managerChainID (direct
    ids.ID), now SYMMETRIC with ConvertNetworkTx's manager ref. ids.Empty =>
    P-Chain-governed; a set chainID => Contract-governed staking-contract host.

Net: less code (write as little as possible), one-and-one-way chain creation,
no stub. platformvm build+vet PASS; SovereignL1/InheritedL2/HybridL2/Convert
round-trips PASS.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 14:33:13 -07:00
zeekayandHanzo Dev c44cc2289f platformvm builds GREEN off the codec: executor fold + full consumer flip
vms/platformvm/... build + vet PASS with pcodecs/txs.Codec gone from the VM.

Executor semantics (standard_tx_executor):
- registerOwnSet(): shared primitive — per-validator state.L1Validator with
  native owner blobs (txs.MarshalOwner/UnmarshalOwner, no codec), active-set
  capacity check, EndAccumulatedFee=balance+accruedFees, SetNetToL1Conversion
  manager-authority recording (byte-for-byte legacy tail).
- ConvertNetworkTx: promote endomorphism (owner-authorized, folds old
  ConvertNetworkToL1Tx), gated by security.Mode.Manager.
- CreateNetworkTx: base (AddNet/SetNetOwner) + sovereign path registers own set.
- Deleted CreateSovereignL1Tx + ConvertNetworkToL1Tx.
- txs.UnmarshalOwner added: canonical owner marshal/unmarshal pair, no codec.

All ~13 txs.Visitor impls carry ConvertNetworkTx; gossip/block Parse(b) codec-free;
wallet/network/primary off txs.Codec.

KNOWN GAP (pending design decision, NOT silently green): CreateNetworkTx reads
tx.Chains() only to derive managerChainID — it does NOT create the genesis
chains (no AddChain). Atomic-spawn-with-chains vs decomplect-to-CreateChainTx is
the open call. 17 codec-era _test.go parked as .bak for follow-up rewrite.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 14:25:08 -07:00
zeekayandHanzo Dev c73eba739f Flip 4 platformvm consumers off txs.Codec (MarshalOwner + New*Tx + native genesis Bytes)
- txs.MarshalOwner(any): ONE canonical owner encoding (same layout as embedded
  tx owner), lifted to a standalone buffer for lock-owner hash keys in utxo
  handler+verifier. Replaces txs.Codec.Marshal(owner). Re-genesis-safe (ownerID
  only matched within a tx's own consumed/produced sets).
- validators/manager: chain.ChainID -> chain.ChainID() (field->method).
- api/static_service: genesis txs via NewAddValidatorTx / NewAddPermissionless
  ValidatorTx / NewCreateChainTx + codec-free Initialize(); genesis blob via
  native g.Bytes(). Mid-flip: executor + remaining visitors still pending.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 13:40:18 -07:00
zeekayandHanzo Dev f1a3e85b8e Rename LuxAddress -> UTXOAddr (decomplect place-from-value)
LuxAddress in airdrop.AirdropClaim + bridgevmroot.SignerLeaf is the NATIVE
20-byte Lux address (ids.ShortID / [20]byte), held beside the EVM common.Address
it disambiguates. 'Lux' prefix is banned place-in-value naming; the canonical
pair is EVMAddr / UTXOAddr. Named by address KIND, not brand.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 13:30:17 -07:00
zeekayandHanzo Dev d008eba286 security.Mode: decomplect network security into its own package
Pull the network security model out of the tx wire into a small orthogonal
package (vms/platformvm/security): pure values, stdlib-only, no zap/tx/state
deps. One definition — security.Mode{RestakeParent, Admission, Threshold,
Manager} + Sovereign() + Valid() — composed by tx wire, executor, and state
(Rich Hickey: values not places; Rob Pike: no stutter, small orthogonal pkg).

Two orthogonal axes replace the flat Inherited/Sovereign byte:
  - RestakeParent: lean on parent's validator set
  - own set: Admission(NoOwnSet|Open|Gated) + Manager(PChain|Contract)
Their product spans every mode incl. HYBRID L2 (restake AND additive own set),
which the flat byte could not express. Invariant RestakeParent || own-set on
Mode.Valid(); Sovereign derived, never flagged.

CreateNetworkTx + ConvertNetworkTx carry security.Mode on the wire via shared
setSecurity/readSecurity. Round-trip green incl. new HybridL2 case + Convert
target-mode. Wallet builders pass the explicit restaked-L2 Mode.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 12:59:29 -07:00
zeekay b6143b5d53 Block codec → native ZAP (struct-is-wire): builds + round-trips green
Blocks are now the zap buffer: kind + parentID@1 + height@33 + time@41 +
tx-length-list@49 + tx-blob@57 (+ proposal-tx@65). commonZapBlock embedded base
mirrors spendingTx. Parse = zap.Parse + kind dispatch (no codec, no version).
Deleted block/codec.go + block/v0 + lift_v0. Round-trip green for abort/commit/
proposal/standard incl real signed txs. go build + go test ./block/ = green.

FINDING: block.GenesisCodec was double-duty — also serialized NON-block STATE
values (feeState, owners, L1Validator, metadata, chains). That's a THIRD codec
surface (state DB serialization) still to migrate for the full kill.
2026-07-10 12:43:20 -07:00
zeekay 20d36b36aa Restore ConvertNetworkTx as the promote endomorphism (Create ≠ Convert)
Create and Convert are orthogonal arrows: CreateNetworkTx = ∅→Network (birth,
sovereign-or-inherited); ConvertNetworkTx = Network→Network (promote an
existing network: inherited→sovereign, re-anchor parent — L2→L1, L3→L1, with
owner auth). Only CreateSovereignL1 stays folded (it was birth+sovereign, not
a distinct op). Both reuse the NetworkValidator component. Round-trip green
incl L2→L1 promote.
2026-07-10 12:13:15 -07:00
zeekay 4610978bcc Decomplect network creation: one CreateNetworkTx folds Convert+CreateSovereign
CreateNetworkTx{parent, owner, security, validators, chains, manager} creates a
network at ANY level in one tx — parent is the level axis (Primary⇒L1, L1⇒L2,
recurse; level=depth, never stored). security is a coproduct: SecuritySovereign
(own validators+manager) | SecurityInherited (restaked from parent). manager
lives on the Network so an inherited L2 holds local admin. NetworkValidator +
NetworkChain are shared value components (reused by CreateChainTx). Deleted
ConvertNetworkToL1Tx + CreateSovereignL1Tx (folded / migration artifacts).

Round-trip green: sovereign L1 (own validators+chains+manager) AND inherited L2
(parent recorded, no own validators, local manager) both survive. One and one
way to make a network at any depth.
2026-07-10 11:39:12 -07:00
zeekay e869b7bcd1 Consumer flip: fee + wallet field->method accessors + codec-free signing
fee/complexity + static_calculator + wallet/chain/p signer/backend visitors
moved off removed struct fields onto method accessors (tx.Ins->tx.Inputs()
etc). sign() rewritten to the codec-free unsigned‖creds model (tx.Unsigned.
Bytes() + tx.Initialize()). txs/fee + wallet/chain/p/signer build clean.
2026-07-10 11:11:42 -07:00
zeekay bbba24bad6 txs pure-zap wire PROVEN: round-trip green (fix AddBytes element-count bug)
zap.ListBuilder.AddBytes counts BYTES not elements; fixed-stride lists were
storing byte-inflated counts -> stride clamp rejected them. Fixed every write
helper to store the real element count (len(entries)). Round-trip test now
GREEN across the hardest cases: multisig+stakeable outputs/inputs, the nested
ConvertNetworkToL1Validator (NodeID+BLS PoP+2 owners), SovereignL1Chain
manifest, and signed unsigned‖creds. go build ./vms/platformvm/txs = exit 0.

The P-chain tx wire is now struct-IS-wire, codec-free, and verified correct.
2026-07-10 10:57:47 -07:00
zeekay 34cb5c1684 txs package GREEN: pure zap struct-is-wire compiles (Convert + CreateSovereign done)
All 21 real P-chain tx types are now the zap buffer — no codec, no marshal/
unmarshal, no Manager, no version, decompounded, on luxfi/zap. ConvertNetworkToL1Tx
+ CreateSovereignL1Tx hand-written with a shared ConvertNetworkToL1Validator
nested encoder (fixed-stride records + shared NodeID/addr pools) and a
SovereignL1Chain encoder. go build ./vms/platformvm/txs/ = exit 0.

Next: round-trip correctness test, then the external consumer flip.
2026-07-10 10:53:14 -07:00
zeekay e288b67008 Decomplect P-chain tx set: rip Slash + P-chain CreateAsset/Operation, restore staker ifaces
- SlashValidatorTx ripped (unwired stub; Avalanche has no slashing; consensus
  only detects, never enforces). Type+executor+tests+Visitor surface removed.
- CreateAssetTx + OperationTx ripped from P-CHAIN (X-chain/AVM types; LP-0130:
  asset creation + fx ops are X-chain money-rail, not P staking-rail; no P
  producer). xvm/avm untouched.
- Staker interfaces (StakerTx/ValidatorTx/DelegatorTx/ScheduledStaker) restored
  on the 5 validator types via pure accessors + FxID-on-stake preserved.
- Remaining real complex types: ConvertNetworkToL1Tx + CreateSovereignL1Tx.
2026-07-10 10:36:35 -07:00
zeekay 33e005c869 fix register_l1 verifyBaseTx (return-form) 2026-07-10 09:18:19 -07:00
zeekay 949210730d Pure zap tx: 18 type files converted (parallel) + verifyBaseTx reconciled
Batches 1-3 (proposal + spending + validator-family) landed pure: struct is
the buffer, New*Tx builds, accessors read, SyntacticVerify via verifyBaseTx.
Remaining package-internal: 5 complex types (Convert/CreateSovereign/CreateAsset/
Operation/Slash) + staker interface methods + consumer flip.
2026-07-10 09:17:15 -07:00
zeekay 8d2fb750b6 Remove stale codec tests (codec deleted) 2026-07-10 09:08:31 -07:00
zeekay 1bc07cb1ca Pure zap tx: pure Tx (Parse=wrap+split, Sign=build) + delete reflection codec
tx.go: Tx holds Unsigned (zap-backed) + Creds; Parse wraps signed bytes and
splits unsigned/creds at the self-delimiting boundary; Sign builds unsigned‖creds;
no codec.Manager param anywhere. Deleted codec.go (V0/V1/V2 reflection Manager)
and codec_activation.go. WIP: consumer sites that passed txs.Codec / called the
old Parse(codec, bytes) flip next.
2026-07-10 09:08:15 -07:00
zeekay 1dede2e3d2 Pure zap tx: Parse kind-dispatch + native credential wire (no codec)
Parse wraps the buffer zero-copy and dispatches on the 1-byte kind. Signed =
unsigned ‖ creds (both self-delimiting), so unsigned is a byte-prefix of
signed. writeCredsBuf/parseCredsBuf encode credentials natively (shared 65B
sig-blob array). No Marshal/Unmarshal/Manager. WIP: references the 24 pure
type constructors (5 hand-written + 18 in parallel conversion + 1 template).
2026-07-10 09:07:13 -07:00
zeekay 636944a63a Pure zap tx: spendingTx embedded base (envelope surface for all types) 2026-07-10 09:02:58 -07:00
zeekay 7750ca1829 gofmt spending.go 2026-07-10 01:43:56 -07:00
zeekayandHanzo Dev c526c0aaa4 Pure zap tx: reusable delta encoders (owner/auth/validator/signer/idlist)
Shared delta-field encoders every non-proposal tx composes on the spending
envelope: owner (fx.Owner), auth (*secp256k1fx.Input), inline Validator (44B)
+ Signer (145B: kind+BLS pubkey+PoP), id lists, extra spending lists. Built on
luxfi/zap. With spending.go, the full reusable foundation — type files are now
pure compositions.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 01:42:43 -07:00
zeekayandHanzo Dev 7354ce84b9 Pure zap tx: shared spending wire (envelope + Output/Input/owner/creds)
The envelope (NetworkID/BlockchainID/Outs/Ins/Memo) + multisig/stakeable
Output/Input entries + shared owner-address/sig-index arrays, built on
luxfi/zap generic primitives — no codec, no zap_native package. writeSpending/
setEnvelope build inside New*Tx; readEnvelope reads lazily. Polymorphism
(TransferOutput/LockOut, TransferInput/LockIn) handled in explode/assemble.
Foundation every non-proposal tx composes.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 01:40:27 -07:00
zeekayandHanzo Dev 8e7253a3c5 Pure zap tx: kind dispatch + AdvanceTimeTx template (struct IS the wire)
kind.go: 1-byte discriminator @ object offset 0 = the whole dispatch. No
codec, no version, no slot map. AdvanceTimeTx converted to the pure model:
holds *zap.Message, Time() is an offset read, NewAdvanceTimeTx builds once,
Bytes() returns the buffer. No marshal/unmarshal anywhere.

Template for the remaining 21 types. Package migrates atomically (codec.go +
all types + Parse/Sign together), so it compiles green again only when the
whole set + concentrated consumers (executor/builder/fee/api, ~63 New sites)
are converted. WIP.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 01:24:10 -07:00
zeekayandHanzo Dev 67ec31a344 Rip the marshal/unmarshal bridge — it was a codec by another name
ZAP has no serialization step. The bridge copied fields between plain txs.*
structs and the buffer, which is exactly the codec ZAP deletes. Removed.
The right way: tx type IS the zap buffer (accessors, Parse=wrap, Bytes=buffer),
no codec / manager / compat / version. Wire primitives stay in luxfi/zap.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 01:13:02 -07:00
zeekayandHanzo Dev 77389092c2 LP-023: bridge 5 more tx types (extra-list/bytes/idlist deltas)
Shared extra-out/in list helpers (Import/Export second spending set), id-list
+ BLS-PoP encoders. Bridges TransferChainOwnership, RegisterL1Validator,
Import, Export, CreateChain. Round-trip green (13/22 P-tx types native).

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 01:03:37 -07:00
zeekayandHanzo Dev 13cca30a71 LP-023: bridge 5 delta tx types (owner/auth/scalar deltas)
Shared delta-encoders (owner=fx.Owner->OutputOwners, auth=verify.Verifiable->
secp256k1fx.Input, fixed-id helpers). Bridges IncreaseL1ValidatorBalance,
SetL1ValidatorWeight, DisableL1Validator, RemoveChainValidator, CreateNetwork
= spending envelope + fixed-offset delta fields. Round-trip green (8/22 P-tx
types now native: proposal x2 + BaseTx + these 5).

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 01:00:42 -07:00
zeekayandHanzo Dev ae3e3a45f5 LP-023: native spending envelope + full component converters
Shared spending envelope (NetworkID/BlockchainID/Outs/Ins/Memo) + polymorphic
converters bridging the txs struct graph to proto/zap_native primitives with
zero reflection: TransferOutput/stakeable.LockOut, TransferInput/stakeable.LockIn,
secp256k1fx.Credential. BaseTx (TxKindBaseFull) bridged; creds travel in the
separate creds buffer of the unsigned‖creds envelope.

Round-trip GREEN: 2-of-3 multisig + owner-locktime output, stakeable.LockOut,
stakeable.LockIn input, memo, AND signed secp256k1 credentials all survive
Marshal->Unmarshal with exact field equality; unsigned stays a byte-prefix of
signed. Every embedding tx type now composes this envelope + its delta fields.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-09 21:39:14 -07:00