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 }