Files
node/vms/platformvm/genesis/genesis.go
T
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

375 lines
9.6 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package genesis
import (
"cmp"
"errors"
"fmt"
"github.com/luxfi/address"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
"github.com/luxfi/node/vms/platformvm/stakeable"
"github.com/luxfi/node/vms/platformvm/txs"
"github.com/luxfi/node/vms/platformvm/txs/txheap"
"github.com/luxfi/utils"
"github.com/luxfi/math"
"github.com/luxfi/node/vms/platformvm/signer"
"github.com/luxfi/utxo/secp256k1fx"
)
// Note that since a Lux network has exactly one Platform Chain,
// and the Platform Chain defines the genesis state of the network
// (who is staking, which chains exist, etc.), defining the genesis
// state of the Platform Chain is the same as defining the genesis
// state of the network.
var (
errUTXOHasNoValue = errors.New("genesis UTXO has no value")
errValidatorHasZeroWeight = errors.New("validator has zero weight")
errValidatorAlreadyExited = errors.New("validator would have already unstaked")
errStakeOverflow = errors.New("validator stake exceeds limit")
_ utils.Sortable[Allocation] = Allocation{}
)
// UTXO adds messages to UTXOs
type UTXO struct {
lux.UTXO `serialize:"true"`
Message []byte `serialize:"true" json:"message"`
}
// Genesis represents a genesis state of the platform chain
type Genesis struct {
UTXOs []*UTXO `serialize:"true"`
Validators []*txs.Tx `serialize:"true"`
Chains []*txs.Tx `serialize:"true"`
Timestamp uint64 `serialize:"true"`
InitialSupply uint64 `serialize:"true"`
Message string `serialize:"true"`
}
// Parse deserializes a P-Chain genesis blob from its native-ZAP wire form
// (see genesiswire.go). Embedded validator + chain txs are re-parsed via
// txs.Parse from their own self-delimiting signed bytes, so each TxID
// (= hash(signedBytes)) is preserved byte-for-byte with no re-encoding.
func Parse(genesisBytes []byte) (*Genesis, error) {
return parseGenesis(genesisBytes)
}
// Allocation is a UTXO on the Platform Chain that exists at the chain's genesis
type Allocation struct {
Locktime uint64
Amount uint64
Address string
Message []byte
}
// Compare compares two allocations
func (a Allocation) Compare(other Allocation) int {
if locktimeCmp := cmp.Compare(a.Locktime, other.Locktime); locktimeCmp != 0 {
return locktimeCmp
}
if amountCmp := cmp.Compare(a.Amount, other.Amount); amountCmp != 0 {
return amountCmp
}
addr, err := bech32ToID(a.Address)
if err != nil {
return 0
}
otherAddr, err := bech32ToID(other.Address)
if err != nil {
return 0
}
return addr.Compare(otherAddr)
}
// Validator represents a validator at genesis
type Validator struct {
TxID ids.ID
StartTime uint64
EndTime uint64
Weight uint64
NodeID ids.NodeID
}
// Owner is the repr. of a reward owner at genesis
type Owner struct {
Locktime uint64
Threshold uint32
Addresses []string
}
// GenesisPermissionlessValidator represents a permissionless validator at genesis
type PermissionlessValidator struct {
Validator
RewardOwner *Owner
DelegationFee float32
ExactDelegationFee uint32
Staked []Allocation
Signer *signer.ProofOfPossession
}
// Chain defines a chain that exists at the network's genesis
// [GenesisData] is the initial state of the chain.
// [VMID] is the ID of the VM this blockchain runs.
// [FxIDs] are the IDs of the Fxs the blockchain supports.
// [Name] is a human-readable, non-unique name for the blockchain.
// [ChainID] is the ID of the chain that validates the blockchain
type Chain struct {
GenesisData []byte
VMID ids.ID
FxIDs []ids.ID
Name string
ChainID ids.ID
}
// bech32ToID takes bech32 address and produces a shortID
func bech32ToID(addrStr string) (ids.ShortID, error) {
_, addrBytes, err := address.ParseBech32(addrStr)
if err != nil {
return ids.ShortID{}, err
}
return ids.ToShortID(addrBytes)
}
// New builds the genesis state of the P-Chain (and thereby the Lux network.)
// [utxoAssetID] is the ID of the LUX asset
// [networkID] is the ID of the network
// [allocations] are the UTXOs on the Platform Chain that exist at genesis.
// [validators] are the validators of the primary network at genesis.
// [chains] are the chains that exist at genesis.
// [time] is the Platform Chain's time at network genesis.
// [initialSupply] is the initial supply of the LUX asset.
// [message] is the message to be sent to the genesis UTXOs.
func New(
utxoAssetID ids.ID,
networkID uint32,
allocations []Allocation,
validators []PermissionlessValidator,
chains []Chain,
time uint64,
initialSupply uint64,
message string,
) (*Genesis, error) {
// Specify the UTXOs on the Platform chain that exist at genesis
utxos := make([]*UTXO, 0, len(allocations))
for i, allocation := range allocations {
if allocation.Amount == 0 {
return nil, errUTXOHasNoValue
}
addrID, err := bech32ToID(allocation.Address)
if err != nil {
return nil, err
}
utxo := lux.UTXO{
UTXOID: lux.UTXOID{
TxID: ids.Empty,
OutputIndex: uint32(i),
},
Asset: lux.Asset{ID: utxoAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: allocation.Amount,
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Threshold: 1,
Addrs: []ids.ShortID{addrID},
},
},
}
if allocation.Locktime > time {
utxo.Out = &stakeable.LockOut{
Locktime: allocation.Locktime,
TransferableOut: utxo.Out.(lux.TransferableOut),
}
}
if err != nil {
return nil, fmt.Errorf("problem decoding UTXO message bytes: %w", err)
}
utxos = append(utxos, &UTXO{
UTXO: utxo,
Message: allocation.Message,
})
}
// Specify the validators that are validating the primary network at genesis
vdrs := txheap.NewByEndTime()
for _, vdr := range validators {
weight := uint64(0)
stake := make([]*lux.TransferableOutput, len(vdr.Staked))
utils.Sort(vdr.Staked)
for i, allocation := range vdr.Staked {
addrID, err := bech32ToID(allocation.Address)
if err != nil {
return nil, err
}
utxo := &lux.TransferableOutput{
Asset: lux.Asset{ID: utxoAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: allocation.Amount,
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Threshold: 1,
Addrs: []ids.ShortID{addrID},
},
},
}
if allocation.Locktime > time {
utxo.Out = &stakeable.LockOut{
Locktime: allocation.Locktime,
TransferableOut: utxo.Out,
}
}
stake[i] = utxo
newWeight, err := math.Add(weight, allocation.Amount)
if err != nil {
return nil, errStakeOverflow
}
weight = newWeight
}
// Use explicit weight from validator config if Staked allocations are empty
// This allows validators to be created with explicit weights for testing/development
if weight == 0 && vdr.Weight > 0 {
weight = vdr.Weight
}
if weight == 0 {
return nil, errValidatorHasZeroWeight
}
if vdr.EndTime <= time {
return nil, errValidatorAlreadyExited
}
owner := &secp256k1fx.OutputOwners{
Locktime: vdr.RewardOwner.Locktime,
Threshold: vdr.RewardOwner.Threshold,
}
for _, addrStr := range vdr.RewardOwner.Addresses {
addrID, err := bech32ToID(addrStr)
if err != nil {
return nil, err
}
owner.Addrs = append(owner.Addrs, addrID)
}
utils.Sort(owner.Addrs)
// When stakers have explicit weight but no staked allocations,
// synthesize a stake output so that AddValidatorTx/AddPermissionlessValidatorTx
// passes SyntacticVerify (which requires StakeOuts sum == Wght).
if len(stake) == 0 && weight > 0 {
stakeAddr := owner.Addrs[0]
stake = []*lux.TransferableOutput{
{
Asset: lux.Asset{ID: utxoAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: weight,
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Threshold: 1,
Addrs: []ids.ShortID{stakeAddr},
},
},
},
}
}
delegationFee := vdr.ExactDelegationFee
base := &lux.BaseTx{
NetworkID: networkID,
BlockchainID: ids.Empty,
}
validator := txs.Validator{
NodeID: vdr.NodeID,
Start: time,
End: vdr.EndTime,
Wght: weight,
}
var (
unsigned txs.UnsignedTx
err error
)
if vdr.Signer == nil {
unsigned, err = txs.NewAddValidatorTx(base, validator, stake, owner, delegationFee)
} else {
unsigned, err = txs.NewAddPermissionlessValidatorTx(
base,
validator,
constants.PrimaryNetworkID,
vdr.Signer,
stake,
owner, // validator rewards owner
owner, // delegator rewards owner
delegationFee,
)
}
if err != nil {
return nil, err
}
tx := &txs.Tx{Unsigned: unsigned}
if err := tx.Initialize(); err != nil {
return nil, err
}
vdrs.Add(tx)
}
// Specify the chains that exist at genesis
chainsTxs := []*txs.Tx{}
for _, chain := range chains {
base := &lux.BaseTx{
NetworkID: networkID,
BlockchainID: ids.Empty,
}
unsigned, err := txs.NewCreateChainTx(
base,
chain.ChainID,
chain.Name,
chain.VMID,
chain.FxIDs,
chain.GenesisData,
&secp256k1fx.Input{},
)
if err != nil {
return nil, err
}
tx := &txs.Tx{Unsigned: unsigned}
if err := tx.Initialize(); err != nil {
return nil, err
}
chainsTxs = append(chainsTxs, tx)
}
validatorTxs := vdrs.List()
g := &Genesis{
UTXOs: utxos,
Validators: validatorTxs,
Chains: chainsTxs,
Timestamp: time,
InitialSupply: initialSupply,
Message: message,
}
return g, nil
}
// Bytes serializes the Genesis to its native-ZAP wire form (see genesiswire.go).
func (g *Genesis) Bytes() ([]byte, error) {
return marshalGenesis(g)
}