mirror of
https://github.com/luxfi/genesis.git
synced 2026-10-30 16:25:45 +00:00
The X-Chain asset declared nine decimals in every network's genesis while the node's units, the genesis parameters, and every amount on the live P-Chain are six. Denomination is display metadata — nothing computes with it — so the label told every wallet and explorer to place the point three places wrong. Six is also the only precision at which the two-trillion cap fits a uint64; nine overflows it, which is why six was chosen. The mainnet validator floor is 1,000 LUX for any network built from this config. The running chain keeps 2,000 until the node's staking-policy history moves it, which is that mechanism's job. Co-authored-by: Hanzo Dev <[email protected]>
1071 lines
36 KiB
Go
1071 lines
36 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package builder provides genesis byte generation for Lux networks.
|
|
// This package depends on node types and is responsible for building
|
|
// the actual genesis state from genesis config.
|
|
package builder
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/luxfi/address"
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/container/sampler"
|
|
"github.com/luxfi/formatting"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/math/set"
|
|
pchainblock "github.com/luxfi/proto/p/block"
|
|
"github.com/luxfi/proto/p/genesis"
|
|
"github.com/luxfi/proto/p/reward"
|
|
"github.com/luxfi/proto/p/signer"
|
|
pchaintxs "github.com/luxfi/proto/p/txs"
|
|
"github.com/luxfi/proto/p/validators/fee"
|
|
"github.com/luxfi/proto/x/fxs"
|
|
xgenesis "github.com/luxfi/proto/x/genesis"
|
|
xchaintxs "github.com/luxfi/proto/x/txs"
|
|
protozapcodec "github.com/luxfi/proto/zap_codec"
|
|
"github.com/luxfi/utxo/nftfx"
|
|
"github.com/luxfi/utxo/propertyfx"
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
"github.com/luxfi/vm/components/gas"
|
|
|
|
genesiscfg "github.com/luxfi/genesis/pkg/genesis"
|
|
)
|
|
|
|
var (
|
|
// PChainAliases are the default aliases for the P-Chain — the
|
|
// protocol chain that holds the validator set, chain registry,
|
|
// and ordering. Served by ProtocolVM (formerly PlatformVM).
|
|
PChainAliases = []string{"P", "protocol"}
|
|
// XChainAliases are the default aliases for the X-Chain
|
|
XChainAliases = []string{"X", "xvm"}
|
|
// CChainAliases are the default aliases for the C-Chain
|
|
CChainAliases = []string{"C", "evm"}
|
|
// DChainAliases are the default aliases for the D-Chain (DEX)
|
|
DChainAliases = []string{"D", "dex", "dexvm"}
|
|
// QChainAliases are the default aliases for the Q-Chain (Quantum)
|
|
QChainAliases = []string{"Q", "quantum", "quantumvm"}
|
|
// AChainAliases are the default aliases for the A-Chain (AI)
|
|
AChainAliases = []string{"A", "ai", "aivm"}
|
|
// BChainAliases are the default aliases for the B-Chain (Bridge)
|
|
BChainAliases = []string{"B", "bridge", "bridgevm"}
|
|
// FChainAliases are the default aliases for the F-Chain (threshold-FHE).
|
|
// LP-134: F-Chain runs the ThresholdVM substrate in FHE mode (confidential
|
|
// compute / off-EVM threshold DECRYPT). It supersedes the retired T-Chain;
|
|
// the legacy "T"/"threshold" labels now belong only to teleportvm
|
|
// (LP-6332). On-disk shard: fchain.json.
|
|
FChainAliases = []string{"F", "fhe", "fhevm"}
|
|
// ZChainAliases are the default aliases for the Z-Chain (ZK)
|
|
ZChainAliases = []string{"Z", "zk", "zkvm"}
|
|
// GChainAliases are the default aliases for the G-Chain (Graph)
|
|
GChainAliases = []string{"G", "graph", "graphvm"}
|
|
// KChainAliases are the default aliases for the K-Chain (KMS).
|
|
// LP-134: "K stays keyvm" — the threshold-MPC labels M/mpc/mpcvm moved off
|
|
// K onto the M-Chain (MChainAliases). K-Chain is pure KMS key management.
|
|
KChainAliases = []string{"K", "key", "keyvm", "kms", "kmsvm"}
|
|
// MChainAliases are the default aliases for the M-Chain (threshold-MPC).
|
|
// LP-134: M-Chain runs the ThresholdVM substrate in MPC mode (CGGMP21 /
|
|
// FROST threshold SIGNING for bridge custody). On-disk shard: mchain.json.
|
|
MChainAliases = []string{"M", "mpc", "mpcvm"}
|
|
|
|
// VMAliases are the default aliases for VMs
|
|
VMAliases = map[ids.ID][]string{
|
|
constants.PlatformVMID: {"protocol"},
|
|
constants.XVMID: {"xvm"},
|
|
constants.EVMID: {"evm"},
|
|
constants.DexVMID: {"dexvm", "dex"},
|
|
constants.QuantumVMID: {"quantumvm", "quantum"},
|
|
constants.AIVMID: {"aivm", "ai"},
|
|
constants.BridgeVMID: {"bridgevm", "bridge"},
|
|
constants.MPCVMID: {"mpc", "mpcvm"},
|
|
constants.FHEVMID: {"fhe", "fhevm"},
|
|
constants.ZKVMID: {"zkvm", "zk"},
|
|
constants.GraphVMID: {"graphvm", "graph"},
|
|
constants.KeyVMID: {"kmsvm", "kms"},
|
|
secp256k1fx.ID: {"secp256k1fx"},
|
|
nftfx.ID: {"nftfx"},
|
|
propertyfx.ID: {"propertyfx"},
|
|
}
|
|
|
|
errNoTxs = errors.New("genesis creates no transactions")
|
|
errOverridesStandardNetworkConfig = errors.New("overrides standard network genesis config")
|
|
)
|
|
|
|
// Bootstrapper represents a network bootstrap node with parsed types
|
|
type Bootstrapper struct {
|
|
ID ids.NodeID
|
|
IP netip.AddrPort
|
|
}
|
|
|
|
// ParseBootstrapper converts a genesis config bootstrapper to a parsed Bootstrapper
|
|
func ParseBootstrapper(b genesiscfg.Bootstrapper) (Bootstrapper, error) {
|
|
nodeID, err := ids.NodeIDFromString(b.ID)
|
|
if err != nil {
|
|
return Bootstrapper{}, fmt.Errorf("invalid bootstrapper ID %q: %w", b.ID, err)
|
|
}
|
|
ip, err := netip.ParseAddrPort(b.IP)
|
|
if err != nil {
|
|
return Bootstrapper{}, fmt.Errorf("invalid bootstrapper IP %q: %w", b.IP, err)
|
|
}
|
|
return Bootstrapper{ID: nodeID, IP: ip}, nil
|
|
}
|
|
|
|
// parseProofOfPossession converts a genesis config ProofOfPossession (with hex strings)
|
|
// to a node signer.ProofOfPossession (with byte arrays).
|
|
func parseProofOfPossession(pop *genesiscfg.ProofOfPossession) (*signer.ProofOfPossession, error) {
|
|
if pop == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Decode the public key from hex string (may have 0x prefix)
|
|
pkBytes, err := formatting.Decode(formatting.HexNC, pop.PublicKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode publicKey: %w", err)
|
|
}
|
|
|
|
// Decode the proof of possession from hex string (may have 0x prefix)
|
|
popBytes, err := formatting.Decode(formatting.HexNC, pop.ProofOfPossession)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode proofOfPossession: %w", err)
|
|
}
|
|
|
|
result := &signer.ProofOfPossession{}
|
|
copy(result.PublicKey[:], pkBytes)
|
|
copy(result.ProofOfPossession[:], popBytes)
|
|
|
|
// Verify the proof of possession is valid
|
|
if err := result.Verify(); err != nil {
|
|
return nil, fmt.Errorf("invalid proof of possession: %w", err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetBootstrappers returns parsed bootstrappers for the network
|
|
func GetBootstrappers(networkID uint32) ([]Bootstrapper, error) {
|
|
cfgBootstrappers := genesiscfg.GetBootstrappers(networkID)
|
|
result := make([]Bootstrapper, 0, len(cfgBootstrappers))
|
|
for _, b := range cfgBootstrappers {
|
|
parsed, err := ParseBootstrapper(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, parsed)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// SampleBootstrappers returns a random sample of bootstrappers for the network
|
|
func SampleBootstrappers(networkID uint32, count int) ([]Bootstrapper, error) {
|
|
allBootstrappers, err := GetBootstrappers(networkID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
count = min(count, len(allBootstrappers))
|
|
if count <= 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
s := sampler.NewUniform()
|
|
s.Initialize(uint64(len(allBootstrappers)))
|
|
indices, _ := s.Sample(count)
|
|
|
|
sampled := make([]Bootstrapper, 0, len(indices))
|
|
for _, index := range indices {
|
|
sampled = append(sampled, allBootstrappers[int(index)])
|
|
}
|
|
return sampled, nil
|
|
}
|
|
|
|
// StakingConfig is the staking configuration with time.Duration types
|
|
type StakingConfig struct {
|
|
UptimeRequirement float64
|
|
MinValidatorStake uint64
|
|
MaxValidatorStake uint64
|
|
MinDelegatorStake uint64
|
|
MinDelegationFee uint32
|
|
MinStakeDuration time.Duration
|
|
MaxStakeDuration time.Duration
|
|
RewardConfig reward.Config
|
|
|
|
// BLS key information for genesis replay
|
|
NodeID string `json:"nodeID"`
|
|
BLSPublicKey []byte `json:"blsPublicKey"`
|
|
BLSProofOfPossession []byte `json:"blsProofOfPossession"`
|
|
}
|
|
|
|
// GetStakingConfig returns the staking config with time.Duration types
|
|
func GetStakingConfig(networkID uint32) StakingConfig {
|
|
cfg := genesiscfg.GetStakingConfig(networkID)
|
|
return StakingConfig{
|
|
UptimeRequirement: cfg.UptimeRequirement,
|
|
MinValidatorStake: cfg.MinValidatorStake,
|
|
MaxValidatorStake: cfg.MaxValidatorStake,
|
|
MinDelegatorStake: cfg.MinDelegatorStake,
|
|
MinDelegationFee: cfg.MinDelegationFee,
|
|
MinStakeDuration: time.Duration(cfg.MinStakeDuration) * time.Second,
|
|
MaxStakeDuration: time.Duration(cfg.MaxStakeDuration) * time.Second,
|
|
RewardConfig: reward.Config{
|
|
MaxConsumptionRate: cfg.RewardConfig.MaxConsumptionRate,
|
|
MinConsumptionRate: cfg.RewardConfig.MinConsumptionRate,
|
|
MintingPeriod: time.Duration(cfg.RewardConfig.MintingPeriod) * time.Second,
|
|
SupplyCap: cfg.RewardConfig.SupplyCap,
|
|
},
|
|
}
|
|
}
|
|
|
|
// TxFeeConfig contains transaction fee configuration
|
|
// This includes the basic fee config from genesis plus dynamic/validator fees
|
|
type TxFeeConfig struct {
|
|
TxFee uint64 `json:"txFee"`
|
|
CreateAssetTxFee uint64 `json:"createAssetTxFee"`
|
|
DynamicFeeConfig gas.Config `json:"dynamicFeeConfig"`
|
|
ValidatorFeeConfig fee.Config `json:"validatorFeeConfig"`
|
|
}
|
|
|
|
// Default dynamic fee parameters
|
|
var (
|
|
MainnetDynamicFeeConfig = gas.Config{
|
|
Weights: gas.Dimensions{
|
|
gas.Bandwidth: 1,
|
|
gas.DBRead: 1,
|
|
gas.DBWrite: 1,
|
|
gas.Compute: 1,
|
|
},
|
|
MaxCapacity: 1_000_000,
|
|
MaxPerSecond: 100_000,
|
|
TargetPerSecond: 50_000,
|
|
MinPrice: 1,
|
|
ExcessConversionConstant: 5_000,
|
|
}
|
|
|
|
TestnetDynamicFeeConfig = gas.Config{
|
|
Weights: gas.Dimensions{
|
|
gas.Bandwidth: 1,
|
|
gas.DBRead: 1,
|
|
gas.DBWrite: 1,
|
|
gas.Compute: 1,
|
|
},
|
|
MaxCapacity: 1_000_000,
|
|
MaxPerSecond: 100_000,
|
|
TargetPerSecond: 50_000,
|
|
MinPrice: 1,
|
|
ExcessConversionConstant: 5_000,
|
|
}
|
|
|
|
LocalDynamicFeeConfig = gas.Config{
|
|
Weights: gas.Dimensions{
|
|
gas.Bandwidth: 1,
|
|
gas.DBRead: 1,
|
|
gas.DBWrite: 1,
|
|
gas.Compute: 1,
|
|
},
|
|
MaxCapacity: 1_000_000,
|
|
MaxPerSecond: 100_000,
|
|
TargetPerSecond: 50_000,
|
|
MinPrice: 1,
|
|
ExcessConversionConstant: 5_000,
|
|
}
|
|
|
|
MainnetValidatorFeeConfig = fee.Config{
|
|
Capacity: 20_000,
|
|
Target: 10_000,
|
|
MinPrice: 512,
|
|
ExcessConversionConstant: 1_587,
|
|
}
|
|
|
|
TestnetValidatorFeeConfig = fee.Config{
|
|
Capacity: 20_000,
|
|
Target: 10_000,
|
|
MinPrice: 512,
|
|
ExcessConversionConstant: 1_587,
|
|
}
|
|
|
|
LocalValidatorFeeConfig = fee.Config{
|
|
Capacity: 20_000,
|
|
Target: 10_000,
|
|
MinPrice: 512,
|
|
ExcessConversionConstant: 1_587,
|
|
}
|
|
)
|
|
|
|
// GetTxFeeConfig returns the tx fee config
|
|
func GetTxFeeConfig(networkID uint32) TxFeeConfig {
|
|
cfg := genesiscfg.GetTxFeeConfig(networkID)
|
|
|
|
var dynamicCfg gas.Config
|
|
var validatorCfg fee.Config
|
|
|
|
switch networkID {
|
|
case constants.MainnetID:
|
|
dynamicCfg = MainnetDynamicFeeConfig
|
|
validatorCfg = MainnetValidatorFeeConfig
|
|
case constants.TestnetID:
|
|
dynamicCfg = TestnetDynamicFeeConfig
|
|
validatorCfg = TestnetValidatorFeeConfig
|
|
default:
|
|
dynamicCfg = LocalDynamicFeeConfig
|
|
validatorCfg = LocalValidatorFeeConfig
|
|
}
|
|
|
|
return TxFeeConfig{
|
|
TxFee: cfg.TxFee,
|
|
CreateAssetTxFee: cfg.CreateAssetTxFee,
|
|
DynamicFeeConfig: dynamicCfg,
|
|
ValidatorFeeConfig: validatorCfg,
|
|
}
|
|
}
|
|
|
|
// GetConfig returns the genesis config for the given network ID
|
|
func GetConfig(networkID uint32) *genesiscfg.Config {
|
|
return genesiscfg.GetConfig(networkID)
|
|
}
|
|
|
|
// FromConfig builds genesis bytes from a config.
|
|
//
|
|
// X-Chain is opt-in via config.XChainGenesis. The shard is a small JSON
|
|
// asset descriptor:
|
|
//
|
|
// {"symbol":"LUX","name":"Lux","denomination":6}
|
|
//
|
|
// When present, the builder constructs an XVM genesis whose primary
|
|
// asset is that descriptor (with initial holders sourced from
|
|
// config.Allocations) and emits a CreateChainTx for the X-Chain.
|
|
// utxoAssetID returned to the caller is the X-Chain primary asset ID
|
|
// (sha256 over xvmGenesisBytes) — the same ID P-Chain stake UTXOs are
|
|
// denominated in.
|
|
//
|
|
// When absent, no XVM genesis is built, no X-Chain CreateChainTx is
|
|
// emitted, and utxoAssetID is returned as ids.Empty. The primary network
|
|
// then has no native UTXO asset: validator stakes denominated against
|
|
// ids.Empty have no on-chain asset backing, which is the
|
|
// permissioned-set semantics used by primary networks whose value capture
|
|
// lives on a downstream chain (EVM etc.) with its own asset model. Minting
|
|
// a real primary-network asset requires X — there is no other genesis
|
|
// site for it.
|
|
func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
|
// HRP is needed for X-Chain holder addresses, P-Chain protocol
|
|
// allocations, staker allocations, and reward addresses — define
|
|
// once, before the X-Chain conditional, so it's available on both
|
|
// paths.
|
|
hrp := constants.GetHRP(config.NetworkID)
|
|
|
|
var (
|
|
xvmGenesisBytes []byte
|
|
utxoAssetID = ids.Empty
|
|
)
|
|
if config.XChainGenesis != "" {
|
|
var asset struct {
|
|
Symbol string `json:"symbol"`
|
|
Name string `json:"name"`
|
|
Denomination byte `json:"denomination"`
|
|
}
|
|
if err := json.Unmarshal([]byte(config.XChainGenesis), &asset); err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("invalid xChainGenesis shard: %w", err)
|
|
}
|
|
primary := xgenesis.GenesisAssetDefinition{
|
|
Name: asset.Name,
|
|
Symbol: asset.Symbol,
|
|
Denomination: asset.Denomination,
|
|
InitialState: xgenesis.AssetInitialState{},
|
|
}
|
|
memoBytes := []byte{}
|
|
|
|
// Sort allocations for deterministic output
|
|
type allocation struct {
|
|
EVMAddr ids.ShortID
|
|
UTXOAddr ids.ShortID
|
|
InitialAmount uint64
|
|
}
|
|
xAllocations := []allocation{}
|
|
for _, a := range config.Allocations {
|
|
if a.InitialAmount > 0 {
|
|
xAllocations = append(xAllocations, allocation{
|
|
EVMAddr: a.EVMAddr,
|
|
UTXOAddr: a.UTXOAddr,
|
|
InitialAmount: a.InitialAmount,
|
|
})
|
|
}
|
|
}
|
|
|
|
for _, a := range xAllocations {
|
|
bech32Addr, err := address.FormatBech32(hrp, a.UTXOAddr[:])
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address: %w", err)
|
|
}
|
|
primary.InitialState.FixedCap = append(primary.InitialState.FixedCap, xgenesis.GenesisHolder{
|
|
Amount: a.InitialAmount,
|
|
Address: bech32Addr,
|
|
})
|
|
evmAddrStr := a.EVMAddr.Hex()
|
|
if len(evmAddrStr) > 2 { // "0x" prefix
|
|
memoBytes = append(memoBytes, []byte(evmAddrStr[2:])...)
|
|
}
|
|
}
|
|
primary.Memo = memoBytes
|
|
|
|
xvmCodecs, err := newXVMParserCodecs()
|
|
if err != nil {
|
|
return nil, ids.Empty, err
|
|
}
|
|
// NewParser registers the canonical XVM tx types
|
|
// (BaseTx/CreateAsset/Operation/Import/Export) plus the fx-owned
|
|
// payload types (secp256k1fx) onto xvmCodecs.GenesisRegistry —
|
|
// without this, the GenesisCodec can't serialize TransferOutput
|
|
// and the like. proto/x stayed Codec-free after Wave 1A so the
|
|
// SDK owns type registration; we replicate that contract here.
|
|
xvmParser, err := xchaintxs.NewParser(xvmCodecs, []fxs.Fx{
|
|
&secp256k1fx.Fx{},
|
|
})
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("couldn't construct xvm parser: %w", err)
|
|
}
|
|
xvmGenesisCodec := xvmParser.GenesisCodec()
|
|
xvmGenesis, err := xgenesis.NewGenesis(
|
|
config.NetworkID,
|
|
map[string]xgenesis.GenesisAssetDefinition{
|
|
asset.Symbol: primary,
|
|
},
|
|
xvmGenesisCodec,
|
|
)
|
|
if err != nil {
|
|
return nil, ids.Empty, err
|
|
}
|
|
xvmGenesisBytes, err = xvmGenesis.Bytes(xvmGenesisCodec)
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("couldn't serialize xvm genesis: %w", err)
|
|
}
|
|
|
|
utxoAssetID, err = UTXOAssetID(xvmGenesisBytes)
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("couldn't generate LUX asset ID: %w", err)
|
|
}
|
|
}
|
|
|
|
genesisTime := time.Unix(int64(config.StartTime), 0)
|
|
|
|
// Calculate initial supply
|
|
initialSupply := uint64(0)
|
|
for _, a := range config.Allocations {
|
|
initialSupply += a.InitialAmount
|
|
for _, unlock := range a.UnlockSchedule {
|
|
initialSupply += unlock.Amount
|
|
}
|
|
}
|
|
|
|
// Build protocol allocations
|
|
initiallyStaked := set.Set[ids.ShortID]{}
|
|
for _, addr := range config.InitialStakedFunds {
|
|
initiallyStaked.Add(addr)
|
|
}
|
|
|
|
protocolAllocations := []genesis.Allocation{}
|
|
skippedAllocations := []genesiscfg.Allocation{}
|
|
for _, a := range config.Allocations {
|
|
if initiallyStaked.Contains(a.UTXOAddr) {
|
|
skippedAllocations = append(skippedAllocations, a)
|
|
continue
|
|
}
|
|
for _, unlock := range a.UnlockSchedule {
|
|
if unlock.Amount > 0 {
|
|
// Format address as bech32 for the P-Chain
|
|
bech32Addr, err := address.FormatBech32(hrp, a.UTXOAddr[:])
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address for P-chain: %w", err)
|
|
}
|
|
protocolAllocations = append(protocolAllocations, genesis.Allocation{
|
|
Locktime: unlock.Locktime,
|
|
Amount: unlock.Amount,
|
|
Address: bech32Addr,
|
|
Message: a.EVMAddr.Bytes(),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build validators
|
|
validators := []genesis.PermissionlessValidator{}
|
|
allNodeAllocations := splitAllocations(skippedAllocations, len(config.InitialStakers))
|
|
endStakingTime := genesisTime.Add(time.Duration(config.InitialStakeDuration) * time.Second)
|
|
stakingOffset := time.Duration(0)
|
|
|
|
for i, staker := range config.InitialStakers {
|
|
// Safely get node allocations (may be empty if no initialStakedFunds)
|
|
var nodeAllocations []genesiscfg.Allocation
|
|
if i < len(allNodeAllocations) {
|
|
nodeAllocations = allNodeAllocations[i]
|
|
}
|
|
|
|
// Use explicit staker times if provided, otherwise use calculated values
|
|
startTime := uint64(genesisTime.Unix())
|
|
if staker.StartTime > 0 {
|
|
startTime = staker.StartTime
|
|
}
|
|
|
|
endTime := endStakingTime.Add(-stakingOffset)
|
|
stakingOffset += time.Duration(config.InitialStakeDurationOffset) * time.Second
|
|
endTimeUnix := uint64(endTime.Unix())
|
|
if staker.EndTime > 0 {
|
|
endTimeUnix = staker.EndTime
|
|
}
|
|
|
|
allocations := []genesis.Allocation{}
|
|
for _, a := range nodeAllocations {
|
|
for _, unlock := range a.UnlockSchedule {
|
|
// Format address as bech32 for staker allocations
|
|
bech32Addr, err := address.FormatBech32(hrp, a.UTXOAddr[:])
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address for staker allocation: %w", err)
|
|
}
|
|
allocations = append(allocations, genesis.Allocation{
|
|
Locktime: unlock.Locktime,
|
|
Amount: unlock.Amount,
|
|
Address: bech32Addr,
|
|
Message: a.EVMAddr.Bytes(),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Calculate weight from allocations or use explicit weight
|
|
weight := staker.Weight
|
|
if weight == 0 {
|
|
for _, a := range allocations {
|
|
weight += a.Amount
|
|
}
|
|
}
|
|
|
|
// Format reward address as bech32
|
|
rewardBech32, err := address.FormatBech32(hrp, staker.RewardAddress[:])
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("failed to format bech32 reward address: %w", err)
|
|
}
|
|
|
|
// Parse the BLS proof of possession if present
|
|
var blsSigner *signer.ProofOfPossession
|
|
if staker.Signer != nil {
|
|
blsSigner, err = parseProofOfPossession(staker.Signer)
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("failed to parse proof of possession for staker %d: %w", i, err)
|
|
}
|
|
}
|
|
|
|
validators = append(validators, genesis.PermissionlessValidator{
|
|
Validator: genesis.Validator{
|
|
StartTime: startTime,
|
|
EndTime: endTimeUnix,
|
|
Weight: weight,
|
|
NodeID: staker.NodeID,
|
|
},
|
|
RewardOwner: &genesis.Owner{
|
|
Threshold: 1,
|
|
Addresses: []string{rewardBech32},
|
|
},
|
|
Staked: allocations,
|
|
ExactDelegationFee: staker.DelegationFee,
|
|
Signer: blsSigner,
|
|
})
|
|
}
|
|
|
|
// Primary-network chain registry. Each entry pairs a genesis-data
|
|
// source with the VM that consumes it. Entries with empty
|
|
// GenesisData are SKIPPED — luxd will not start a chain manager
|
|
// for them. This is the data-driven contract: which primary-network
|
|
// chains exist is a function of which shards the operator shipped,
|
|
// not a runtime knob.
|
|
//
|
|
// Order is fixed (X→C→D→Q→A→B→F→Z→G→K→M) and preserved across
|
|
// presence/absence so byte-identical genesis holds when the same
|
|
// shard set is supplied. Append-only — reordering shifts the
|
|
// P-Chain genesis byte layout.
|
|
//
|
|
// LP-0130: F-Chain (FHEVMID) and M-Chain (MPCVMID) run distinct VMs.
|
|
// The legacy shared substrate is retired; F occupies the retired
|
|
// T slot for byte-order stability, M is appended.
|
|
chainEntries := []struct {
|
|
GenesisData []byte
|
|
VMID ids.ID
|
|
Name string
|
|
FxIDs []ids.ID // X-Chain only; nil for everything else
|
|
}{
|
|
{GenesisData: xvmGenesisBytes, VMID: constants.XVMID, Name: "X-Chain",
|
|
FxIDs: []ids.ID{secp256k1fx.ID, nftfx.ID, propertyfx.ID}},
|
|
{GenesisData: []byte(config.CChainGenesis), VMID: constants.EVMID, Name: "C-Chain"},
|
|
{GenesisData: []byte(config.DChainGenesis), VMID: constants.DexVMID, Name: "D-Chain"},
|
|
{GenesisData: []byte(config.QChainGenesis), VMID: constants.QuantumVMID, Name: "Q-Chain"},
|
|
{GenesisData: []byte(config.AChainGenesis), VMID: constants.AIVMID, Name: "A-Chain"},
|
|
{GenesisData: []byte(config.BChainGenesis), VMID: constants.BridgeVMID, Name: "B-Chain"},
|
|
{GenesisData: []byte(config.FChainGenesis), VMID: constants.FHEVMID, Name: "F-Chain"},
|
|
{GenesisData: []byte(config.ZChainGenesis), VMID: constants.ZKVMID, Name: "Z-Chain"},
|
|
{GenesisData: []byte(config.GChainGenesis), VMID: constants.GraphVMID, Name: "G-Chain"},
|
|
{GenesisData: []byte(config.KChainGenesis), VMID: constants.KeyVMID, Name: "K-Chain"},
|
|
{GenesisData: []byte(config.MChainGenesis), VMID: constants.MPCVMID, Name: "M-Chain"},
|
|
}
|
|
chains := []genesis.Chain{}
|
|
for _, e := range chainEntries {
|
|
if len(e.GenesisData) == 0 {
|
|
continue
|
|
}
|
|
chains = append(chains, genesis.Chain{
|
|
GenesisData: e.GenesisData,
|
|
ChainID: constants.PrimaryNetworkID,
|
|
VMID: e.VMID,
|
|
FxIDs: e.FxIDs,
|
|
Name: e.Name,
|
|
})
|
|
}
|
|
|
|
pgc, err := pvmGenesisCodec()
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("problem while constructing protocol chain's genesis codec: %w", err)
|
|
}
|
|
pChainGenesis, err := genesis.New(
|
|
pgc,
|
|
utxoAssetID,
|
|
config.NetworkID,
|
|
protocolAllocations,
|
|
validators,
|
|
chains,
|
|
config.StartTime,
|
|
initialSupply,
|
|
config.Message,
|
|
)
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("problem while building protocol chain's genesis state: %w", err)
|
|
}
|
|
pChainGenesisBytes, err := pChainGenesis.Bytes(pgc)
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("problem while serializing protocol chain's genesis state: %w", err)
|
|
}
|
|
return pChainGenesisBytes, utxoAssetID, nil
|
|
}
|
|
|
|
// FromFile loads genesis config from file and builds genesis bytes
|
|
func FromFile(networkID uint32, filepath string, stakingCfg *StakingConfig, allowCustomGenesis bool) ([]byte, ids.ID, error) {
|
|
// Protect standard networks from custom genesis unless explicitly allowed
|
|
if !allowCustomGenesis {
|
|
switch networkID {
|
|
case constants.MainnetID, constants.TestnetID:
|
|
return nil, ids.Empty, fmt.Errorf(
|
|
"%w: %s",
|
|
errOverridesStandardNetworkConfig,
|
|
constants.NetworkName(networkID),
|
|
)
|
|
}
|
|
}
|
|
|
|
config, err := genesiscfg.GetConfigFile(filepath)
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("failed to load genesis file %s: %w", filepath, err)
|
|
}
|
|
if err := validateConfig(networkID, config, stakingCfg); err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("genesis config validation failed: %w", err)
|
|
}
|
|
return FromConfig(config)
|
|
}
|
|
|
|
// FromFlag parses base64-encoded genesis content and builds genesis bytes
|
|
func FromFlag(networkID uint32, genesisContent string, stakingCfg *StakingConfig, allowCustomGenesis bool) ([]byte, ids.ID, error) {
|
|
// Protect standard networks from custom genesis unless explicitly allowed
|
|
if !allowCustomGenesis {
|
|
switch networkID {
|
|
case constants.MainnetID, constants.TestnetID:
|
|
return nil, ids.Empty, fmt.Errorf(
|
|
"%w: %s",
|
|
errOverridesStandardNetworkConfig,
|
|
constants.NetworkName(networkID),
|
|
)
|
|
}
|
|
}
|
|
|
|
data, err := base64.StdEncoding.DecodeString(genesisContent)
|
|
if err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("failed to decode base64 genesis content: %w", err)
|
|
}
|
|
var config genesiscfg.Config
|
|
if err := json.Unmarshal(data, &config); err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("failed to parse genesis config: %w", err)
|
|
}
|
|
if err := validateConfig(networkID, &config, stakingCfg); err != nil {
|
|
return nil, ids.Empty, fmt.Errorf("genesis config validation failed: %w", err)
|
|
}
|
|
return FromConfig(&config)
|
|
}
|
|
|
|
// FromDatabase returns genesis data for database replay mode
|
|
func FromDatabase(networkID uint32, dbPath string, dbType string, stakingCfg *StakingConfig) ([]byte, ids.ID, error) {
|
|
config := genesiscfg.GetConfig(constants.LocalID)
|
|
config.NetworkID = networkID
|
|
config.Message = "DATABASE_REPLAY_MODE"
|
|
return FromConfig(config)
|
|
}
|
|
|
|
// VMGenesis returns the genesis tx for a specific VM
|
|
func VMGenesis(genesisBytes []byte, vmID ids.ID) (*pchaintxs.Tx, error) {
|
|
pgc, err := pvmGenesisCodec()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to construct genesis codec: %w", err)
|
|
}
|
|
gen, err := genesis.Parse(pgc, genesisBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse genesis: %w", err)
|
|
}
|
|
for _, chain := range gen.Chains {
|
|
uChain := chain.Unsigned.(*pchaintxs.CreateChainTx)
|
|
if uChain.VMID == vmID {
|
|
return chain, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("couldn't find blockchain with VM ID %s", vmID)
|
|
}
|
|
|
|
// Aliases returns the default aliases for chains and APIs
|
|
func Aliases(genesisBytes []byte) (map[string][]string, map[ids.ID][]string, error) {
|
|
apiAliases := map[string][]string{
|
|
path.Join(constants.ChainAliasPrefix, constants.PlatformChainID.String()): {
|
|
"P",
|
|
"protocol",
|
|
path.Join(constants.ChainAliasPrefix, "P"),
|
|
path.Join(constants.ChainAliasPrefix, "protocol"),
|
|
},
|
|
}
|
|
chainAliases := map[ids.ID][]string{
|
|
constants.PlatformChainID: PChainAliases,
|
|
}
|
|
|
|
pgc, err := pvmGenesisCodec()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to construct genesis codec: %w", err)
|
|
}
|
|
gen, err := genesis.Parse(pgc, genesisBytes)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
for _, chain := range gen.Chains {
|
|
uChain := chain.Unsigned.(*pchaintxs.CreateChainTx)
|
|
chainID := chain.ID()
|
|
endpoint := path.Join(constants.ChainAliasPrefix, chainID.String())
|
|
switch uChain.VMID {
|
|
case constants.XVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"X", "xvm",
|
|
path.Join(constants.ChainAliasPrefix, "X"),
|
|
path.Join(constants.ChainAliasPrefix, "xvm"),
|
|
}
|
|
chainAliases[chainID] = XChainAliases
|
|
case constants.EVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"C", "evm",
|
|
path.Join(constants.ChainAliasPrefix, "C"),
|
|
path.Join(constants.ChainAliasPrefix, "evm"),
|
|
}
|
|
chainAliases[chainID] = CChainAliases
|
|
case constants.DexVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"D", "dex", "dexvm",
|
|
path.Join(constants.ChainAliasPrefix, "D"),
|
|
path.Join(constants.ChainAliasPrefix, "dex"),
|
|
path.Join(constants.ChainAliasPrefix, "dexvm"),
|
|
}
|
|
chainAliases[chainID] = DChainAliases
|
|
case constants.QuantumVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"Q", "quantum", "quantumvm",
|
|
path.Join(constants.ChainAliasPrefix, "Q"),
|
|
path.Join(constants.ChainAliasPrefix, "quantum"),
|
|
}
|
|
chainAliases[chainID] = QChainAliases
|
|
case constants.AIVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"A", "ai", "aivm",
|
|
path.Join(constants.ChainAliasPrefix, "A"),
|
|
path.Join(constants.ChainAliasPrefix, "ai"),
|
|
}
|
|
chainAliases[chainID] = AChainAliases
|
|
case constants.BridgeVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"B", "bridge", "bridgevm",
|
|
path.Join(constants.ChainAliasPrefix, "B"),
|
|
path.Join(constants.ChainAliasPrefix, "bridge"),
|
|
}
|
|
chainAliases[chainID] = BChainAliases
|
|
case constants.MPCVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"M", "mpc", "mpcvm",
|
|
path.Join(constants.ChainAliasPrefix, "M"),
|
|
path.Join(constants.ChainAliasPrefix, "mpc"),
|
|
}
|
|
chainAliases[chainID] = MChainAliases
|
|
case constants.FHEVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"F", "fhe", "fhevm",
|
|
path.Join(constants.ChainAliasPrefix, "F"),
|
|
path.Join(constants.ChainAliasPrefix, "fhe"),
|
|
}
|
|
chainAliases[chainID] = FChainAliases
|
|
case constants.ZKVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"Z", "zk", "zkvm",
|
|
path.Join(constants.ChainAliasPrefix, "Z"),
|
|
path.Join(constants.ChainAliasPrefix, "zk"),
|
|
}
|
|
chainAliases[chainID] = ZChainAliases
|
|
case constants.GraphVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"G", "graph", "graphvm",
|
|
path.Join(constants.ChainAliasPrefix, "G"),
|
|
path.Join(constants.ChainAliasPrefix, "graph"),
|
|
}
|
|
chainAliases[chainID] = GChainAliases
|
|
case constants.KeyVMID:
|
|
apiAliases[endpoint] = []string{
|
|
"K", "key", "keyvm", "kms",
|
|
path.Join(constants.ChainAliasPrefix, "K"),
|
|
path.Join(constants.ChainAliasPrefix, "key"),
|
|
}
|
|
chainAliases[chainID] = KChainAliases
|
|
}
|
|
}
|
|
return apiAliases, chainAliases, nil
|
|
}
|
|
|
|
// pvmGenesisCodec constructs the ZAP-native proto/p genesis codec via
|
|
// the canonical proto/zap_codec.NewPVMGenesis constructor. proto/p
|
|
// carries no github.com/luxfi/codec import after the Wave 2A rip;
|
|
// genesis.New / Genesis.Bytes / genesis.Parse all require the
|
|
// genesis-sized codec (math.MaxInt32 budget) because the PVM genesis
|
|
// blob can be significantly larger than runtime txs — the full set of
|
|
// initial validator stake txs and CreateChainTx entries for every
|
|
// primary-network chain lives in a single blob.
|
|
//
|
|
// block.RegisterTypes pulls in the full Apricot/Banff/Durango/Quasar
|
|
// block + tx type set in the canonical wire-byte order required by
|
|
// proto/p (block.RegisterTypes is a superset of txs.RegisterTypes —
|
|
// see proto/p/block/codec.go).
|
|
//
|
|
// Wave 2G-Archive (#101): the local staging shim that previously
|
|
// mirrored proto/zap_codec's surface has been deleted; this function
|
|
// now imports the canonical constructor directly. The returned codec
|
|
// is ZAP-native little-endian — forward-only, no rollback path,
|
|
// aligned with LP-023 ZAP-native activation (ZAPActivationUnix=0).
|
|
//
|
|
// Mirrors sdk/wallet/chain/p/pcodecs.NewPVMGenesisCodec.
|
|
func pvmGenesisCodec() (pchaintxs.Codec, error) {
|
|
cm := protozapcodec.NewPVMGenesis(pchaintxs.CodecVersion)
|
|
if err := pchainblock.RegisterTypes(cm); err != nil {
|
|
return nil, err
|
|
}
|
|
return cm, nil
|
|
}
|
|
|
|
// newXVMParserCodecs constructs the ZAP-native ParserCodecs for proto/x
|
|
// via the canonical proto/zap_codec.NewXVMParser constructor.
|
|
//
|
|
// Mirrors sdk/wallet/chain/x/constants.go::newXVMParserCodecs — both
|
|
// thread the same ZAP-native zapcodec backend. proto/x carries no
|
|
// github.com/luxfi/codec import after the Wave 1A rip; the runtime
|
|
// codec is 1 MiB-bounded and the genesis codec is math.MaxInt32-
|
|
// bounded — both budgets are baked into NewXVMParser.
|
|
//
|
|
// Tx-level and fx-owned wire payload types are registered when this
|
|
// bundle is handed to xchaintxs.NewParser — see proto/x/txs/parser.go
|
|
// (fxOwnedTypes).
|
|
func newXVMParserCodecs() (xchaintxs.ParserCodecs, error) {
|
|
runtime, genesis := protozapcodec.NewXVMParser(xchaintxs.CodecVersion)
|
|
return xchaintxs.ParserCodecs{
|
|
Codec: runtime,
|
|
GenesisCodec: genesis,
|
|
Registry: runtime,
|
|
GenesisRegistry: genesis,
|
|
}, nil
|
|
}
|
|
|
|
// UTXOAssetID returns the LUX asset ID from XVM genesis bytes
|
|
func UTXOAssetID(xvmGenesisBytes []byte) (ids.ID, error) {
|
|
codecs, err := newXVMParserCodecs()
|
|
if err != nil {
|
|
return ids.Empty, err
|
|
}
|
|
parser, err := xchaintxs.NewParser(
|
|
codecs,
|
|
[]fxs.Fx{
|
|
&secp256k1fx.Fx{},
|
|
},
|
|
)
|
|
if err != nil {
|
|
return ids.Empty, err
|
|
}
|
|
|
|
genesisCodec := parser.GenesisCodec()
|
|
gen := xgenesis.Genesis{}
|
|
if _, err := genesisCodec.Unmarshal(xvmGenesisBytes, &gen); err != nil {
|
|
return ids.Empty, err
|
|
}
|
|
|
|
if len(gen.Txs) == 0 {
|
|
return ids.Empty, errNoTxs
|
|
}
|
|
genesisTx := gen.Txs[0]
|
|
|
|
tx := xchaintxs.Tx{Unsigned: &genesisTx.CreateAssetTx}
|
|
if err := tx.Initialize(genesisCodec); err != nil {
|
|
return ids.Empty, err
|
|
}
|
|
return tx.ID(), nil
|
|
}
|
|
|
|
// validateConfig validates the genesis config
|
|
func validateConfig(networkID uint32, config *genesiscfg.Config, stakingCfg *StakingConfig) error {
|
|
if config.NetworkID != networkID {
|
|
return fmt.Errorf("network ID mismatch: expected %d, got %d", networkID, config.NetworkID)
|
|
}
|
|
if config.InitialStakeDuration > uint64(stakingCfg.MaxStakeDuration.Seconds()) {
|
|
return fmt.Errorf("initial stake duration %d exceeds max %d", config.InitialStakeDuration, uint64(stakingCfg.MaxStakeDuration.Seconds()))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DevModeConfig holds configuration for dev mode genesis
|
|
type DevModeConfig struct {
|
|
NodeID ids.NodeID // The validator node ID
|
|
BLSPublicKey string // BLS public key hex
|
|
BLSPopProof string // BLS proof of possession hex
|
|
RewardAddress ids.ShortID // Reward/allocation address
|
|
CChainGenesis string // C-Chain genesis JSON
|
|
StartTime uint64 // Genesis start time (if 0, uses time.Now())
|
|
}
|
|
|
|
// ForDevMode creates a genesis configuration suitable for single-node development mode.
|
|
// It creates a single validator with far-future stake time and funds the treasury address.
|
|
func ForDevMode(cfg DevModeConfig, stakingCfg *StakingConfig) ([]byte, ids.ID, error) {
|
|
// Genesis start time: use provided time or fall back to now
|
|
startTime := cfg.StartTime
|
|
if startTime == 0 {
|
|
startTime = uint64(time.Now().Unix())
|
|
}
|
|
|
|
// Far-future stake duration: 100 years in seconds
|
|
// This ensures the validator never expires during development
|
|
const hundredYears = 100 * 365 * 24 * 60 * 60
|
|
|
|
// Create allocation for the reward address
|
|
// Initial staked amount: 1B LUX (enough to be a validator)
|
|
const oneMillionLUX = 1_000_000_000_000_000 // 1M LUX in nLUX
|
|
const oneBillionLUX = 1_000_000_000_000_000_000 // 1B LUX in nLUX
|
|
|
|
allocation := genesiscfg.Allocation{
|
|
EVMAddr: cfg.RewardAddress, // Same as UTXO addr for simplicity
|
|
UTXOAddr: cfg.RewardAddress,
|
|
InitialAmount: oneMillionLUX, // Initial unlocked amount
|
|
UnlockSchedule: []genesiscfg.LockedAmount{
|
|
{
|
|
Amount: oneBillionLUX, // Staked amount
|
|
Locktime: 0, // No lock time
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create the single staker
|
|
var signer *genesiscfg.ProofOfPossession
|
|
if cfg.BLSPublicKey != "" && cfg.BLSPopProof != "" {
|
|
signer = &genesiscfg.ProofOfPossession{
|
|
PublicKey: cfg.BLSPublicKey,
|
|
ProofOfPossession: cfg.BLSPopProof,
|
|
}
|
|
}
|
|
|
|
staker := genesiscfg.Staker{
|
|
NodeID: cfg.NodeID,
|
|
RewardAddress: cfg.RewardAddress,
|
|
DelegationFee: 1000000, // 100% delegation fee (no delegators in dev mode)
|
|
Signer: signer,
|
|
Weight: oneBillionLUX,
|
|
StartTime: startTime,
|
|
EndTime: startTime + hundredYears,
|
|
}
|
|
|
|
// Build the genesis config
|
|
config := &genesiscfg.Config{
|
|
NetworkID: constants.LocalID,
|
|
Allocations: []genesiscfg.Allocation{allocation},
|
|
StartTime: startTime,
|
|
InitialStakeDuration: hundredYears,
|
|
InitialStakeDurationOffset: 0,
|
|
InitialStakedFunds: []ids.ShortID{cfg.RewardAddress},
|
|
InitialStakers: []genesiscfg.Staker{staker},
|
|
CChainGenesis: cfg.CChainGenesis,
|
|
Message: "Lux Development Mode Genesis",
|
|
}
|
|
|
|
return FromConfig(config)
|
|
}
|
|
|
|
// splitAllocations splits allocations across multiple stakers
|
|
func splitAllocations(allocations []genesiscfg.Allocation, numSplits int) [][]genesiscfg.Allocation {
|
|
if numSplits == 0 {
|
|
return [][]genesiscfg.Allocation{}
|
|
}
|
|
|
|
totalAmount := uint64(0)
|
|
for _, a := range allocations {
|
|
for _, unlock := range a.UnlockSchedule {
|
|
totalAmount += unlock.Amount
|
|
}
|
|
}
|
|
|
|
nodeWeight := totalAmount / uint64(numSplits)
|
|
allNodeAllocations := make([][]genesiscfg.Allocation, 0, numSplits)
|
|
|
|
currentNodeAllocation := []genesiscfg.Allocation{}
|
|
currentNodeAmount := uint64(0)
|
|
|
|
for _, allocation := range allocations {
|
|
currentAllocation := allocation
|
|
currentAllocation.InitialAmount = 0
|
|
currentAllocation.UnlockSchedule = nil
|
|
|
|
for _, unlock := range allocation.UnlockSchedule {
|
|
for currentNodeAmount+unlock.Amount > nodeWeight && len(allNodeAllocations) < numSplits-1 {
|
|
amountToAdd := nodeWeight - currentNodeAmount
|
|
currentAllocation.UnlockSchedule = append(currentAllocation.UnlockSchedule, genesiscfg.LockedAmount{
|
|
Amount: amountToAdd,
|
|
Locktime: unlock.Locktime,
|
|
})
|
|
unlock.Amount -= amountToAdd
|
|
|
|
currentNodeAllocation = append(currentNodeAllocation, currentAllocation)
|
|
allNodeAllocations = append(allNodeAllocations, currentNodeAllocation)
|
|
|
|
currentNodeAllocation = nil
|
|
currentNodeAmount = 0
|
|
|
|
currentAllocation = allocation
|
|
currentAllocation.InitialAmount = 0
|
|
currentAllocation.UnlockSchedule = nil
|
|
}
|
|
|
|
if unlock.Amount == 0 {
|
|
continue
|
|
}
|
|
|
|
currentAllocation.UnlockSchedule = append(currentAllocation.UnlockSchedule, genesiscfg.LockedAmount{
|
|
Amount: unlock.Amount,
|
|
Locktime: unlock.Locktime,
|
|
})
|
|
currentNodeAmount += unlock.Amount
|
|
}
|
|
|
|
if len(currentAllocation.UnlockSchedule) > 0 {
|
|
currentNodeAllocation = append(currentNodeAllocation, currentAllocation)
|
|
}
|
|
}
|
|
|
|
return append(allNodeAllocations, currentNodeAllocation)
|
|
}
|