Files
zeekayandHanzo Dev 2902f66a4f Conditions that could not be reached from the code around them
Three error checks in config outlived the assignments they belonged to:
the calls above them are commented out, so err was whatever the last
live statement left, which nilness proves is nil. The genesis check
names a decode of UTXO message bytes that the loop does not do, and it
held the only use of fmt in that file.

The proposervm conjunct is the same shape read forwards: the branch
above it returns whenever forkErr is non-nil, so testing it again below
asks a question already answered.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 20:04:05 -07:00

371 lines
9.3 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package genesis
import (
"cmp"
"errors"
"github.com/luxfi/address"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/math"
"github.com/luxfi/node/vms/platformvm/signer"
"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/util"
lux "github.com/luxfi/utxo"
"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
Message []byte `json:"message"`
}
// Genesis represents a genesis state of the platform chain
type Genesis struct {
UTXOs []*UTXO
Validators []*txs.Tx
Chains []*txs.Tx
Timestamp uint64
InitialSupply uint64
Message string
}
// 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),
}
}
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)
}