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