github.com/amazechain/amc@v0.1.3/cmd/amc/app.go (about)

     1  // Copyright 2022 The AmazeChain Authors
     2  // This file is part of the AmazeChain library.
     3  //
     4  // The AmazeChain library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The AmazeChain library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the AmazeChain library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"fmt"
    21  	"github.com/amazechain/amc/common/types"
    22  	"github.com/amazechain/amc/log"
    23  	"net/http"
    24  	_ "net/http/pprof"
    25  	"os"
    26  	"os/signal"
    27  	"runtime"
    28  	"strings"
    29  	"syscall"
    30  	"time"
    31  
    32  	"github.com/amazechain/amc/accounts"
    33  
    34  	"github.com/amazechain/amc/accounts/keystore"
    35  	"github.com/amazechain/amc/cmd/utils"
    36  
    37  	"github.com/amazechain/amc/conf"
    38  	"github.com/amazechain/amc/internal/node"
    39  	"github.com/urfave/cli/v2"
    40  )
    41  
    42  func appRun(ctx *cli.Context) error {
    43  	if len(cfgFile) > 0 {
    44  		if err := conf.LoadConfigFromFile(cfgFile, &DefaultConfig); err != nil {
    45  			return err
    46  		}
    47  	} else {
    48  		DefaultConfig.NetworkCfg.ListenersAddress = listenAddress.Value()
    49  		DefaultConfig.NetworkCfg.BootstrapPeers = bootstraps.Value()
    50  		if len(privateKey) > 0 {
    51  			DefaultConfig.NetworkCfg.LocalPeerKey = privateKey
    52  		}
    53  
    54  		DefaultConfig.P2PCfg.StaticPeers = p2pStaticPeers.Value()
    55  		DefaultConfig.P2PCfg.BootstrapNodeAddr = p2pBootstrapNode.Value()
    56  		DefaultConfig.P2PCfg.DenyListCIDR = p2pDenyList.Value()
    57  
    58  		//
    59  		DefaultConfig.P2PCfg.DataDir = DefaultConfig.NodeCfg.DataDir
    60  	}
    61  
    62  	log.Init(DefaultConfig.NodeCfg, DefaultConfig.LoggerCfg)
    63  
    64  	if DefaultConfig.PprofCfg.Pprof {
    65  		if DefaultConfig.PprofCfg.MaxCpu > 0 {
    66  			runtime.GOMAXPROCS(DefaultConfig.PprofCfg.MaxCpu)
    67  		}
    68  		if DefaultConfig.PprofCfg.TraceMutex {
    69  			runtime.SetMutexProfileFraction(1)
    70  		}
    71  		if DefaultConfig.PprofCfg.TraceBlock {
    72  			runtime.SetBlockProfileRate(1)
    73  		}
    74  
    75  		go func() {
    76  			if err := http.ListenAndServe(fmt.Sprintf(":%d", DefaultConfig.PprofCfg.Port), nil); err != nil {
    77  				log.Error("failed to setup go pprof", "err", err)
    78  				os.Exit(0)
    79  			}
    80  		}()
    81  	}
    82  
    83  	stack, err := node.NewNode(ctx, &DefaultConfig)
    84  	if err != nil {
    85  		log.Error("Failed start Node", "err", err)
    86  		return err
    87  	}
    88  
    89  	StartNode(ctx, stack, false)
    90  
    91  	// Unlock any account specifically requested
    92  	unlockAccounts(ctx, stack, &DefaultConfig)
    93  
    94  	// Register wallet event handlers to open and auto-derive wallets
    95  	events := make(chan accounts.WalletEvent, 16)
    96  	stack.AccountManager().Subscribe(events)
    97  
    98  	go func() {
    99  		// Open any wallets already attached
   100  		for _, wallet := range stack.AccountManager().Wallets() {
   101  			if err := wallet.Open(""); err != nil {
   102  				log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
   103  			}
   104  		}
   105  		// Listen for wallet event till termination
   106  		for event := range events {
   107  			switch event.Kind {
   108  			case accounts.WalletArrived:
   109  				if err := event.Wallet.Open(""); err != nil {
   110  					log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
   111  				}
   112  			case accounts.WalletOpened:
   113  				status, _ := event.Wallet.Status()
   114  				log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
   115  
   116  				var derivationPaths []accounts.DerivationPath
   117  				if event.Wallet.URL().Scheme == "ledger" {
   118  					derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath)
   119  				}
   120  				derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath)
   121  
   122  				event.Wallet.SelfDerive(derivationPaths, nil)
   123  
   124  			case accounts.WalletDropped:
   125  				log.Info("Old wallet dropped", "url", event.Wallet.URL())
   126  				event.Wallet.Close()
   127  			}
   128  		}
   129  	}()
   130  
   131  	stack.Wait()
   132  
   133  	return nil
   134  }
   135  
   136  // unlockAccounts unlocks any account specifically requested.
   137  func unlockAccounts(ctx *cli.Context, stack *node.Node, cfg *conf.Config) {
   138  	var unlocks []string
   139  	inputs := strings.Split(ctx.String(UnlockedAccountFlag.Name), ",")
   140  	for _, input := range inputs {
   141  		if trimmed := strings.TrimSpace(input); trimmed != "" {
   142  			unlocks = append(unlocks, trimmed)
   143  		}
   144  	}
   145  
   146  	// Short circuit if there is no account to unlock.
   147  	if len(unlocks) == 0 {
   148  		return
   149  	}
   150  	// If insecure account unlocking is not allowed if node's APIs are exposed to external.
   151  	// Print warning log to user and skip unlocking.
   152  	if !cfg.NodeCfg.InsecureUnlockAllowed && cfg.NodeCfg.ExtRPCEnabled() {
   153  		utils.Fatalf("Account unlock with HTTP access is forbidden!")
   154  	}
   155  	ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   156  	passwords := MakePasswordList(ctx)
   157  	for i, account := range unlocks {
   158  		unlockAccount(ks, account, i, passwords)
   159  	}
   160  }
   161  
   162  // MakePasswordList reads password lines from the file specified by the global --password flag.
   163  func MakePasswordList(ctx *cli.Context) []string {
   164  	path := ctx.Path(PasswordFileFlag.Name)
   165  	if path == "" {
   166  		return nil
   167  	}
   168  	text, err := os.ReadFile(path)
   169  	if err != nil {
   170  		log.Error("Failed to read password ", "file", err)
   171  	}
   172  	lines := strings.Split(string(text), "\n")
   173  	// Sanitise DOS line endings.
   174  	for i := range lines {
   175  		lines[i] = strings.TrimRight(lines[i], "\r")
   176  	}
   177  	return lines
   178  }
   179  
   180  func StartNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
   181  	if err := stack.Start(); err != nil {
   182  		log.Critf("Error starting protocol stack: %v", err)
   183  	}
   184  	go func() {
   185  		sigc := make(chan os.Signal, 1)
   186  		signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
   187  		defer signal.Stop(sigc)
   188  
   189  		if ctx.IsSet(MinFreeDiskSpaceFlag.Name) {
   190  			minFreeDiskSpace := ctx.Int(MinFreeDiskSpaceFlag.Name)
   191  			go monitorFreeDiskSpace(sigc, stack.InstanceDir(), uint64(minFreeDiskSpace)*1024*1024*1024)
   192  		}
   193  
   194  		shutdown := func() {
   195  			log.Info("Got interrupt, shutting down...")
   196  			go stack.Close()
   197  			for i := 10; i > 0; i-- {
   198  				<-sigc
   199  				if i > 1 {
   200  					log.Warn("Already shutting down, interrupt more to panic.", "times", i-1)
   201  				}
   202  			}
   203  			panic("Panic closing the amc node")
   204  		}
   205  
   206  		if isConsole {
   207  			// In JS console mode, SIGINT is ignored because it's handled by the console.
   208  			// However, SIGTERM still shuts down the node.
   209  			for {
   210  				sig := <-sigc
   211  				if sig == syscall.SIGTERM {
   212  					shutdown()
   213  					return
   214  				}
   215  			}
   216  		} else {
   217  			<-sigc
   218  			shutdown()
   219  		}
   220  	}()
   221  }
   222  
   223  func monitorFreeDiskSpace(sigc chan os.Signal, path string, freeDiskSpaceCritical uint64) {
   224  	if path == "" {
   225  		return
   226  	}
   227  	for {
   228  		freeSpace, err := getFreeDiskSpace(path)
   229  		if err != nil {
   230  			log.Warn("Failed to get free disk space", "path", path, "err", err)
   231  			break
   232  		}
   233  		if freeSpace < freeDiskSpaceCritical {
   234  			log.Error("Low disk space. Gracefully shutting down Geth to prevent database corruption.", "available", types.StorageSize(freeSpace), "path", path)
   235  			sigc <- syscall.SIGTERM
   236  			break
   237  		} else if freeSpace < 2*freeDiskSpaceCritical {
   238  			log.Warn("Disk space is running low. Geth will shutdown if disk space runs below critical level.", "available", types.StorageSize(freeSpace), "critical_level", types.StorageSize(freeDiskSpaceCritical), "path", path)
   239  		}
   240  		time.Sleep(30 * time.Second)
   241  	}
   242  }