github.com/cryptohub-digital/blockbook-fork@v0.0.0-20230713133354-673c927af7f1/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-fork/api" 19 "github.com/cryptohub-digital/blockbook-fork/bchain" 20 "github.com/cryptohub-digital/blockbook-fork/bchain/coins" 21 "github.com/cryptohub-digital/blockbook-fork/common" 22 "github.com/cryptohub-digital/blockbook-fork/db" 23 "github.com/cryptohub-digital/blockbook-fork/fiat" 24 "github.com/cryptohub-digital/blockbook-fork/fourbyte" 25 "github.com/cryptohub-digital/blockbook-fork/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 }