github.com/deso-protocol/core@v1.2.9/cmd/node.go (about) 1 package cmd 2 3 import ( 4 "encoding/hex" 5 "flag" 6 "fmt" 7 "net" 8 "os" 9 "time" 10 11 "github.com/DataDog/datadog-go/statsd" 12 "github.com/btcsuite/btcd/addrmgr" 13 "github.com/btcsuite/btcd/wire" 14 "github.com/davecgh/go-spew/spew" 15 "github.com/deso-protocol/core/lib" 16 "github.com/deso-protocol/core/migrate" 17 "github.com/deso-protocol/go-deadlock" 18 "github.com/dgraph-io/badger/v3" 19 "github.com/go-pg/pg/v10" 20 "github.com/golang/glog" 21 migrations "github.com/robinjoseph08/go-pg-migrations/v3" 22 "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" 23 "gopkg.in/DataDog/dd-trace-go.v1/profiler" 24 ) 25 26 type Node struct { 27 Server *lib.Server 28 chainDB *badger.DB 29 TXIndex *lib.TXIndex 30 Params *lib.DeSoParams 31 Config *Config 32 Postgres *lib.Postgres 33 } 34 35 func NewNode(config *Config) *Node { 36 result := Node{} 37 result.Config = config 38 result.Params = config.Params 39 40 return &result 41 } 42 43 func (node *Node) Start() { 44 // TODO: Replace glog with logrus so we can also get rid of flag library 45 flag.Set("log_dir", node.Config.LogDirectory) 46 flag.Set("v", fmt.Sprintf("%d", node.Config.GlogV)) 47 flag.Set("vmodule", node.Config.GlogVmodule) 48 flag.Set("alsologtostderr", "true") 49 flag.Parse() 50 glog.CopyStandardLogTo("INFO") 51 52 // Print config 53 node.Config.Print() 54 55 // Check for regtest mode 56 if node.Config.Regtest { 57 node.Params.EnableRegtest() 58 } 59 60 // Validate params 61 validateParams(node.Params) 62 63 // Setup Datadog span tracer and profiler 64 if node.Config.DatadogProfiler { 65 tracer.Start() 66 err := profiler.Start(profiler.WithProfileTypes(profiler.CPUProfile, profiler.BlockProfile, profiler.MutexProfile, profiler.GoroutineProfile, profiler.HeapProfile)) 67 if err != nil { 68 glog.Fatal(err) 69 } 70 } 71 72 // Setup statsd 73 statsdClient, err := statsd.New(fmt.Sprintf("%s:%d", os.Getenv("DD_AGENT_HOST"), 8125)) 74 if err != nil { 75 glog.Fatal(err) 76 } 77 78 // Setup listeners and peers 79 desoAddrMgr := addrmgr.New(node.Config.DataDirectory, net.LookupIP) 80 desoAddrMgr.Start() 81 82 listeningAddrs, listeners := getAddrsToListenOn(node.Config.ProtocolPort) 83 84 for _, addr := range listeningAddrs { 85 netAddr := wire.NewNetAddress(&addr, 0) 86 _ = desoAddrMgr.AddLocalAddress(netAddr, addrmgr.BoundPrio) 87 } 88 89 if len(node.Config.ConnectIPs) == 0 { 90 for _, host := range node.Config.AddIPs { 91 addIPsForHost(desoAddrMgr, host, node.Params) 92 } 93 94 for _, host := range node.Params.DNSSeeds { 95 addIPsForHost(desoAddrMgr, host, node.Params) 96 } 97 98 if !node.Config.PrivateMode { 99 go addSeedAddrsFromPrefixes(desoAddrMgr, node.Params) 100 } 101 } 102 103 // Setup chain database 104 dbDir := lib.GetBadgerDbPath(node.Config.DataDirectory) 105 opts := badger.DefaultOptions(dbDir) 106 opts.ValueDir = dbDir 107 opts.MemTableSize = 1024 << 20 108 node.chainDB, err = badger.Open(opts) 109 if err != nil { 110 panic(err) 111 } 112 113 // Setup snapshot logger 114 if node.Config.LogDBSummarySnapshots { 115 lib.StartDBSummarySnapshots(node.chainDB) 116 } 117 118 // Setup postgres using a remote URI 119 var db *pg.DB 120 if node.Config.PostgresURI != "" { 121 options, err := pg.ParseURL(node.Config.PostgresURI) 122 if err != nil { 123 panic(err) 124 } 125 126 db = pg.Connect(options) 127 node.Postgres = lib.NewPostgres(db) 128 129 // LoadMigrations registers all the migration files in the migrate package. 130 // See LoadMigrations for more info. 131 migrate.LoadMigrations() 132 133 // Migrate the database after loading all the migrations. This is equivalent 134 // to running "go run migrate.go migrate". See migrate.go for a migrations CLI tool 135 err = migrations.Run(db, "migrate", []string{"", "migrate"}) 136 if err != nil { 137 panic(err) 138 } 139 } 140 141 // Setup eventManager 142 eventManager := lib.NewEventManager() 143 144 // Setup the server 145 node.Server, err = lib.NewServer( 146 node.Params, 147 listeners, 148 desoAddrMgr, 149 node.Config.ConnectIPs, 150 node.chainDB, 151 node.Postgres, 152 node.Config.TargetOutboundPeers, 153 node.Config.MaxInboundPeers, 154 node.Config.MinerPublicKeys, 155 node.Config.NumMiningThreads, 156 node.Config.OneInboundPerIp, 157 node.Config.RateLimitFeerate, 158 node.Config.MinFeerate, 159 node.Config.StallTimeoutSeconds, 160 node.Config.MaxBlockTemplatesCache, 161 node.Config.MinBlockUpdateInterval, 162 node.Config.BlockCypherAPIKey, 163 true, 164 node.Config.DataDirectory, 165 node.Config.MempoolDumpDirectory, 166 node.Config.DisableNetworking, 167 node.Config.ReadOnlyMode, 168 node.Config.IgnoreInboundInvs, 169 statsdClient, 170 node.Config.BlockProducerSeed, 171 node.Config.TrustedBlockProducerPublicKeys, 172 node.Config.TrustedBlockProducerStartHeight, 173 eventManager, 174 ) 175 if err != nil { 176 panic(err) 177 } 178 179 node.Server.Start() 180 181 // Setup TXIndex - not compatible with postgres 182 if node.Config.TXIndex && node.Postgres == nil { 183 node.TXIndex, err = lib.NewTXIndex(node.Server.GetBlockchain(), node.Params, node.Config.DataDirectory) 184 if err != nil { 185 glog.Fatal(err) 186 } 187 188 node.TXIndex.Start() 189 } 190 } 191 192 func (node *Node) Stop() { 193 node.Server.Stop() 194 195 if node.TXIndex != nil { 196 node.TXIndex.Stop() 197 } 198 199 node.chainDB.Close() 200 } 201 202 func validateParams(params *lib.DeSoParams) { 203 if params.BitcoinBurnAddress == "" { 204 glog.Fatalf("The DeSoParams being used are missing the BitcoinBurnAddress field.") 205 } 206 207 // Check that TimeBetweenDifficultyRetargets is evenly divisible 208 // by TimeBetweenBlocks. 209 if params.TimeBetweenBlocks == 0 { 210 glog.Fatalf("The DeSoParams being used have TimeBetweenBlocks=0") 211 } 212 numBlocks := params.TimeBetweenDifficultyRetargets / params.TimeBetweenBlocks 213 truncatedTime := params.TimeBetweenBlocks * numBlocks 214 if truncatedTime != params.TimeBetweenDifficultyRetargets { 215 glog.Fatalf("TimeBetweenDifficultyRetargets (%v) should be evenly divisible by "+ 216 "TimeBetweenBlocks (%v)", params.TimeBetweenDifficultyRetargets, 217 params.TimeBetweenBlocks) 218 } 219 220 if params.GenesisBlock == nil || params.GenesisBlockHashHex == "" { 221 glog.Fatalf("The DeSoParams are missing genesis block info.") 222 } 223 224 // Compute the merkle root for the genesis block and make sure it matches. 225 merkle, _, err := lib.ComputeMerkleRoot(params.GenesisBlock.Txns) 226 if err != nil { 227 glog.Fatalf("Could not compute a merkle root for the genesis block: %v", err) 228 } 229 if *merkle != *params.GenesisBlock.Header.TransactionMerkleRoot { 230 glog.Fatalf("Genesis block merkle root (%s) not equal to computed merkle root (%s)", 231 hex.EncodeToString(params.GenesisBlock.Header.TransactionMerkleRoot[:]), 232 hex.EncodeToString(merkle[:])) 233 } 234 235 genesisHash, err := params.GenesisBlock.Header.Hash() 236 if err != nil { 237 glog.Fatalf("Problem hashing header for the GenesisBlock in "+ 238 "the DeSoParams (%+v): %v", params.GenesisBlock.Header, err) 239 } 240 genesisHashHex := hex.EncodeToString(genesisHash[:]) 241 if genesisHashHex != params.GenesisBlockHashHex { 242 glog.Fatalf("GenesisBlockHash in DeSoParams (%s) does not match the block "+ 243 "hash computed (%s) %d %d", params.GenesisBlockHashHex, genesisHashHex, len(params.GenesisBlockHashHex), len(genesisHashHex)) 244 } 245 246 if params.MinDifficultyTargetHex == "" { 247 glog.Fatalf("The DeSoParams MinDifficultyTargetHex (%s) should be non-empty", 248 params.MinDifficultyTargetHex) 249 } 250 251 // Check to ensure the genesis block hash meets the initial difficulty target. 252 hexBytes, err := hex.DecodeString(params.MinDifficultyTargetHex) 253 if err != nil || len(hexBytes) != 32 { 254 glog.Fatalf("The DeSoParams MinDifficultyTargetHex (%s) with length (%d) is "+ 255 "invalid: %v", params.MinDifficultyTargetHex, len(params.MinDifficultyTargetHex), err) 256 } 257 258 if params.MaxDifficultyRetargetFactor == 0 { 259 glog.Fatalf("The DeSoParams MaxDifficultyRetargetFactor is unset") 260 } 261 } 262 263 func getAddrsToListenOn(protocolPort uint16) ([]net.TCPAddr, []net.Listener) { 264 listeningAddrs := []net.TCPAddr{} 265 listeners := []net.Listener{} 266 ifaceAddrs, err := net.InterfaceAddrs() 267 if err != nil { 268 return nil, nil 269 } 270 271 for _, iAddr := range ifaceAddrs { 272 ifaceIP, _, err := net.ParseCIDR(iAddr.String()) 273 if err != nil { 274 continue 275 } 276 277 if ifaceIP.IsLinkLocalUnicast() { 278 continue 279 } 280 281 netAddr := net.TCPAddr{ 282 IP: ifaceIP, 283 Port: int(protocolPort), 284 } 285 286 listener, err := net.Listen(netAddr.Network(), netAddr.String()) 287 if err != nil { 288 continue 289 } 290 291 listeners = append(listeners, listener) 292 listeningAddrs = append(listeningAddrs, netAddr) 293 } 294 295 return listeningAddrs, listeners 296 } 297 298 func addIPsForHost(desoAddrMgr *addrmgr.AddrManager, host string, params *lib.DeSoParams) { 299 ipAddrs, err := net.LookupIP(host) 300 if err != nil { 301 glog.V(2).Infof("_addSeedAddrs: DNS discovery failed on seed host (continuing on): %s %v\n", host, err) 302 return 303 } 304 if len(ipAddrs) == 0 { 305 glog.V(2).Infof("_addSeedAddrs: No IPs found for host: %s\n", host) 306 return 307 } 308 309 // Don't take more than 5 IPs per host. 310 ipsPerHost := 5 311 if len(ipAddrs) > ipsPerHost { 312 glog.V(1).Infof("_addSeedAddrs: Truncating IPs found from %d to %d\n", len(ipAddrs), ipsPerHost) 313 ipAddrs = ipAddrs[:ipsPerHost] 314 } 315 316 glog.V(1).Infof("_addSeedAddrs: Adding seed IPs from seed %s: %v\n", host, ipAddrs) 317 318 // Convert addresses to NetAddress'es. 319 netAddrs := make([]*wire.NetAddress, len(ipAddrs)) 320 for ii, ip := range ipAddrs { 321 netAddrs[ii] = wire.NewNetAddressTimestamp( 322 // We initialize addresses with a 323 // randomly selected "last seen time" between 3 324 // and 7 days ago similar to what bitcoind does. 325 time.Now().Add(-1*time.Second*time.Duration(lib.SecondsIn3Days+ 326 lib.RandInt32(lib.SecondsIn4Days))), 327 0, 328 ip, 329 params.DefaultSocketPort) 330 } 331 glog.V(1).Infof("_addSeedAddrs: Computed the following wire.NetAddress'es: %s", spew.Sdump(netAddrs)) 332 333 // Normally the second argument is the source who told us about the 334 // addresses we're adding. In this case since the source is a DNS seed 335 // just use the first address in the fetch as the source. 336 desoAddrMgr.AddAddresses(netAddrs, netAddrs[0]) 337 } 338 339 // Must be run in a goroutine. This function continuously adds IPs from a DNS seed 340 // prefix+suffix by iterating up through all of the possible numeric values, which are typically 341 // [0, 10] 342 func addSeedAddrsFromPrefixes(desoAddrMgr *addrmgr.AddrManager, params *lib.DeSoParams) { 343 MaxIterations := 20 344 345 go func() { 346 for dnsNumber := 0; dnsNumber < MaxIterations; dnsNumber++ { 347 var wg deadlock.WaitGroup 348 for _, dnsGeneratorOuter := range params.DNSSeedGenerators { 349 wg.Add(1) 350 go func(dnsGenerator []string) { 351 dnsString := fmt.Sprintf("%s%d%s", dnsGenerator[0], dnsNumber, dnsGenerator[1]) 352 glog.V(2).Infof("_addSeedAddrsFromPrefixes: Querying DNS seed: %s", dnsString) 353 addIPsForHost(desoAddrMgr, dnsString, params) 354 wg.Done() 355 }(dnsGeneratorOuter) 356 } 357 wg.Wait() 358 } 359 }() 360 }