mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
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]>
371 lines
9.3 KiB
Go
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)
|
|
}
|