mirror of
https://github.com/luxfi/netrunner.git
synced 2026-07-27 00:04:23 +00:00
Update logging, examples
This commit is contained in:
+174
-19
@@ -9,12 +9,15 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/genesis/pkg/genesis"
|
||||
"github.com/luxfi/keys"
|
||||
"github.com/luxfi/node/vms/platformvm/reward"
|
||||
|
||||
"github.com/luxfi/node/vms/components/lux"
|
||||
@@ -97,7 +100,7 @@ func (ln *localNetwork) getNode() node.Node {
|
||||
// get node client URI for an arbitrary node in the network
|
||||
func (ln *localNetwork) getClientURI() (string, error) { //nolint
|
||||
node := ln.getNode()
|
||||
clientURI := fmt.Sprintf("http://%s:%d", node.GetURL(), node.GetAPIPort())
|
||||
clientURI := node.GetURL() // GetURL now returns full URL http://host:port
|
||||
ln.log.Info("getClientURI",
|
||||
"nodeName", node.GetName(),
|
||||
"uri", clientURI)
|
||||
@@ -233,7 +236,7 @@ func (ln *localNetwork) waitForChainsDiscoveredOnAllNodes(
|
||||
fmt.Println()
|
||||
ln.log.Info(luxlog.Blue.Wrap(luxlog.Bold.Wrap("waiting for chains to be discovered on all nodes...")))
|
||||
|
||||
maxWait := 60 * time.Second
|
||||
maxWait := 2 * time.Minute
|
||||
pollInterval := 2 * time.Second
|
||||
deadline := time.Now().Add(maxWait)
|
||||
|
||||
@@ -250,9 +253,11 @@ func (ln *localNetwork) waitForChainsDiscoveredOnAllNodes(
|
||||
|
||||
discovered := false
|
||||
for time.Now().Before(deadline) {
|
||||
// Try to query the chain RPC endpoint - if it responds, the chain is discovered
|
||||
// Try to query the chain RPC endpoint with eth_chainId - if it responds, the chain is discovered
|
||||
chainRPCURL := fmt.Sprintf("%s/ext/bc/%s/rpc", node.GetURL(), blockchainID)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", chainRPCURL, nil)
|
||||
// Use a proper JSON-RPC request body to ensure the EVM endpoint responds
|
||||
jsonRPCBody := strings.NewReader(`{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}`)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", chainRPCURL, jsonRPCBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
@@ -261,21 +266,34 @@ func (ln *localNetwork) waitForChainsDiscoveredOnAllNodes(
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err == nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
// Any response (even error) means the chain endpoint exists
|
||||
discovered = true
|
||||
ln.log.Debug("chain discovered on node",
|
||||
// Check if we got a valid response (not a 404 or other error)
|
||||
if resp.StatusCode == 200 {
|
||||
discovered = true
|
||||
ln.log.Debug("chain discovered on node",
|
||||
"node", nodeName,
|
||||
"blockchain-id", blockchainID,
|
||||
"status", resp.StatusCode,
|
||||
"response", string(body),
|
||||
)
|
||||
break
|
||||
}
|
||||
ln.log.Debug("chain endpoint returned non-200",
|
||||
"node", nodeName,
|
||||
"blockchain-id", blockchainID,
|
||||
"status", resp.StatusCode,
|
||||
"response", string(body),
|
||||
)
|
||||
} else {
|
||||
ln.log.Debug("chain not yet discovered, waiting",
|
||||
"node", nodeName,
|
||||
"blockchain-id", blockchainID,
|
||||
"error", err.Error(),
|
||||
"url", chainRPCURL,
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
ln.log.Debug("chain not yet discovered, waiting",
|
||||
"node", nodeName,
|
||||
"blockchain-id", blockchainID,
|
||||
)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
@@ -446,6 +464,15 @@ func (ln *localNetwork) installCustomChains(
|
||||
return nil, fmt.Errorf("failed to add primary validators: %w", err)
|
||||
}
|
||||
|
||||
// Ensure P-chain has liquid funds for chain creation
|
||||
// Genesis allocations go to X-chain, so we may need to export/import to P-chain
|
||||
if err := fundPChainFromXChain(ctx, w, ln.log); err != nil {
|
||||
ln.log.Error("installCustomChains: failed to fund P-chain from X-chain",
|
||||
"error", err.Error(),
|
||||
)
|
||||
return nil, fmt.Errorf("failed to fund P-chain from X-chain: %w", err)
|
||||
}
|
||||
|
||||
// create missing chains
|
||||
chainIDs, err := createChains(ctx, uint32(len(participantsSpecs)), w, ln.log)
|
||||
if err != nil {
|
||||
@@ -864,17 +891,58 @@ type wallet struct {
|
||||
}
|
||||
|
||||
// getDefaultKey loads the first key from ~/.lux/keys for wallet operations.
|
||||
// Keys are loaded from disk, never hardcoded in source.
|
||||
// Priority: LUX_MNEMONIC > LUX_PRIVATE_KEY > disk keys
|
||||
func getDefaultKey() (*secp256k1.PrivateKey, error) {
|
||||
keys, err := LoadOrGenerateKeys("", 1)
|
||||
// If LUX_MNEMONIC is set, derive key from mnemonic (index 0)
|
||||
if mnemonic := os.Getenv("LUX_MNEMONIC"); mnemonic != "" {
|
||||
fmt.Printf("🔑 getDefaultKey: Using LUX_MNEMONIC (len=%d)\n", len(mnemonic))
|
||||
vk, err := keys.DeriveValidatorFromMnemonic(mnemonic, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive key from mnemonic: %w", err)
|
||||
}
|
||||
fmt.Printf("🔑 VK.PChainAddr (from mnemonic derivation): %s\n", vk.PChainAddr.String())
|
||||
|
||||
privKey, err := secp256k1.ToPrivateKey(vk.ECPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKey := privKey.PublicKey()
|
||||
walletAddr := ids.ShortID(pubKey.Address())
|
||||
fmt.Printf("🔑 Wallet address (from secp256k1 key): %s\n", walletAddr.String())
|
||||
fmt.Printf("🔑 Addresses match: %v\n", vk.PChainAddr == walletAddr)
|
||||
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
// If LUX_PRIVATE_KEY is set, use it directly (hex encoded, 64 chars = 32 bytes)
|
||||
if privKeyHex := os.Getenv("LUX_PRIVATE_KEY"); privKeyHex != "" {
|
||||
fmt.Printf("🔑 getDefaultKey: Using LUX_PRIVATE_KEY (len=%d): %s...\n", len(privKeyHex), privKeyHex[:16])
|
||||
privKeyBytes, err := hex.DecodeString(privKeyHex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode LUX_PRIVATE_KEY: %w", err)
|
||||
}
|
||||
privKey, err := secp256k1.ToPrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKey := privKey.PublicKey()
|
||||
addr := ids.ShortID(pubKey.Address())
|
||||
fmt.Printf("🔑 getDefaultKey: Derived address ShortID: %s\n", addr.String())
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
// Fall back to loading from disk
|
||||
fmt.Printf("🔑 getDefaultKey: Falling back to disk keys\n")
|
||||
loadedKeys, err := LoadOrGenerateKeys("", 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load keys from ~/.lux/keys: %w", err)
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
if len(loadedKeys) == 0 {
|
||||
return nil, errors.New("no keys found in ~/.lux/keys")
|
||||
}
|
||||
fmt.Printf("🔑 getDefaultKey: Loaded key from disk: %s...\n", loadedKeys[0].PrivKeyHex[:16])
|
||||
// Convert hex to private key
|
||||
privKeyBytes, err := hex.DecodeString(keys[0].PrivKeyHex)
|
||||
privKeyBytes, err := hex.DecodeString(loadedKeys[0].PrivKeyHex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid key format: %w", err)
|
||||
}
|
||||
@@ -892,6 +960,10 @@ func newWallet(
|
||||
return nil, fmt.Errorf("failed to get wallet key: %w", err)
|
||||
}
|
||||
kc := secp256k1fx.NewKeychain(privKey)
|
||||
|
||||
// Debug: print addresses being queried
|
||||
fmt.Printf("🔍 Wallet querying for addresses: %v\n", kc.Addrs)
|
||||
|
||||
// Use dedicated timeout context for FetchState to avoid parent context cancellation propagation
|
||||
fetchCtx, fetchCancel := createDefaultCtx(ctx)
|
||||
luxState, err := primary.FetchState(fetchCtx, uri, kc.Addrs)
|
||||
@@ -899,6 +971,16 @@ func newWallet(
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch state from %s: %w", uri, err)
|
||||
}
|
||||
|
||||
// Debug: print network context and UTXOs
|
||||
xChainIDForDebug := luxState.XCTX.BlockchainID
|
||||
fmt.Printf("🔍 Network ID: %d, X-chain ID: %s\n", luxState.XCTX.NetworkID, xChainIDForDebug)
|
||||
fmt.Printf("🔍 LUX Asset ID: %s\n", luxState.XCTX.XAssetID)
|
||||
xUtxos, _ := luxState.UTXOs.UTXOs(ctx, xChainIDForDebug, xChainIDForDebug)
|
||||
fmt.Printf("🔍 Fetched %d X-chain UTXOs\n", len(xUtxos))
|
||||
for i, utxo := range xUtxos {
|
||||
fmt.Printf(" UTXO[%d]: ID=%s, AssetID=%s\n", i, utxo.UTXOID.TxID, utxo.AssetID())
|
||||
}
|
||||
pClient := platformvm.NewClient(uri)
|
||||
pTXs := make(map[ids.ID]*txs.Tx)
|
||||
for _, id := range preloadTXs {
|
||||
@@ -967,8 +1049,14 @@ func (ln *localNetwork) addPrimaryValidators(
|
||||
for _, v := range vdrs {
|
||||
curValidators.Add(v.NodeID)
|
||||
}
|
||||
// Debug: log current validators from genesis
|
||||
fmt.Printf("🔍 GetCurrentValidators returned %d validators:\n", len(vdrs))
|
||||
for _, v := range vdrs {
|
||||
fmt.Printf(" - %s (weight: %d)\n", v.NodeID.String(), v.Weight)
|
||||
}
|
||||
for nodeName, node := range ln.nodes {
|
||||
nodeID := node.GetNodeID()
|
||||
fmt.Printf("🔍 Checking node %s (ID: %s) - in curValidators: %v\n", nodeName, nodeID.String(), curValidators.Contains(nodeID))
|
||||
|
||||
if curValidators.Contains(nodeID) {
|
||||
continue
|
||||
@@ -1092,6 +1180,73 @@ func importPChainFromXChain(ctx context.Context, w *wallet, owner *secp256k1fx.O
|
||||
return err
|
||||
}
|
||||
|
||||
// fundPChainFromXChain ensures the wallet has liquid P-chain funds for chain creation.
|
||||
// With direct P-chain genesis allocations, this may not need to do anything.
|
||||
// Falls back to X->P export/import if P-chain has insufficient funds.
|
||||
func fundPChainFromXChain(ctx context.Context, w *wallet, log luxlog.Logger) error {
|
||||
// Amount needed for chain operations (1 LUX should be plenty for fees)
|
||||
const requiredAmount = uint64(1_000_000_000) // 1 LUX in nLUX
|
||||
|
||||
// Debug: print wallet address for verification against genesis
|
||||
fmt.Printf("🔍 DEBUG: Wallet address (short): %s\n", w.addr.String())
|
||||
fmt.Printf("🔍 DEBUG: Wallet P-chain address: P-lux1%s (bech32 would differ)\n", w.addr.String())
|
||||
fmt.Printf("🔍 DEBUG: LUX asset ID: %s\n", w.luxAssetID.String())
|
||||
|
||||
// First check if P-chain already has sufficient funds
|
||||
balances, err := w.pBuilder.GetBalance()
|
||||
if err != nil {
|
||||
log.Warn("failed to check P-chain balance, will try X->P transfer", "error", err.Error())
|
||||
fmt.Printf("⚠️ DEBUG: P-chain balance check failed: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("🔍 DEBUG: P-chain balances map has %d entries\n", len(balances))
|
||||
for assetID, bal := range balances {
|
||||
fmt.Printf(" - Asset %s: %d nLUX\n", assetID.String(), bal)
|
||||
}
|
||||
pBalance := balances[w.luxAssetID]
|
||||
log.Info("P-chain balance check", "balance", pBalance, "required", requiredAmount)
|
||||
fmt.Printf("💰 P-chain balance: %d nLUX (need %d)\n", pBalance, requiredAmount)
|
||||
if pBalance >= requiredAmount {
|
||||
log.Info(luxlog.Green.Wrap("P-chain already has sufficient funds, skipping X->P transfer"))
|
||||
fmt.Printf("✅ P-chain already funded, no transfer needed\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Also check X-chain balance for debugging
|
||||
xBalances, xErr := w.xWallet.Builder().GetFTBalance()
|
||||
if xErr != nil {
|
||||
fmt.Printf("⚠️ DEBUG: X-chain balance check failed: %v\n", xErr)
|
||||
} else {
|
||||
fmt.Printf("🔍 DEBUG: X-chain balances:\n")
|
||||
for assetID, bal := range xBalances {
|
||||
fmt.Printf(" - Asset %s: %d nLUX\n", assetID.String(), bal)
|
||||
}
|
||||
}
|
||||
|
||||
// P-chain doesn't have enough, try to export from X-chain
|
||||
owner := &secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{w.addr},
|
||||
}
|
||||
|
||||
log.Info(luxlog.Green.Wrap("funding P-chain from X-chain for chain creation"))
|
||||
fmt.Printf("💰 Exporting %d nLUX from X-chain to P-chain for address %s\n", requiredAmount, w.addr.String())
|
||||
|
||||
// Export LUX from X-chain to P-chain
|
||||
if err := exportXChainToPChain(ctx, w, owner, w.luxAssetID, requiredAmount); err != nil {
|
||||
return fmt.Errorf("failed to export from X-chain: %w", err)
|
||||
}
|
||||
fmt.Printf("✅ Export from X-chain completed\n")
|
||||
|
||||
// Import on P-chain
|
||||
if err := importPChainFromXChain(ctx, w, owner, w.xChainID); err != nil {
|
||||
return fmt.Errorf("failed to import on P-chain: %w", err)
|
||||
}
|
||||
fmt.Printf("✅ Import on P-chain completed, wallet funded\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ln *localNetwork) removeChainValidators(
|
||||
ctx context.Context,
|
||||
removeParticipantsSpecs []network.RemoveChainValidatorSpec,
|
||||
@@ -1591,7 +1746,7 @@ func (ln *localNetwork) reloadVMPlugins(ctx context.Context) error {
|
||||
if node.paused {
|
||||
continue
|
||||
}
|
||||
uri := fmt.Sprintf("http://%s:%d", node.GetURL(), node.GetAPIPort())
|
||||
uri := node.GetURL()
|
||||
adminCli := admin.NewClient(uri)
|
||||
// Use different variable name to avoid shadowing outer context
|
||||
apiCtx, cancel := createDefaultCtx(ctx)
|
||||
@@ -1654,7 +1809,7 @@ func (ln *localNetwork) verifyVMsAvailable(ctx context.Context, vmIDs []ids.ID)
|
||||
if node.paused {
|
||||
continue
|
||||
}
|
||||
uri := fmt.Sprintf("http://%s:%d", node.GetURL(), node.GetAPIPort())
|
||||
uri := node.GetURL()
|
||||
adminCli := admin.NewClient(uri)
|
||||
|
||||
for _, vmID := range vmIDs {
|
||||
|
||||
+313
-141
@@ -4,7 +4,6 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/crypto/bls/signer/localsigner"
|
||||
luxcrypto "github.com/luxfi/crypto/secp256k1"
|
||||
"github.com/luxfi/genesis/configs"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/keys"
|
||||
@@ -23,18 +23,17 @@ import (
|
||||
"github.com/luxfi/node/config"
|
||||
"github.com/luxfi/node/staking"
|
||||
"github.com/luxfi/node/utils/formatting/address"
|
||||
"github.com/luxfi/node/vms/platformvm/signer"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// NewConfigForNetwork creates a network config for the specified network ID.
|
||||
// This uses the proper genesis configuration from github.com/luxfi/genesis/configs.
|
||||
// For non-local networks (mainnet/testnet), it dynamically injects initial stakers
|
||||
// based on the generated node staking keys.
|
||||
//
|
||||
// When LUX_MNEMONIC environment variable is set, validator keys are derived
|
||||
// deterministically from the mnemonic, ensuring consistent node identities
|
||||
// and P-Chain allocations across network restarts.
|
||||
// For mainnet/testnet, this function:
|
||||
// 1. Uses embedded validator keys from defaultNetworkConfig (not generating new ones)
|
||||
// 2. Preserves initialStakers from the genesis package (already matches embedded keys)
|
||||
// 3. Generates P-chain allocations for the embedded validators (10M LUX each)
|
||||
// 4. Preserves cchainGenesis from the genesis package
|
||||
//
|
||||
// Supported network IDs:
|
||||
// - 96369: LUX Mainnet
|
||||
@@ -47,7 +46,7 @@ func NewConfigForNetwork(binaryPath string, numNodes uint32, networkID uint32) (
|
||||
return network.Config{}, fmt.Errorf("failed to get genesis for network %d: %w", networkID, err)
|
||||
}
|
||||
|
||||
// Start with the default config structure
|
||||
// Start with the default config structure (includes embedded validator keys)
|
||||
netConfig := NewDefaultConfig(binaryPath)
|
||||
|
||||
// For local network (1337), use the genesis as-is since it already has stakers
|
||||
@@ -96,8 +95,10 @@ func NewConfigForNetwork(binaryPath string, numNodes uint32, networkID uint32) (
|
||||
return netConfig, nil
|
||||
}
|
||||
|
||||
// For mainnet/testnet, we need to generate new staking keys and inject them
|
||||
// as initial stakers in the genesis
|
||||
// For mainnet/testnet:
|
||||
// - Use embedded validator keys from defaultNetworkConfig
|
||||
// - Keep initialStakers from genesis (already matches embedded keys)
|
||||
// - Generate P-chain allocations for the embedded validators
|
||||
|
||||
// Parse genesis to modify it
|
||||
var genesis map[string]interface{}
|
||||
@@ -105,136 +106,112 @@ func NewConfigForNetwork(binaryPath string, numNodes uint32, networkID uint32) (
|
||||
return network.Config{}, fmt.Errorf("failed to parse genesis: %w", err)
|
||||
}
|
||||
|
||||
// Generate staking keys for all nodes and collect staker info
|
||||
type stakerInfo struct {
|
||||
nodeID ids.NodeID
|
||||
stakingKey string
|
||||
stakingCrt string
|
||||
signerKey string
|
||||
pop *signer.ProofOfPossession
|
||||
}
|
||||
stakers := make([]stakerInfo, numNodes)
|
||||
|
||||
for i := uint32(0); i < numNodes; i++ {
|
||||
// Generate new staking cert/key
|
||||
stakingCert, stakingKey, err := staking.NewCertAndKeyBytes()
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("couldn't generate staking Cert/Key for node %d: %w", i, err)
|
||||
}
|
||||
|
||||
// Parse the cert to get NodeID
|
||||
tlsCert, err := tls.X509KeyPair(stakingCert, stakingKey)
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("couldn't parse TLS cert for node %d: %w", i, err)
|
||||
}
|
||||
|
||||
// Convert to ids.Certificate for NodeID computation
|
||||
if len(tlsCert.Certificate) == 0 {
|
||||
return network.Config{}, fmt.Errorf("no certificate data for node %d", i)
|
||||
}
|
||||
idsCert := &ids.Certificate{
|
||||
Raw: tlsCert.Certificate[0],
|
||||
PublicKey: tlsCert.PrivateKey,
|
||||
}
|
||||
nodeID := ids.NodeIDFromCert(idsCert)
|
||||
|
||||
// Generate BLS signer key and proof of possession
|
||||
blsKey, err := localsigner.New()
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("couldn't generate BLS key for node %d: %w", i, err)
|
||||
}
|
||||
|
||||
pop, err := signer.NewProofOfPossession(blsKey)
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("couldn't generate proof of possession for node %d: %w", i, err)
|
||||
}
|
||||
|
||||
stakers[i] = stakerInfo{
|
||||
nodeID: nodeID,
|
||||
stakingKey: string(stakingKey),
|
||||
stakingCrt: string(stakingCert),
|
||||
signerKey: base64.StdEncoding.EncodeToString(blsKey.ToBytes()),
|
||||
pop: pop,
|
||||
}
|
||||
}
|
||||
|
||||
// Create initial stakers for genesis
|
||||
hrp := constants.GetHRP(networkID)
|
||||
|
||||
// Treasury address short ID (derived from 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714)
|
||||
// P-Chain Address: P-lux1c7wevm4667l4umtzh93r25wpxlpsadkhka6gv6
|
||||
var treasuryShortID ids.ShortID
|
||||
treasuryBytes, _ := hex.DecodeString("c79d966ebad7bf5e6d62b9623551c137c30eb6d7")
|
||||
copy(treasuryShortID[:], treasuryBytes)
|
||||
rewardAddr, err := address.Format("P", hrp, treasuryShortID[:])
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("couldn't format reward address: %w", err)
|
||||
// Number of embedded validators available
|
||||
numEmbedded := uint32(len(defaultNetworkConfig.NodeConfigs))
|
||||
if numEmbedded > 5 {
|
||||
numEmbedded = 5 // Cap at 5 embedded validators
|
||||
}
|
||||
|
||||
initialStakers := make([]map[string]interface{}, numNodes)
|
||||
for i, s := range stakers {
|
||||
initialStakers[i] = map[string]interface{}{
|
||||
"nodeID": s.nodeID.String(),
|
||||
"rewardAddress": rewardAddr,
|
||||
"delegationFee": 20000, // 2% delegation fee
|
||||
"signer": map[string]interface{}{
|
||||
"publicKey": "0x" + hex.EncodeToString(s.pop.PublicKey[:]),
|
||||
"proofOfPossession": "0x" + hex.EncodeToString(s.pop.ProofOfPossession[:]),
|
||||
},
|
||||
// Use min(numNodes, numEmbedded) for node configs with embedded keys
|
||||
numEmbeddedToUse := numNodes
|
||||
if numEmbeddedToUse > numEmbedded {
|
||||
numEmbeddedToUse = numEmbedded
|
||||
}
|
||||
|
||||
// For P/X-chain allocations, we need to create them for keys the wallet can use.
|
||||
// C-chain allocations are historic (embedded in genesis), but P/X can be set freely.
|
||||
// REPLACE allocations with ones for the wallet's key (mnemonic or private key).
|
||||
var newAllocations []interface{}
|
||||
|
||||
// If LUX_MNEMONIC is set, create allocations for mnemonic-derived key
|
||||
// Need BOTH X-chain (for transfers) and P-chain (for chain creation/validators)
|
||||
if mnemonic := os.Getenv("LUX_MNEMONIC"); mnemonic != "" {
|
||||
validatorKeys, err := keys.DeriveValidatorsFromMnemonic(mnemonic, 1)
|
||||
if err == nil && len(validatorKeys) > 0 {
|
||||
vk := validatorKeys[0]
|
||||
addr := vk.PChainAddr // Same underlying secp256k1 address
|
||||
xLuxAddr, errX := address.Format("X", hrp, addr[:])
|
||||
pLuxAddr, errP := address.Format("P", hrp, addr[:])
|
||||
if errX == nil && errP == nil {
|
||||
fmt.Printf("🔑 Setting X+P allocations for LUX_MNEMONIC: X=%s, P=%s (2B each)\n", xLuxAddr, pLuxAddr)
|
||||
// X-chain allocation (for asset transfers)
|
||||
newAllocations = append(newAllocations, map[string]interface{}{
|
||||
"ethAddr": vk.CChainAddrHex(),
|
||||
"luxAddr": xLuxAddr,
|
||||
"initialAmount": uint64(2000000000000000000), // 2B LUX
|
||||
"unlockSchedule": []map[string]interface{}{},
|
||||
})
|
||||
// P-chain allocation (for chain creation, validators)
|
||||
// IMPORTANT: Must have unlockSchedule entries for builder to create UTXOs
|
||||
newAllocations = append(newAllocations, map[string]interface{}{
|
||||
"ethAddr": vk.CChainAddrHex(),
|
||||
"luxAddr": pLuxAddr,
|
||||
"initialAmount": uint64(0), // Not used for P-chain, unlockSchedule is used
|
||||
"unlockSchedule": []map[string]interface{}{
|
||||
{
|
||||
"amount": uint64(2000000000000000000), // 2B LUX
|
||||
"locktime": uint64(0), // Immediately available
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update genesis with initial stakers
|
||||
genesis["initialStakers"] = initialStakers
|
||||
|
||||
// Check if genesis already has allocations from the configs package
|
||||
// (which may include dynamic allocations from ~/.lux/genesis/{network}/pchain.json)
|
||||
existingAllocs, hasAllocs := genesis["allocations"].([]interface{})
|
||||
if hasAllocs && len(existingAllocs) > 0 {
|
||||
// Use existing allocations from genesis - don't overwrite
|
||||
// This preserves dynamic P-Chain allocations configured via:
|
||||
// - LUX_PCHAIN_ALLOCS environment variable
|
||||
// - LUX_PCHAIN_ALLOCS_FILE environment variable
|
||||
// - ~/.lux/genesis/{network}/pchain.json file
|
||||
} else {
|
||||
// No allocations in genesis - auto-generate based on numNodes (--num-validators)
|
||||
// Each validator gets 1M LUX (DefaultValidatorStake) immediately available
|
||||
// First validator gets 10M LUX for fees, chain creation, etc.
|
||||
validatorAllocs, _, err := GenerateValidatorAllocations(numNodes, hrp)
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("failed to generate validator allocations: %w", err)
|
||||
}
|
||||
|
||||
// Convert to []interface{} for JSON marshaling
|
||||
allocations := make([]interface{}, len(validatorAllocs))
|
||||
for i, alloc := range validatorAllocs {
|
||||
allocations[i] = alloc
|
||||
}
|
||||
genesis["allocations"] = allocations
|
||||
}
|
||||
|
||||
// C-chain genesis is immutable - don't modify it
|
||||
// The C-chain genesis from configs package contains the real mainnet state
|
||||
now := time.Now().Unix()
|
||||
|
||||
// Check if initialStakedFunds already exists in genesis from configs package
|
||||
// If not, extract from the second allocation's address
|
||||
if _, hasStakedFunds := genesis["initialStakedFunds"]; !hasStakedFunds {
|
||||
// Extract second allocation address for initialStakedFunds
|
||||
if allocs, ok := genesis["allocations"].([]interface{}); ok && len(allocs) > 1 {
|
||||
if secondAlloc, ok := allocs[1].(map[string]interface{}); ok {
|
||||
if luxAddr, ok := secondAlloc["luxAddr"].(string); ok {
|
||||
genesis["initialStakedFunds"] = []string{luxAddr}
|
||||
// If LUX_PRIVATE_KEY is set, also create X+P allocations for it
|
||||
if privKeyHex := os.Getenv("LUX_PRIVATE_KEY"); privKeyHex != "" {
|
||||
privKeyBytes, err := hex.DecodeString(privKeyHex)
|
||||
if err == nil && len(privKeyBytes) == 32 {
|
||||
luxPrivKey, err := luxcrypto.ToPrivateKey(privKeyBytes)
|
||||
if err == nil {
|
||||
pubKey := luxPrivKey.PublicKey()
|
||||
addr := ids.ShortID(pubKey.Address())
|
||||
xLuxAddr, errX := address.Format("X", hrp, addr[:])
|
||||
pLuxAddr, errP := address.Format("P", hrp, addr[:])
|
||||
if errX == nil && errP == nil {
|
||||
fmt.Printf("🔑 Adding X+P allocations for LUX_PRIVATE_KEY: X=%s, P=%s (2B each)\n", xLuxAddr, pLuxAddr)
|
||||
ethAddr := "0x" + hex.EncodeToString(addr[:])
|
||||
// X-chain allocation
|
||||
newAllocations = append(newAllocations, map[string]interface{}{
|
||||
"ethAddr": ethAddr,
|
||||
"luxAddr": xLuxAddr,
|
||||
"initialAmount": uint64(2000000000000000000), // 2B LUX
|
||||
"unlockSchedule": []map[string]interface{}{},
|
||||
})
|
||||
// P-chain allocation - must use unlockSchedule for builder to create UTXOs
|
||||
newAllocations = append(newAllocations, map[string]interface{}{
|
||||
"ethAddr": ethAddr,
|
||||
"luxAddr": pLuxAddr,
|
||||
"initialAmount": uint64(0), // Not used for P-chain
|
||||
"unlockSchedule": []map[string]interface{}{
|
||||
{
|
||||
"amount": uint64(2000000000000000000), // 2B LUX
|
||||
"locktime": uint64(0), // Immediately available
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall back to empty if we couldn't extract
|
||||
if _, hasStakedFunds := genesis["initialStakedFunds"]; !hasStakedFunds {
|
||||
genesis["initialStakedFunds"] = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// If we created new allocations, replace the genesis allocations
|
||||
// Otherwise, keep the genesis package's allocations (for backward compatibility)
|
||||
if len(newAllocations) > 0 {
|
||||
genesis["allocations"] = newAllocations
|
||||
}
|
||||
|
||||
// Keep initialStakers from genesis package - they already match the embedded keys
|
||||
// The genesis package's pchain.json has the correct NodeIDs and BLS signers
|
||||
// DO NOT overwrite initialStakers
|
||||
|
||||
// Set initialStakedFunds to empty - allocations are for free balance, not staking
|
||||
genesis["initialStakedFunds"] = []string{}
|
||||
|
||||
// Update start time to now
|
||||
now := time.Now().Unix()
|
||||
genesis["startTime"] = uint64(now)
|
||||
|
||||
// Re-serialize genesis
|
||||
@@ -244,22 +221,52 @@ func NewConfigForNetwork(binaryPath string, numNodes uint32, networkID uint32) (
|
||||
}
|
||||
netConfig.Genesis = string(updatedGenesis)
|
||||
|
||||
// Configure node configs with the generated staking keys
|
||||
// Configure node configs using embedded keys for the first numEmbeddedToUse nodes
|
||||
netConfig.NodeConfigs = make([]node.Config, numNodes)
|
||||
for i := uint32(0); i < numNodes; i++ {
|
||||
port := 9630 + int(i)*2
|
||||
netConfig.NodeConfigs[i] = node.Config{
|
||||
Flags: map[string]interface{}{
|
||||
config.HTTPPortKey: port,
|
||||
config.StakingPortKey: port + 1,
|
||||
},
|
||||
StakingKey: stakers[i].stakingKey,
|
||||
StakingCert: stakers[i].stakingCrt,
|
||||
StakingSigningKey: stakers[i].signerKey,
|
||||
IsBeacon: true,
|
||||
ChainConfigFiles: map[string]string{},
|
||||
UpgradeConfigFiles: map[string]string{},
|
||||
PChainConfigFiles: map[string]string{},
|
||||
|
||||
if i < numEmbeddedToUse {
|
||||
// Use embedded validator keys
|
||||
embeddedConfig := defaultNetworkConfig.NodeConfigs[i]
|
||||
netConfig.NodeConfigs[i] = node.Config{
|
||||
Flags: map[string]interface{}{
|
||||
config.HTTPPortKey: port,
|
||||
config.StakingPortKey: port + 1,
|
||||
},
|
||||
StakingKey: embeddedConfig.StakingKey,
|
||||
StakingCert: embeddedConfig.StakingCert,
|
||||
StakingSigningKey: embeddedConfig.StakingSigningKey,
|
||||
IsBeacon: true,
|
||||
ChainConfigFiles: map[string]string{},
|
||||
UpgradeConfigFiles: map[string]string{},
|
||||
PChainConfigFiles: map[string]string{},
|
||||
}
|
||||
} else {
|
||||
// Generate new keys for additional nodes beyond the 5 embedded ones
|
||||
stakingCert, stakingKey, err := staking.NewCertAndKeyBytes()
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("couldn't generate staking Cert/Key for node %d: %w", i, err)
|
||||
}
|
||||
|
||||
blsKey, err := localsigner.New()
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("couldn't generate BLS key for node %d: %w", i, err)
|
||||
}
|
||||
|
||||
netConfig.NodeConfigs[i] = node.Config{
|
||||
Flags: map[string]interface{}{
|
||||
config.HTTPPortKey: port,
|
||||
config.StakingPortKey: port + 1,
|
||||
},
|
||||
StakingKey: string(stakingKey),
|
||||
StakingCert: string(stakingCert),
|
||||
StakingSigningKey: base64.StdEncoding.EncodeToString(blsKey.ToBytes()),
|
||||
IsBeacon: true,
|
||||
ChainConfigFiles: map[string]string{},
|
||||
UpgradeConfigFiles: map[string]string{},
|
||||
PChainConfigFiles: map[string]string{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +387,8 @@ func NewConfigWithPreExistingKeys(binaryPath string, networkID uint32, keysDir s
|
||||
staker := map[string]interface{}{
|
||||
"nodeID": vk.NodeID.String(),
|
||||
"rewardAddress": rewardAddr,
|
||||
"delegationFee": 20000, // 2% delegation fee
|
||||
"delegationFee": 20000, // 2% delegation fee
|
||||
"weight": GigaLux, // 1,000,000,000 LUX (1B) per validator
|
||||
}
|
||||
|
||||
// Add BLS signer if available
|
||||
@@ -506,3 +514,167 @@ func NewTestnetConfigWithKeys(binaryPath string, keysDir string) (network.Config
|
||||
}
|
||||
return NewConfigWithPreExistingKeys(binaryPath, configs.LuxTestnetID, keysDir)
|
||||
}
|
||||
|
||||
// NewConfigFromMnemonic creates a network config by deriving validator keys from LUX_MNEMONIC.
|
||||
// This is the preferred method for starting mainnet/testnet as it doesn't depend on files on disk.
|
||||
// The mnemonic is used to derive 5 validator EC keys (for P-chain allocations).
|
||||
// TLS staking certs and BLS keys are generated fresh for each run.
|
||||
func NewConfigFromMnemonic(binaryPath string, networkID uint32, numNodes uint32) (network.Config, error) {
|
||||
mnemonic := os.Getenv("LUX_MNEMONIC")
|
||||
if mnemonic == "" {
|
||||
return network.Config{}, fmt.Errorf("LUX_MNEMONIC environment variable not set")
|
||||
}
|
||||
|
||||
fmt.Printf("🔑 Deriving %d validators from LUX_MNEMONIC...\n", numNodes)
|
||||
|
||||
// Derive validator keys from mnemonic
|
||||
validatorKeys, err := keys.DeriveValidatorsFromMnemonic(mnemonic, int(numNodes))
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("failed to derive validator keys: %w", err)
|
||||
}
|
||||
|
||||
// Get base genesis
|
||||
genesisJSON, err := configs.GetGenesis(networkID)
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("failed to get genesis for network %d: %w", networkID, err)
|
||||
}
|
||||
|
||||
// Start with default config
|
||||
netConfig := NewDefaultConfig(binaryPath)
|
||||
|
||||
// Parse genesis to modify it
|
||||
var genesis map[string]interface{}
|
||||
if err := json.Unmarshal(genesisJSON, &genesis); err != nil {
|
||||
return network.Config{}, fmt.Errorf("failed to parse genesis: %w", err)
|
||||
}
|
||||
|
||||
hrp := constants.GetHRP(networkID)
|
||||
|
||||
// Build initial stakers from derived keys
|
||||
initialStakers := make([]map[string]interface{}, numNodes)
|
||||
// Each validator gets BOTH X-chain AND P-chain allocations (2 entries per validator)
|
||||
allocations := make([]interface{}, 0, numNodes*2)
|
||||
|
||||
for i, vk := range validatorKeys {
|
||||
fmt.Printf("🔍 Validator %d: PChainAddr ShortID = %s\n", i, vk.PChainAddr.String())
|
||||
|
||||
// Build P-chain address for staker rewards and P-chain operations
|
||||
pChainAddr, err := address.Format("P", hrp, vk.PChainAddr[:])
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("failed to format P-chain address for validator %d: %w", i, err)
|
||||
}
|
||||
fmt.Printf("🔍 Validator %d: P-chain bech32 = %s\n", i, pChainAddr)
|
||||
|
||||
// Build X-chain address for asset transfers
|
||||
xChainAddr, err := address.Format("X", hrp, vk.PChainAddr[:])
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("failed to format X-chain address for validator %d: %w", i, err)
|
||||
}
|
||||
fmt.Printf("🔍 Validator %d: X-chain bech32 = %s\n", i, xChainAddr)
|
||||
|
||||
// Initial staker entry
|
||||
staker := map[string]interface{}{
|
||||
"nodeID": vk.NodeID.String(),
|
||||
"rewardAddress": xChainAddr, // Rewards go to X-chain address
|
||||
"delegationFee": 20000, // 2% delegation fee
|
||||
"weight": GigaLux, // 1B LUX per validator
|
||||
}
|
||||
if len(vk.BLSPublicKey) > 0 && len(vk.BLSPoP) > 0 {
|
||||
staker["signer"] = map[string]interface{}{
|
||||
"publicKey": vk.BLSPublicKeyHex(),
|
||||
"proofOfPossession": vk.BLSPoPHex(),
|
||||
}
|
||||
}
|
||||
initialStakers[i] = staker
|
||||
|
||||
// X-chain allocation (2B LUX) - for asset transfers
|
||||
allocations = append(allocations, map[string]interface{}{
|
||||
"ethAddr": vk.CChainAddrHex(),
|
||||
"luxAddr": xChainAddr,
|
||||
"initialAmount": uint64(2000000000000000000), // 2B LUX
|
||||
"unlockSchedule": []map[string]interface{}{},
|
||||
})
|
||||
|
||||
// P-chain allocation (2B LUX) - for chain creation, validators
|
||||
// IMPORTANT: Must use unlockSchedule for builder to create UTXOs
|
||||
allocations = append(allocations, map[string]interface{}{
|
||||
"ethAddr": vk.CChainAddrHex(),
|
||||
"luxAddr": pChainAddr,
|
||||
"initialAmount": uint64(0), // Not used for P-chain
|
||||
"unlockSchedule": []map[string]interface{}{
|
||||
{
|
||||
"amount": uint64(2000000000000000000), // 2B LUX
|
||||
"locktime": uint64(0), // Immediately available
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
fmt.Printf(" Validator %d: %s -> X:%s P:%s (2B each)\n", i+1, vk.NodeID.String(), xChainAddr, pChainAddr)
|
||||
}
|
||||
|
||||
genesis["initialStakers"] = initialStakers
|
||||
genesis["allocations"] = allocations
|
||||
|
||||
// IMPORTANT: Set initialStakedFunds to EMPTY so that P-chain allocations
|
||||
// are NOT filtered by the builder. The builder filters allocations where
|
||||
// the underlying ShortID matches initialStakedFunds, which would incorrectly
|
||||
// filter P-chain allocations that share the same address as X-chain validators.
|
||||
// We use explicit weight in initialStakers instead of deriving from stakes.
|
||||
genesis["initialStakedFunds"] = []string{}
|
||||
|
||||
// Update start time to now
|
||||
now := time.Now().Unix()
|
||||
genesis["startTime"] = uint64(now)
|
||||
|
||||
// Re-serialize genesis
|
||||
updatedGenesis, err := json.MarshalIndent(genesis, "", " ")
|
||||
if err != nil {
|
||||
return network.Config{}, fmt.Errorf("failed to serialize genesis: %w", err)
|
||||
}
|
||||
netConfig.Genesis = string(updatedGenesis)
|
||||
|
||||
// Debug: write genesis to file
|
||||
if err := os.WriteFile("/tmp/mnemonic_genesis.json", updatedGenesis, 0644); err != nil {
|
||||
fmt.Printf("Warning: could not write debug genesis: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("📄 Genesis written to /tmp/mnemonic_genesis.json\n")
|
||||
}
|
||||
|
||||
// Configure node configs with derived keys
|
||||
netConfig.NodeConfigs = make([]node.Config, numNodes)
|
||||
for i, vk := range validatorKeys {
|
||||
port := 9630 + int(i)*2
|
||||
netConfig.NodeConfigs[i] = node.Config{
|
||||
Flags: map[string]interface{}{
|
||||
config.HTTPPortKey: port,
|
||||
config.StakingPortKey: port + 1,
|
||||
},
|
||||
StakingKey: string(vk.StakerKey),
|
||||
StakingCert: string(vk.StakerCert),
|
||||
StakingSigningKey: base64.StdEncoding.EncodeToString(vk.BLSSecretKey),
|
||||
IsBeacon: true,
|
||||
ChainConfigFiles: map[string]string{},
|
||||
UpgradeConfigFiles: map[string]string{},
|
||||
PChainConfigFiles: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Network config ready with %d validators\n", numNodes)
|
||||
return netConfig, nil
|
||||
}
|
||||
|
||||
// NewMainnetConfigFromMnemonic creates mainnet config from LUX_MNEMONIC
|
||||
func NewMainnetConfigFromMnemonic(binaryPath string, numNodes uint32) (network.Config, error) {
|
||||
return NewConfigFromMnemonic(binaryPath, configs.LuxMainnetID, numNodes)
|
||||
}
|
||||
|
||||
// NewTestnetConfigFromMnemonic creates testnet config from LUX_MNEMONIC
|
||||
func NewTestnetConfigFromMnemonic(binaryPath string, numNodes uint32) (network.Config, error) {
|
||||
return NewConfigFromMnemonic(binaryPath, configs.LuxTestnetID, numNodes)
|
||||
}
|
||||
|
||||
// NewLocalConfigFromMnemonic creates local network config from LUX_MNEMONIC
|
||||
// This uses network ID 1337 with "custom" HRP, which is simpler for testing
|
||||
func NewLocalConfigFromMnemonic(binaryPath string, numNodes uint32) (network.Config, error) {
|
||||
return NewConfigFromMnemonic(binaryPath, configs.LocalID, numNodes)
|
||||
}
|
||||
|
||||
+8
-2
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/luxfi/netrunner/network/node"
|
||||
"github.com/luxfi/node/config"
|
||||
"github.com/luxfi/constants"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -97,7 +96,9 @@ func writeFiles(networkID uint32, genesis []byte, nodeRootDir string, nodeConfig
|
||||
contents: decodedStakingSigningKey,
|
||||
},
|
||||
}
|
||||
if networkID != constants.LocalID {
|
||||
// Always write genesis file if provided, even for LocalID
|
||||
// This allows custom genesis (e.g., with mnemonic-derived validators) to override defaults
|
||||
if len(genesis) > 0 {
|
||||
files = append(files, file{
|
||||
flagValue: filepath.Join(nodeRootDir, genesisFileName),
|
||||
path: filepath.Join(nodeRootDir, genesisFileName),
|
||||
@@ -191,12 +192,15 @@ func getPort(
|
||||
switch gotPort := portIntf.(type) {
|
||||
case int:
|
||||
port = uint16(gotPort)
|
||||
fmt.Printf("🔍 getPort: %s found in flags as int=%d\n", portKey, port)
|
||||
case float64:
|
||||
port = uint16(gotPort)
|
||||
fmt.Printf("🔍 getPort: %s found in flags as float64=%d\n", portKey, port)
|
||||
default:
|
||||
return 0, fmt.Errorf("expected flag %q to be int/float64 but got %T", portKey, portIntf)
|
||||
}
|
||||
} else if portIntf, ok := configFile[portKey]; ok {
|
||||
fmt.Printf("🔍 getPort: %s NOT in flags, checking configFile\n", portKey)
|
||||
portFromConfigFile, ok := portIntf.(float64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("expected flag %q to be float64 but got %T", portKey, portIntf)
|
||||
@@ -205,12 +209,14 @@ func getPort(
|
||||
} else {
|
||||
// Use a random free port.
|
||||
// Note: it is possible but unlikely for getFreePort to return the same port multiple times.
|
||||
fmt.Printf("🔍 getPort: %s NOT in flags or configFile, using random port\n", portKey)
|
||||
port, err = getFreePort()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("couldn't get free port: %w", err)
|
||||
}
|
||||
}
|
||||
if reassignIfUsed && isFreePort(port) != nil {
|
||||
fmt.Printf("🔍 getPort: %s port %d NOT free, reassigning to random\n", portKey, port)
|
||||
port, err = getFreePort()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("couldn't get free port: %w", err)
|
||||
|
||||
+112
-4
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -19,6 +20,12 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/crypto/bls"
|
||||
luxcrypto "github.com/luxfi/crypto/secp256k1"
|
||||
"github.com/luxfi/ids"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/keys"
|
||||
"github.com/luxfi/netrunner/api"
|
||||
"github.com/luxfi/netrunner/network"
|
||||
"github.com/luxfi/netrunner/network/node"
|
||||
@@ -26,13 +33,10 @@ import (
|
||||
"github.com/luxfi/netrunner/utils"
|
||||
"github.com/luxfi/netrunner/utils/constants"
|
||||
"github.com/luxfi/node/config"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/network/peer"
|
||||
"github.com/luxfi/node/staking"
|
||||
"github.com/luxfi/node/utils/beacon"
|
||||
"github.com/luxfi/crypto/bls"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/node/utils/formatting/address"
|
||||
"github.com/luxfi/node/utils/wrappers"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/maps"
|
||||
@@ -182,6 +186,44 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// If LUX_PRIVATE_KEY is set, also allocate funds to that address for chain creation
|
||||
if privKeyHex := os.Getenv("LUX_PRIVATE_KEY"); privKeyHex != "" {
|
||||
privKeyBytes, err := hex.DecodeString(privKeyHex)
|
||||
if err == nil && len(privKeyBytes) == 32 {
|
||||
// Derive P-chain address from private key
|
||||
luxPrivKey, err := luxcrypto.ToPrivateKey(privKeyBytes)
|
||||
if err == nil {
|
||||
pubKey := luxPrivKey.PublicKey()
|
||||
pChainAddr := ids.ShortID(pubKey.Address())
|
||||
// For default network (local, ID 1337), use "custom" HRP
|
||||
luxAddr, err := address.Format("X", "custom", pChainAddr[:])
|
||||
if err == nil {
|
||||
fmt.Printf("🔑 Adding default network allocation for LUX_PRIVATE_KEY address: %s\n", luxAddr)
|
||||
privKeyAlloc := map[string]interface{}{
|
||||
"ethAddr": "0x" + hex.EncodeToString(pChainAddr[:]),
|
||||
"luxAddr": luxAddr,
|
||||
"initialAmount": float64(0),
|
||||
"unlockSchedule": []interface{}{
|
||||
map[string]interface{}{
|
||||
"amount": float64(100000000000000000), // 100M LUX in nLUX
|
||||
"locktime": float64(0), // Immediately available
|
||||
},
|
||||
},
|
||||
}
|
||||
allocations = append(allocations, privKeyAlloc)
|
||||
genesisMap["allocations"] = allocations
|
||||
|
||||
// CRITICAL: Also add to initialStakedFunds so funds are available on P-chain
|
||||
// This is required for CreateChainTx and other P-chain operations
|
||||
initialStakedFunds, _ := genesisMap["initialStakedFunds"].([]interface{})
|
||||
initialStakedFunds = append(initialStakedFunds, luxAddr)
|
||||
genesisMap["initialStakedFunds"] = initialStakedFunds
|
||||
fmt.Printf("🔑 Added %s to initialStakedFunds for P-chain access\n", luxAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now we can marshal the *whole* thing into bytes
|
||||
updatedGenesis, err := json.Marshal(genesisMap)
|
||||
if err != nil {
|
||||
@@ -415,6 +457,72 @@ func NewDefaultConfigNNodes(binaryPath string, numNodes uint32) (network.Config,
|
||||
if int(numNodes) < len(netConfig.NodeConfigs) {
|
||||
netConfig.NodeConfigs = netConfig.NodeConfigs[:numNodes]
|
||||
}
|
||||
|
||||
// If LUX_MNEMONIC is set, add allocations for mnemonic-derived address
|
||||
mnemonic := os.Getenv("LUX_MNEMONIC")
|
||||
fmt.Printf("🔍 DEBUG: NewDefaultConfigNNodes called, LUX_MNEMONIC set=%v\n", mnemonic != "")
|
||||
if mnemonic != "" {
|
||||
vk, err := keys.DeriveValidatorFromMnemonic(mnemonic, 0)
|
||||
if err != nil {
|
||||
return netConfig, fmt.Errorf("failed to derive key from mnemonic: %w", err)
|
||||
}
|
||||
|
||||
// Parse genesis
|
||||
var genesis map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(netConfig.Genesis), &genesis); err != nil {
|
||||
return netConfig, fmt.Errorf("failed to parse genesis: %w", err)
|
||||
}
|
||||
|
||||
// Create X-chain and P-chain addresses
|
||||
// Network ID 1337 uses "local" HRP (per constants.NetworkIDToHRP)
|
||||
const hrp = "local"
|
||||
fmt.Printf("🔍 DEBUG: vk.PChainAddr (base58): %s\n", vk.PChainAddr.String())
|
||||
fmt.Printf("🔍 DEBUG: vk.PChainAddr (hex): %x\n", vk.PChainAddr[:])
|
||||
xLuxAddr, err := address.Format("X", hrp, vk.PChainAddr[:])
|
||||
if err != nil {
|
||||
return netConfig, fmt.Errorf("failed to format X-chain address: %w", err)
|
||||
}
|
||||
pLuxAddr, err := address.Format("P", hrp, vk.PChainAddr[:])
|
||||
if err != nil {
|
||||
return netConfig, fmt.Errorf("failed to format P-chain address: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("🔑 Adding local network allocations for LUX_MNEMONIC: X=%s, P=%s (2B each)\n", xLuxAddr, pLuxAddr)
|
||||
|
||||
// Get existing allocations
|
||||
allocations, _ := genesis["allocations"].([]interface{})
|
||||
|
||||
// Add X-chain allocation
|
||||
allocations = append(allocations, map[string]interface{}{
|
||||
"luxAddr": xLuxAddr,
|
||||
"ethAddr": "0x0000000000000000000000000000000000000000",
|
||||
"initialAmount": 2_000_000_000_000_000_000, // 2B LUX
|
||||
"unlockSchedule": []interface{}{},
|
||||
})
|
||||
|
||||
// Add P-chain allocation with unlockSchedule (builder only reads P-chain amounts from unlockSchedule)
|
||||
allocations = append(allocations, map[string]interface{}{
|
||||
"luxAddr": pLuxAddr,
|
||||
"ethAddr": "0x0000000000000000000000000000000000000000",
|
||||
"initialAmount": 0, // Not used for P-chain - amounts come from unlockSchedule
|
||||
"unlockSchedule": []interface{}{
|
||||
map[string]interface{}{
|
||||
"amount": uint64(2_000_000_000_000_000_000), // 2B LUX
|
||||
"locktime": uint64(0), // Immediately available
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
genesis["allocations"] = allocations
|
||||
|
||||
// Re-encode genesis
|
||||
updatedGenesis, err := json.MarshalIndent(genesis, "", " ")
|
||||
if err != nil {
|
||||
return netConfig, fmt.Errorf("failed to encode updated genesis: %w", err)
|
||||
}
|
||||
netConfig.Genesis = string(updatedGenesis)
|
||||
}
|
||||
|
||||
return netConfig, nil
|
||||
}
|
||||
|
||||
|
||||
+9
-2
@@ -83,7 +83,7 @@ type localNode struct {
|
||||
|
||||
func defaultGetConnFunc(ctx context.Context, node node.Node) (net.Conn, error) {
|
||||
dialer := net.Dialer{}
|
||||
return dialer.DialContext(ctx, constants.NetworkType, net.JoinHostPort(node.GetURL(), fmt.Sprintf("%d", node.GetP2PPort())))
|
||||
return dialer.DialContext(ctx, constants.NetworkType, net.JoinHostPort(node.GetHost(), fmt.Sprintf("%d", node.GetP2PPort())))
|
||||
}
|
||||
|
||||
// AttachPeer: see Network
|
||||
@@ -195,14 +195,21 @@ func (node *localNode) GetAPIClient() api.Client {
|
||||
return node.client
|
||||
}
|
||||
|
||||
// GetHost returns the node's host/IP (e.g. 127.0.0.1).
|
||||
// See node.Node
|
||||
func (node *localNode) GetURL() string {
|
||||
func (node *localNode) GetHost() string {
|
||||
if node.httpHost == "0.0.0.0" || node.httpHost == "." {
|
||||
return "0.0.0.0"
|
||||
}
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
// GetURL returns the full HTTP API URL (e.g. http://127.0.0.1:9630).
|
||||
// See node.Node
|
||||
func (node *localNode) GetURL() string {
|
||||
return fmt.Sprintf("http://%s:%d", node.GetHost(), node.apiPort)
|
||||
}
|
||||
|
||||
// See node.Node
|
||||
func (node *localNode) GetP2PPort() uint16 {
|
||||
return node.p2pPort
|
||||
|
||||
Reference in New Issue
Block a user