github.com/dawnbass68/myfirst@v0.0.0-20200312013042-216d947956bd/blockbook.go (about)

     1  package main
     2  
     3  import (
     4  	"blockbook/api"
     5  	"blockbook/bchain"
     6  	"blockbook/bchain/coins"
     7  	"blockbook/common"
     8  	"blockbook/db"
     9  	"blockbook/fiat"
    10  	"blockbook/server"
    11  	"context"
    12  	"encoding/json"
    13  	"flag"
    14  	"io/ioutil"
    15  	"log"
    16  	"math/rand"
    17  	"net/http"
    18  	_ "net/http/pprof"
    19  	"os"
    20  	"os/signal"
    21  	"runtime/debug"
    22  	"strings"
    23  	"sync/atomic"
    24  	"syscall"
    25  	"time"
    26  
    27  	"github.com/golang/glog"
    28  	"github.com/juju/errors"
    29  )
    30  
    31  // debounce too close requests for resync
    32  const debounceResyncIndexMs = 1009
    33  
    34  // debounce too close requests for resync mempool (ZeroMQ sends message for each tx, when new block there are many transactions)
    35  const debounceResyncMempoolMs = 1009
    36  
    37  // store internal state about once every minute
    38  const storeInternalStatePeriodMs = 59699
    39  
    40  // exit codes from the main function
    41  const exitCodeOK = 0
    42  const exitCodeFatal = 255
    43  
    44  var (
    45  	blockchain = flag.String("blockchaincfg", "", "path to blockchain RPC service configuration json file")
    46  
    47  	dbPath         = flag.String("datadir", "./data", "path to database directory")
    48  	dbCache        = flag.Int("dbcache", 1<<29, "size of the rocksdb cache")
    49  	dbMaxOpenFiles = flag.Int("dbmaxopenfiles", 1<<14, "max open files by rocksdb")
    50  
    51  	blockFrom      = flag.Int("blockheight", -1, "height of the starting block")
    52  	blockUntil     = flag.Int("blockuntil", -1, "height of the final block")
    53  	rollbackHeight = flag.Int("rollback", -1, "rollback to the given height and quit")
    54  
    55  	synchronize = flag.Bool("sync", false, "synchronizes until tip, if together with zeromq, keeps index synchronized")
    56  	repair      = flag.Bool("repair", false, "repair the database")
    57  	fixUtxo     = flag.Bool("fixutxo", false, "check and fix utxo db and exit")
    58  	prof        = flag.String("prof", "", "http server binding [address]:port of the interface to profiling data /debug/pprof/ (default no profiling)")
    59  
    60  	syncChunk   = flag.Int("chunk", 100, "block chunk size for processing in bulk mode")
    61  	syncWorkers = flag.Int("workers", 8, "number of workers to process blocks in bulk mode")
    62  	dryRun      = flag.Bool("dryrun", false, "do not index blocks, only download")
    63  
    64  	debugMode = flag.Bool("debug", false, "debug mode, return more verbose errors, reload templates on each request")
    65  
    66  	internalBinding = flag.String("internal", "", "internal http server binding [address]:port, (default no internal server)")
    67  
    68  	publicBinding = flag.String("public", "", "public http server binding [address]:port[/path] (default no public server)")
    69  
    70  	certFiles = flag.String("certfile", "", "to enable SSL specify path to certificate files without extension, expecting <certfile>.crt and <certfile>.key (default no SSL)")
    71  
    72  	explorerURL = flag.String("explorer", "", "address of blockchain explorer")
    73  
    74  	noTxCache = flag.Bool("notxcache", false, "disable tx cache")
    75  
    76  	computeColumnStats  = flag.Bool("computedbstats", false, "compute column stats and exit")
    77  	computeFeeStatsFlag = flag.Bool("computefeestats", false, "compute fee stats for blocks in blockheight-blockuntil range and exit")
    78  	dbStatsPeriodHours  = flag.Int("dbstatsperiod", 24, "period of db stats collection in hours, 0 disables stats collection")
    79  
    80  	// resync index at least each resyncIndexPeriodMs (could be more often if invoked by message from ZeroMQ)
    81  	resyncIndexPeriodMs = flag.Int("resyncindexperiod", 935093, "resync index period in milliseconds")
    82  
    83  	// resync mempool at least each resyncMempoolPeriodMs (could be more often if invoked by message from ZeroMQ)
    84  	resyncMempoolPeriodMs = flag.Int("resyncmempoolperiod", 60017, "resync mempool period in milliseconds")
    85  )
    86  
    87  var (
    88  	chanSyncIndex                 = make(chan struct{})
    89  	chanSyncMempool               = make(chan struct{})
    90  	chanStoreInternalState        = make(chan struct{})
    91  	chanSyncIndexDone             = make(chan struct{})
    92  	chanSyncMempoolDone           = make(chan struct{})
    93  	chanStoreInternalStateDone    = make(chan struct{})
    94  	chain                         bchain.BlockChain
    95  	mempool                       bchain.Mempool
    96  	index                         *db.RocksDB
    97  	txCache                       *db.TxCache
    98  	metrics                       *common.Metrics
    99  	syncWorker                    *db.SyncWorker
   100  	internalState                 *common.InternalState
   101  	callbacksOnNewBlock           []bchain.OnNewBlockFunc
   102  	callbacksOnNewTxAddr          []bchain.OnNewTxAddrFunc
   103  	callbacksOnNewFiatRatesTicker []fiat.OnNewFiatRatesTicker
   104  	chanOsSignal                  chan os.Signal
   105  	inShutdown                    int32
   106  )
   107  
   108  func init() {
   109  	glog.MaxSize = 1024 * 1024 * 8
   110  	glog.CopyStandardLogTo("INFO")
   111  }
   112  
   113  func main() {
   114  	defer func() {
   115  		if e := recover(); e != nil {
   116  			glog.Error("main recovered from panic: ", e)
   117  			debug.PrintStack()
   118  			os.Exit(-1)
   119  		}
   120  	}()
   121  	os.Exit(mainWithExitCode())
   122  }
   123  
   124  // allow deferred functions to run even in case of fatal error
   125  func mainWithExitCode() int {
   126  	flag.Parse()
   127  
   128  	defer glog.Flush()
   129  
   130  	rand.Seed(time.Now().UTC().UnixNano())
   131  
   132  	chanOsSignal = make(chan os.Signal, 1)
   133  	signal.Notify(chanOsSignal, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
   134  
   135  	glog.Infof("Blockbook: %+v, debug mode %v", common.GetVersionInfo(), *debugMode)
   136  
   137  	if *prof != "" {
   138  		go func() {
   139  			log.Println(http.ListenAndServe(*prof, nil))
   140  		}()
   141  	}
   142  
   143  	if *repair {
   144  		if err := db.RepairRocksDB(*dbPath); err != nil {
   145  			glog.Errorf("RepairRocksDB %s: %v", *dbPath, err)
   146  			return exitCodeFatal
   147  		}
   148  		return exitCodeOK
   149  	}
   150  
   151  	if *blockchain == "" {
   152  		glog.Error("Missing blockchaincfg configuration parameter")
   153  		return exitCodeFatal
   154  	}
   155  
   156  	coin, coinShortcut, coinLabel, err := coins.GetCoinNameFromConfig(*blockchain)
   157  	if err != nil {
   158  		glog.Error("config: ", err)
   159  		return exitCodeFatal
   160  	}
   161  
   162  	// gspt.SetProcTitle("blockbook-" + normalizeName(coin))
   163  
   164  	metrics, err = common.GetMetrics(coin)
   165  	if err != nil {
   166  		glog.Error("metrics: ", err)
   167  		return exitCodeFatal
   168  	}
   169  
   170  	if chain, mempool, err = getBlockChainWithRetry(coin, *blockchain, pushSynchronizationHandler, metrics, 120); err != nil {
   171  		glog.Error("rpc: ", err)
   172  		return exitCodeFatal
   173  	}
   174  
   175  	index, err = db.NewRocksDB(*dbPath, *dbCache, *dbMaxOpenFiles, chain.GetChainParser(), metrics)
   176  	if err != nil {
   177  		glog.Error("rocksDB: ", err)
   178  		return exitCodeFatal
   179  	}
   180  	defer index.Close()
   181  
   182  	internalState, err = newInternalState(coin, coinShortcut, coinLabel, index)
   183  	if err != nil {
   184  		glog.Error("internalState: ", err)
   185  		return exitCodeFatal
   186  	}
   187  
   188  	// fix possible inconsistencies in the UTXO index
   189  	if *fixUtxo || !internalState.UtxoChecked {
   190  		err = index.FixUtxos(chanOsSignal)
   191  		if err != nil {
   192  			glog.Error("fixUtxos: ", err)
   193  			return exitCodeFatal
   194  		}
   195  		internalState.UtxoChecked = true
   196  	}
   197  	index.SetInternalState(internalState)
   198  	if *fixUtxo {
   199  		err = index.StoreInternalState(internalState)
   200  		if err != nil {
   201  			glog.Error("StoreInternalState: ", err)
   202  			return exitCodeFatal
   203  		}
   204  		return exitCodeOK
   205  	}
   206  
   207  	if internalState.DbState != common.DbStateClosed {
   208  		if internalState.DbState == common.DbStateInconsistent {
   209  			glog.Error("internalState: database is in inconsistent state and cannot be used")
   210  			return exitCodeFatal
   211  		}
   212  		glog.Warning("internalState: database was left in open state, possibly previous ungraceful shutdown")
   213  	}
   214  
   215  	if *computeFeeStatsFlag {
   216  		internalState.DbState = common.DbStateOpen
   217  		err = computeFeeStats(chanOsSignal, *blockFrom, *blockUntil, index, chain, txCache, internalState, metrics)
   218  		if err != nil && err != db.ErrOperationInterrupted {
   219  			glog.Error("computeFeeStats: ", err)
   220  			return exitCodeFatal
   221  		}
   222  		return exitCodeOK
   223  	}
   224  
   225  	if *computeColumnStats {
   226  		internalState.DbState = common.DbStateOpen
   227  		err = index.ComputeInternalStateColumnStats(chanOsSignal)
   228  		if err != nil {
   229  			glog.Error("internalState: ", err)
   230  			return exitCodeFatal
   231  		}
   232  		glog.Info("DB size on disk: ", index.DatabaseSizeOnDisk(), ", DB size as computed: ", internalState.DBSizeTotal())
   233  		return exitCodeOK
   234  	}
   235  
   236  	syncWorker, err = db.NewSyncWorker(index, chain, *syncWorkers, *syncChunk, *blockFrom, *dryRun, chanOsSignal, metrics, internalState)
   237  	if err != nil {
   238  		glog.Errorf("NewSyncWorker %v", err)
   239  		return exitCodeFatal
   240  	}
   241  
   242  	// set the DbState to open at this moment, after all important workers are initialized
   243  	internalState.DbState = common.DbStateOpen
   244  	err = index.StoreInternalState(internalState)
   245  	if err != nil {
   246  		glog.Error("internalState: ", err)
   247  		return exitCodeFatal
   248  	}
   249  
   250  	if *rollbackHeight >= 0 {
   251  		err = performRollback()
   252  		if err != nil {
   253  			return exitCodeFatal
   254  		}
   255  		return exitCodeOK
   256  	}
   257  
   258  	if txCache, err = db.NewTxCache(index, chain, metrics, internalState, !*noTxCache); err != nil {
   259  		glog.Error("txCache ", err)
   260  		return exitCodeFatal
   261  	}
   262  
   263  	// report BlockbookAppInfo metric, only log possible error
   264  	if err = blockbookAppInfoMetric(index, chain, txCache, internalState, metrics); err != nil {
   265  		glog.Error("blockbookAppInfoMetric ", err)
   266  	}
   267  
   268  	var internalServer *server.InternalServer
   269  	if *internalBinding != "" {
   270  		internalServer, err = startInternalServer()
   271  		if err != nil {
   272  			glog.Error("internal server: ", err)
   273  			return exitCodeFatal
   274  		}
   275  	}
   276  
   277  	var publicServer *server.PublicServer
   278  	if *publicBinding != "" {
   279  		publicServer, err = startPublicServer()
   280  		if err != nil {
   281  			glog.Error("public server: ", err)
   282  			return exitCodeFatal
   283  		}
   284  	}
   285  
   286  	if *synchronize {
   287  		internalState.SyncMode = true
   288  		internalState.InitialSync = true
   289  		if err := syncWorker.ResyncIndex(nil, true); err != nil {
   290  			if err != db.ErrOperationInterrupted {
   291  				glog.Error("resyncIndex ", err)
   292  				return exitCodeFatal
   293  			}
   294  			return exitCodeOK
   295  		}
   296  		// initialize mempool after the initial sync is complete
   297  		var addrDescForOutpoint bchain.AddrDescForOutpointFunc
   298  		if chain.GetChainParser().GetChainType() == bchain.ChainBitcoinType {
   299  			addrDescForOutpoint = index.AddrDescForOutpoint
   300  		}
   301  		err = chain.InitializeMempool(addrDescForOutpoint, onNewTxAddr)
   302  		if err != nil {
   303  			glog.Error("initializeMempool ", err)
   304  			return exitCodeFatal
   305  		}
   306  		var mempoolCount int
   307  		if mempoolCount, err = mempool.Resync(); err != nil {
   308  			glog.Error("resyncMempool ", err)
   309  			return exitCodeFatal
   310  		}
   311  		internalState.FinishedMempoolSync(mempoolCount)
   312  		go syncIndexLoop()
   313  		go syncMempoolLoop()
   314  		internalState.InitialSync = false
   315  	}
   316  	go storeInternalStateLoop()
   317  
   318  	if publicServer != nil {
   319  		// start full public interface
   320  		callbacksOnNewBlock = append(callbacksOnNewBlock, publicServer.OnNewBlock)
   321  		callbacksOnNewTxAddr = append(callbacksOnNewTxAddr, publicServer.OnNewTxAddr)
   322  		callbacksOnNewFiatRatesTicker = append(callbacksOnNewFiatRatesTicker, publicServer.OnNewFiatRatesTicker)
   323  		publicServer.ConnectFullPublicInterface()
   324  	}
   325  
   326  	if *blockFrom >= 0 {
   327  		if *blockUntil < 0 {
   328  			*blockUntil = *blockFrom
   329  		}
   330  		height := uint32(*blockFrom)
   331  		until := uint32(*blockUntil)
   332  
   333  		if !*synchronize {
   334  			if err = syncWorker.ConnectBlocksParallel(height, until); err != nil {
   335  				if err != db.ErrOperationInterrupted {
   336  					glog.Error("connectBlocksParallel ", err)
   337  					return exitCodeFatal
   338  				}
   339  				return exitCodeOK
   340  			}
   341  		}
   342  	}
   343  
   344  	if internalServer != nil || publicServer != nil || chain != nil {
   345  		// start fiat rates downloader only if not shutting down immediately
   346  		initFiatRatesDownloader(index, *blockchain)
   347  		waitForSignalAndShutdown(internalServer, publicServer, chain, 10*time.Second)
   348  	}
   349  
   350  	if *synchronize {
   351  		close(chanSyncIndex)
   352  		close(chanSyncMempool)
   353  		close(chanStoreInternalState)
   354  		<-chanSyncIndexDone
   355  		<-chanSyncMempoolDone
   356  		<-chanStoreInternalStateDone
   357  	}
   358  	return exitCodeOK
   359  }
   360  
   361  func getBlockChainWithRetry(coin string, configfile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics, seconds int) (bchain.BlockChain, bchain.Mempool, error) {
   362  	var chain bchain.BlockChain
   363  	var mempool bchain.Mempool
   364  	var err error
   365  	timer := time.NewTimer(time.Second)
   366  	for i := 0; ; i++ {
   367  		if chain, mempool, err = coins.NewBlockChain(coin, configfile, pushHandler, metrics); err != nil {
   368  			if i < seconds {
   369  				glog.Error("rpc: ", err, " Retrying...")
   370  				select {
   371  				case <-chanOsSignal:
   372  					return nil, nil, errors.New("Interrupted")
   373  				case <-timer.C:
   374  					timer.Reset(time.Second)
   375  					continue
   376  				}
   377  			} else {
   378  				return nil, nil, err
   379  			}
   380  		}
   381  		return chain, mempool, nil
   382  	}
   383  }
   384  
   385  func startInternalServer() (*server.InternalServer, error) {
   386  	internalServer, err := server.NewInternalServer(*internalBinding, *certFiles, index, chain, mempool, txCache, internalState)
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  	go func() {
   391  		err = internalServer.Run()
   392  		if err != nil {
   393  			if err.Error() == "http: Server closed" {
   394  				glog.Info("internal server: closed")
   395  			} else {
   396  				glog.Error(err)
   397  				return
   398  			}
   399  		}
   400  	}()
   401  	return internalServer, nil
   402  }
   403  
   404  func startPublicServer() (*server.PublicServer, error) {
   405  	// start public server in limited functionality, extend it after sync is finished by calling ConnectFullPublicInterface
   406  	publicServer, err := server.NewPublicServer(*publicBinding, *certFiles, index, chain, mempool, txCache, *explorerURL, metrics, internalState, *debugMode)
   407  	if err != nil {
   408  		return nil, err
   409  	}
   410  	go func() {
   411  		err = publicServer.Run()
   412  		if err != nil {
   413  			if err.Error() == "http: Server closed" {
   414  				glog.Info("public server: closed")
   415  			} else {
   416  				glog.Error(err)
   417  				return
   418  			}
   419  		}
   420  	}()
   421  	return publicServer, err
   422  }
   423  
   424  func performRollback() error {
   425  	bestHeight, bestHash, err := index.GetBestBlock()
   426  	if err != nil {
   427  		glog.Error("rollbackHeight: ", err)
   428  		return err
   429  	}
   430  	if uint32(*rollbackHeight) > bestHeight {
   431  		glog.Infof("nothing to rollback, rollbackHeight %d, bestHeight: %d", *rollbackHeight, bestHeight)
   432  	} else {
   433  		hashes := []string{bestHash}
   434  		for height := bestHeight - 1; height >= uint32(*rollbackHeight); height-- {
   435  			hash, err := index.GetBlockHash(height)
   436  			if err != nil {
   437  				glog.Error("rollbackHeight: ", err)
   438  				return err
   439  			}
   440  			hashes = append(hashes, hash)
   441  		}
   442  		err = syncWorker.DisconnectBlocks(uint32(*rollbackHeight), bestHeight, hashes)
   443  		if err != nil {
   444  			glog.Error("rollbackHeight: ", err)
   445  			return err
   446  		}
   447  	}
   448  	return nil
   449  }
   450  
   451  func blockbookAppInfoMetric(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState, metrics *common.Metrics) error {
   452  	api, err := api.NewWorker(db, chain, mempool, txCache, is)
   453  	if err != nil {
   454  		return err
   455  	}
   456  	si, err := api.GetSystemInfo(false)
   457  	if err != nil {
   458  		return err
   459  	}
   460  	metrics.BlockbookAppInfo.Reset()
   461  	metrics.BlockbookAppInfo.With(common.Labels{
   462  		"blockbook_version":        si.Blockbook.Version,
   463  		"blockbook_commit":         si.Blockbook.GitCommit,
   464  		"blockbook_buildtime":      si.Blockbook.BuildTime,
   465  		"backend_version":          si.Backend.Version,
   466  		"backend_subversion":       si.Backend.Subversion,
   467  		"backend_protocol_version": si.Backend.ProtocolVersion}).Set(float64(0))
   468  	return nil
   469  }
   470  
   471  func newInternalState(coin, coinShortcut, coinLabel string, d *db.RocksDB) (*common.InternalState, error) {
   472  	is, err := d.LoadInternalState(coin)
   473  	if err != nil {
   474  		return nil, err
   475  	}
   476  	is.CoinShortcut = coinShortcut
   477  	if coinLabel == "" {
   478  		coinLabel = coin
   479  	}
   480  	is.CoinLabel = coinLabel
   481  	name, err := os.Hostname()
   482  	if err != nil {
   483  		glog.Error("get hostname ", err)
   484  	} else {
   485  		if i := strings.IndexByte(name, '.'); i > 0 {
   486  			name = name[:i]
   487  		}
   488  		is.Host = name
   489  	}
   490  	return is, nil
   491  }
   492  
   493  func tickAndDebounce(tickTime time.Duration, debounceTime time.Duration, input chan struct{}, f func()) {
   494  	timer := time.NewTimer(tickTime)
   495  	var firstDebounce time.Time
   496  Loop:
   497  	for {
   498  		select {
   499  		case _, ok := <-input:
   500  			if !timer.Stop() {
   501  				<-timer.C
   502  			}
   503  			// exit loop on closed input channel
   504  			if !ok {
   505  				break Loop
   506  			}
   507  			if firstDebounce.IsZero() {
   508  				firstDebounce = time.Now()
   509  			}
   510  			// debounce for up to debounceTime period
   511  			// afterwards execute immediately
   512  			if firstDebounce.Add(debounceTime).After(time.Now()) {
   513  				timer.Reset(debounceTime)
   514  			} else {
   515  				timer.Reset(0)
   516  			}
   517  		case <-timer.C:
   518  			// do the action, if not in shutdown, then start the loop again
   519  			if atomic.LoadInt32(&inShutdown) == 0 {
   520  				f()
   521  			}
   522  			timer.Reset(tickTime)
   523  			firstDebounce = time.Time{}
   524  		}
   525  	}
   526  }
   527  
   528  func syncIndexLoop() {
   529  	defer close(chanSyncIndexDone)
   530  	glog.Info("syncIndexLoop starting")
   531  	// resync index about every 15 minutes if there are no chanSyncIndex requests, with debounce 1 second
   532  	tickAndDebounce(time.Duration(*resyncIndexPeriodMs)*time.Millisecond, debounceResyncIndexMs*time.Millisecond, chanSyncIndex, func() {
   533  		if err := syncWorker.ResyncIndex(onNewBlockHash, false); err != nil {
   534  			glog.Error("syncIndexLoop ", errors.ErrorStack(err), ", will retry...")
   535  			// retry once in case of random network error, after a slight delay
   536  			time.Sleep(time.Millisecond * 2500)
   537  			if err := syncWorker.ResyncIndex(onNewBlockHash, false); err != nil {
   538  				glog.Error("syncIndexLoop ", errors.ErrorStack(err))
   539  			}
   540  		}
   541  	})
   542  	glog.Info("syncIndexLoop stopped")
   543  }
   544  
   545  func onNewBlockHash(hash string, height uint32) {
   546  	for _, c := range callbacksOnNewBlock {
   547  		c(hash, height)
   548  	}
   549  }
   550  
   551  func onNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) {
   552  	for _, c := range callbacksOnNewFiatRatesTicker {
   553  		c(ticker)
   554  	}
   555  }
   556  
   557  func syncMempoolLoop() {
   558  	defer close(chanSyncMempoolDone)
   559  	glog.Info("syncMempoolLoop starting")
   560  	// resync mempool about every minute if there are no chanSyncMempool requests, with debounce 1 second
   561  	tickAndDebounce(time.Duration(*resyncMempoolPeriodMs)*time.Millisecond, debounceResyncMempoolMs*time.Millisecond, chanSyncMempool, func() {
   562  		internalState.StartedMempoolSync()
   563  		if count, err := mempool.Resync(); err != nil {
   564  			glog.Error("syncMempoolLoop ", errors.ErrorStack(err))
   565  		} else {
   566  			internalState.FinishedMempoolSync(count)
   567  
   568  		}
   569  	})
   570  	glog.Info("syncMempoolLoop stopped")
   571  }
   572  
   573  func storeInternalStateLoop() {
   574  	stopCompute := make(chan os.Signal)
   575  	defer func() {
   576  		close(stopCompute)
   577  		close(chanStoreInternalStateDone)
   578  	}()
   579  	signal.Notify(stopCompute, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
   580  	var computeRunning bool
   581  	lastCompute := time.Now()
   582  	lastAppInfo := time.Now()
   583  	logAppInfoPeriod := 15 * time.Minute
   584  	// randomize the duration between ComputeInternalStateColumnStats to avoid peaks after reboot of machine with multiple blockbooks
   585  	computePeriod := time.Duration(*dbStatsPeriodHours)*time.Hour + time.Duration(rand.Float64()*float64((4*time.Hour).Nanoseconds()))
   586  	if (*dbStatsPeriodHours) > 0 {
   587  		glog.Info("storeInternalStateLoop starting with db stats recompute period ", computePeriod)
   588  	} else {
   589  		glog.Info("storeInternalStateLoop starting with db stats compute disabled")
   590  	}
   591  	tickAndDebounce(storeInternalStatePeriodMs*time.Millisecond, (storeInternalStatePeriodMs-1)*time.Millisecond, chanStoreInternalState, func() {
   592  		if (*dbStatsPeriodHours) > 0 && !computeRunning && lastCompute.Add(computePeriod).Before(time.Now()) {
   593  			computeRunning = true
   594  			go func() {
   595  				err := index.ComputeInternalStateColumnStats(stopCompute)
   596  				if err != nil {
   597  					glog.Error("computeInternalStateColumnStats error: ", err)
   598  				}
   599  				lastCompute = time.Now()
   600  				computeRunning = false
   601  			}()
   602  		}
   603  		if err := index.StoreInternalState(internalState); err != nil {
   604  			glog.Error("storeInternalStateLoop ", errors.ErrorStack(err))
   605  		}
   606  		if lastAppInfo.Add(logAppInfoPeriod).Before(time.Now()) {
   607  			glog.Info(index.GetMemoryStats())
   608  			if err := blockbookAppInfoMetric(index, chain, txCache, internalState, metrics); err != nil {
   609  				glog.Error("blockbookAppInfoMetric ", err)
   610  			}
   611  			lastAppInfo = time.Now()
   612  		}
   613  	})
   614  	glog.Info("storeInternalStateLoop stopped")
   615  }
   616  
   617  func onNewTxAddr(tx *bchain.Tx, desc bchain.AddressDescriptor) {
   618  	for _, c := range callbacksOnNewTxAddr {
   619  		c(tx, desc)
   620  	}
   621  }
   622  
   623  func pushSynchronizationHandler(nt bchain.NotificationType) {
   624  	glog.V(1).Info("MQ: notification ", nt)
   625  	if atomic.LoadInt32(&inShutdown) != 0 {
   626  		return
   627  	}
   628  	if nt == bchain.NotificationNewBlock {
   629  		chanSyncIndex <- struct{}{}
   630  	} else if nt == bchain.NotificationNewTx {
   631  		chanSyncMempool <- struct{}{}
   632  	} else {
   633  		glog.Error("MQ: unknown notification sent")
   634  	}
   635  }
   636  
   637  func waitForSignalAndShutdown(internal *server.InternalServer, public *server.PublicServer, chain bchain.BlockChain, timeout time.Duration) {
   638  	sig := <-chanOsSignal
   639  	atomic.StoreInt32(&inShutdown, 1)
   640  	glog.Infof("shutdown: %v", sig)
   641  
   642  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   643  	defer cancel()
   644  
   645  	if internal != nil {
   646  		if err := internal.Shutdown(ctx); err != nil {
   647  			glog.Error("internal server: shutdown error: ", err)
   648  		}
   649  	}
   650  
   651  	if public != nil {
   652  		if err := public.Shutdown(ctx); err != nil {
   653  			glog.Error("public server: shutdown error: ", err)
   654  		}
   655  	}
   656  
   657  	if chain != nil {
   658  		if err := chain.Shutdown(ctx); err != nil {
   659  			glog.Error("rpc: shutdown error: ", err)
   660  		}
   661  	}
   662  }
   663  
   664  func printResult(txid string, vout int32, isOutput bool) error {
   665  	glog.Info(txid, vout, isOutput)
   666  	return nil
   667  }
   668  
   669  func normalizeName(s string) string {
   670  	s = strings.ToLower(s)
   671  	s = strings.Replace(s, " ", "-", -1)
   672  	return s
   673  }
   674  
   675  // computeFeeStats computes fee distribution in defined blocks
   676  func computeFeeStats(stopCompute chan os.Signal, blockFrom, blockTo int, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState, metrics *common.Metrics) error {
   677  	start := time.Now()
   678  	glog.Info("computeFeeStats start")
   679  	api, err := api.NewWorker(db, chain, mempool, txCache, is)
   680  	if err != nil {
   681  		return err
   682  	}
   683  	err = api.ComputeFeeStats(blockFrom, blockTo, stopCompute)
   684  	glog.Info("computeFeeStats finished in ", time.Since(start))
   685  	return err
   686  }
   687  
   688  func initFiatRatesDownloader(db *db.RocksDB, configfile string) {
   689  	data, err := ioutil.ReadFile(configfile)
   690  	if err != nil {
   691  		glog.Errorf("Error reading file %v, %v", configfile, err)
   692  		return
   693  	}
   694  
   695  	var config struct {
   696  		FiatRates       string `json:"fiat_rates"`
   697  		FiatRatesParams string `json:"fiat_rates_params"`
   698  	}
   699  
   700  	err = json.Unmarshal(data, &config)
   701  	if err != nil {
   702  		glog.Errorf("Error parsing config file %v, %v", configfile, err)
   703  		return
   704  	}
   705  
   706  	if config.FiatRates == "" || config.FiatRatesParams == "" {
   707  		glog.Infof("FiatRates config (%v) is empty, so the functionality is disabled.", configfile)
   708  	} else {
   709  		fiatRates, err := fiat.NewFiatRatesDownloader(db, config.FiatRates, config.FiatRatesParams, nil, onNewFiatRatesTicker)
   710  		if err != nil {
   711  			glog.Errorf("NewFiatRatesDownloader Init error: %v", err)
   712  			return
   713  		}
   714  		glog.Infof("Starting %v FiatRates downloader...", config.FiatRates)
   715  		go fiatRates.Run()
   716  	}
   717  }