github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/blockbook.go (about) 1 package main 2 3 import ( 4 "context" 5 "flag" 6 "log" 7 "math/rand" 8 "net/http" 9 _ "net/http/pprof" 10 "os" 11 "os/signal" 12 "runtime/debug" 13 "strconv" 14 "strings" 15 "syscall" 16 "time" 17 18 "github.com/golang/glog" 19 "github.com/juju/errors" 20 "github.com/trezor/blockbook/api" 21 "github.com/trezor/blockbook/bchain" 22 "github.com/trezor/blockbook/bchain/coins" 23 "github.com/trezor/blockbook/common" 24 "github.com/trezor/blockbook/db" 25 "github.com/trezor/blockbook/fiat" 26 "github.com/trezor/blockbook/fourbyte" 27 "github.com/trezor/blockbook/server" 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 config, err := common.GetConfig(*configFile) 156 if err != nil { 157 glog.Error("config: ", err) 158 return exitCodeFatal 159 } 160 161 metrics, err = common.GetMetrics(config.CoinName) 162 if err != nil { 163 glog.Error("metrics: ", err) 164 return exitCodeFatal 165 } 166 167 if chain, mempool, err = getBlockChainWithRetry(config.CoinName, *configFile, pushSynchronizationHandler, metrics, 120); err != nil { 168 glog.Error("rpc: ", err) 169 return exitCodeFatal 170 } 171 172 index, err = db.NewRocksDB(*dbPath, *dbCache, *dbMaxOpenFiles, chain.GetChainParser(), metrics, *extendedIndex) 173 if err != nil { 174 glog.Error("rocksDB: ", err) 175 return exitCodeFatal 176 } 177 defer index.Close() 178 179 internalState, err = newInternalState(config, index, *enableSubNewTx) 180 if err != nil { 181 glog.Error("internalState: ", err) 182 return exitCodeFatal 183 } 184 185 // fix possible inconsistencies in the UTXO index 186 if *fixUtxo || !internalState.UtxoChecked { 187 err = index.FixUtxos(chanOsSignal) 188 if err != nil { 189 glog.Error("fixUtxos: ", err) 190 return exitCodeFatal 191 } 192 internalState.UtxoChecked = true 193 } 194 195 // sort addressContracts if necessary 196 if !internalState.SortedAddressContracts { 197 err = index.SortAddressContracts(chanOsSignal) 198 if err != nil { 199 glog.Error("sortAddressContracts: ", err) 200 return exitCodeFatal 201 } 202 internalState.SortedAddressContracts = true 203 } 204 205 index.SetInternalState(internalState) 206 if *fixUtxo { 207 err = index.StoreInternalState(internalState) 208 if err != nil { 209 glog.Error("StoreInternalState: ", err) 210 return exitCodeFatal 211 } 212 return exitCodeOK 213 } 214 215 if internalState.DbState != common.DbStateClosed { 216 if internalState.DbState == common.DbStateInconsistent { 217 glog.Error("internalState: database is in inconsistent state and cannot be used") 218 return exitCodeFatal 219 } 220 glog.Warning("internalState: database was left in open state, possibly previous ungraceful shutdown") 221 } 222 223 if *computeFeeStatsFlag { 224 internalState.DbState = common.DbStateOpen 225 err = computeFeeStats(chanOsSignal, *blockFrom, *blockUntil, index, chain, txCache, internalState, metrics) 226 if err != nil && err != db.ErrOperationInterrupted { 227 glog.Error("computeFeeStats: ", err) 228 return exitCodeFatal 229 } 230 return exitCodeOK 231 } 232 233 if *computeColumnStats { 234 internalState.DbState = common.DbStateOpen 235 err = index.ComputeInternalStateColumnStats(chanOsSignal) 236 if err != nil { 237 glog.Error("internalState: ", err) 238 return exitCodeFatal 239 } 240 glog.Info("DB size on disk: ", index.DatabaseSizeOnDisk(), ", DB size as computed: ", internalState.DBSizeTotal()) 241 return exitCodeOK 242 } 243 244 syncWorker, err = db.NewSyncWorker(index, chain, *syncWorkers, *syncChunk, *blockFrom, *dryRun, chanOsSignal, metrics, internalState) 245 if err != nil { 246 glog.Errorf("NewSyncWorker %v", err) 247 return exitCodeFatal 248 } 249 250 // set the DbState to open at this moment, after all important workers are initialized 251 internalState.DbState = common.DbStateOpen 252 err = index.StoreInternalState(internalState) 253 if err != nil { 254 glog.Error("internalState: ", err) 255 return exitCodeFatal 256 } 257 258 if *rollbackHeight >= 0 { 259 err = performRollback() 260 if err != nil { 261 return exitCodeFatal 262 } 263 return exitCodeOK 264 } 265 266 if txCache, err = db.NewTxCache(index, chain, metrics, internalState, !*noTxCache); err != nil { 267 glog.Error("txCache ", err) 268 return exitCodeFatal 269 } 270 271 if fiatRates, err = fiat.NewFiatRates(index, config, metrics, onNewFiatRatesTicker); err != nil { 272 glog.Error("fiatRates ", err) 273 return exitCodeFatal 274 } 275 276 // report BlockbookAppInfo metric, only log possible error 277 if err = blockbookAppInfoMetric(index, chain, txCache, internalState, metrics); err != nil { 278 glog.Error("blockbookAppInfoMetric ", err) 279 } 280 281 var internalServer *server.InternalServer 282 if *internalBinding != "" { 283 internalServer, err = startInternalServer() 284 if err != nil { 285 glog.Error("internal server: ", err) 286 return exitCodeFatal 287 } 288 } 289 290 var publicServer *server.PublicServer 291 if *publicBinding != "" { 292 publicServer, err = startPublicServer() 293 if err != nil { 294 glog.Error("public server: ", err) 295 return exitCodeFatal 296 } 297 } 298 299 if *synchronize { 300 internalState.SyncMode = true 301 internalState.InitialSync = true 302 if err := syncWorker.ResyncIndex(nil, true); err != nil { 303 if err != db.ErrOperationInterrupted { 304 glog.Error("resyncIndex ", err) 305 return exitCodeFatal 306 } 307 return exitCodeOK 308 } 309 // initialize mempool after the initial sync is complete 310 var addrDescForOutpoint bchain.AddrDescForOutpointFunc 311 if chain.GetChainParser().GetChainType() == bchain.ChainBitcoinType { 312 addrDescForOutpoint = index.AddrDescForOutpoint 313 } 314 err = chain.InitializeMempool(addrDescForOutpoint, onNewTxAddr, onNewTx) 315 if err != nil { 316 glog.Error("initializeMempool ", err) 317 return exitCodeFatal 318 } 319 var mempoolCount int 320 if mempoolCount, err = mempool.Resync(); err != nil { 321 glog.Error("resyncMempool ", err) 322 return exitCodeFatal 323 } 324 internalState.FinishedMempoolSync(mempoolCount) 325 go syncIndexLoop() 326 go syncMempoolLoop() 327 internalState.InitialSync = false 328 } 329 go storeInternalStateLoop() 330 331 if publicServer != nil { 332 // start full public interface 333 callbacksOnNewBlock = append(callbacksOnNewBlock, publicServer.OnNewBlock) 334 callbacksOnNewTxAddr = append(callbacksOnNewTxAddr, publicServer.OnNewTxAddr) 335 callbacksOnNewTx = append(callbacksOnNewTx, publicServer.OnNewTx) 336 callbacksOnNewFiatRatesTicker = append(callbacksOnNewFiatRatesTicker, publicServer.OnNewFiatRatesTicker) 337 publicServer.ConnectFullPublicInterface() 338 } 339 340 if *blockFrom >= 0 { 341 if *blockUntil < 0 { 342 *blockUntil = *blockFrom 343 } 344 height := uint32(*blockFrom) 345 until := uint32(*blockUntil) 346 347 if !*synchronize { 348 if err = syncWorker.ConnectBlocksParallel(height, until); err != nil { 349 if err != db.ErrOperationInterrupted { 350 glog.Error("connectBlocksParallel ", err) 351 return exitCodeFatal 352 } 353 return exitCodeOK 354 } 355 } 356 } 357 358 if internalServer != nil || publicServer != nil || chain != nil { 359 // start fiat rates downloader only if not shutting down immediately 360 initDownloaders(index, chain, config) 361 waitForSignalAndShutdown(internalServer, publicServer, chain, 10*time.Second) 362 } 363 364 if *synchronize { 365 close(chanSyncIndex) 366 close(chanSyncMempool) 367 close(chanStoreInternalState) 368 <-chanSyncIndexDone 369 <-chanSyncMempoolDone 370 <-chanStoreInternalStateDone 371 } 372 return exitCodeOK 373 } 374 375 func getBlockChainWithRetry(coin string, configFile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics, seconds int) (bchain.BlockChain, bchain.Mempool, error) { 376 var chain bchain.BlockChain 377 var mempool bchain.Mempool 378 var err error 379 timer := time.NewTimer(time.Second) 380 for i := 0; ; i++ { 381 if chain, mempool, err = coins.NewBlockChain(coin, configFile, pushHandler, metrics); err != nil { 382 if i < seconds { 383 glog.Error("rpc: ", err, " Retrying...") 384 select { 385 case <-chanOsSignal: 386 return nil, nil, errors.New("Interrupted") 387 case <-timer.C: 388 timer.Reset(time.Second) 389 continue 390 } 391 } else { 392 return nil, nil, err 393 } 394 } 395 return chain, mempool, nil 396 } 397 } 398 399 func startInternalServer() (*server.InternalServer, error) { 400 internalServer, err := server.NewInternalServer(*internalBinding, *certFiles, index, chain, mempool, txCache, metrics, internalState, fiatRates) 401 if err != nil { 402 return nil, err 403 } 404 go func() { 405 err = internalServer.Run() 406 if err != nil { 407 if err.Error() == "http: Server closed" { 408 glog.Info("internal server: closed") 409 } else { 410 glog.Error(err) 411 return 412 } 413 } 414 }() 415 return internalServer, nil 416 } 417 418 func startPublicServer() (*server.PublicServer, error) { 419 // start public server in limited functionality, extend it after sync is finished by calling ConnectFullPublicInterface 420 publicServer, err := server.NewPublicServer(*publicBinding, *certFiles, index, chain, mempool, txCache, *explorerURL, metrics, internalState, fiatRates, *debugMode) 421 if err != nil { 422 return nil, err 423 } 424 go func() { 425 err = publicServer.Run() 426 if err != nil { 427 if err.Error() == "http: Server closed" { 428 glog.Info("public server: closed") 429 } else { 430 glog.Error(err) 431 return 432 } 433 } 434 }() 435 return publicServer, err 436 } 437 438 func performRollback() error { 439 bestHeight, bestHash, err := index.GetBestBlock() 440 if err != nil { 441 glog.Error("rollbackHeight: ", err) 442 return err 443 } 444 if uint32(*rollbackHeight) > bestHeight { 445 glog.Infof("nothing to rollback, rollbackHeight %d, bestHeight: %d", *rollbackHeight, bestHeight) 446 } else { 447 hashes := []string{bestHash} 448 for height := bestHeight - 1; height >= uint32(*rollbackHeight); height-- { 449 hash, err := index.GetBlockHash(height) 450 if err != nil { 451 glog.Error("rollbackHeight: ", err) 452 return err 453 } 454 hashes = append(hashes, hash) 455 } 456 err = syncWorker.DisconnectBlocks(uint32(*rollbackHeight), bestHeight, hashes) 457 if err != nil { 458 glog.Error("rollbackHeight: ", err) 459 return err 460 } 461 } 462 return nil 463 } 464 465 func blockbookAppInfoMetric(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState, metrics *common.Metrics) error { 466 api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) 467 if err != nil { 468 return err 469 } 470 si, err := api.GetSystemInfo(false) 471 if err != nil { 472 return err 473 } 474 subversion := si.Backend.Subversion 475 if subversion == "" { 476 // for coins without subversion (ETH) use ConsensusVersion as subversion in metrics 477 subversion = si.Backend.ConsensusVersion 478 } 479 480 metrics.BlockbookAppInfo.Reset() 481 metrics.BlockbookAppInfo.With(common.Labels{ 482 "blockbook_version": si.Blockbook.Version, 483 "blockbook_commit": si.Blockbook.GitCommit, 484 "blockbook_buildtime": si.Blockbook.BuildTime, 485 "backend_version": si.Backend.Version, 486 "backend_subversion": subversion, 487 "backend_protocol_version": si.Backend.ProtocolVersion}).Set(float64(0)) 488 metrics.BackendBestHeight.Set(float64(si.Backend.Blocks)) 489 metrics.BlockbookBestHeight.Set(float64(si.Blockbook.BestHeight)) 490 return nil 491 } 492 493 func newInternalState(config *common.Config, d *db.RocksDB, enableSubNewTx bool) (*common.InternalState, error) { 494 is, err := d.LoadInternalState(config) 495 if err != nil { 496 return nil, err 497 } 498 499 is.EnableSubNewTx = enableSubNewTx 500 name, err := os.Hostname() 501 if err != nil { 502 glog.Error("get hostname ", err) 503 } else { 504 if i := strings.IndexByte(name, '.'); i > 0 { 505 name = name[:i] 506 } 507 is.Host = name 508 } 509 510 is.WsGetAccountInfoLimit, _ = strconv.Atoi(os.Getenv(strings.ToUpper(is.CoinShortcut) + "_WS_GETACCOUNTINFO_LIMIT")) 511 if is.WsGetAccountInfoLimit > 0 { 512 glog.Info("WsGetAccountInfoLimit enabled with limit ", is.WsGetAccountInfoLimit) 513 is.WsLimitExceedingIPs = make(map[string]int) 514 } 515 return is, nil 516 } 517 518 func syncIndexLoop() { 519 defer close(chanSyncIndexDone) 520 glog.Info("syncIndexLoop starting") 521 // resync index about every 15 minutes if there are no chanSyncIndex requests, with debounce 1 second 522 common.TickAndDebounce(time.Duration(*resyncIndexPeriodMs)*time.Millisecond, debounceResyncIndexMs*time.Millisecond, chanSyncIndex, func() { 523 if err := syncWorker.ResyncIndex(onNewBlockHash, false); err != nil { 524 glog.Error("syncIndexLoop ", errors.ErrorStack(err), ", will retry...") 525 // retry once in case of random network error, after a slight delay 526 time.Sleep(time.Millisecond * 2500) 527 if err := syncWorker.ResyncIndex(onNewBlockHash, false); err != nil { 528 glog.Error("syncIndexLoop ", errors.ErrorStack(err)) 529 } 530 } 531 }) 532 glog.Info("syncIndexLoop stopped") 533 } 534 535 func onNewBlockHash(hash string, height uint32) { 536 defer func() { 537 if r := recover(); r != nil { 538 glog.Error("onNewBlockHash recovered from panic: ", r) 539 } 540 }() 541 for _, c := range callbacksOnNewBlock { 542 c(hash, height) 543 } 544 } 545 546 func onNewFiatRatesTicker(ticker *common.CurrencyRatesTicker) { 547 defer func() { 548 if r := recover(); r != nil { 549 glog.Error("onNewFiatRatesTicker recovered from panic: ", r) 550 debug.PrintStack() 551 } 552 }() 553 for _, c := range callbacksOnNewFiatRatesTicker { 554 c(ticker) 555 } 556 } 557 558 func syncMempoolLoop() { 559 defer close(chanSyncMempoolDone) 560 glog.Info("syncMempoolLoop starting") 561 // resync mempool about every minute if there are no chanSyncMempool requests, with debounce 1 second 562 common.TickAndDebounce(time.Duration(*resyncMempoolPeriodMs)*time.Millisecond, debounceResyncMempoolMs*time.Millisecond, chanSyncMempool, func() { 563 internalState.StartedMempoolSync() 564 if count, err := mempool.Resync(); err != nil { 565 glog.Error("syncMempoolLoop ", errors.ErrorStack(err)) 566 } else { 567 internalState.FinishedMempoolSync(count) 568 569 } 570 }) 571 glog.Info("syncMempoolLoop stopped") 572 } 573 574 func storeInternalStateLoop() { 575 stopCompute := make(chan os.Signal) 576 defer func() { 577 close(stopCompute) 578 close(chanStoreInternalStateDone) 579 }() 580 signal.Notify(stopCompute, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) 581 var computeRunning bool 582 lastCompute := time.Now() 583 lastAppInfo := time.Now() 584 logAppInfoPeriod := 15 * time.Minute 585 // randomize the duration between ComputeInternalStateColumnStats to avoid peaks after reboot of machine with multiple blockbooks 586 computePeriod := time.Duration(*dbStatsPeriodHours)*time.Hour + time.Duration(rand.Float64()*float64((4*time.Hour).Nanoseconds())) 587 if (*dbStatsPeriodHours) > 0 { 588 glog.Info("storeInternalStateLoop starting with db stats recompute period ", computePeriod) 589 } else { 590 glog.Info("storeInternalStateLoop starting with db stats compute disabled") 591 } 592 common.TickAndDebounce(storeInternalStatePeriodMs*time.Millisecond, (storeInternalStatePeriodMs-1)*time.Millisecond, chanStoreInternalState, func() { 593 if (*dbStatsPeriodHours) > 0 && !computeRunning && lastCompute.Add(computePeriod).Before(time.Now()) { 594 computeRunning = true 595 go func() { 596 err := index.ComputeInternalStateColumnStats(stopCompute) 597 if err != nil { 598 glog.Error("computeInternalStateColumnStats error: ", err) 599 } 600 lastCompute = time.Now() 601 computeRunning = false 602 }() 603 } 604 if err := index.StoreInternalState(internalState); err != nil { 605 glog.Error("storeInternalStateLoop ", errors.ErrorStack(err)) 606 } 607 if lastAppInfo.Add(logAppInfoPeriod).Before(time.Now()) { 608 if glog.V(1) { 609 glog.Info(index.GetMemoryStats()) 610 } 611 if err := blockbookAppInfoMetric(index, chain, txCache, internalState, metrics); err != nil { 612 glog.Error("blockbookAppInfoMetric ", err) 613 } 614 lastAppInfo = time.Now() 615 } 616 }) 617 glog.Info("storeInternalStateLoop stopped") 618 } 619 620 func onNewTxAddr(tx *bchain.Tx, desc bchain.AddressDescriptor) { 621 defer func() { 622 if r := recover(); r != nil { 623 glog.Error("onNewTxAddr recovered from panic: ", r) 624 } 625 }() 626 for _, c := range callbacksOnNewTxAddr { 627 c(tx, desc) 628 } 629 } 630 631 func onNewTx(tx *bchain.MempoolTx) { 632 defer func() { 633 if r := recover(); r != nil { 634 glog.Error("onNewTx recovered from panic: ", r) 635 } 636 }() 637 for _, c := range callbacksOnNewTx { 638 c(tx) 639 } 640 } 641 642 func pushSynchronizationHandler(nt bchain.NotificationType) { 643 glog.V(1).Info("MQ: notification ", nt) 644 if common.IsInShutdown() { 645 return 646 } 647 if nt == bchain.NotificationNewBlock { 648 chanSyncIndex <- struct{}{} 649 } else if nt == bchain.NotificationNewTx { 650 chanSyncMempool <- struct{}{} 651 } else { 652 glog.Error("MQ: unknown notification sent") 653 } 654 } 655 656 func waitForSignalAndShutdown(internal *server.InternalServer, public *server.PublicServer, chain bchain.BlockChain, timeout time.Duration) { 657 sig := <-chanOsSignal 658 common.SetInShutdown() 659 glog.Infof("shutdown: %v", sig) 660 661 ctx, cancel := context.WithTimeout(context.Background(), timeout) 662 defer cancel() 663 664 if internal != nil { 665 if err := internal.Shutdown(ctx); err != nil { 666 glog.Error("internal server: shutdown error: ", err) 667 } 668 } 669 670 if public != nil { 671 if err := public.Shutdown(ctx); err != nil { 672 glog.Error("public server: shutdown error: ", err) 673 } 674 } 675 676 if chain != nil { 677 if err := chain.Shutdown(ctx); err != nil { 678 glog.Error("rpc: shutdown error: ", err) 679 } 680 } 681 } 682 683 // computeFeeStats computes fee distribution in defined blocks 684 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 { 685 start := time.Now() 686 glog.Info("computeFeeStats start") 687 api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates) 688 if err != nil { 689 return err 690 } 691 err = api.ComputeFeeStats(blockFrom, blockTo, stopCompute) 692 glog.Info("computeFeeStats finished in ", time.Since(start)) 693 return err 694 } 695 696 func initDownloaders(db *db.RocksDB, chain bchain.BlockChain, config *common.Config) { 697 if fiatRates.Enabled { 698 go fiatRates.RunDownloader() 699 } 700 701 if config.FourByteSignatures != "" && chain.GetChainParser().GetChainType() == bchain.ChainEthereumType { 702 fbsd, err := fourbyte.NewFourByteSignaturesDownloader(db, config.FourByteSignatures) 703 if err != nil { 704 glog.Errorf("NewFourByteSignaturesDownloader Init error: %v", err) 705 } else { 706 glog.Infof("Starting FourByteSignatures downloader...") 707 go fbsd.Run() 708 } 709 710 } 711 712 }