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  }