github.com/cryptohub-digital/blockbook@v0.3.5-0.20240403155730-99ab40b9104c/blockbook.go (about)

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