refactor(blockchain): spell out command sync names
Some checks failed
Security Scan / security (push) Has been cancelled
Test / Test (push) Has been cancelled

Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
Virgil 2026-04-04 04:18:37 +00:00
parent 243749a6d8
commit a2df164822
4 changed files with 69 additions and 55 deletions

View file

@ -16,6 +16,8 @@ import (
"github.com/spf13/cobra"
)
const defaultChainSeed = "seeds.lthn.io:36942"
// AddChainCommands registers the `chain` command group on a Cobra root.
//
// Example:
@ -37,8 +39,8 @@ func AddChainCommands(root *cobra.Command) {
Long: "Manage the Lethean blockchain — sync, explore, and mine.",
}
chainCmd.PersistentFlags().StringVar(&dataDir, "data-dir", defaultChainDataDir(), "blockchain data directory")
chainCmd.PersistentFlags().StringVar(&seed, "seed", "seeds.lthn.io:36942", "seed peer address (host:port)")
chainCmd.PersistentFlags().StringVar(&dataDir, "data-dir", defaultChainDataDirPath(), "blockchain data directory")
chainCmd.PersistentFlags().StringVar(&seed, "seed", defaultChainSeed, "seed peer address (host:port)")
chainCmd.PersistentFlags().BoolVar(&testnet, "testnet", false, "use testnet")
chainCmd.AddCommand(
@ -49,17 +51,17 @@ func AddChainCommands(root *cobra.Command) {
root.AddCommand(chainCmd)
}
func resolveChainConfig(testnet bool, seed *string) (config.ChainConfig, []config.HardFork) {
func chainConfigForSeed(testnet bool, seed string) (config.ChainConfig, []config.HardFork, string) {
if testnet {
if *seed == "seeds.lthn.io:36942" {
*seed = "localhost:46942"
if seed == defaultChainSeed {
seed = "localhost:46942"
}
return config.Testnet, config.TestnetForks
return config.Testnet, config.TestnetForks, seed
}
return config.Mainnet, config.MainnetForks
return config.Mainnet, config.MainnetForks, seed
}
func defaultChainDataDir() string {
func defaultChainDataDirPath() string {
home, err := os.UserHomeDir()
if err != nil {
return ".lethean"
@ -67,9 +69,9 @@ func defaultChainDataDir() string {
return filepath.Join(home, ".lethean", "chain")
}
func ensureChainDataDir(dataDir string) error {
func ensureChainDataDirExists(dataDir string) error {
if err := coreio.Local.EnsureDir(dataDir); err != nil {
return coreerr.E("ensureChainDataDir", "create data dir", err)
return coreerr.E("ensureChainDataDirExists", "create data dir", err)
}
return nil
}

View file

@ -23,6 +23,11 @@ import (
)
// newChainExplorerCommand builds the interactive `chain explorer` command.
//
// Example:
//
// chain explorer --data-dir ~/.lethean/chain
//
// Use it alongside `AddChainCommands` to expose the TUI node view.
func newChainExplorerCommand(dataDir, seed *string, testnet *bool) *cobra.Command {
return &cobra.Command{
@ -36,19 +41,19 @@ func newChainExplorerCommand(dataDir, seed *string, testnet *bool) *cobra.Comman
}
func runChainExplorer(dataDir, seed string, testnet bool) error {
if err := ensureChainDataDir(dataDir); err != nil {
if err := ensureChainDataDirExists(dataDir); err != nil {
return err
}
dbPath := filepath.Join(dataDir, "chain.db")
s, err := store.New(dbPath)
chainStore, err := store.New(dbPath)
if err != nil {
return coreerr.E("runChainExplorer", "open store", err)
}
defer s.Close()
defer chainStore.Close()
c := chain.New(s)
cfg, forks := resolveChainConfig(testnet, &seed)
blockchain := chain.New(chainStore)
chainConfig, hardForks, resolvedSeed := chainConfigForSeed(testnet, seed)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
@ -57,12 +62,12 @@ func runChainExplorer(dataDir, seed string, testnet bool) error {
wg.Add(1)
go func() {
defer wg.Done()
runChainSyncLoop(ctx, c, &cfg, forks, seed)
runChainSyncLoop(ctx, blockchain, &chainConfig, hardForks, resolvedSeed)
}()
node := tui.NewNode(c)
node := tui.NewNode(blockchain)
status := tui.NewStatusModel(node)
explorer := tui.NewExplorerModel(c)
explorer := tui.NewExplorerModel(blockchain)
hints := tui.NewKeyHintsModel()
frame := cli.NewFrame("HCF")

View file

@ -24,6 +24,13 @@ import (
)
// newChainSyncCommand builds the `chain sync` command family.
//
// Example:
//
// chain sync
// chain sync --daemon
// chain sync --stop
//
// It keeps the foreground and daemon modes behind a predictable command path.
func newChainSyncCommand(dataDir, seed *string, testnet *bool) *cobra.Command {
var (
@ -53,37 +60,37 @@ func newChainSyncCommand(dataDir, seed *string, testnet *bool) *cobra.Command {
}
func runChainSyncForeground(dataDir, seed string, testnet bool) error {
if err := ensureChainDataDir(dataDir); err != nil {
if err := ensureChainDataDirExists(dataDir); err != nil {
return err
}
dbPath := filepath.Join(dataDir, "chain.db")
s, err := store.New(dbPath)
chainStore, err := store.New(dbPath)
if err != nil {
return coreerr.E("runChainSyncForeground", "open store", err)
}
defer s.Close()
defer chainStore.Close()
c := chain.New(s)
cfg, forks := resolveChainConfig(testnet, &seed)
blockchain := chain.New(chainStore)
chainConfig, hardForks, resolvedSeed := chainConfigForSeed(testnet, seed)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
log.Println("Starting headless P2P sync...")
runChainSyncLoop(ctx, c, &cfg, forks, seed)
runChainSyncLoop(ctx, blockchain, &chainConfig, hardForks, resolvedSeed)
log.Println("Sync stopped.")
return nil
}
func runChainSyncDaemon(dataDir, seed string, testnet bool) error {
if err := ensureChainDataDir(dataDir); err != nil {
if err := ensureChainDataDirExists(dataDir); err != nil {
return err
}
pidFile := filepath.Join(dataDir, "sync.pid")
d := process.NewDaemon(process.DaemonOptions{
daemon := process.NewDaemon(process.DaemonOptions{
PIDFile: pidFile,
Registry: process.DefaultRegistry(),
RegistryEntry: process.DaemonEntry{
@ -92,35 +99,35 @@ func runChainSyncDaemon(dataDir, seed string, testnet bool) error {
},
})
if err := d.Start(); err != nil {
if err := daemon.Start(); err != nil {
return coreerr.E("runChainSyncDaemon", "daemon start", err)
}
dbPath := filepath.Join(dataDir, "chain.db")
s, err := store.New(dbPath)
chainStore, err := store.New(dbPath)
if err != nil {
_ = d.Stop()
_ = daemon.Stop()
return coreerr.E("runChainSyncDaemon", "open store", err)
}
defer s.Close()
defer chainStore.Close()
c := chain.New(s)
cfg, forks := resolveChainConfig(testnet, &seed)
blockchain := chain.New(chainStore)
chainConfig, hardForks, resolvedSeed := chainConfigForSeed(testnet, seed)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
d.SetReady(true)
daemon.SetReady(true)
log.Println("Sync daemon started.")
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
runChainSyncLoop(ctx, c, &cfg, forks, seed)
runChainSyncLoop(ctx, blockchain, &chainConfig, hardForks, resolvedSeed)
}()
err = d.Run(ctx)
err = daemon.Run(ctx)
wg.Wait() // Wait for the sync loop to finish before closing the store.
return err
}
@ -132,12 +139,12 @@ func stopChainSyncDaemon(dataDir string) error {
return coreerr.E("stopChainSyncDaemon", "no running sync daemon found", nil)
}
proc, err := os.FindProcess(pid)
processHandle, err := os.FindProcess(pid)
if err != nil {
return coreerr.E("stopChainSyncDaemon", fmt.Sprintf("find process %d", pid), err)
}
if err := proc.Signal(syscall.SIGTERM); err != nil {
if err := processHandle.Signal(syscall.SIGTERM); err != nil {
return coreerr.E("stopChainSyncDaemon", fmt.Sprintf("signal process %d", pid), err)
}

View file

@ -22,10 +22,10 @@ import (
levin "dappco.re/go/core/p2p/node/levin"
)
func runChainSyncLoop(ctx context.Context, c *chain.Chain, cfg *config.ChainConfig, forks []config.HardFork, seed string) {
func runChainSyncLoop(ctx context.Context, blockchain *chain.Chain, chainConfig *config.ChainConfig, hardForks []config.HardFork, seed string) {
opts := chain.SyncOptions{
VerifySignatures: false,
Forks: forks,
Forks: hardForks,
}
for {
@ -35,7 +35,7 @@ func runChainSyncLoop(ctx context.Context, c *chain.Chain, cfg *config.ChainConf
default:
}
if err := runChainSyncOnce(ctx, c, cfg, opts, seed); err != nil {
if err := runChainSyncOnce(ctx, blockchain, chainConfig, opts, seed); err != nil {
log.Printf("sync: %v (retrying in 10s)", err)
select {
case <-ctx.Done():
@ -53,24 +53,24 @@ func runChainSyncLoop(ctx context.Context, c *chain.Chain, cfg *config.ChainConf
}
}
func runChainSyncOnce(ctx context.Context, c *chain.Chain, cfg *config.ChainConfig, opts chain.SyncOptions, seed string) error {
func runChainSyncOnce(ctx context.Context, blockchain *chain.Chain, chainConfig *config.ChainConfig, opts chain.SyncOptions, seed string) error {
conn, err := net.DialTimeout("tcp", seed, 10*time.Second)
if err != nil {
return coreerr.E("runChainSyncOnce", fmt.Sprintf("dial %s", seed), err)
}
defer conn.Close()
lc := levin.NewConnection(conn)
levinConn := levin.NewConnection(conn)
var peerIDBuf [8]byte
rand.Read(peerIDBuf[:])
peerID := binary.LittleEndian.Uint64(peerIDBuf[:])
var peerIDBytes [8]byte
rand.Read(peerIDBytes[:])
peerID := binary.LittleEndian.Uint64(peerIDBytes[:])
localHeight, _ := c.Height()
localHeight, _ := blockchain.Height()
req := p2p.HandshakeRequest{
handshakeReq := p2p.HandshakeRequest{
NodeData: p2p.NodeData{
NetworkID: cfg.NetworkID,
NetworkID: chainConfig.NetworkID,
PeerID: peerID,
LocalTime: time.Now().Unix(),
MyPort: 0,
@ -81,15 +81,15 @@ func runChainSyncOnce(ctx context.Context, c *chain.Chain, cfg *config.ChainConf
NonPruningMode: true,
},
}
payload, err := p2p.EncodeHandshakeRequest(&req)
payload, err := p2p.EncodeHandshakeRequest(&handshakeReq)
if err != nil {
return coreerr.E("runChainSyncOnce", "encode handshake", err)
}
if err := lc.WritePacket(p2p.CommandHandshake, payload, true); err != nil {
if err := levinConn.WritePacket(p2p.CommandHandshake, payload, true); err != nil {
return coreerr.E("runChainSyncOnce", "write handshake", err)
}
hdr, data, err := lc.ReadPacket()
hdr, data, err := levinConn.ReadPacket()
if err != nil {
return coreerr.E("runChainSyncOnce", "read handshake", err)
}
@ -97,8 +97,8 @@ func runChainSyncOnce(ctx context.Context, c *chain.Chain, cfg *config.ChainConf
return coreerr.E("runChainSyncOnce", fmt.Sprintf("unexpected command %d", hdr.Command), nil)
}
var resp p2p.HandshakeResponse
if err := resp.Decode(data); err != nil {
var handshakeResp p2p.HandshakeResponse
if err := handshakeResp.Decode(data); err != nil {
return coreerr.E("runChainSyncOnce", "decode handshake", err)
}
@ -107,7 +107,7 @@ func runChainSyncOnce(ctx context.Context, c *chain.Chain, cfg *config.ChainConf
ClientVersion: config.ClientVersion,
NonPruningMode: true,
}
p2pConn := chain.NewLevinP2PConn(lc, resp.PayloadData.CurrentHeight, localSync)
p2pConn := chain.NewLevinP2PConn(levinConn, handshakeResp.PayloadData.CurrentHeight, localSync)
return c.P2PSync(ctx, p2pConn, opts)
return blockchain.P2PSync(ctx, p2pConn, opts)
}