fix: correct network ID usage and remove HID conflicts

- Remove ledger-lux-go transitive dependency
- Fix Network ID vs Chain ID confusion in genesis_config.go
- Use constants.MainnetID/TestnetID instead of configs.ChainID
- Fix port base calculation for multi-network mode
- Add snapshot server functionality
- Update protobuf definitions with new RPC methods
- Add mnemonic test coverage
This commit is contained in:
Zach Kelling
2025-12-29 10:07:43 -08:00
parent ac91b3c853
commit cd0dd2fccc
24 changed files with 862 additions and 1685 deletions
+4 -1
View File
@@ -6,6 +6,9 @@ breaking:
- FILE
lint:
use:
- DEFAULT
- STANDARD
except:
- PACKAGE_VERSION_SUFFIX
- RPC_REQUEST_RESPONSE_UNIQUE
- RPC_REQUEST_STANDARD_NAME
- RPC_RESPONSE_STANDARD_NAME
+6
View File
@@ -52,6 +52,7 @@ type Client interface {
SendOutboundMessage(ctx context.Context, nodeName string, peerID string, op uint32, msgBody []byte) (*rpcpb.SendOutboundMessageResponse, error)
Close() error
SaveSnapshot(ctx context.Context, snapshotName string) (*rpcpb.SaveSnapshotResponse, error)
SaveHotSnapshot(ctx context.Context, snapshotName string) (*rpcpb.SaveSnapshotResponse, error)
LoadSnapshot(ctx context.Context, snapshotName string, opts ...OpOption) (*rpcpb.LoadSnapshotResponse, error)
RemoveSnapshot(ctx context.Context, snapshotName string) (*rpcpb.RemoveSnapshotResponse, error)
GetSnapshotNames(ctx context.Context) ([]string, error)
@@ -343,6 +344,11 @@ func (c *client) SaveSnapshot(ctx context.Context, snapshotName string) (*rpcpb.
return c.controlc.SaveSnapshot(ctx, &rpcpb.SaveSnapshotRequest{SnapshotName: snapshotName})
}
func (c *client) SaveHotSnapshot(ctx context.Context, snapshotName string) (*rpcpb.SaveSnapshotResponse, error) {
c.logger.Info("save hot snapshot", log.String("snapshot-name", snapshotName))
return c.controlc.SaveHotSnapshot(ctx, &rpcpb.SaveSnapshotRequest{SnapshotName: snapshotName})
}
func (c *client) LoadSnapshot(ctx context.Context, snapshotName string, opts ...OpOption) (*rpcpb.LoadSnapshotResponse, error) {
c.logger.Info("load snapshot", log.String("snapshot-name", snapshotName))
ret := &Op{}
+61 -27
View File
@@ -54,19 +54,7 @@ var startCmd = &cobra.Command{
}
func runMultiNetwork(cmd *cobra.Command, args []string) error {
fmt.Println("🚀 Starting networks in parallel mode...")
dbPath := "/tmp/netrunner-shared-db"
if sharedDB {
fmt.Println("📦 Using shared BadgerDB for cross-chain transactions")
}
// Create multi-network manager
manager, err := multinet.NewMultiNetworkManager(startLogger, dbPath)
if err != nil {
return fmt.Errorf("failed to create multi-network manager: %w", err)
}
defer manager.Shutdown()
fmt.Println("🚀 Starting multiple networks...")
// Determine which networks to start
networksToStart := networks
@@ -74,25 +62,71 @@ func runMultiNetwork(cmd *cobra.Command, args []string) error {
networksToStart = []string{"mainnet", "testnet"}
}
// Configure networks
for _, network := range networksToStart {
config := getNetworkConfig(network)
if err := manager.AddNetwork(config); err != nil {
return fmt.Errorf("failed to add network %s: %w", network, err)
// Load validator keys once
ks := keys.NewKeyStore(keysDir)
validatorKeys, err := ks.LoadAll()
if err != nil {
return fmt.Errorf("failed to load validator keys: %w", err)
}
if len(validatorKeys) == 0 {
return fmt.Errorf("no validator keys found in %s", keysDir)
}
fmt.Printf("📋 Loaded %d validator keys\n", len(validatorKeys))
// Track running networks for cleanup
var runningNetworks []network.Network
defer func() {
for _, ln := range runningNetworks {
_ = ln.Stop(cmd.Context())
}
}()
// Start each network
for _, networkName := range networksToStart {
fmt.Printf("\n🔧 Starting %s...\n", networkName)
var netConfig network.Config
switch networkName {
case "mainnet":
netConfig, err = local.NewMainnetConfigWithKeys(binaryPath, keysDir)
case "testnet":
netConfig, err = local.NewTestnetConfigWithKeys(binaryPath, keysDir)
default:
return fmt.Errorf("unknown network: %s", networkName)
}
if err != nil {
return fmt.Errorf("failed to create %s config: %w", networkName, err)
}
ln, err := local.NewNetwork(
startLogger,
netConfig,
"/tmp/netrunner/"+networkName,
"", // snapshot dir
true, // reassign ports if busy
)
if err != nil {
return fmt.Errorf("failed to start %s: %w", networkName, err)
}
runningNetworks = append(runningNetworks, ln)
nodes, _ := ln.GetAllNodes()
for name, n := range nodes {
fmt.Printf(" %s: http://localhost:%d\n", name, n.GetAPIPort())
}
}
// Start all networks
if err := manager.StartAll(); err != nil {
return fmt.Errorf("failed to start networks: %w", err)
}
fmt.Println("\n✅ Networks started successfully!")
fmt.Println("\n✅ All networks started!")
printNetworkEndpoints(networksToStart)
fmt.Println("\nPress Ctrl+C to stop...")
select {}
// Wait for interrupt
fmt.Println("\n⏳ Press Ctrl+C to stop...")
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
fmt.Println("\n🛑 Shutting down all networks...")
return nil
}
@@ -214,7 +248,7 @@ func printNetworkEndpoints(networks []string) {
case "mainnet":
fmt.Println(" Lux Mainnet: http://localhost:9630")
case "testnet":
fmt.Println(" Lux Testnet: http://localhost:9620")
fmt.Println(" Lux Testnet: http://localhost:9640")
}
}
}
+34 -25
View File
@@ -6,19 +6,28 @@ exclude google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
exclude github.com/luxfi/geth v1.16.1
replace github.com/luxfi/genesis => /Users/z/work/lux/genesis
replace github.com/luxfi/sdk => /Users/z/work/lux/sdk
replace github.com/luxfi/node => /Users/z/work/lux/node
require (
github.com/btcsuite/btcd v0.24.2
github.com/btcsuite/btcd/btcutil v1.1.6
github.com/dgraph-io/badger/v4 v4.8.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1
github.com/luxfi/config v1.0.0
github.com/luxfi/consensus v1.22.41
github.com/luxfi/constants v1.2.8
github.com/luxfi/consensus v1.22.46
github.com/luxfi/constants v1.3.0
github.com/luxfi/crypto v1.17.26
github.com/luxfi/genesis v1.5.11
github.com/luxfi/geth v1.16.60
github.com/luxfi/genesis v1.5.16
github.com/luxfi/geth v1.16.64
github.com/luxfi/go-bip39 v1.1.2
github.com/luxfi/ids v1.2.5
github.com/luxfi/keys v1.0.3
github.com/luxfi/log v1.1.26
github.com/luxfi/math v1.1.1
github.com/luxfi/math v1.2.0
github.com/luxfi/metric v1.4.8
github.com/luxfi/node v1.22.61
github.com/luxfi/units v1.0.0
@@ -29,11 +38,10 @@ require (
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.11.1
github.com/tyler-smith/go-bip32 v1.0.0
github.com/tyler-smith/go-bip39 v1.1.0
go.uber.org/multierr v1.11.0
golang.org/x/crypto v0.46.0
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
golang.org/x/mod v0.30.0
golang.org/x/mod v0.31.0
golang.org/x/sync v0.19.0
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a
google.golang.org/grpc v1.75.1
@@ -43,14 +51,13 @@ require (
require (
github.com/DataDog/zstd v1.5.7 // indirect
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251211224604-2e727cd2e6fe // indirect
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251221085550-b8e13ca38217 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/btcsuite/btcd/btcutil v1.1.6 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.5 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.2-0.20251204010831-23491bd573cf // indirect
@@ -95,17 +102,19 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/luxfi/address v1.0.0 // indirect
github.com/luxfi/cache v1.1.0 // indirect
github.com/luxfi/database v1.2.14 // indirect
github.com/luxfi/database v1.2.17 // indirect
github.com/luxfi/go-bip32 v1.0.2 // indirect
github.com/luxfi/go-bip39 v1.1.2 // indirect
github.com/luxfi/ledger-lux-go v1.0.2 // indirect
github.com/luxfi/keychain v1.0.0 // indirect
github.com/luxfi/mock v0.1.0 // indirect
github.com/luxfi/p2p v1.5.0 // indirect
github.com/luxfi/p2p v1.18.2 // indirect
github.com/luxfi/sampler v1.0.0 // indirect
github.com/luxfi/staking v1.0.0 // indirect
github.com/luxfi/trace v0.1.4 // indirect
github.com/luxfi/utils v1.1.0 // indirect
github.com/luxfi/vm v1.0.1 // indirect
github.com/luxfi/warp v1.18.0 // indirect
github.com/luxfi/warp v1.18.2 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/otiai10/mint v1.6.3 // indirect
@@ -118,13 +127,13 @@ require (
github.com/prometheus/procfs v0.19.2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/sagikazarmark/locafero v0.10.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/spf13/viper v1.20.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/supranational/blst v0.3.16 // indirect
@@ -143,14 +152,14 @@ require (
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.47.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.39.0 // indirect
golang.org/x/tools v0.40.0 // indirect
gonum.org/v1/gonum v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
+62 -62
View File
@@ -1,15 +1,11 @@
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc=
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw=
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc=
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251211224604-2e727cd2e6fe h1:Z93WiwkZABbBBb0hGVFSF9nofjiYRvdF7PUxB75oeyE=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251211224604-2e727cd2e6fe/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251221085550-b8e13ca38217 h1:RNTau6sw5B3ltE+2GlzEom63eBMzoLUc+dhjmdIwNpI=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251221085550-b8e13ca38217/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
@@ -20,9 +16,12 @@ github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY=
github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU=
github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
@@ -30,6 +29,7 @@ github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/
github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@@ -51,8 +51,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.6.2-0.20251204010831-23491bd573cf h1:+0huF756d72ZdHxachRP5HYmvRkkOk8CLyKUmJM138c=
github.com/cloudflare/circl v1.6.2-0.20251204010831-23491bd573cf/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw=
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo=
@@ -233,44 +231,48 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
github.com/luxfi/address v1.0.0 h1:9BPx5VyUfzIET3xqrSnvhmi3jY9ZIjcThO1dDdHnqlU=
github.com/luxfi/address v1.0.0/go.mod h1:K6EISvpZ4Ovgna4fsqVRv2xCHw7L1J12LxzqD2jAvv0=
github.com/luxfi/cache v1.1.0 h1:6LUyGGZ+rrMAJBbAU6+UwkcamXj3zsboRUodIof2Ong=
github.com/luxfi/cache v1.1.0/go.mod h1:9GvlEEE9rFPaaWxvVpSPwW8ZMo2+8VMNNcuPa4AwzPg=
github.com/luxfi/config v1.0.0 h1:EFIFkdus3YdDklhlwHKJNYLpSVuvWqHm+fs/TGckv5U=
github.com/luxfi/config v1.0.0/go.mod h1:71q4A4m7vj+o1qnL1dNk/tVII+Ugiwvi9+jVMN2xxTU=
github.com/luxfi/consensus v1.22.41 h1:YU5/QI3TADwVpWwPpsxWII8T7Ioohp84nCqadUdHR7w=
github.com/luxfi/consensus v1.22.41/go.mod h1:M7+JXcLOeyCMeNZzH2+UEKwEcfnooON385zTu14decM=
github.com/luxfi/constants v1.2.8 h1:VmtbkwiCz5IEIfjiuLb54L4C7ayn7MqshcWzd+JpIag=
github.com/luxfi/constants v1.2.8/go.mod h1:YyHBi205pBpzgYCmD44QPb6dCOzKTK0jwpGrfB4kcRA=
github.com/luxfi/consensus v1.22.46 h1:w97OZjtW/anWvgM0N/q8Y70LEJlqlFG6yeS10rKcnx4=
github.com/luxfi/consensus v1.22.46/go.mod h1:cbvtxHH5pab/XJIlHekg66tYaIChTwTt9SNMMgz8+as=
github.com/luxfi/constants v1.3.0 h1:nPWEhGyhYt+fJS00TUgV1FsyDlSVvsXf0fXq7UBI1RY=
github.com/luxfi/constants v1.3.0/go.mod h1:xHHeq8ALnhuDrc63B5dIhaOtSetBnA/LO17JbZFd0SU=
github.com/luxfi/crypto v1.17.26 h1:B4hebyl2whu9Og+mb7QoQavD4LM1cUpwWQEnwi3H6fU=
github.com/luxfi/crypto v1.17.26/go.mod h1:v6eaW5ejuzW2gecChWKQqx4CtVhoM1b+e40ro/6WVPg=
github.com/luxfi/database v1.2.14 h1:0lxsLiCUOb/42tffrRFgIQN/imD0N9rZdAb9r97pbJE=
github.com/luxfi/database v1.2.14/go.mod h1:5xrFyVRGw8z50vkwt9fF1GkvkFvvvEqxrdeSZqGar0U=
github.com/luxfi/genesis v1.5.11 h1:Lv8VGecA1fsF0pEH/SF5u4SSK9RXUH3c7XIAQuEOQMw=
github.com/luxfi/genesis v1.5.11/go.mod h1:a8NdOhQxUrl88cvI6dQBIC8PiTbKBe4xH5vbzu4NXRo=
github.com/luxfi/geth v1.16.60 h1:0fm8U815I4U6JhjCDWaVO0cF9jjksggcxSc/TuK3T9M=
github.com/luxfi/geth v1.16.60/go.mod h1:vRIzu5NRkevNIx50kksP+M+xBeLj0jTWbfn7KvMBqSo=
github.com/luxfi/database v1.2.17 h1:1PE0FlAarorfAMl+vAvO6EDbkS+a4ktL7kvtRk58DWY=
github.com/luxfi/database v1.2.17/go.mod h1:IzvNHGFZWdRs5CL5vswqz8/uu8t796cMGlbqPWs032s=
github.com/luxfi/geth v1.16.64 h1:kfuVA6FkgHOsOKiz75e+h6UG4eHLOInqXmJEhx9NKTs=
github.com/luxfi/geth v1.16.64/go.mod h1:7lQ25do4pQizMtrVKdepji2Pl/pL+8DUo5Q0SbSuWpE=
github.com/luxfi/go-bip32 v1.0.2 h1:7vFbb+Wr4Z499q2tuCLdd7wWjtn8sH+HWBlx76mhH9Y=
github.com/luxfi/go-bip32 v1.0.2/go.mod h1:bc7/LXDKAJQZ/F0Xjf5yXaTZxY9/ssLb4FC+Hxn/cDk=
github.com/luxfi/go-bip39 v1.1.2 h1:p+wLMPGs6MLQh7q0YIsmy2EhHL7LHiELEGTJko6t/Jg=
github.com/luxfi/go-bip39 v1.1.2/go.mod h1:96de9VkR2kY/ASAnhMtvt3TSh+PZkAFAngNj0GjRGDo=
github.com/luxfi/ids v1.2.5 h1:w4YSj3S6LQiUXhiChUmcbrMCRMfkCaBFIGn0Eo4ZSxk=
github.com/luxfi/ids v1.2.5/go.mod h1:vZBjJWSmqMIZuodgnoszvTDvXIwsbnhM2pkktg6FGrg=
github.com/luxfi/keychain v1.0.0 h1:FzRW9ZlfsFQZ8RsMGL5X9Jhcg8G1DEmK+WAeGTA2tfM=
github.com/luxfi/keychain v1.0.0/go.mod h1:X7HLk5QRZ1VHJxovAyBL6z7f/nlosoXlveEtvUGhoaw=
github.com/luxfi/keys v1.0.3 h1:O1f2h42G1hI6EIoRPl6zSYwrRF7gtUlskHMHbguRHlE=
github.com/luxfi/keys v1.0.3/go.mod h1:YrmQQEJWaSdDJym1Wzs0ngNzvaBR5oJNRat0sHj6R1o=
github.com/luxfi/ledger-lux-go v1.0.2 h1:+B8wcF+bcmJ2Kr/dszMmbj20q8wUV5jLY60fkDkWqWo=
github.com/luxfi/ledger-lux-go v1.0.2/go.mod h1:BM5rJ3UPxhmElPRqjP+AHbB+JzwylUGmiGA/Gh4SKUU=
github.com/luxfi/log v1.1.26 h1:ECnJ4wV7TF6WZ5gC6CD6ddZ4OhF+zhQij4bgVUhumDg=
github.com/luxfi/log v1.1.26/go.mod h1:ACN1FtMlJ/NRuWROYH2TtiFmJuUbSG+OxINlRzJM/38=
github.com/luxfi/math v1.1.1 h1:lR8iS/WQpbNxZsLFVagVee/t90xs0qohL5eC3kK0JR0=
github.com/luxfi/math v1.1.1/go.mod h1:02NrpmajRYR8V1e6o669DWF12gztdtaF6RpiweVuA/k=
github.com/luxfi/math v1.2.0 h1:cZVQQj1PrIWh3Oy6xfbBL44xay4QFuAXZBg9+pWPBmE=
github.com/luxfi/math v1.2.0/go.mod h1:gcmMy6raVf6xrQW/4UmFXEEXC/UC2tZeGkxdiDzeBSg=
github.com/luxfi/metric v1.4.8 h1:mMbcJW+E1z6qQxn34+BE109HlvT6ciiDXuc10qEBJRs=
github.com/luxfi/metric v1.4.8/go.mod h1:AUQ7NSNz9WndAcr/SKnOkP7XSFFnBXOa+ihtJYfDaQY=
github.com/luxfi/mock v0.1.0 h1:IwElfNu+T9sXvzFX6tudPDx1vqPuACRSRdxpD5lxW+o=
github.com/luxfi/mock v0.1.0/go.mod h1:izF+9K0gGzFC9zERn6Po37v46eLdPB+EIsDjL3GLk+U=
github.com/luxfi/node v1.22.61 h1:BU+hJeyPqxr2nH8hSLzbCJr8tVW1DwroktufzK90XFY=
github.com/luxfi/node v1.22.61/go.mod h1:zTMpqUkfVeer0D6s0ZZShI1W8u+1ZIGotGlkC0h8NsU=
github.com/luxfi/p2p v1.5.0 h1:g03aCreOBRwMXuxFAT2ca0jDbW3RF32vBaM2/rfDmF8=
github.com/luxfi/p2p v1.5.0/go.mod h1:yVPJQbCm2uxnFfDwOiBWh0gfG0GschIroLUsLn5Fhm8=
github.com/luxfi/p2p v1.18.2 h1:elpyXr58wht1KP64D4BcKXft1AEjJ3IRACj/W8vdLI8=
github.com/luxfi/p2p v1.18.2/go.mod h1:eM6XpmBFbDjaI7fFuGYBPab77TS1esxHxAOluBLAC0Y=
github.com/luxfi/precompiles v0.1.8 h1:GWigA4sE+jKbO43Oum4apDcLHbhATv6rRivPsnUc5nY=
github.com/luxfi/precompiles v0.1.8/go.mod h1:bzX6yzT1FNhNwCSBu1XB7X/mQ8WO/vKORtJ4ejUbEHM=
github.com/luxfi/sampler v1.0.0 h1:k8Sf6otW83w4pQp0jXLA+g3J/joB7w7SqXQsWmNTOV0=
github.com/luxfi/sampler v1.0.0/go.mod h1:f96/ozlj9vFfZj+akLtrHn4VpulQahwB+MQQhpeIekk=
github.com/luxfi/staking v1.0.0 h1:pYI7XMGGb/zK2ATfTIkush/7HPWE7IS2yfmJqj4nzes=
github.com/luxfi/staking v1.0.0/go.mod h1:E+fm3upfQTOu0fLCr3Uo4xYgAm7r/vEzgcUpNAhJuSE=
github.com/luxfi/trace v0.1.4 h1:ttCRyXGwWuz232se+lIUqhWHBoTuvPLhHH/hLWyqtaM=
github.com/luxfi/trace v0.1.4/go.mod h1:Az7HWh+PCuPftXjQu+ssjv51nKauaFu+q2un7bmZYBA=
github.com/luxfi/units v1.0.0 h1:2aNVB+WsP1XeDob71IsO0w3jJqP3FtZdYnFsmORkJZg=
@@ -279,8 +281,8 @@ github.com/luxfi/utils v1.1.0 h1:ti7HvjNwJd4ILDMERJtOAWE9mF8l+zqDVkgWnF7Agic=
github.com/luxfi/utils v1.1.0/go.mod h1:ABqhBdGNig0CaDcnNYldv1byS0BTNi5IKPbJSPF1p98=
github.com/luxfi/vm v1.0.1 h1:eZqrp6mEFPkhiv5kf5ZXmYAbXXjpS9axpUQG3cd3yts=
github.com/luxfi/vm v1.0.1/go.mod h1:PJinuF4McLaHN57iKf8p7h89wIQhQT//LD/fac+/Dk8=
github.com/luxfi/warp v1.18.0 h1:4FRpB+mnrQeOhPvcKUWOdQbNODV1bY67ll4FKi1mYKc=
github.com/luxfi/warp v1.18.0/go.mod h1:8kjDhuE5UKdd0IUGvoD2rnNphLuATCBOddyriz91pTM=
github.com/luxfi/warp v1.18.2 h1:TWmXjKXI7nxaDOnXIrw8BQTI/nCEniA17bs06bwNB4Q=
github.com/luxfi/warp v1.18.2/go.mod h1:qu3TZv98repNhIDf04I6pzXePSgHjzP+JoSfWmF4PBA=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -328,18 +330,18 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63 h1:+FZIDR/D97YOPik4N4lPDaUcLDF/EQPogxtlHB2ZZRM=
github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg=
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -367,8 +369,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
@@ -377,23 +379,22 @@ github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrel
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -415,12 +416,10 @@ github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8O
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tyler-smith/go-bip32 v1.0.0 h1:sDR9juArbUgX+bO/iblgZnMPeWY1KZMUC2AFUJdv5KE=
github.com/tyler-smith/go-bip32 v1.0.0/go.mod h1:onot+eHknzV4BVPwrzqY5OoVpyCvnwD7lMawL5aQupE=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -448,6 +447,8 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -456,11 +457,12 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -472,8 +474,8 @@ golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1Rac
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -486,8 +488,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -535,8 +537,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -577,5 +579,3 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
+34 -7
View File
@@ -17,8 +17,9 @@ import (
"strings"
"time"
"github.com/luxfi/genesis/pkg/genesis"
"github.com/luxfi/keys"
"github.com/luxfi/genesis/pkg/genesis"
"github.com/luxfi/node/vms/platformvm/reward"
"github.com/luxfi/node/vms/components/lux"
@@ -1277,23 +1278,26 @@ type wallet struct {
// getDefaultKey loads the first key from ~/.lux/keys for wallet operations.
// Priority: LUX_MNEMONIC > LUX_PRIVATE_KEY > disk keys
func getDefaultKey() (*secp256k1.PrivateKey, error) {
// If LUX_MNEMONIC is set, derive key from mnemonic (index 0)
// If LUX_MNEMONIC is set, derive key using BIP44 path m/44'/9000'/0'/0/0 (LUX native)
// CRITICAL: This MUST match the derivation path used in genesis allocations
// (keys.DeriveValidatorFromMnemonic uses m/44'/9000'/0'/0/{index})
if mnemonic := os.Getenv("LUX_MNEMONIC"); mnemonic != "" {
fmt.Printf("🔑 getDefaultKey: Using LUX_MNEMONIC (len=%d)\n", len(mnemonic))
// Use the SAME derivation function as genesis allocations
// This ensures wallet operations use keys that have funds allocated in genesis
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
return nil, fmt.Errorf("failed to convert key: %w", 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)
fmt.Printf("🔑 Wallet address (from mnemonic m/44'/9000'/0'/0/0): %s\n", walletAddr.String())
return privKey, nil
}
@@ -2440,6 +2444,29 @@ func (ln *localNetwork) setBlockchainConfigFiles(
participants = nodeNames
}
chainAlias := blockchainTxs[i].ID().String()
// For EVM chains, write genesis.json to chainConfigs directory
// This is required for the EVM to load the chain configuration
if len(chainSpec.Genesis) > 0 {
for _, nodeName := range participants {
_, b := ln.nodes[nodeName]
if !b {
return nil, fmt.Errorf("participant node %s is not in network nodes", nodeName)
}
// Initialize GenesisConfigFiles map if nil
if ln.nodes[nodeName].config.GenesisConfigFiles == nil {
ln.nodes[nodeName].config.GenesisConfigFiles = make(map[string]string)
}
ln.nodes[nodeName].config.GenesisConfigFiles[chainAlias] = string(chainSpec.Genesis)
nodesToRestart.Add(nodeName)
}
log.Info("set genesis config for chain",
"chainAlias", chainAlias,
"participants", len(participants),
"genesisLength", len(chainSpec.Genesis),
)
}
// update config info. set defaults and node specifics
if chainSpec.ChainConfig != nil || len(chainSpec.PerNodeChainConfig) != 0 {
for _, nodeName := range participants {
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -2,14 +2,16 @@
"consensus-api-enabled": false,
"geth-admin-api-enabled": false,
"geth-admin-api-dir": "",
"eth-apis": ["eth", "eth-filter", "net", "web3", "internal-eth", "internal-blockchain", "internal-transaction", "internal-account"],
"eth-apis": ["eth", "eth-filter", "net", "web3", "internal-eth", "internal-blockchain", "internal-transaction", "internal-account", "admin"],
"admin-api-enabled": true,
"continuous-profiler-dir": "",
"continuous-profiler-frequency": 900000000000,
"continuous-profiler-max-files": 5,
"rpc-gas-cap": 50000000,
"rpc-tx-fee-cap": 100,
"preimages-enabled": false,
"pruning-enabled": true,
"pruning-enabled": false,
"allow-missing-tries": true,
"snapshot-async": true,
"snapshot-verification-enabled": false,
"metrics-enabled": false,
+1 -2
View File
@@ -7,8 +7,7 @@
"index-enabled":true,
"log-display-level":"ERROR",
"log-level": "INFO",
"sybil-protection-enabled": false,
"sybil-protection-disabled-weight": 100,
"sybil-protection-enabled": true,
"network-health-min-conn-peers": 0,
"track-all-chains": true
}
+119 -3
View File
@@ -10,6 +10,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/luxfi/constants"
@@ -335,6 +336,85 @@ func NewLocalConfig(binaryPath string, numNodes uint32) (network.Config, error)
return NewConfigForNetwork(binaryPath, numNodes, configs.CustomID)
}
// NewCanonicalMainnetConfig creates a mainnet network config using the CANONICAL genesis bytes.
// This function loads the pre-serialized genesis.json file directly, ensuring byte-for-byte
// deterministic output. Use this to avoid "db contains invalid genesis hash" errors on restart.
//
// CRITICAL: For mainnet/testnet, always use this function or NewCanonicalTestnetConfig
// rather than functions that regenerate genesis (which causes non-deterministic bytes).
func NewCanonicalMainnetConfig(binaryPath string, numNodes uint32) (network.Config, error) {
return newCanonicalConfig(binaryPath, numNodes, configs.MainnetID, 9630)
}
// NewCanonicalTestnetConfig creates a testnet network config using the CANONICAL genesis bytes.
// See NewCanonicalMainnetConfig for details on why this is critical.
func NewCanonicalTestnetConfig(binaryPath string, numNodes uint32) (network.Config, error) {
return newCanonicalConfig(binaryPath, numNodes, configs.TestnetID, 9640)
}
// NewCanonicalDevnetConfig creates a devnet network config using the CANONICAL genesis bytes.
// See NewCanonicalMainnetConfig for details on why this is critical.
func NewCanonicalDevnetConfig(binaryPath string, numNodes uint32) (network.Config, error) {
return newCanonicalConfig(binaryPath, numNodes, configs.DevnetID, 9650)
}
// NewCanonicalCustomConfig creates a custom (local) network config using the CANONICAL genesis bytes.
// See NewCanonicalMainnetConfig for details on why this is critical.
func NewCanonicalCustomConfig(binaryPath string, numNodes uint32) (network.Config, error) {
return newCanonicalConfig(binaryPath, numNodes, configs.CustomID, 9660)
}
// newCanonicalConfig creates a network config with canonical (pre-serialized) genesis bytes
// and loads validator keys from ~/.lux/keys/node{0..n-1}/
func newCanonicalConfig(binaryPath string, numNodes uint32, networkID uint32, portBase int) (network.Config, error) {
// Load CANONICAL genesis bytes - no parsing/re-serialization
genesisBytes, err := configs.GetCanonicalGenesisBytes(networkID)
if err != nil {
return network.Config{}, fmt.Errorf("failed to load canonical genesis: %w", err)
}
// Start with default config
netConfig := NewDefaultConfig(binaryPath)
netConfig.Genesis = string(genesisBytes)
// Load validator keys from ~/.lux/keys/
keysDir := os.Getenv("LUX_KEYS_DIR")
if keysDir == "" {
keysDir = validatorKeysDir()
}
ks := keys.NewKeyStore(keysDir)
// Load keys for each node
nodeConfigs := make([]node.Config, numNodes)
for i := uint32(0); i < numNodes; i++ {
name := fmt.Sprintf("node%d", i)
vk, err := ks.Load(name)
if err != nil {
return network.Config{}, fmt.Errorf("failed to load validator key %s from %s: %w (run 'lux key generate' first)", name, keysDir, err)
}
port := portBase + int(i)*2
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(" Loaded validator %d: %s\n", i, vk.NodeID.String())
}
netConfig.NodeConfigs = nodeConfigs
fmt.Printf("✅ Loaded canonical genesis for network %d with %d validators\n", networkID, numNodes)
return netConfig, nil
}
// NewConfigForNetworkWithCustomGenesis creates a network config with a custom genesis string.
// Use this for networks not defined in the configs package or for testing.
func NewConfigForNetworkWithCustomGenesis(binaryPath string, numNodes uint32, genesisJSON string) (network.Config, error) {
@@ -394,11 +474,15 @@ func NewConfigForNetworkWithCustomGenesis(binaryPath string, numNodes uint32, ge
// - ec/private.key for P-Chain addresses (optional)
func NewConfigWithPreExistingKeys(binaryPath string, networkID uint32, keysDir string) (network.Config, error) {
// Determine port base based on network ID
// Mainnet (96369): 9630 base
// Testnet (96368): 9640 base
// Mainnet (1): 9630 base
// Testnet (2): 9640 base
// Devnet (3): 9650 base
portBase := 9630
if networkID == configs.TestnetChainID {
switch networkID {
case constants.TestnetID: // 2
portBase = 9640
case constants.DevnetID: // 3
portBase = 9650
}
// Get genesis for the specified network
@@ -920,6 +1004,38 @@ func NewConfigFromMnemonic(binaryPath string, networkID uint32, numNodes uint32)
genesis["initialStakers"] = initialStakers
genesis["allocations"] = allocations
// CRITICAL: Also update xChainGenesis.allocations!
// The X-chain reads allocations from the embedded xChainGenesis JSON string,
// NOT from the top-level allocations array. We must update both.
if xChainGenesisStr, ok := genesis["xChainGenesis"].(string); ok {
var xChainGenesis map[string]interface{}
if err := json.Unmarshal([]byte(xChainGenesisStr), &xChainGenesis); err == nil {
// Build X-chain specific allocations (only X-chain addresses, use avaxAddr format)
xAllocations := make([]map[string]interface{}, 0)
for _, alloc := range allocations {
allocMap := alloc.(map[string]interface{})
luxAddr, _ := allocMap["luxAddr"].(string)
// Only include X-chain allocations (start with "X-")
if strings.HasPrefix(luxAddr, "X-") {
initialAmount, _ := allocMap["initialAmount"].(uint64)
if initialAmount > 0 {
xAllocations = append(xAllocations, map[string]interface{}{
"avaxAddr": luxAddr, // X-chain uses avaxAddr field name
"ethAddr": allocMap["ethAddr"],
"initialAmount": initialAmount,
"unlockSchedule": []interface{}{},
})
}
}
}
xChainGenesis["allocations"] = xAllocations
if updatedXChain, err := json.Marshal(xChainGenesis); err == nil {
genesis["xChainGenesis"] = string(updatedXChain)
fmt.Printf("✅ Updated xChainGenesis with %d allocations\n", len(xAllocations))
}
}
}
// 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
+58 -7
View File
@@ -11,9 +11,11 @@ import (
"path/filepath"
"time"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/node/config"
"github.com/luxfi/constants"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/netrunner/utils"
"github.com/luxfi/node/config"
)
func init() {
@@ -95,12 +97,50 @@ func writeFiles(networkID uint32, genesis []byte, nodeRootDir string, nodeConfig
contents: decodedStakingSigningKey,
},
}
// 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 {
// Check if this is a resume from existing state:
// If genesis.json already exists AND db directory has chain data, preserve existing genesis
// This prevents the "db contains invalid genesis hash" error when resuming from snapshots
// EXCEPTION: For canonical networks (mainnet/testnet), if the existing genesis has a different
// networkID, we must clean the db and write the correct canonical genesis.
existingGenesisPath := filepath.Join(nodeRootDir, genesisFileName)
dbDir := filepath.Join(nodeRootDir, "db")
shouldPreserveGenesis := false
if _, err := os.Stat(existingGenesisPath); err == nil {
// Genesis exists, check if db has data (indicating we're resuming)
if entries, err := os.ReadDir(dbDir); err == nil && len(entries) > 0 {
shouldPreserveGenesis = true
// For canonical networks, verify the existing genesis matches
if networkID == constants.MainnetID || networkID == constants.TestnetID {
existingGenesisBytes, readErr := os.ReadFile(existingGenesisPath)
if readErr == nil {
existingNetworkID, parseErr := utils.NetworkIDFromGenesis(existingGenesisBytes)
if parseErr == nil && existingNetworkID != networkID {
// NetworkID mismatch! Clean db and write correct canonical genesis
log.Info("Genesis networkID mismatch for canonical network, cleaning db",
log.Uint32("existing", existingNetworkID),
log.Uint32("target", networkID),
log.String("dbDir", dbDir))
if err := os.RemoveAll(dbDir); err != nil {
return nil, fmt.Errorf("failed to clean db directory for canonical network: %w", err)
}
// Recreate empty db directory
if err := os.MkdirAll(dbDir, 0o750); err != nil {
return nil, fmt.Errorf("failed to recreate db directory: %w", err)
}
shouldPreserveGenesis = false
}
}
}
}
}
if len(genesis) > 0 && !shouldPreserveGenesis {
// Write new genesis (fresh start or custom genesis with mnemonic-derived validators)
files = append(files, file{
flagValue: filepath.Join(nodeRootDir, genesisFileName),
path: filepath.Join(nodeRootDir, genesisFileName),
flagValue: existingGenesisPath,
path: existingGenesisPath,
pathKey: config.GenesisFileKey,
contents: genesis,
})
@@ -120,6 +160,10 @@ func writeFiles(networkID uint32, genesis []byte, nodeRootDir string, nodeConfig
return nil, fmt.Errorf("couldn't write file at %q: %w", f.path, err)
}
}
// If preserving existing genesis, add the flag without writing the file
if shouldPreserveGenesis {
flags[config.GenesisFileKey] = existingGenesisPath
}
// chain configs dir
chainConfigDir := filepath.Join(nodeRootDir, chainConfigSubDir)
if err := os.MkdirAll(chainConfigDir, 0o750); err != nil {
@@ -139,6 +183,13 @@ func writeFiles(networkID uint32, genesis []byte, nodeRootDir string, nodeConfig
return nil, fmt.Errorf("couldn't write file at %q: %w", chainConfigPath, err)
}
}
// genesis files for EVM chains
for chainAlias, genesisFile := range nodeConfig.GenesisConfigFiles {
genesisPath := filepath.Join(chainConfigDir, chainAlias, genesisFileName)
if err := createFileAndWrite(genesisPath, []byte(genesisFile)); err != nil {
return nil, fmt.Errorf("couldn't write genesis file at %q: %w", genesisPath, err)
}
}
// network upgrades
for chainAlias, chainUpgradeFile := range nodeConfig.UpgradeConfigFiles {
chainUpgradePath := filepath.Join(chainConfigDir, chainAlias, upgradeConfigFileName)
+72
View File
@@ -0,0 +1,72 @@
package local
import (
"fmt"
"os"
"testing"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/luxfi/go-bip39"
luxcrypto "github.com/luxfi/crypto/secp256k1"
"golang.org/x/crypto/sha3"
)
func TestMnemonicDerivation(t *testing.T) {
mnemonic := os.Getenv("LUX_MNEMONIC")
if mnemonic == "" {
t.Skip("LUX_MNEMONIC not set")
}
fmt.Printf("Mnemonic: %s...\n", mnemonic[:20])
if !bip39.IsMnemonicValid(mnemonic) {
t.Fatal("Invalid mnemonic")
}
seed := bip39.NewSeed(mnemonic, "")
// Derive using BIP44 m/44'/60'/0'/0/0
masterKey, _ := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
purpose, _ := masterKey.Derive(hdkeychain.HardenedKeyStart + 44)
coinType, _ := purpose.Derive(hdkeychain.HardenedKeyStart + 60)
account, _ := coinType.Derive(hdkeychain.HardenedKeyStart + 0)
change, _ := account.Derive(0)
addressKey, _ := change.Derive(0)
ecPrivKey, _ := addressKey.ECPrivKey()
privKeyBytes := ecPrivKey.Serialize()
privKey, err := luxcrypto.ToPrivateKey(privKeyBytes)
if err != nil {
t.Fatalf("Error: %v", err)
}
pubKey := privKey.PublicKey()
shortID := pubKey.Address()
// Get Ethereum address
pubKeyBytes := pubKey.Bytes()
hash := sha3.NewLegacyKeccak256()
hash.Write(pubKeyBytes[1:]) // skip the 0x04 prefix
ethAddr := hash.Sum(nil)[12:]
fmt.Printf("\n=== Key Derivation Results (m/44'/60'/0'/0/0) ===\n")
fmt.Printf("Lux Short ID: %s\n", shortID.String())
fmt.Printf("Ethereum Address: 0x%x\n", ethAddr)
fmt.Printf("Expected Treasury: 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714\n")
// Also derive using coin type 9000 for comparison
coinType9000, _ := purpose.Derive(hdkeychain.HardenedKeyStart + 9000)
account9000, _ := coinType9000.Derive(hdkeychain.HardenedKeyStart + 0)
change9000, _ := account9000.Derive(0)
addressKey9000, _ := change9000.Derive(0)
ecPrivKey9000, _ := addressKey9000.ECPrivKey()
privKey9000, _ := luxcrypto.ToPrivateKey(ecPrivKey9000.Serialize())
shortID9000 := privKey9000.PublicKey().Address()
fmt.Printf("\n=== Comparison: Coin Type 9000 (m/44'/9000'/0'/0/0) ===\n")
fmt.Printf("Lux Short ID (9000): %s\n", shortID9000.String())
fmt.Printf("\n=== Genesis Treasury P-chain Address ===\n")
fmt.Printf("Expected: P-lux1jqg73zp9r2c98daarnd4nrd5l80dj3c5eha5fl\n")
}
+7 -5
View File
@@ -55,7 +55,7 @@ const (
// healthCheckFreq reduced from 3s to 1s for faster health polling
healthCheckFreq = 1 * time.Second
DefaultNumNodes = 5
snapshotPrefix = "anr-snapshot-"
snapshotPrefix = "lux-snapshot-"
networkRootDirPrefix = "network"
defaultDBSubdir = "db"
defaultLogsSubdir = "logs"
@@ -114,6 +114,8 @@ type localNetwork struct {
binaryPath string
// chain config files to use per default
chainConfigFiles map[string]string
// genesis config files for EVM chains (per blockchain ID)
genesisConfigFiles map[string]string
// upgrade config files to use per default
upgradeConfigFiles map[string]string
// P-chain config files to use per default
@@ -546,14 +548,14 @@ func (ln *localNetwork) loadConfig(ctx context.Context, networkConfig network.Co
if ln.chainConfigFiles == nil {
ln.chainConfigFiles = map[string]string{}
}
ln.genesisConfigFiles = networkConfig.GenesisConfigFiles
if ln.genesisConfigFiles == nil {
ln.genesisConfigFiles = map[string]string{}
}
ln.upgradeConfigFiles = networkConfig.UpgradeConfigFiles
if ln.upgradeConfigFiles == nil {
ln.upgradeConfigFiles = map[string]string{}
}
ln.chainConfigFiles = networkConfig.ChainConfigFiles
if ln.chainConfigFiles == nil {
ln.chainConfigFiles = map[string]string{}
}
// Sort node configs so beacons start first
var nodeConfigs []node.Config
+3 -2
View File
@@ -12,15 +12,16 @@ import (
"github.com/luxfi/netrunner/api"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/netrunner/network/node/status"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/node/message"
"github.com/luxfi/node/network/peer"
"github.com/luxfi/node/network/throttling"
"github.com/luxfi/node/network/tracker"
"github.com/luxfi/node/utils/compression"
"github.com/luxfi/consensus/validator" // package name is validators
"github.com/luxfi/node/staking"
"github.com/luxfi/node/utils"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/log"
"github.com/luxfi/math/set"
@@ -99,7 +100,7 @@ func (node *localNode) AttachPeer(ctx context.Context, router peer.InboundHandle
}
mc, err := message.NewCreator(
prometheus.NewRegistry(),
constants.DefaultNetworkCompressionType,
compression.TypeZstd,
10*time.Second,
)
if err != nil {
+117
View File
@@ -151,6 +151,7 @@ func (ln *localNetwork) SaveSnapshot(ctx context.Context, snapshotName string) (
NodeConfigs: []node.Config{},
BinaryPath: ln.binaryPath,
ChainConfigFiles: ln.chainConfigFiles,
GenesisConfigFiles: ln.genesisConfigFiles,
UpgradeConfigFiles: ln.upgradeConfigFiles,
PChainConfigFiles: ln.pChainConfigFiles,
}
@@ -293,6 +294,122 @@ func (ln *localNetwork) loadSnapshot(
return ln.loadConfig(ctx, networkConfig)
}
// SaveHotSnapshot saves a snapshot without stopping the network.
// Uses copy-on-write on filesystems that support it (APFS on macOS).
// WARNING: Hot snapshots may have minor inconsistencies if writes occur during copy.
// For guaranteed consistency, use SaveSnapshot which stops the network.
func (ln *localNetwork) SaveHotSnapshot(ctx context.Context, snapshotName string) (string, error) {
ln.lock.RLock() // Read lock - doesn't block network operations
defer ln.lock.RUnlock()
if ln.stopCalled() {
return "", network.ErrStopped
}
if len(snapshotName) == 0 {
return "", fmt.Errorf("invalid snapshotName %q", snapshotName)
}
// check if snapshot already exists
snapshotDir := filepath.Join(ln.snapshotsDir, snapshotPrefix+snapshotName)
if _, err := os.Stat(snapshotDir); err == nil {
return "", fmt.Errorf("snapshot %q already exists", snapshotName)
}
// Collect node info (read-only, safe during operation)
nodesConfig := map[string]node.Config{}
nodesDBDir := map[string]string{}
for nodeName, node := range ln.nodes {
nodeConfig := node.config
nodeConfig.Flags = maps.Clone(nodeConfig.Flags)
nodesConfig[nodeName] = nodeConfig
nodesDBDir[nodeName] = node.GetDbDir()
}
// Preserve current node ports
for nodeName, nodeConfig := range nodesConfig {
nodeConfig.Flags[config.HTTPPortKey] = ln.nodes[nodeName].GetAPIPort()
nodeConfig.Flags[config.StakingPortKey] = ln.nodes[nodeName].GetP2PPort()
}
// Make copy of network flags
networkConfigFlags := maps.Clone(ln.flags)
delete(networkConfigFlags, config.DataDirKey)
delete(networkConfigFlags, config.LogsDirKey)
for nodeName, nodeConfig := range nodesConfig {
if nodeConfig.ConfigFile != "" {
var err error
nodeConfig.ConfigFile, err = utils.SetJSONKey(nodeConfig.ConfigFile, config.LogsDirKey, "")
if err != nil {
return "", err
}
}
delete(nodeConfig.Flags, config.DataDirKey)
delete(nodeConfig.Flags, config.LogsDirKey)
nodesConfig[nodeName] = nodeConfig
}
// Sync filesystem to flush pending writes
syncFilesystem()
// Create main snapshot dirs
snapshotDBDir := filepath.Join(snapshotDir, defaultDBSubdir)
if err := os.MkdirAll(snapshotDBDir, os.ModePerm); err != nil {
return "", err
}
// Copy db directories (hot - network still running)
// On APFS (macOS), dircopy uses clonefile which is CoW and instant
for _, nodeConfig := range nodesConfig {
sourceDBDir, ok := nodesDBDir[nodeConfig.Name]
if !ok {
return "", fmt.Errorf("failure obtaining db path for node %q", nodeConfig.Name)
}
sourceDBDir = filepath.Join(sourceDBDir, constants.NetworkName(ln.networkID))
targetDBDir := filepath.Join(filepath.Join(snapshotDBDir, nodeConfig.Name), constants.NetworkName(ln.networkID))
if err := dircopy.Copy(sourceDBDir, targetDBDir); err != nil {
return "", fmt.Errorf("failure saving node %q db dir: %w", nodeConfig.Name, err)
}
}
// Save network config
networkConfig := network.Config{
Genesis: string(ln.genesis),
Flags: networkConfigFlags,
NodeConfigs: []node.Config{},
BinaryPath: ln.binaryPath,
ChainConfigFiles: ln.chainConfigFiles,
GenesisConfigFiles: ln.genesisConfigFiles,
UpgradeConfigFiles: ln.upgradeConfigFiles,
PChainConfigFiles: ln.pChainConfigFiles,
}
networkConfig.NodeConfigs = append(networkConfig.NodeConfigs, maps.Values(nodesConfig)...)
networkConfigJSON, err := json.MarshalIndent(networkConfig, "", " ")
if err != nil {
return "", err
}
if err := createFileAndWrite(filepath.Join(snapshotDir, "network.json"), networkConfigJSON); err != nil {
return "", err
}
// Save dynamic network state
chainID2ElasticChainID := map[string]string{}
for chainID, elasticChainID := range ln.chainID2ElasticChainID {
chainID2ElasticChainID[chainID.String()] = elasticChainID.String()
}
networkState := NetworkState{
ChainID2ElasticChainID: chainID2ElasticChainID,
}
networkStateJSON, err := json.MarshalIndent(networkState, "", " ")
if err != nil {
return "", err
}
if err := createFileAndWrite(filepath.Join(snapshotDir, "state.json"), networkStateJSON); err != nil {
return "", err
}
ln.logger.Info("Hot snapshot saved", log.String("snapshot", snapshotName), log.String("path", snapshotDir))
return snapshotDir, nil
}
// Remove network snapshot
func (ln *localNetwork) RemoveSnapshot(snapshotName string) error {
snapshotDir := filepath.Join(ln.snapshotsDir, snapshotPrefix+snapshotName)
+2
View File
@@ -71,6 +71,8 @@ type Config struct {
BinaryPath string `json:"binaryPath"`
// Chain config files to use per default, if not specified in node config
ChainConfigFiles map[string]string `json:"chainConfigFiles"`
// Genesis config files for EVM chains (per blockchain ID)
GenesisConfigFiles map[string]string `json:"genesisConfigFiles"`
// Upgrade config files to use per default, if not specified in node config
UpgradeConfigFiles map[string]string `json:"upgradeConfigFiles"`
// P-Chain config files to use per default, if not specified in node config
+4
View File
@@ -99,6 +99,10 @@ type Network interface {
// Network is stopped in order to do a safe preservation
// Returns the full local path to the snapshot dir
SaveSnapshot(context.Context, string) (string, error)
// SaveHotSnapshot saves a snapshot without stopping the network
// Uses CoW on APFS (macOS) for instant snapshots
// WARNING: May have minor inconsistencies if writes occur during copy
SaveHotSnapshot(context.Context, string) (string, error)
// Remove network snapshot
RemoveSnapshot(string) error
// Get name of available snapshots
+2
View File
@@ -79,6 +79,8 @@ type Config struct {
ConfigFile string `json:"configFile"`
// May be nil.
ChainConfigFiles map[string]string `json:"chainConfigFiles"`
// May be nil. Genesis files for EVM chains (genesis.json)
GenesisConfigFiles map[string]string `json:"genesisConfigFiles"`
// May be nil.
UpgradeConfigFiles map[string]string `json:"upgradeConfigFiles"`
// May be nil. P-Chain chain (formerly subnet) config files.
+35 -32
View File
@@ -3796,7 +3796,7 @@ const file_rpcpb_rpc_proto_rawDesc = "" +
"\x18GetSnapshotNamesResponse\x12%\n" +
"\x0esnapshot_names\x18\x01 \x03(\tR\rsnapshotNames2S\n" +
"\vPingService\x12D\n" +
"\x04Ping\x12\x12.rpcpb.PingRequest\x1a\x13.rpcpb.PingResponse\"\x13\x82\xd3\xe4\x93\x02\r:\x01*\"\b/v1/ping2\xe7\x14\n" +
"\x04Ping\x12\x12.rpcpb.PingRequest\x1a\x13.rpcpb.PingResponse\"\x13\x82\xd3\xe4\x93\x02\r:\x01*\"\b/v1/ping2\xdb\x15\n" +
"\x0eControlService\x12d\n" +
"\n" +
"RPCVersion\x12\x18.rpcpb.RPCVersionRequest\x1a\x19.rpcpb.RPCVersionResponse\"!\x82\xd3\xe4\x93\x02\x1b:\x01*\"\x16/v1/control/rpcversion\x12P\n" +
@@ -3822,7 +3822,8 @@ const file_rpcpb_rpc_proto_rawDesc = "" +
"\n" +
"AttachPeer\x12\x18.rpcpb.AttachPeerRequest\x1a\x19.rpcpb.AttachPeerResponse\"!\x82\xd3\xe4\x93\x02\x1b:\x01*\"\x16/v1/control/attachpeer\x12\x88\x01\n" +
"\x13SendOutboundMessage\x12!.rpcpb.SendOutboundMessageRequest\x1a\".rpcpb.SendOutboundMessageResponse\"*\x82\xd3\xe4\x93\x02$:\x01*\"\x1f/v1/control/sendoutboundmessage\x12l\n" +
"\fSaveSnapshot\x12\x1a.rpcpb.SaveSnapshotRequest\x1a\x1b.rpcpb.SaveSnapshotResponse\"#\x82\xd3\xe4\x93\x02\x1d:\x01*\"\x18/v1/control/savesnapshot\x12l\n" +
"\fSaveSnapshot\x12\x1a.rpcpb.SaveSnapshotRequest\x1a\x1b.rpcpb.SaveSnapshotResponse\"#\x82\xd3\xe4\x93\x02\x1d:\x01*\"\x18/v1/control/savesnapshot\x12r\n" +
"\x0fSaveHotSnapshot\x12\x1a.rpcpb.SaveSnapshotRequest\x1a\x1b.rpcpb.SaveSnapshotResponse\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/v1/control/savehotsnapshot\x12l\n" +
"\fLoadSnapshot\x12\x1a.rpcpb.LoadSnapshotRequest\x1a\x1b.rpcpb.LoadSnapshotResponse\"#\x82\xd3\xe4\x93\x02\x1d:\x01*\"\x18/v1/control/loadsnapshot\x12t\n" +
"\x0eRemoveSnapshot\x12\x1c.rpcpb.RemoveSnapshotRequest\x1a\x1d.rpcpb.RemoveSnapshotResponse\"%\x82\xd3\xe4\x93\x02\x1f:\x01*\"\x1a/v1/control/removesnapshot\x12|\n" +
"\x10GetSnapshotNames\x12\x1e.rpcpb.GetSnapshotNamesRequest\x1a\x1f.rpcpb.GetSnapshotNamesResponse\"'\x82\xd3\xe4\x93\x02!:\x01*\"\x1c/v1/control/getsnapshotnamesB\"Z github.com/luxfi/netrunner;rpcpbb\x06proto3"
@@ -3993,36 +3994,38 @@ var file_rpcpb_rpc_proto_depIdxs = []int32{
50, // 68: rpcpb.ControlService.AttachPeer:input_type -> rpcpb.AttachPeerRequest
52, // 69: rpcpb.ControlService.SendOutboundMessage:input_type -> rpcpb.SendOutboundMessageRequest
54, // 70: rpcpb.ControlService.SaveSnapshot:input_type -> rpcpb.SaveSnapshotRequest
56, // 71: rpcpb.ControlService.LoadSnapshot:input_type -> rpcpb.LoadSnapshotRequest
58, // 72: rpcpb.ControlService.RemoveSnapshot:input_type -> rpcpb.RemoveSnapshotRequest
60, // 73: rpcpb.ControlService.GetSnapshotNames:input_type -> rpcpb.GetSnapshotNamesRequest
1, // 74: rpcpb.PingService.Ping:output_type -> rpcpb.PingResponse
11, // 75: rpcpb.ControlService.RPCVersion:output_type -> rpcpb.RPCVersionResponse
12, // 76: rpcpb.ControlService.Start:output_type -> rpcpb.StartResponse
25, // 77: rpcpb.ControlService.CreateBlockchains:output_type -> rpcpb.CreateBlockchainsResponse
16, // 78: rpcpb.ControlService.TransformElasticChains:output_type -> rpcpb.TransformElasticChainsResponse
19, // 79: rpcpb.ControlService.AddPermissionlessValidator:output_type -> rpcpb.AddPermissionlessValidatorResponse
22, // 80: rpcpb.ControlService.RemoveChainValidator:output_type -> rpcpb.RemoveChainValidatorResponse
27, // 81: rpcpb.ControlService.CreateChains:output_type -> rpcpb.CreateChainsResponse
29, // 82: rpcpb.ControlService.Health:output_type -> rpcpb.HealthResponse
31, // 83: rpcpb.ControlService.URIs:output_type -> rpcpb.URIsResponse
33, // 84: rpcpb.ControlService.WaitForHealthy:output_type -> rpcpb.WaitForHealthyResponse
35, // 85: rpcpb.ControlService.Status:output_type -> rpcpb.StatusResponse
37, // 86: rpcpb.ControlService.StreamStatus:output_type -> rpcpb.StreamStatusResponse
41, // 87: rpcpb.ControlService.RemoveNode:output_type -> rpcpb.RemoveNodeResponse
47, // 88: rpcpb.ControlService.AddNode:output_type -> rpcpb.AddNodeResponse
39, // 89: rpcpb.ControlService.RestartNode:output_type -> rpcpb.RestartNodeResponse
43, // 90: rpcpb.ControlService.PauseNode:output_type -> rpcpb.PauseNodeResponse
45, // 91: rpcpb.ControlService.ResumeNode:output_type -> rpcpb.ResumeNodeResponse
49, // 92: rpcpb.ControlService.Stop:output_type -> rpcpb.StopResponse
51, // 93: rpcpb.ControlService.AttachPeer:output_type -> rpcpb.AttachPeerResponse
53, // 94: rpcpb.ControlService.SendOutboundMessage:output_type -> rpcpb.SendOutboundMessageResponse
55, // 95: rpcpb.ControlService.SaveSnapshot:output_type -> rpcpb.SaveSnapshotResponse
57, // 96: rpcpb.ControlService.LoadSnapshot:output_type -> rpcpb.LoadSnapshotResponse
59, // 97: rpcpb.ControlService.RemoveSnapshot:output_type -> rpcpb.RemoveSnapshotResponse
61, // 98: rpcpb.ControlService.GetSnapshotNames:output_type -> rpcpb.GetSnapshotNamesResponse
74, // [74:99] is the sub-list for method output_type
49, // [49:74] is the sub-list for method input_type
54, // 71: rpcpb.ControlService.SaveHotSnapshot:input_type -> rpcpb.SaveSnapshotRequest
56, // 72: rpcpb.ControlService.LoadSnapshot:input_type -> rpcpb.LoadSnapshotRequest
58, // 73: rpcpb.ControlService.RemoveSnapshot:input_type -> rpcpb.RemoveSnapshotRequest
60, // 74: rpcpb.ControlService.GetSnapshotNames:input_type -> rpcpb.GetSnapshotNamesRequest
1, // 75: rpcpb.PingService.Ping:output_type -> rpcpb.PingResponse
11, // 76: rpcpb.ControlService.RPCVersion:output_type -> rpcpb.RPCVersionResponse
12, // 77: rpcpb.ControlService.Start:output_type -> rpcpb.StartResponse
25, // 78: rpcpb.ControlService.CreateBlockchains:output_type -> rpcpb.CreateBlockchainsResponse
16, // 79: rpcpb.ControlService.TransformElasticChains:output_type -> rpcpb.TransformElasticChainsResponse
19, // 80: rpcpb.ControlService.AddPermissionlessValidator:output_type -> rpcpb.AddPermissionlessValidatorResponse
22, // 81: rpcpb.ControlService.RemoveChainValidator:output_type -> rpcpb.RemoveChainValidatorResponse
27, // 82: rpcpb.ControlService.CreateChains:output_type -> rpcpb.CreateChainsResponse
29, // 83: rpcpb.ControlService.Health:output_type -> rpcpb.HealthResponse
31, // 84: rpcpb.ControlService.URIs:output_type -> rpcpb.URIsResponse
33, // 85: rpcpb.ControlService.WaitForHealthy:output_type -> rpcpb.WaitForHealthyResponse
35, // 86: rpcpb.ControlService.Status:output_type -> rpcpb.StatusResponse
37, // 87: rpcpb.ControlService.StreamStatus:output_type -> rpcpb.StreamStatusResponse
41, // 88: rpcpb.ControlService.RemoveNode:output_type -> rpcpb.RemoveNodeResponse
47, // 89: rpcpb.ControlService.AddNode:output_type -> rpcpb.AddNodeResponse
39, // 90: rpcpb.ControlService.RestartNode:output_type -> rpcpb.RestartNodeResponse
43, // 91: rpcpb.ControlService.PauseNode:output_type -> rpcpb.PauseNodeResponse
45, // 92: rpcpb.ControlService.ResumeNode:output_type -> rpcpb.ResumeNodeResponse
49, // 93: rpcpb.ControlService.Stop:output_type -> rpcpb.StopResponse
51, // 94: rpcpb.ControlService.AttachPeer:output_type -> rpcpb.AttachPeerResponse
53, // 95: rpcpb.ControlService.SendOutboundMessage:output_type -> rpcpb.SendOutboundMessageResponse
55, // 96: rpcpb.ControlService.SaveSnapshot:output_type -> rpcpb.SaveSnapshotResponse
55, // 97: rpcpb.ControlService.SaveHotSnapshot:output_type -> rpcpb.SaveSnapshotResponse
57, // 98: rpcpb.ControlService.LoadSnapshot:output_type -> rpcpb.LoadSnapshotResponse
59, // 99: rpcpb.ControlService.RemoveSnapshot:output_type -> rpcpb.RemoveSnapshotResponse
61, // 100: rpcpb.ControlService.GetSnapshotNames:output_type -> rpcpb.GetSnapshotNamesResponse
75, // [75:101] is the sub-list for method output_type
49, // [49:75] is the sub-list for method input_type
49, // [49:49] is the sub-list for extension type_name
49, // [49:49] is the sub-list for extension extendee
0, // [0:49] is the sub-list for field type_name
+66
View File
@@ -625,6 +625,33 @@ func local_request_ControlService_SaveSnapshot_0(ctx context.Context, marshaler
return msg, metadata, err
}
func request_ControlService_SaveHotSnapshot_0(ctx context.Context, marshaler runtime.Marshaler, client ControlServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq SaveSnapshotRequest
metadata runtime.ServerMetadata
)
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if req.Body != nil {
_, _ = io.Copy(io.Discard, req.Body)
}
msg, err := client.SaveHotSnapshot(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ControlService_SaveHotSnapshot_0(ctx context.Context, marshaler runtime.Marshaler, server ControlServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq SaveSnapshotRequest
metadata runtime.ServerMetadata
)
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SaveHotSnapshot(ctx, &protoReq)
return msg, metadata, err
}
func request_ControlService_LoadSnapshot_0(ctx context.Context, marshaler runtime.Marshaler, client ControlServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq LoadSnapshotRequest
@@ -1149,6 +1176,26 @@ func RegisterControlServiceHandlerServer(ctx context.Context, mux *runtime.Serve
}
forward_ControlService_SaveSnapshot_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodPost, pattern_ControlService_SaveHotSnapshot_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/rpcpb.ControlService/SaveHotSnapshot", runtime.WithHTTPPathPattern("/v1/control/savehotsnapshot"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ControlService_SaveHotSnapshot_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ControlService_SaveHotSnapshot_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodPost, pattern_ControlService_LoadSnapshot_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -1670,6 +1717,23 @@ func RegisterControlServiceHandlerClient(ctx context.Context, mux *runtime.Serve
}
forward_ControlService_SaveSnapshot_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodPost, pattern_ControlService_SaveHotSnapshot_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/rpcpb.ControlService/SaveHotSnapshot", runtime.WithHTTPPathPattern("/v1/control/savehotsnapshot"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ControlService_SaveHotSnapshot_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ControlService_SaveHotSnapshot_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodPost, pattern_ControlService_LoadSnapshot_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -1746,6 +1810,7 @@ var (
pattern_ControlService_AttachPeer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "control", "attachpeer"}, ""))
pattern_ControlService_SendOutboundMessage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "control", "sendoutboundmessage"}, ""))
pattern_ControlService_SaveSnapshot_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "control", "savesnapshot"}, ""))
pattern_ControlService_SaveHotSnapshot_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "control", "savehotsnapshot"}, ""))
pattern_ControlService_LoadSnapshot_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "control", "loadsnapshot"}, ""))
pattern_ControlService_RemoveSnapshot_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "control", "removesnapshot"}, ""))
pattern_ControlService_GetSnapshotNames_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "control", "getsnapshotnames"}, ""))
@@ -1773,6 +1838,7 @@ var (
forward_ControlService_AttachPeer_0 = runtime.ForwardResponseMessage
forward_ControlService_SendOutboundMessage_0 = runtime.ForwardResponseMessage
forward_ControlService_SaveSnapshot_0 = runtime.ForwardResponseMessage
forward_ControlService_SaveHotSnapshot_0 = runtime.ForwardResponseMessage
forward_ControlService_LoadSnapshot_0 = runtime.ForwardResponseMessage
forward_ControlService_RemoveSnapshot_0 = runtime.ForwardResponseMessage
forward_ControlService_GetSnapshotNames_0 = runtime.ForwardResponseMessage
+9
View File
@@ -169,6 +169,15 @@ service ControlService {
};
}
// SaveHotSnapshot saves a snapshot without stopping the network
// Uses Copy-on-Write on APFS (macOS) for instant snapshots
rpc SaveHotSnapshot(SaveSnapshotRequest) returns (SaveSnapshotResponse) {
option (google.api.http) = {
post: "/v1/control/savehotsnapshot"
body: "*"
};
}
rpc LoadSnapshot(LoadSnapshotRequest) returns (LoadSnapshotResponse) {
option (google.api.http) = {
post: "/v1/control/loadsnapshot"
+42
View File
@@ -142,6 +142,7 @@ const (
ControlService_AttachPeer_FullMethodName = "/rpcpb.ControlService/AttachPeer"
ControlService_SendOutboundMessage_FullMethodName = "/rpcpb.ControlService/SendOutboundMessage"
ControlService_SaveSnapshot_FullMethodName = "/rpcpb.ControlService/SaveSnapshot"
ControlService_SaveHotSnapshot_FullMethodName = "/rpcpb.ControlService/SaveHotSnapshot"
ControlService_LoadSnapshot_FullMethodName = "/rpcpb.ControlService/LoadSnapshot"
ControlService_RemoveSnapshot_FullMethodName = "/rpcpb.ControlService/RemoveSnapshot"
ControlService_GetSnapshotNames_FullMethodName = "/rpcpb.ControlService/GetSnapshotNames"
@@ -172,6 +173,9 @@ type ControlServiceClient interface {
AttachPeer(ctx context.Context, in *AttachPeerRequest, opts ...grpc.CallOption) (*AttachPeerResponse, error)
SendOutboundMessage(ctx context.Context, in *SendOutboundMessageRequest, opts ...grpc.CallOption) (*SendOutboundMessageResponse, error)
SaveSnapshot(ctx context.Context, in *SaveSnapshotRequest, opts ...grpc.CallOption) (*SaveSnapshotResponse, error)
// SaveHotSnapshot saves a snapshot without stopping the network
// Uses Copy-on-Write on APFS (macOS) for instant snapshots
SaveHotSnapshot(ctx context.Context, in *SaveSnapshotRequest, opts ...grpc.CallOption) (*SaveSnapshotResponse, error)
LoadSnapshot(ctx context.Context, in *LoadSnapshotRequest, opts ...grpc.CallOption) (*LoadSnapshotResponse, error)
RemoveSnapshot(ctx context.Context, in *RemoveSnapshotRequest, opts ...grpc.CallOption) (*RemoveSnapshotResponse, error)
GetSnapshotNames(ctx context.Context, in *GetSnapshotNamesRequest, opts ...grpc.CallOption) (*GetSnapshotNamesResponse, error)
@@ -404,6 +408,16 @@ func (c *controlServiceClient) SaveSnapshot(ctx context.Context, in *SaveSnapsho
return out, nil
}
func (c *controlServiceClient) SaveHotSnapshot(ctx context.Context, in *SaveSnapshotRequest, opts ...grpc.CallOption) (*SaveSnapshotResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SaveSnapshotResponse)
err := c.cc.Invoke(ctx, ControlService_SaveHotSnapshot_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *controlServiceClient) LoadSnapshot(ctx context.Context, in *LoadSnapshotRequest, opts ...grpc.CallOption) (*LoadSnapshotResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(LoadSnapshotResponse)
@@ -459,6 +473,9 @@ type ControlServiceServer interface {
AttachPeer(context.Context, *AttachPeerRequest) (*AttachPeerResponse, error)
SendOutboundMessage(context.Context, *SendOutboundMessageRequest) (*SendOutboundMessageResponse, error)
SaveSnapshot(context.Context, *SaveSnapshotRequest) (*SaveSnapshotResponse, error)
// SaveHotSnapshot saves a snapshot without stopping the network
// Uses Copy-on-Write on APFS (macOS) for instant snapshots
SaveHotSnapshot(context.Context, *SaveSnapshotRequest) (*SaveSnapshotResponse, error)
LoadSnapshot(context.Context, *LoadSnapshotRequest) (*LoadSnapshotResponse, error)
RemoveSnapshot(context.Context, *RemoveSnapshotRequest) (*RemoveSnapshotResponse, error)
GetSnapshotNames(context.Context, *GetSnapshotNamesRequest) (*GetSnapshotNamesResponse, error)
@@ -535,6 +552,9 @@ func (UnimplementedControlServiceServer) SendOutboundMessage(context.Context, *S
func (UnimplementedControlServiceServer) SaveSnapshot(context.Context, *SaveSnapshotRequest) (*SaveSnapshotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SaveSnapshot not implemented")
}
func (UnimplementedControlServiceServer) SaveHotSnapshot(context.Context, *SaveSnapshotRequest) (*SaveSnapshotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SaveHotSnapshot not implemented")
}
func (UnimplementedControlServiceServer) LoadSnapshot(context.Context, *LoadSnapshotRequest) (*LoadSnapshotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method LoadSnapshot not implemented")
}
@@ -936,6 +956,24 @@ func _ControlService_SaveSnapshot_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler)
}
func _ControlService_SaveHotSnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SaveSnapshotRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ControlServiceServer).SaveHotSnapshot(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ControlService_SaveHotSnapshot_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ControlServiceServer).SaveHotSnapshot(ctx, req.(*SaveSnapshotRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ControlService_LoadSnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoadSnapshotRequest)
if err := dec(in); err != nil {
@@ -1077,6 +1115,10 @@ var ControlService_ServiceDesc = grpc.ServiceDesc{
MethodName: "SaveSnapshot",
Handler: _ControlService_SaveSnapshot_Handler,
},
{
MethodName: "SaveHotSnapshot",
Handler: _ControlService_SaveHotSnapshot_Handler,
},
{
MethodName: "LoadSnapshot",
Handler: _ControlService_LoadSnapshot_Handler,
+93 -8
View File
@@ -137,6 +137,7 @@ func newLocalNetwork(opts localNetworkOptions) (*localNetwork, error) {
// TODO document.
// Assumes [lc.lock] is held.
func (lc *localNetwork) createConfig() error {
lc.log.Info("createConfig() ENTERED", "globalNodeConfig", lc.options.globalNodeConfig)
var globalConfig map[string]interface{}
if lc.options.globalNodeConfig != "" {
@@ -164,36 +165,82 @@ func (lc *localNetwork) createConfig() error {
}
}
lc.log.Info("createConfig networkID parsed", "networkID", networkID, "MainnetID", luxd_constants.MainnetID, "TestnetID", luxd_constants.TestnetID)
// CRITICAL: Check if we're resuming from existing state.
// If node1/genesis.json exists AND node1/db has data, we MUST use the existing genesis
// to avoid "db contains invalid genesis hash" errors. JSON serialization is non-deterministic
// due to Go map iteration order, so regenerating genesis produces different bytes.
existingGenesis, isResume := lc.checkForExistingGenesis()
if isResume {
ux.Print(lc.log, log.Green.Wrap("📂 RESUME DETECTED: Using existing genesis from %s"), lc.options.rootDataDir)
fmt.Fprintf(os.Stderr, "📂 RESUME DETECTED: Using existing genesis from %s\n", lc.options.rootDataDir)
} else {
fmt.Fprintf(os.Stderr, "DEBUG: Not resuming - checkForExistingGenesis returned false (rootDataDir=%s)\n", lc.options.rootDataDir)
}
// Use the appropriate genesis configuration based on network ID
// If LUX_MNEMONIC is set, derive validators from it (preferred for mainnet/testnet)
// CRITICAL: For mainnet/testnet, ALWAYS use canonical genesis to ensure byte-for-byte
// deterministic output. This prevents "db contains invalid genesis hash" errors on restart.
// The canonical genesis files are pre-serialized and never re-generated.
// Check if LUX_MNEMONIC is set - if so, use mnemonic-based config to generate
// genesis with funds allocated to the mnemonic-derived address
mnemonic := os.Getenv("LUX_MNEMONIC")
useMnemonic := mnemonic != ""
fmt.Printf("🔍 DEBUG: LUX_MNEMONIC set: %v (len=%d)\n", useMnemonic, len(mnemonic))
switch networkID {
case luxd_constants.MainnetID: // LUX Mainnet (1)
if useMnemonic {
fmt.Printf("🔑 Using NewMainnetConfigFromMnemonic (mnemonic-derived keys)\n")
ux.Print(lc.log, "%s", log.Green.Wrap("Loading mainnet genesis FROM MNEMONIC (funds allocated to derived address)"))
cfg, err = local.NewMainnetConfigFromMnemonic(lc.options.execPath, lc.options.numNodes)
} else {
fmt.Printf("📦 Using NewMainnetConfig (embedded keys)\n")
cfg, err = local.NewMainnetConfig(lc.options.execPath, lc.options.numNodes)
// ALWAYS use canonical genesis for mainnet - never regenerate
ux.Print(lc.log, "%s", log.Green.Wrap("Loading CANONICAL mainnet genesis (deterministic bytes)"))
cfg, err = local.NewCanonicalMainnetConfig(lc.options.execPath, lc.options.numNodes)
}
case luxd_constants.TestnetID: // LUX Testnet (2)
if useMnemonic {
ux.Print(lc.log, "%s", log.Green.Wrap("Loading testnet genesis FROM MNEMONIC (funds allocated to derived address)"))
cfg, err = local.NewTestnetConfigFromMnemonic(lc.options.execPath, lc.options.numNodes)
} else {
// Use pre-existing keys from ~/.lux/keys if available
fmt.Printf("📦 Using NewTestnetConfigWithKeys (pre-existing keys from ~/.lux/keys)\n")
cfg, err = local.NewTestnetConfigWithKeys(lc.options.execPath, "")
// ALWAYS use canonical genesis for testnet - never regenerate
ux.Print(lc.log, "%s", log.Green.Wrap("Loading CANONICAL testnet genesis (deterministic bytes)"))
cfg, err = local.NewCanonicalTestnetConfig(lc.options.execPath, lc.options.numNodes)
}
case luxd_constants.DevnetID: // LUX Devnet (3)
// ALWAYS use canonical genesis for devnet - never regenerate
ux.Print(lc.log, "%s", log.Green.Wrap("Loading CANONICAL devnet genesis (deterministic bytes)"))
cfg, err = local.NewCanonicalDevnetConfig(lc.options.execPath, lc.options.numNodes)
case luxd_constants.CustomID: // Custom/Local (1337)
// ALWAYS use canonical genesis for custom network - never regenerate
ux.Print(lc.log, "%s", log.Green.Wrap("Loading CANONICAL custom genesis (deterministic bytes)"))
cfg, err = local.NewCanonicalCustomConfig(lc.options.execPath, lc.options.numNodes)
default:
// Fallback for unknown network IDs
ux.Print(lc.log, "%s", log.Orange.Wrap(fmt.Sprintf("Unknown network ID %d, using default config", networkID)))
cfg, err = local.NewDefaultConfigNNodes(lc.options.execPath, lc.options.numNodes)
}
if err != nil {
return err
}
// For resume scenarios, verify genesis matches (but don't override for mainnet/testnet
// since canonical genesis is already deterministic)
if isResume && existingGenesis != "" {
if cfg.Genesis != existingGenesis {
if networkID == luxd_constants.MainnetID || networkID == luxd_constants.TestnetID {
// Log warning but use canonical - this shouldn't happen with proper setup
ux.Print(lc.log, "%s", log.Orange.Wrap("WARNING: Existing genesis differs from canonical. Using canonical."))
fmt.Fprintf(os.Stderr, "WARNING: Genesis mismatch detected. Using canonical genesis.\n")
} else {
// For custom networks, use existing genesis to preserve state
ux.Print(lc.log, "%s", log.Green.Wrap("Using existing genesis for custom network"))
cfg.Genesis = existingGenesis
}
}
}
// Ensure required staking flags are set for non-local networks
// stake-minting-period must be >= max-stake-duration (1 year = 8760h)
if _, ok := cfg.Flags["stake-minting-period"]; !ok {
@@ -314,6 +361,44 @@ func (lc *localNetwork) Start(ctx context.Context) error {
return nil
}
// checkForExistingGenesis checks if we're resuming from existing state.
// Returns (genesisJSON, isResume) where isResume is true if:
// 1. node1/genesis.json exists, AND
// 2. node1/db directory has data (meaning blocks have been committed)
//
// This is critical because JSON serialization is non-deterministic in Go
// (map iteration order varies), so regenerating genesis from the same source
// data produces different bytes, causing "db contains invalid genesis hash" errors.
func (lc *localNetwork) checkForExistingGenesis() (string, bool) {
if lc.options.rootDataDir == "" {
return "", false
}
// Check node1 for existing genesis and db data
node1Dir := filepath.Join(lc.options.rootDataDir, "node1")
genesisPath := filepath.Join(node1Dir, "genesis.json")
dbDir := filepath.Join(node1Dir, "db")
// Check if genesis.json exists
genesisBytes, err := os.ReadFile(genesisPath)
if err != nil {
// No existing genesis file - this is a fresh start
return "", false
}
// Check if db directory has data (indicating we're resuming)
entries, err := os.ReadDir(dbDir)
if err != nil || len(entries) == 0 {
// No db data - this is a fresh start even though genesis file exists
// (perhaps from a failed previous start)
return "", false
}
// Both genesis.json exists AND db has data - we're resuming
// Note: We can't log here since lc.log may not be set yet during config creation
return string(genesisBytes), true
}
// Creates the blockchains specified in [chainSpecs].
// Assumes [lc.lock] isn't held.
func (lc *localNetwork) CreateChains(
+26
View File
@@ -1493,6 +1493,32 @@ func (s *server) SaveSnapshot(ctx context.Context, req *rpcpb.SaveSnapshotReques
return &rpcpb.SaveSnapshotResponse{SnapshotPath: snapshotPath}, nil
}
// SaveHotSnapshot saves a snapshot without stopping the network
// Uses Copy-on-Write on APFS (macOS) for instant snapshots
func (s *server) SaveHotSnapshot(ctx context.Context, req *rpcpb.SaveSnapshotRequest) (*rpcpb.SaveSnapshotResponse, error) {
s.mu.RLock() // Read lock - doesn't block network operations
defer s.mu.RUnlock()
s.logger.Info("SaveHotSnapshot", log.String("snapshot-name", req.SnapshotName))
if s.network == nil {
return nil, ErrNotBootstrapped
}
snapshotPath, err := s.network.nw.SaveHotSnapshot(ctx, req.SnapshotName)
if err != nil {
s.logger.Warn("hot snapshot save failed to complete", log.Err(err))
return nil, err
}
// Note: We do NOT stop the network for hot snapshots
s.logger.Info("Hot snapshot saved successfully",
log.String("snapshot-name", req.SnapshotName),
log.String("path", snapshotPath))
return &rpcpb.SaveSnapshotResponse{SnapshotPath: snapshotPath}, nil
}
func (s *server) RemoveSnapshot(_ context.Context, req *rpcpb.RemoveSnapshotRequest) (*rpcpb.RemoveSnapshotResponse, error) {
s.mu.Lock()
defer s.mu.Unlock()