190 lines
5.2 KiB
Go
190 lines
5.2 KiB
Go
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
|
//
|
|
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package blockchain
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"sync"
|
|
"syscall"
|
|
|
|
coreerr "dappco.re/go/core/log"
|
|
|
|
"dappco.re/go/core/blockchain/chain"
|
|
"dappco.re/go/core/process"
|
|
store "dappco.re/go/core/store"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const chainSyncDisplayName = chainSyncCommandPath
|
|
|
|
// newChainSyncCommand builds the `chain sync` command family.
|
|
//
|
|
// Example:
|
|
//
|
|
// core-chain chain sync
|
|
// core-chain chain sync --daemon
|
|
// core-chain chain sync --stop
|
|
//
|
|
// It keeps the foreground and background modes behind a predictable command path.
|
|
func newChainSyncCommand(dataDir, seed *string, testnet *bool) *cobra.Command {
|
|
var (
|
|
runAsDaemon bool
|
|
stopDaemon bool
|
|
)
|
|
|
|
cmd := &cobra.Command{
|
|
Use: chainSyncCommandName,
|
|
Short: chainSyncCommandShort,
|
|
Long: chainSyncCommandLong,
|
|
Example: chainSyncExample,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if err := validateChainSyncModeSelection(runAsDaemon, stopDaemon); err != nil {
|
|
return err
|
|
}
|
|
if stopDaemon {
|
|
return stopChainSyncDaemon(*dataDir)
|
|
}
|
|
if runAsDaemon {
|
|
return runChainSyncDaemon(*dataDir, *seed, *testnet)
|
|
}
|
|
return runChainSyncForeground(*dataDir, *seed, *testnet)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVar(&runAsDaemon, "daemon", false, "keep syncing in the background")
|
|
cmd.Flags().BoolVar(&stopDaemon, "stop", false, "stop the background sync for this data directory")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func runChainSyncForeground(dataDir, seed string, testnet bool) error {
|
|
if err := validateChainRuntimeInputs(dataDir, seed); err != nil {
|
|
return err
|
|
}
|
|
if err := ensureChainDataDirExists(dataDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
dbPath := filepath.Join(dataDir, "chain.db")
|
|
chainStore, err := store.New(dbPath)
|
|
if err != nil {
|
|
return coreerr.E("runChainSyncForeground", "open store", err)
|
|
}
|
|
defer chainStore.Close()
|
|
|
|
blockchain := chain.New(chainStore)
|
|
chainConfig, hardForks, resolvedSeed := chainConfigForSeed(testnet, seed)
|
|
|
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer cancel()
|
|
|
|
log.Print(formatChainSyncForegroundStart(dataDir, resolvedSeed))
|
|
runChainSyncLoop(ctx, blockchain, &chainConfig, hardForks, resolvedSeed)
|
|
log.Print(formatChainSyncForegroundStop())
|
|
return nil
|
|
}
|
|
|
|
func runChainSyncDaemon(dataDir, seed string, testnet bool) error {
|
|
if err := validateChainRuntimeInputs(dataDir, seed); err != nil {
|
|
return err
|
|
}
|
|
if err := ensureChainDataDirExists(dataDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
pidFile := filepath.Join(dataDir, "sync.pid")
|
|
|
|
syncDaemon := process.NewDaemon(process.DaemonOptions{
|
|
PIDFile: pidFile,
|
|
Registry: process.DefaultRegistry(),
|
|
RegistryEntry: process.DaemonEntry{
|
|
Code: "dappco.re/go/core/blockchain",
|
|
Daemon: "sync",
|
|
},
|
|
})
|
|
|
|
if err := syncDaemon.Start(); err != nil {
|
|
return coreerr.E("runChainSyncDaemon", "start background sync", err)
|
|
}
|
|
|
|
dbPath := filepath.Join(dataDir, "chain.db")
|
|
chainStore, err := store.New(dbPath)
|
|
if err != nil {
|
|
_ = syncDaemon.Stop()
|
|
return coreerr.E("runChainSyncDaemon", "open store", err)
|
|
}
|
|
defer chainStore.Close()
|
|
|
|
blockchain := chain.New(chainStore)
|
|
chainConfig, hardForks, resolvedSeed := chainConfigForSeed(testnet, seed)
|
|
|
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer cancel()
|
|
|
|
syncDaemon.SetReady(true)
|
|
log.Print(formatChainSyncDaemonStarted(dataDir, resolvedSeed))
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
runChainSyncLoop(ctx, blockchain, &chainConfig, hardForks, resolvedSeed)
|
|
}()
|
|
|
|
err = syncDaemon.Run(ctx)
|
|
wg.Wait() // Wait for the sync loop to finish before closing the store.
|
|
return err
|
|
}
|
|
|
|
func stopChainSyncDaemon(dataDir string) error {
|
|
if err := validateChainDataDir(dataDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
pidFile := filepath.Join(dataDir, "sync.pid")
|
|
pid, running := process.ReadPID(pidFile)
|
|
if pid == 0 || !running {
|
|
return coreerr.E("stopChainSyncDaemon", "no running background sync process found", nil)
|
|
}
|
|
|
|
processHandle, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return coreerr.E("stopChainSyncDaemon", fmt.Sprintf("find process %d", pid), err)
|
|
}
|
|
|
|
if err := processHandle.Signal(syscall.SIGTERM); err != nil {
|
|
return coreerr.E("stopChainSyncDaemon", fmt.Sprintf("signal process %d", pid), err)
|
|
}
|
|
|
|
log.Print(formatChainSyncDaemonStopping(pid))
|
|
return nil
|
|
}
|
|
|
|
func formatChainSyncForegroundStart(dataDir, seed string) string {
|
|
return fmt.Sprintf("Starting chain sync in the foreground with data dir %q and seed %q. Press Ctrl+C to stop.", dataDir, seed)
|
|
}
|
|
|
|
func formatChainSyncForegroundStop() string {
|
|
return "Chain sync stopped."
|
|
}
|
|
|
|
func formatChainSyncDaemonStarted(dataDir, seed string) string {
|
|
return fmt.Sprintf("Background chain sync started with data dir %q and seed %q. Stop it with %s.", dataDir, seed, formatChainSyncStopCommand(dataDir))
|
|
}
|
|
|
|
func formatChainSyncStopCommand(dataDir string) string {
|
|
return fmt.Sprintf("%s --stop --data-dir %q", chainSyncCommandPath, dataDir)
|
|
}
|
|
|
|
func formatChainSyncDaemonStopping(pid int) string {
|
|
return fmt.Sprintf("Stopping background chain sync (PID %d).", pid)
|
|
}
|