Files
genesis/cmd/derive100/main.go
T
Hanzo AI 4383ad4583 genesis: unify mnemonic env to LUX_MNEMONIC + kill vesting docstring rot
One env, one way. The genesis mnemonic is now read from exactly one
env var: LUX_MNEMONIC. The prior two-env fallback chain
(MNEMONIC > LIGHT_MNEMONIC) is gone. Per-env isolation is by a
DIFFERENT mnemonic per env (loaded from KMS), not by env-var name.

pkg/genesis/keys.go:
- New MnemonicEnvVar = "LUX_MNEMONIC" constant.
- getMnemonicEnv() reads only LUX_MNEMONIC; no fallback.
- LightMnemonic constant kept (it's the well-known dev seed VALUE;
  pass it as the value of LUX_MNEMONIC for local nets).
- IsProductionNetwork docstring updated to spell out the canonical 4:
  mainnet(1) / testnet(2) / devnet(3) refuse public mnemonics;
  local(1337)+ allows LightMnemonic.
- Docstring rot purged: the prior "50M free + 50M vesting at 1%/year
  over 100y" claim was a lie — buildConfigFromKeyInfos never wired the
  100-period schedule (the code admits the schedule "overflows zapdb
  batch limit"). Replaced with the truth: 1000 × 50M LUX on X-Chain
  AND P-Chain, immediate spend; C-Chain is treasury-only (2T LUX).
- Dead code deleted: StakingStartTime, UnlockInterval consts,
  buildUnlockSchedule() function.

pkg/genesis/allocations.go:
- VestingConfig struct removed.
- DefaultVesting() removed.
- WithVesting() builder method removed.
- Vesting branches in PChain() / PChainMap() collapsed to immediate-only.
- MainnetAllocations no longer calls WithVesting(DefaultVesting()).

pkg/genesis/validator_keys.go:
- VestingPeriods const removed.
- GeneratePChainAllocationsWithVesting() removed.

pkg/genesis/networks.go (new):
- Canonical primary-network-id table: MainnetID / TestnetID / DevnetID
  / LocalID = 1 / 2 / 3 / 1337. Matching C-Chain (EVM) chain IDs:
  96369 / 96368 / 96370 / 31337.
- IsCanonicalPrimaryNetwork helper.

cmd/checkkeys/main.go:
- Reads LUX_MNEMONIC via genesis.MnemonicEnvVar (no fallback).

cmd/derive100/main.go:
- Usage hint updated: mnemonic-env: LUX_MNEMONIC.

cmd/genesis/main.go:
- Flag help, env-var docs, examples, and error messages all reference
  LUX_MNEMONIC.

pkg/genesis/light_mnemonic_guard_test.go:
- All t.Setenv calls use LUX_MNEMONIC.
- Test labels reference LightMnemonic (the constant) not the dead env name.

configs/getgenesis_test.go:
- Comment updated: LightMnemonic instead of LIGHT_MNEMONIC.

CHANGELOG.md + LLM.md:
- Documented the unification + the dead-code removal.

The validator stake-lock (3-entry 5y/10y/20y schedule attached to the
validator's UTXO inside buildConfigFromKeyInfos at keys.go:1077-1083)
is unchanged — that locked-stake bucket is the only UnlockSchedule in
the canonical genesis path and the ProtocolVM needs it.

External-caller sweep: grepped ~/work/lux, ~/work/hanzo, ~/work/zoo,
~/work/luxfi for genesis.VestingConfig / genesis.DefaultVesting /
genesis.GeneratePChainAllocationsWithVesting / genesis.StakingStartTime
/ genesis.UnlockInterval — zero hits. Safe to delete. (luxfi/keys at
~/work/lux/keys has its own MainnetAllocations copy — separate repo,
not affected.)

Verification:
- go build ./... → clean
- go vet ./... → clean
- go test ./... -count=1 → ok configs, ok pkg/genesis
- grep '[^_X]MNEMONIC\|LIGHT_MNEMONIC' under pkg/genesis cmd configs →
  zero matches (only CHANGELOG history + Python script local vars).
- grep VestingConfig|buildUnlockSchedule|GeneratePChainAllocationsWithVesting
  under source → zero matches.
2026-06-02 22:21:27 -07:00

183 lines
4.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// derive100 derives 100 P-chain addresses from a mnemonic across testnet/mainnet/devnet
// HRPs, then probes platform.getBalance over JSON-RPC for each to find which idx is funded.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/go-bip32"
"github.com/luxfi/go-bip39"
"github.com/luxfi/ids"
"github.com/luxfi/node/utils/formatting/address"
)
const numAccounts = 100
type rpcReq struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params map[string]interface{} `json:"params"`
}
type rpcResp struct {
Result struct {
Balance string `json:"balance"`
Unlocked string `json:"unlocked"`
LockedStakeable string `json:"lockedStakeable"`
LockedNotStakeable string `json:"lockedNotStakeable"`
UTXOIDs []string `json:"utxoIDs"`
} `json:"result"`
Error *struct {
Message string `json:"message"`
} `json:"error"`
}
func formatPAddr(hrp string, addr ids.ShortID) string {
b32, err := address.FormatBech32(hrp, addr[:])
if err != nil {
return ""
}
return "P-" + b32
}
func deriveAddrs(mnemonic, hrp string) []string {
if !bip39.IsMnemonicValid(mnemonic) {
fmt.Fprintln(os.Stderr, "invalid mnemonic")
os.Exit(1)
}
seed := bip39.NewSeed(mnemonic, "")
master, err := bip32.NewMasterKey(seed)
if err != nil {
panic(err)
}
purpose, _ := master.NewChildKey(bip32.FirstHardenedChild + 44)
coinType, _ := purpose.NewChildKey(bip32.FirstHardenedChild + 9000)
account, _ := coinType.NewChildKey(bip32.FirstHardenedChild + 0)
change, _ := account.NewChildKey(0)
out := make([]string, numAccounts)
for i := 0; i < numAccounts; i++ {
child, err := change.NewChildKey(uint32(i))
if err != nil {
panic(err)
}
priv, err := secp256k1.ToPrivateKey(child.Key)
if err != nil {
panic(err)
}
out[i] = formatPAddr(hrp, priv.Address())
}
return out
}
func getBalance(rpc, paddr string) (uint64, string, error) {
req := rpcReq{
JSONRPC: "2.0",
ID: 1,
Method: "platform.getBalance",
Params: map[string]interface{}{
"addresses": []string{paddr},
},
}
body, _ := json.Marshal(req)
httpReq, err := http.NewRequest("POST", rpc+"/ext/bc/P", bytes.NewReader(body))
if err != nil {
return 0, "", err
}
httpReq.Header.Set("Content-Type", "application/json")
cli := &http.Client{Timeout: 10 * time.Second}
resp, err := cli.Do(httpReq)
if err != nil {
return 0, "", err
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
var r rpcResp
if err := json.Unmarshal(raw, &r); err != nil {
return 0, string(raw), err
}
if r.Error != nil {
return 0, r.Error.Message, fmt.Errorf("rpc error")
}
bal, _ := parseUint64(r.Result.Balance)
return bal, r.Result.Balance, nil
}
func parseUint64(s string) (uint64, error) {
var x uint64
for _, c := range s {
if c < '0' || c > '9' {
return 0, fmt.Errorf("non-digit")
}
x = x*10 + uint64(c-'0')
}
return x, nil
}
func main() {
if len(os.Args) < 4 {
fmt.Fprintln(os.Stderr, "usage: derive100 <hrp> <rpc> <mnemonic-env>")
fmt.Fprintln(os.Stderr, " hrp: test|lux|dev|local")
fmt.Fprintln(os.Stderr, " rpc: http://24.199.71.151:9650")
fmt.Fprintln(os.Stderr, " mnemonic-env: LUX_MNEMONIC")
os.Exit(1)
}
hrp := os.Args[1]
rpc := os.Args[2]
envName := os.Args[3]
mnemonic := strings.TrimSpace(os.Getenv(envName))
if mnemonic == "" {
fmt.Fprintf(os.Stderr, "%s not set\n", envName)
os.Exit(1)
}
addrs := deriveAddrs(mnemonic, hrp)
fmt.Printf("hrp=%s rpc=%s mnemonic=%s\n", hrp, rpc, envName)
fmt.Printf("idx0 = %s\n", addrs[0])
fmt.Printf("idx5 = %s\n", addrs[5])
type hit struct {
idx int
bal uint64
raw string
}
hits := []hit{}
for i, a := range addrs {
bal, raw, err := getBalance(rpc, a)
if err != nil {
fmt.Printf("idx %3d %s ERR: %v %s\n", i, a, err, raw)
continue
}
if bal > 0 {
hits = append(hits, hit{i, bal, raw})
fmt.Printf("idx %3d %s balance=%d nLUX (%s LUX)\n", i, a, bal, formatLUX(bal))
}
}
fmt.Printf("\nFUNDED IDXES (%d): ", len(hits))
for _, h := range hits {
fmt.Printf("%d ", h.idx)
}
fmt.Println()
}
func formatLUX(nLUX uint64) string {
// On P-chain platform.getBalance returns nLUX (1 LUX = 1e9 nLUX)
whole := nLUX / 1_000_000_000
frac := nLUX % 1_000_000_000
if frac == 0 {
return fmt.Sprintf("%d", whole)
}
return fmt.Sprintf("%d.%09d", whole, frac)
}