mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
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>
375 lines
9.6 KiB
Go
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)
|
|
}
|