mirror of
https://github.com/luxfi/genesis.git
synced 2026-07-27 04:11:41 +00:00
fix(mainnet): repair P/X genesis supply overshoot (50T -> 50B, 2T cap)
Mainnet primary-network genesis funded 100 P-Chain allocations of 5e17
base units each. Those are stale 9-decimal magnitudes read under the
current 6-decimal LUX unit (units.Lux = 1e6 microLUX), so each was
mis-read as 500B LUX -> P-total 50T = 25x the 2T supply cap, mirrored
on X-Chain. The sum (5e19 base units) also overflows uint64 and wraps,
which is how live mainnet showed ~13.27T P-supply.
Replace the mainnet allocation set with the canonical uniform set that
testnet/devnet already use: 1000 BIP44 wallet keys (m/44'/9000'/0'/0/i,
coin_type 9000) x 50M LUX, re-encoded for mainnet's `lux` HRP via the
canonical address.Format path (NOT hand-rolled bech32). P-total now
50.0B LUX, 40x under the 2T cap.
Authoritative source is the split shard pchain.json (configs.GetGenesis
assembles from network.json + pchain.json + {x,c,...}chain.json shards;
the combined genesis.json is a derived human-facing snapshot). Both are
updated; the combined snapshot is regenerated from the corrected shards
so it faithfully matches what luxd builds (this also un-stales its
cChainGenesis blob, which lagged the cchain.json shard).
Correctness proof:
- generator reproduces devnet's 1000 funded allocations byte-identically
- mainnet regenerated evmAddrs == testnet's 1000 canonical funded
evmAddrs (same mnemonic -> same keys; evmAddr is HRP-independent)
- utxoAddr differs from testnet only by HRP (lux1.. vs test1..);
regenerated lux-HRP addresses validated against the live testnet file
- all 5 initialStakedFunds / initialStakers reward addresses (BIP44
keys 0-4) remain funded -> luxd staked-funds invariant holds
- initialStakers (NodeIDs + BLS PoP) preserved verbatim -> live
validator identity unchanged
Preserved byte-identical: cChainGenesis (2T treasury @
0x9011E888..4714, chainId 96369, immutable), all other chain shards,
networkID 1, startTime, initialStakers, initialStakedFunds.
X-Chain descriptor denomination 9 -> 6 (xchain.json + genesis.json).
denomination is the LUX asset decimal count and is serialized into the
asset definition (feeds AssetIDFromBytes) -> consensus-relevant. The
canonical unit is 6 decimals; the stale 9 is corrected for mainnet
ahead of its fresh re-genesis. testnet/devnet/localnet left unchanged.
Reconciliation: 2T C-Chain treasury + 100B (P+X user accounts) = 2.1T
at genesis, reduced to 2T max by the 2025-12-25 burn fork (not
implemented here; arithmetic is the intended model).
Tooling (reusable, used to produce this fix):
- cmd/genalloc: emit canonical BIP44 allocations for one network,
scoped to the allocations array only (never touches cChainGenesis)
- cmd/assemblegenesis: regenerate the combined genesis.json snapshot
from the embedded shards via the same GetGenesis path luxd uses
Staging only; mainnet re-genesis relaunch is gated separately by the
owner. testnet/devnet/localnet untouched.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// Copyright (C) 2019-2025, Lux Partners Limited. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Command assemblegenesis writes the canonical combined genesis.json for a
|
||||
// network by assembling it from the embedded split shards (network.json +
|
||||
// pchain.json + {x,c,d,q,a,b,t,z,g,k}chain.json) via the exact same
|
||||
// configs.GetGenesis path luxd uses at boot. The combined genesis.json is a
|
||||
// human-facing snapshot; the shards are the source of truth. Run this after
|
||||
// editing any shard so the snapshot stays faithful to what the node builds.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// assemblegenesis -network-id 1 -output configs/mainnet/genesis.json
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/luxfi/genesis/configs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
networkID := flag.Uint("network-id", 0, "Network ID (1=mainnet, 2=testnet, 3=devnet, 1337=local)")
|
||||
output := flag.String("output", "", "Output file path (default: stdout)")
|
||||
flag.Parse()
|
||||
|
||||
if *networkID == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Error: -network-id is required")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// GetGenesis assembles from the embedded shards exactly as luxd does.
|
||||
data, err := configs.GetGenesis(uint32(*networkID))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error assembling genesis: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Pretty-print for the human-facing snapshot (2-space indent, matching
|
||||
// the existing committed shape). Parsing is format-agnostic, so indent
|
||||
// choice is cosmetic — but consistency keeps diffs readable.
|
||||
var pretty bytes.Buffer
|
||||
if err := json.Indent(&pretty, data, "", " "); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error pretty-printing genesis: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pretty.WriteByte('\n')
|
||||
|
||||
if *output == "" {
|
||||
os.Stdout.Write(pretty.Bytes())
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(*output, pretty.Bytes(), 0o644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", *output, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Wrote combined genesis for networkID %d to %s (%d bytes)\n",
|
||||
*networkID, *output, pretty.Len())
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (C) 2019-2025, Lux Partners Limited. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Command genalloc emits the canonical BIP44 wallet allocations array for a
|
||||
// single network, in the exact AllocationJSON shape the primary-network
|
||||
// genesis files use (initialAmount + a single locktime-0 unlockSchedule
|
||||
// entry, utxoAddr in the network's bech32 HRP, evmAddr in 0x H160 form).
|
||||
//
|
||||
// It is deliberately scoped to ONLY the allocations array: it does not
|
||||
// touch cChainGenesis or any other chain blob. Splice its output into an
|
||||
// existing genesis.json to repair/replace the funded user-account set
|
||||
// without disturbing the immutable C-Chain treasury or validator config.
|
||||
//
|
||||
// The derivation is the canonical BIP44 path m/44'/9000'/0'/0/i
|
||||
// (LoadBIP44WalletKeysFromMnemonic) — the same keys every Lux web wallet,
|
||||
// `lux key derive`, and the testnet/devnet genesis already fund. The
|
||||
// network HRP comes from constants.GetHRP(networkID); the 20-byte UTXO and
|
||||
// EVM payloads are HRP-independent, so the same mnemonic yields identical
|
||||
// evmAddrs across all networks (only the utxoAddr HRP differs).
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// LUX_MNEMONIC="$(cat /path/to/mnemonic)" \
|
||||
// genalloc -network-id 1 -keys 1000 -amount 50000000 > alloc.json
|
||||
//
|
||||
// -network-id Network ID (1=mainnet/lux, 2=testnet/test, 3=devnet/dev, 1337=local)
|
||||
// -keys Number of canonical BIP44 wallet keys to fund
|
||||
// -amount Allocation per key in WHOLE LUX (scaled by 1e6 microLUX internally)
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/genesis/pkg/genesis"
|
||||
)
|
||||
|
||||
func main() {
|
||||
networkID := flag.Uint("network-id", 0, "Network ID (1=mainnet, 2=testnet, 3=devnet, 1337=local)")
|
||||
numKeys := flag.Int("keys", 1000, "Number of canonical BIP44 wallet keys to fund")
|
||||
amountLUX := flag.Uint64("amount", 50_000_000, "Allocation per key in WHOLE LUX")
|
||||
flag.Parse()
|
||||
|
||||
if *networkID == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Error: -network-id is required (1=mainnet, 2=testnet, 3=devnet, 1337=local)")
|
||||
os.Exit(2)
|
||||
}
|
||||
if *numKeys <= 0 {
|
||||
fmt.Fprintln(os.Stderr, "Error: -keys must be > 0")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
mnemonic := os.Getenv(genesis.MnemonicEnvVar)
|
||||
if mnemonic == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s env var must be set\n", genesis.MnemonicEnvVar)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Derive the canonical BIP44 wallet keys (m/44'/9000'/0'/0/i). This is
|
||||
// the same call BuildBIP44WalletAllocations uses internally, surfaced as
|
||||
// KeyInfo so we can feed the canonical ChainAllocations.PChain() path.
|
||||
keys, err := genesis.LoadBIP44WalletKeysFromMnemonic(mnemonic, *numKeys)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error deriving BIP44 wallet keys: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// The 20-byte UTXO/EVM payloads are HRP-independent; the network HRP
|
||||
// only changes how the utxoAddr is bech32-formatted. constants.GetHRP
|
||||
// is the single source of truth (mainnet->"lux", testnet->"test", ...).
|
||||
hrp := constants.GetHRP(uint32(*networkID))
|
||||
|
||||
// Convert KeyInfo -> ValidatorKeyInfo so we can reuse the canonical
|
||||
// PChain() allocation shaper (initialAmount + one locktime-0 unlock),
|
||||
// which is exactly what the existing genesis files carry.
|
||||
vkeys := make([]genesis.ValidatorKeyInfo, len(keys))
|
||||
for i, k := range keys {
|
||||
vkeys[i] = genesis.ValidatorKeyInfo{
|
||||
EVMAddr: fmt.Sprintf("0x%s", k.EVMAddr.Hex()),
|
||||
ShortID: k.StakingAddr,
|
||||
}
|
||||
}
|
||||
|
||||
// 50_000_000 whole LUX * 1e6 microLUX = 5e13 base units per key — the
|
||||
// canonical DefaultAllocationPerAccount magnitude under the 6-decimal unit.
|
||||
amount := *amountLUX * genesis.Lux
|
||||
allocs, err := genesis.NewAllocations(vkeys, hrp).WithAmount(amount).PChain()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error building P-chain allocations: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(allocs, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error marshaling allocations: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
out = append(out, '\n')
|
||||
if _, err := os.Stdout.Write(out); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing output: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Emitted %d BIP44 allocations for networkID %d (hrp %q), %d LUX each\n",
|
||||
len(allocs), *networkID, hrp, *amountLUX)
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func TestGetGenesis_CChainShardPresentEmbedsCChainGenesis(t *testing.T) {
|
||||
// TestGetGenesis_XChainShardPresentEmbedsXChainGenesis is the X-Chain
|
||||
// equivalent of the C-Chain test above. Every Lux network ships an
|
||||
// xchain.json shard (the small asset descriptor
|
||||
// `{"symbol":"LUX","name":"Lux","denomination":9}`); the loader must
|
||||
// `{"symbol":"LUX","name":"Lux","denomination":N}`); the loader must
|
||||
// thread it into the marshalled primary genesis JSON as the
|
||||
// xChainGenesis field, where the builder later parses it to construct
|
||||
// the XVM genesis and CreateChainTx.
|
||||
@@ -53,7 +53,20 @@ func TestGetGenesis_CChainShardPresentEmbedsCChainGenesis(t *testing.T) {
|
||||
// hardcoded `Symbol: "LUX", Name: "Lux", Denomination: 9` literal in
|
||||
// builder.FromConfig is now sourced from the shard, so a P-only
|
||||
// network (P-only shape) can opt out by simply omitting the file.
|
||||
//
|
||||
// Denomination is the X-Chain LUX asset's decimal count and is
|
||||
// serialized into the asset definition (it feeds AssetIDFromBytes), so
|
||||
// it is consensus-relevant, not cosmetic. The canonical LUX unit is
|
||||
// 6 decimals (units.Lux = 1e6 microLUX), so mainnet pins denomination
|
||||
// 6. testnet/localnet still carry the legacy 9 pending their own
|
||||
// re-genesis; this test locks the per-network value so any further
|
||||
// drift is caught.
|
||||
func TestGetGenesis_XChainShardPresentEmbedsXChainGenesis(t *testing.T) {
|
||||
wantDenomination := map[string]byte{
|
||||
"mainnet": 6, // 6-decimal LUX unit (units.Lux = 1e6 microLUX)
|
||||
"testnet": 9, // legacy; unchanged pending testnet re-genesis
|
||||
"localnet": 9, // legacy; unchanged pending localnet re-genesis
|
||||
}
|
||||
for _, name := range []string{"mainnet", "testnet", "localnet"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
data, err := GetGenesis(networkIDFromName(t, name))
|
||||
@@ -77,8 +90,9 @@ func TestGetGenesis_XChainShardPresentEmbedsXChainGenesis(t *testing.T) {
|
||||
if err := json.Unmarshal([]byte(got), &asset); err != nil {
|
||||
t.Fatalf("%s: xChainGenesis shard unparseable: %v", name, err)
|
||||
}
|
||||
if asset.Symbol != "LUX" || asset.Name != "Lux" || asset.Denomination != 9 {
|
||||
t.Fatalf("%s: xChainGenesis shard drift: got %+v want {LUX Lux 9}", name, asset)
|
||||
wantDenom := wantDenomination[name]
|
||||
if asset.Symbol != "LUX" || asset.Name != "Lux" || asset.Denomination != wantDenom {
|
||||
t.Fatalf("%s: xChainGenesis shard drift: got %+v want {LUX Lux %d}", name, asset, wantDenom)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+10327
-40027
File diff suppressed because it is too large
Load Diff
+10301
-40001
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
{"symbol":"LUX","name":"Lux","denomination":9}
|
||||
{"symbol":"LUX","name":"Lux","denomination":6}
|
||||
|
||||
Reference in New Issue
Block a user