diff --git a/api/types.go b/api/types.go index bdc8df04..32280427 100644 --- a/api/types.go +++ b/api/types.go @@ -118,6 +118,8 @@ type BlockbookInfo struct { Version string `json:"version"` GitCommit string `json:"gitcommit"` BuildTime string `json:"buildtime"` + SyncMode bool `json:"syncMode"` + InitialSync bool `json:"initialsync"` InSync bool `json:"inSync"` BestHeight uint32 `json:"bestHeight"` LastBlockTime time.Time `json:"lastBlockTime"` diff --git a/api/worker.go b/api/worker.go index 68d2fdb8..3aba7a10 100644 --- a/api/worker.go +++ b/api/worker.go @@ -547,6 +547,8 @@ func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) { Version: vi.Version, GitCommit: vi.GitCommit, BuildTime: vi.BuildTime, + SyncMode: w.is.SyncMode, + InitialSync: w.is.InitialSync, InSync: ss, BestHeight: bh, LastBlockTime: st, diff --git a/blockbook.go b/blockbook.go index ab076573..8d016a16 100644 --- a/blockbook.go +++ b/blockbook.go @@ -267,19 +267,9 @@ func main() { }() } - if *synchronize { - if err := syncWorker.ResyncIndex(nil); err != nil { - glog.Error("resyncIndex ", err) - return - } - if _, err = chain.ResyncMempool(nil); err != nil { - glog.Error("resyncMempool ", err) - return - } - } - var publicServer *server.PublicServer if *publicBinding != "" { + // start public server in limited functionality, extend it after sync is finished by calling ConnectFullPublicInterface publicServer, err = server.NewPublicServer(*publicBinding, *certFiles, index, chain, txCache, *explorerURL, metrics, internalState, *debugMode) if err != nil { glog.Error("socketio: ", err) @@ -301,10 +291,27 @@ func main() { } if *synchronize { - // start the synchronization loops after the server interfaces are started + internalState.SyncMode = true + internalState.InitialSync = true + if err := syncWorker.ResyncIndex(nil); err != nil { + glog.Error("resyncIndex ", err) + return + } + var mempoolCount int + if mempoolCount, err = chain.ResyncMempool(nil); err != nil { + glog.Error("resyncMempool ", err) + return + } + internalState.FinishedMempoolSync(mempoolCount) go syncIndexLoop() go syncMempoolLoop() - go storeInternalStateLoop() + internalState.InitialSync = false + } + go storeInternalStateLoop() + + if *publicBinding != "" { + // start full public interface + publicServer.ConnectFullPublicInterface() } if *blockFrom >= 0 { diff --git a/common/internalstate.go b/common/internalstate.go index 1053649c..9479d057 100644 --- a/common/internalstate.go +++ b/common/internalstate.go @@ -37,6 +37,10 @@ type InternalState struct { LastStore time.Time `json:"lastStore"` + // true if application is with flag --sync + SyncMode bool `json:"syncMode"` + + InitialSync bool `json:"initialSync"` IsSynchronized bool `json:"isSynchronized"` BestHeight uint32 `json:"bestHeight"` LastSync time.Time `json:"lastSync"` @@ -64,6 +68,14 @@ func (is *InternalState) FinishedSync(bestHeight uint32) { is.LastSync = time.Now() } +// UpdateBestHeight sets new best height, without changing IsSynchronized flag +func (is *InternalState) UpdateBestHeight(bestHeight uint32) { + is.mux.Lock() + defer is.mux.Unlock() + is.BestHeight = bestHeight + is.LastSync = time.Now() +} + // FinishedSyncNoChange marks end of synchronization in case no index update was necessary, it does not update lastSync time func (is *InternalState) FinishedSyncNoChange() { is.mux.Lock() diff --git a/db/rocksdb.go b/db/rocksdb.go index 88a9d5d3..984f856f 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -988,8 +988,10 @@ func (d *RocksDB) writeHeight(wb *gorocksdb.WriteBatch, height uint32, bi *Block return err } wb.PutCF(d.cfh[cfHeight], key, val) + d.is.UpdateBestHeight(height) case opDelete: wb.DeleteCF(d.cfh[cfHeight], key) + d.is.UpdateBestHeight(height - 1) } return nil } @@ -1223,9 +1225,9 @@ func dirSize(path string) (int64, error) { var size int64 err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error { if err == nil { - if !info.IsDir() { - size += info.Size() - } + if !info.IsDir() { + size += info.Size() + } } return err }) @@ -1352,6 +1354,12 @@ func (d *RocksDB) LoadInternalState(rpcCoin string) (*common.InternalState, erro } } is.DbColumns = nc + // after load, reset the synchronization data + is.IsSynchronized = false + is.IsMempoolSynchronized = false + var t time.Time + is.LastMempoolSync = t + is.SyncMode = false return is, nil } diff --git a/server/public.go b/server/public.go index 57ccd7c9..fe48701f 100644 --- a/server/public.go +++ b/server/public.go @@ -42,6 +42,7 @@ type PublicServer struct { } // NewPublicServer creates new public server http interface to blockbook and returns its handle +// only basic functionality is mapped, to map all functions, call func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, explorerURL string, metrics *common.Metrics, is *common.InternalState, debugMode bool) (*PublicServer, error) { api, err := api.NewWorker(db, chain, txCache, is) @@ -76,33 +77,15 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch is: is, debug: debugMode, } + s.templates = parseTemplates() - // favicon + // map only basic functions, the rest is enabled by method MapFullPublicInterface serveMux.Handle(path+"favicon.ico", http.FileServer(http.Dir("./static/"))) - // support for tests of socket.io interface - serveMux.Handle(path+"test.html", http.FileServer(http.Dir("./static/"))) - // redirect to wallet requests for tx and address, possibly to external site - serveMux.HandleFunc(path+"tx/", s.txRedirect) - serveMux.HandleFunc(path+"address/", s.addressRedirect) - // explorer - serveMux.HandleFunc(path+"explorer/tx/", s.htmlTemplateHandler(s.explorerTx)) - serveMux.HandleFunc(path+"explorer/address/", s.htmlTemplateHandler(s.explorerAddress)) - serveMux.HandleFunc(path+"explorer/search/", s.htmlTemplateHandler(s.explorerSearch)) - serveMux.HandleFunc(path+"explorer/blocks", s.htmlTemplateHandler(s.explorerBlocks)) - serveMux.HandleFunc(path+"explorer/block/", s.htmlTemplateHandler(s.explorerBlock)) serveMux.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) - // API calls - serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex)) - serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx)) - serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress)) - serveMux.HandleFunc(path+"api/block/", s.jsonHandler(s.apiBlock)) - serveMux.HandleFunc(path+"api/", s.jsonHandler(s.apiIndex)) - // handle socket.io - serveMux.Handle(path+"socket.io/", socketio.GetHandler()) // default handler serveMux.HandleFunc(path, s.htmlTemplateHandler(s.explorerIndex)) - - s.templates = parseTemplates() + // default API handler + serveMux.HandleFunc(path+"api/", s.jsonHandler(s.apiIndex)) return s, nil } @@ -117,6 +100,30 @@ func (s *PublicServer) Run() error { return s.https.ListenAndServeTLS(fmt.Sprint(s.certFiles, ".crt"), fmt.Sprint(s.certFiles, ".key")) } +// ConnectFullPublicInterface enables complete public functionality +func (s *PublicServer) ConnectFullPublicInterface() { + serveMux := s.https.Handler.(*http.ServeMux) + _, path := splitBinding(s.binding) + // support for tests of socket.io interface + serveMux.Handle(path+"test.html", http.FileServer(http.Dir("./static/"))) + // redirect to wallet requests for tx and address, possibly to external site + serveMux.HandleFunc(path+"tx/", s.txRedirect) + serveMux.HandleFunc(path+"address/", s.addressRedirect) + // explorer + serveMux.HandleFunc(path+"explorer/tx/", s.htmlTemplateHandler(s.explorerTx)) + serveMux.HandleFunc(path+"explorer/address/", s.htmlTemplateHandler(s.explorerAddress)) + serveMux.HandleFunc(path+"explorer/search/", s.htmlTemplateHandler(s.explorerSearch)) + serveMux.HandleFunc(path+"explorer/blocks", s.htmlTemplateHandler(s.explorerBlocks)) + serveMux.HandleFunc(path+"explorer/block/", s.htmlTemplateHandler(s.explorerBlock)) + // API calls + serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex)) + serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx)) + serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress)) + serveMux.HandleFunc(path+"api/block/", s.jsonHandler(s.apiBlock)) + // socket.io interface + serveMux.Handle(path+"socket.io/", s.socketio.GetHandler()) +} + // Close closes the server func (s *PublicServer) Close() error { glog.Infof("public server: closing") diff --git a/static/templates/index.html b/static/templates/index.html index 75d902ef..86d4b114 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -1,5 +1,11 @@ {{define "specific"}}{{$cs := .CoinShortcut}}{{$bb := .Info.Blockbook}}{{$be := .Info.Backend}}

Application status

+{{- if $bb.InitialSync -}} +

Application is now in initial synchronization and does not provide any data.

+{{- end -}} +{{- if not $bb.SyncMode -}} +

Synchronization with backend is disabled, the state of index is not up to date.

+{{- end -}}

Blockbook

@@ -19,7 +25,7 @@ Synchronized - {{$bb.InSync}} + {{$bb.InSync}} Last Block @@ -31,7 +37,7 @@ Mempool in Sync - {{$bb.InSyncMempool}} + {{$bb.InSyncMempool}} Last Mempool Update